From 8f9c5d367726347996ff4cdbac32186b9cdd9773 Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 29 Jun 2024 18:04:58 +0300 Subject: [PATCH 01/19] Change tab size to 3 Implements: https://github.com/bouncepaw/mycorrhiza/issues/237 --- static/default.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/default.css b/static/default.css index c1b708e..10237df 100644 --- a/static/default.css +++ b/static/default.css @@ -122,7 +122,7 @@ article p { margin: .5rem 0; } article ul, ol { padding-left: 1.5rem; margin: .5rem 0; } article code { padding: .1rem .3rem; border-radius: .25rem; font-size: 90%; font-family: 'Menlo', 'PT Mono', monospace; } article pre.codeblock { padding:.5rem; white-space: pre-wrap; border-radius: .25rem;} -.codeblock code {padding:0; font-size:15px;} +.codeblock code {padding:0; font-size:15px; tab-size: 3; } .transclusion { border-radius: .25rem; margin-bottom: .25rem; clear: both; } .transclusion_failed { padding: 0 .5rem; } .transclusion__content > *:not(.binary-container) {margin: 0.5rem; } From 4bfbb6a2d76393ad475f6729b69ec739928e4d5b Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 29 Jun 2024 18:12:23 +0300 Subject: [PATCH 02/19] Update README.md --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1df37af..a282d28 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@ ## Features -* **No database required.** Everything is stored in plain files. It makes installation super easy, and you can modify the content directly by yourself. +* **No database used.** Everything is stored in plain files. It makes installation super easy, and you can modify the content directly by yourself. * **Everything is hyphae.** A hypha is a unit of content such as a picture, video or a text article. Hyphae can [transclude] and link each other, forming a tight network of hypertext pages. -* **Hyphae are authored in [Mycomarkup],** a markup language that's designed to be unambigious yet easy to use. +* **Hyphae are authored in [Mycomarkup],** a markup language that's designed to be unambiguous yet easy to use. * **Categories** let you organize hyphae without any hierarchy restrictions, with all the benefits of a category system. * **Nesting of hyphae** is also supported if you like hierarchies. -* **History of changes** for textual parts of hyphae. Every change is safely stored in [Git]. Web feeds for recent changes included. +* **History of changes.** Every change is safely stored in [Git]. Web feeds (RSS, Atom, JSON Feed) for recent changes included. * **Keyboard-driven navigation.** Press `?` to see the list of shortcuts. * **Support for [authorization].** Both plain username-password pairs and [Telegram]'s login widget are supported. * **[Open Graph] support.** The most relevant info about a hypha is made available through OG meta tags for consumption by other software. @@ -30,14 +30,16 @@ Compare Mycorrhiza Wiki with other engines on [WikiMatrix](https://www.wikimatri ## Installing -See [the deployment guide](https://mycorrhiza.wiki/hypha/deployment) on the wiki. +See [the deployment guide](https://mycorrhiza.wiki/hypha/deployment) on the wiki. Also, Mycorrhiza might be available in your repositories. -## Contributing +## Contributing and community -* [SourceHut](https://sr.ht/~bouncepaw/mycorrhiza) * [GitHub](https://github.com/bouncepaw/mycorrhiza) -* [#mycorrhiza on irc.libera.chat](irc://irc.libera.chat/#mycorrhiza) +* [Fediverse @mycorrhiza@floss.social](https://floss.social/@mycorrhiza) +* Mirrors: + * [SourceHut](https://sr.ht/~bouncepaw/mycorrhiza) + * [Codeberg](https://codeberg.org/bouncepaw/mycorrhiza) * [@mycorrhizadev (Russian) in Telegram](https://t.me/mycorrhizadev) If you want to contribute with code, open a pull request on GitHub or send a patch to the [mailing list](https://lists.sr.ht/~bouncepaw/mycorrhiza-devel). From 522640f8df5248d7bcf571af2d30a2e25a0360c2 Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 29 Jun 2024 18:17:55 +0300 Subject: [PATCH 03/19] Mention that robots.txt can be redefined --- help/en/file_structure.myco | 1 + 1 file changed, 1 insertion(+) diff --git a/help/en/file_structure.myco b/help/en/file_structure.myco index 6798b21..2b0d8fd 100644 --- a/help/en/file_structure.myco +++ b/help/en/file_structure.myco @@ -11,6 +11,7 @@ You can edit all of the files manually, if you want, just do your best to not br ** `static/favicon.ico` is your wiki's favicon, accessed at [[/favicon.ico]] by browsers. ** `static/default.css` redefines the engine's default style, if exists. You probably don't need to use it. ** `static/custom.css` is loaded after the main style. If you want to make visual changes to your wiki, this is probably where you should do that. +** `static/robots.txt` redefines default `robots.txt` file. * `categories.json` contains the information about all categories in your wiki. * `users.json` stores users' information. The passwords are not stored, only their hashes are, this is safe. Their tokens are stored in `cache/tokens.json`. * `interwiki.json` holds the interwiki configuration. From 922181ee93acee7baabfb760275b7c0f84e43034 Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 29 Jun 2024 18:21:31 +0300 Subject: [PATCH 04/19] Add Cmd+' shortcut for local time and change the format of time --- static/shortcuts.js | 1 + static/toolbar.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/static/shortcuts.js b/static/shortcuts.js index b36dd19..9dcd577 100644 --- a/static/shortcuts.js +++ b/static/shortcuts.js @@ -330,6 +330,7 @@ if (document.body.dataset.rrhAddr.startsWith('/edit')) { new Shortcut(isMac ? 'Meta+k' : 'Ctrl+k', wrapLink, 'Inline link', { force: true }), // Apparently, ⌘; conflicts with a Safari's hotkey. Whatever. new Shortcut(isMac ? 'Meta+;' : 'Ctrl+;', insertDateUTC, 'Insert date UTC', { force: true }), + new Shortcut(isMac ? "Meta+'" : "Ctrl+'", insertTimeLocal, 'Insert local time', { force: true }) ])) } } diff --git a/static/toolbar.js b/static/toolbar.js index 8b40967..1785f63 100644 --- a/static/toolbar.js +++ b/static/toolbar.js @@ -93,7 +93,7 @@ function insertDateUTC() { } function insertTimeUTC() { - let time = new Date().toISOString().substring(11, 19) + " UTC" + let time = new Date().toISOString().substring(11, 16) + " UTC " textInserter(time)() } @@ -108,7 +108,7 @@ function insertDateLocal() { function insertTimeLocal() { let d = new Date() - textInserter(`${len2(d.getHours())}:${len2(d.getMinutes())}:${len2(d.getSeconds())}`)() + textInserter(`${len2(d.getHours())}:${len2(d.getMinutes())} `)() } function insertUserlink() { From 7d5636486d206a9cf83f650180ce2f2e3750633d Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 29 Jun 2024 18:32:42 +0300 Subject: [PATCH 05/19] Delete release.yaml It hadn't been working for a couple of years already anyway --- .github/workflows/release.yaml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/release.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml deleted file mode 100644 index 2da5de3..0000000 --- a/.github/workflows/release.yaml +++ /dev/null @@ -1,28 +0,0 @@ -on: - release: - types: [created] - -jobs: - releases-matrix: - name: Release Go Binary - runs-on: ubuntu-latest - strategy: - matrix: - # build and publish in parallel a lot of binaries - # https://golang.org/doc/install/source#environment See supported Go OS/Arch pairs here - goos: [linux, darwin, openbsd, windows] - goarch: ["386", amd64, arm64] - exclude: - - goarch: "386" - goos: darwin - - goarch: arm64 - goos: windows - steps: - - uses: actions/checkout@v2 - - uses: wangyoucao577/go-release-action@v1.17 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - goos: ${{ matrix.goos }} - goarch: ${{ matrix.goarch }} - binary_name: "mycorrhiza" - extra_files: LICENSE README.md help/mycorrhiza.1 From 719de9b5301f1a36b84e99aca11d6c1a17e99a1b Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 29 Jun 2024 18:37:15 +0300 Subject: [PATCH 06/19] Update version to 1.15 --- help/en.myco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/help/en.myco b/help/en.myco index e24d118..f82a3b3 100644 --- a/help/en.myco +++ b/help/en.myco @@ -1,6 +1,6 @@ = Help -This is documentation for Mycorrhiza Wiki 1.14. Choose a topic from the list. +This is documentation for Mycorrhiza Wiki 1.15. Choose a topic from the list. The documentation is incomplete. If you want to contribute to the documentation, open a pull request or an issue on [[https://github.com/bouncepaw/mycorrhiza | GitHub]] or [[https://lists.sr.ht/~bouncepaw/mycorrhiza-devel | send a patch]]. From 00bd7e1f78db12678e817507117352da21acce12 Mon Sep 17 00:00:00 2001 From: Danila Gorelko Date: Wed, 14 Aug 2024 14:03:20 +0300 Subject: [PATCH 07/19] Remove 'Category list' from autocomplete menu (#252) --- static/view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/view.js b/static/view.js index 909a092..78a3996 100644 --- a/static/view.js +++ b/static/view.js @@ -35,7 +35,7 @@ wrapper.appendChild(hamburgerSection); return Array .from(new DOMParser() .parseFromString(html, 'text/html') - .querySelectorAll('.mv-tags .p-name')) + .querySelectorAll('.mv-tag .p-name')) .map(a => a.innerText); }); From ea7d60dd72bf54f100669d83dadb7f98be351396 Mon Sep 17 00:00:00 2001 From: Danila Gorelko Date: Wed, 14 Aug 2024 14:10:03 +0300 Subject: [PATCH 08/19] Trick safari to not recognise any "name" in the input field (#254) Safari looks at id and placeholder in order to enable contacts autocomplete. Fixes: #253 --- categories/view_card.html | 4 ++-- static/default.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/categories/view_card.html b/categories/view_card.html index f2ea7c8..3262154 100644 --- a/categories/view_card.html +++ b/categories/view_card.html @@ -22,8 +22,8 @@ {{if .GivenPermissionToModify}}
  • - + diff --git a/static/default.css b/static/default.css index 10237df..f13975f 100644 --- a/static/default.css +++ b/static/default.css @@ -271,7 +271,7 @@ mark { background: rgba(130, 80, 30, 5); color: inherit; } border: none; background: none; } -.categories-card #_cat-name { +.categories-card #_cat-input { width: 100%; margin: 0; padding: 0 .5rem; From a9ee700aad2133766d06e55df6e8092d6dcaab07 Mon Sep 17 00:00:00 2001 From: Douglas Pi Date: Sun, 25 Aug 2024 20:16:10 +0300 Subject: [PATCH 09/19] Fix Windows slashes --- files/files.go | 2 +- hyphae/files.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/files/files.go b/files/files.go index 97cafb4..b549e83 100644 --- a/files/files.go +++ b/files/files.go @@ -25,7 +25,7 @@ var paths struct { // A separate function is needed to easily know where a general storage path is // needed rather than a concrete Git or the whole wiki storage path, so that we // could easily refactor things later if we'll ever support different storages. -func HyphaeDir() string { return paths.gitRepo } +func HyphaeDir() string { return filepath.ToSlash(paths.gitRepo) } // GitRepo returns the path to the Git repository of the wiki. func GitRepo() string { return paths.gitRepo } diff --git a/hyphae/files.go b/hyphae/files.go index 43b8c67..a0ee9cc 100644 --- a/hyphae/files.go +++ b/hyphae/files.go @@ -73,7 +73,7 @@ func indexHelper(path string, nestLevel uint, ch chan ExistingHypha) { } var ( - hyphaPartPath = filepath.Join(path, node.Name()) + hyphaPartPath = filepath.ToSlash(filepath.Join(path, node.Name())) hyphaName, isText, skip = mimetype.DataFromFilename(hyphaPartPath) ) if !skip { From 41733c50bd566150493236befdfd7232edec3857 Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 7 Sep 2024 21:22:41 +0300 Subject: [PATCH 10/19] New templates #117 (#236) Didn't have the chance to migrate //all// templates just yet. We'll get there. * Implement yet another template system * Move orphans to the new system and fix a bug in it * Link orphans in the admin panel * Move the backlink handlers to the web package * Move auth routing to web * Move /user-list to the new system * Move change password and translate it * Move stuff * Move admin-related stuff to the web * Move a lot of files into internal dir Outside of it are web and stuff that needs further refactoring * Fix static not loading and de-qtpl tree * Move tree to internal * Keep the globe on the same line #230 * Revert "Keep the globe on the same line #230" This reverts commit ae78e5e459b1e980ba89bf29e61f75c0625ed2c7. * Migrate templates from hypview: delete, edit, start empty and existing WIP The delete media view was removed, I didn't even know it still existed as a GET. A rudiment. * Make views multi-file and break compilation * Megarefactoring of hypha views * Auth-related stuffs * Fix some of those weird imports * Migrate cat views * Fix cat js * Lower standards * Internalize trauma --- admin/view.go | 126 --- auth/auth.qtpl | 213 ----- auth/auth.qtpl.go | 805 ------------------ auth/get_rid_of_it.go | 22 - auth/web.go | 225 ----- backlinks/web.go | 85 -- categories/view_card.html | 37 - categories/view_edit.html | 37 - categories/views.go | 107 --- flag.go | 14 +- help/web.go | 10 +- history/feed.go | 3 +- history/history.go | 2 +- history/histweb/histview.go | 52 +- history/operations.go | 2 +- history/revision.go | 2 +- history/view.qtpl | 26 +- history/view.qtpl.go | 187 ++-- httpd.go | 3 +- hypview/hypview.go | 141 +-- hypview/nav.qtpl | 50 -- hypview/nav.qtpl.go | 311 ------- hypview/readers.qtpl | 161 ---- hypview/readers.qtpl.go | 651 -------------- hypview/view_empty_hypha.html | 32 - hypview/view_remove_media.html | 22 - .../backlinks}/backlinks.go | 34 +- {backlinks => internal/backlinks}/hooks.go | 2 +- .../categories}/categories.go | 12 +- {categories => internal/categories}/files.go | 5 +- {cfg => internal/cfg}/config.go | 0 {files => internal/files}/files.go | 5 +- {hyphae => internal/hyphae}/count.go | 0 {hyphae => internal/hyphae}/deprecated.go | 0 {hyphae => internal/hyphae}/empty_hypha.go | 0 {hyphae => internal/hyphae}/existing_hypha.go | 0 {hyphae => internal/hyphae}/files.go | 3 +- {hyphae => internal/hyphae}/hypha.go | 0 {hyphae => internal/hyphae}/iterators.go | 0 {hyphae => internal/hyphae}/media_hypha.go | 3 +- {hyphae => internal/hyphae}/textual_hypha.go | 0 {migration => internal/migration}/headings.go | 2 +- .../migration}/migration.go | 7 +- {migration => internal/migration}/rockets.go | 2 +- {mimetype => internal/mimetype}/mime.go | 0 {shroom => internal/shroom}/can.go | 14 +- {shroom => internal/shroom}/delete.go | 18 +- {shroom => internal/shroom}/header_links.go | 12 +- {shroom => internal/shroom}/log.go | 5 +- {shroom => internal/shroom}/rename.go | 42 +- {shroom => internal/shroom}/search.go | 2 +- {shroom => internal/shroom}/shroom.go | 0 {shroom => internal/shroom}/unattach.go | 10 +- {shroom => internal/shroom}/upload.go | 13 +- {tree => internal/tree}/tree.go | 50 +- {user => internal/user}/files.go | 4 +- {user => internal/user}/net.go | 11 +- {user => internal/user}/user.go | 6 +- {user => internal/user}/users.go | 26 +- {version => internal/version}/version.go | 0 interwiki/interwiki.go | 2 +- interwiki/web.go | 2 +- l18n/en/auth.json | 26 +- l18n/en/ui.json | 37 +- l18n/ru/auth.json | 28 +- l18n/ru/ui.json | 42 +- main.go | 27 +- misc/about.go | 6 +- misc/handlers.go | 16 +- misc/views.go | 4 +- mycoopts/mycoopts.go | 4 +- mycoopts/view.qtpl | 2 +- mycoopts/view.qtpl.go | 2 +- settings/view.go | 47 - tree/view.qtpl | 32 - tree/view.qtpl.go | 126 --- util/util.go | 7 +- {admin => web}/admin.go | 100 ++- categories/handlers.go => web/cats.go | 63 +- web/mutators.go | 44 +- {viewutil => web/newtmpl}/base.html | 0 web/newtmpl/newtmpl.go | 118 +++ web/pages.go | 202 +++++ settings/settings.go => web/password.go | 19 +- web/readers.go | 174 ++-- {static => web/static}/common.js | 0 {static => web/static}/default.css | 2 +- {static => web/static}/editor.js | 0 {static => web/static}/icon/README.md | 0 {static => web/static}/icon/feed.svg | 0 {static => web/static}/icon/gemini-proto.svg | 0 {static => web/static}/icon/gopher-proto.svg | 0 {static => web/static}/icon/http-proto.svg | 0 {static => web/static}/icon/mailto-proto.svg | 0 {static => web/static}/icon/mushroom.png | Bin {static => web/static}/icon/x.svg | 0 {static => web/static}/robots.txt | 0 {static => web/static}/shortcuts.js | 0 {static => web/static}/static.go | 0 {static => web/static}/toolbar.js | 0 {static => web/static}/view.js | 0 .../views/admin-delete-user.html | 0 .../views/admin-edit-user.html | 0 .../views/admin-new-user.html | 0 .../views/admin-panel.html | 1 + .../views/admin-user-list.html | 0 web/views/auth-lock.html | 33 + web/views/auth-login.html | 39 + web/views/auth-logout.html | 18 + web/views/auth-register.html | 35 + web/views/auth-telegram.html | 11 + .../views/backlinks.html | 0 web/views/cat-edit.html | 37 + .../view_list.html => web/views/cat-list.html | 0 .../view_page.html => web/views/cat-page.html | 0 .../views/change-password.html | 0 .../views/hypha-delete.html | 0 .../views/hypha-edit.html | 0 web/views/hypha-media.html | 57 ++ web/views/hypha-revision.html | 18 + web/views/hypha.html | 160 ++++ .../views/orphans.html | 0 web/views/user-list.html | 30 + web/viewutil/base.html | 56 ++ {viewutil => web/viewutil}/chain.go | 0 {viewutil => web/viewutil}/err.go | 0 {viewutil => web/viewutil}/meta.go | 11 +- {viewutil => web/viewutil}/viewutil.go | 2 +- web/web.go | 277 +++++- 129 files changed, 1766 insertions(+), 3765 deletions(-) delete mode 100644 admin/view.go delete mode 100644 auth/auth.qtpl delete mode 100644 auth/auth.qtpl.go delete mode 100644 auth/get_rid_of_it.go delete mode 100644 auth/web.go delete mode 100644 backlinks/web.go delete mode 100644 categories/view_card.html delete mode 100644 categories/view_edit.html delete mode 100644 categories/views.go delete mode 100644 hypview/nav.qtpl delete mode 100644 hypview/nav.qtpl.go delete mode 100644 hypview/readers.qtpl delete mode 100644 hypview/readers.qtpl.go delete mode 100644 hypview/view_empty_hypha.html delete mode 100644 hypview/view_remove_media.html rename {backlinks => internal/backlinks}/backlinks.go (84%) rename {backlinks => internal/backlinks}/hooks.go (97%) rename {categories => internal/categories}/categories.go (91%) rename {categories => internal/categories}/files.go (98%) rename {cfg => internal/cfg}/config.go (100%) rename {files => internal/files}/files.go (97%) rename {hyphae => internal/hyphae}/count.go (100%) rename {hyphae => internal/hyphae}/deprecated.go (100%) rename {hyphae => internal/hyphae}/empty_hypha.go (100%) rename {hyphae => internal/hyphae}/existing_hypha.go (100%) rename {hyphae => internal/hyphae}/files.go (97%) rename {hyphae => internal/hyphae}/hypha.go (100%) rename {hyphae => internal/hyphae}/iterators.go (100%) rename {hyphae => internal/hyphae}/media_hypha.go (93%) rename {hyphae => internal/hyphae}/textual_hypha.go (100%) rename {migration => internal/migration}/headings.go (95%) rename {migration => internal/migration}/migration.go (95%) rename {migration => internal/migration}/rockets.go (96%) rename {mimetype => internal/mimetype}/mime.go (100%) rename {shroom => internal/shroom}/can.go (73%) rename {shroom => internal/shroom}/delete.go (62%) rename {shroom => internal/shroom}/header_links.go (84%) rename {shroom => internal/shroom}/log.go (87%) rename {shroom => internal/shroom}/rename.go (73%) rename {shroom => internal/shroom}/search.go (94%) rename {shroom => internal/shroom}/shroom.go (100%) rename {shroom => internal/shroom}/unattach.go (74%) rename {shroom => internal/shroom}/upload.go (95%) rename {tree => internal/tree}/tree.go (64%) rename {user => internal/user}/files.go (96%) rename {user => internal/user}/net.go (95%) rename {user => internal/user}/user.go (97%) rename {user => internal/user}/users.go (78%) rename {version => internal/version}/version.go (100%) delete mode 100644 settings/view.go delete mode 100644 tree/view.qtpl delete mode 100644 tree/view.qtpl.go rename {admin => web}/admin.go (54%) rename categories/handlers.go => web/cats.go (70%) rename {viewutil => web/newtmpl}/base.html (100%) create mode 100644 web/newtmpl/newtmpl.go create mode 100644 web/pages.go rename settings/settings.go => web/password.go (84%) rename {static => web/static}/common.js (100%) rename {static => web/static}/default.css (99%) rename {static => web/static}/editor.js (100%) rename {static => web/static}/icon/README.md (100%) rename {static => web/static}/icon/feed.svg (100%) rename {static => web/static}/icon/gemini-proto.svg (100%) rename {static => web/static}/icon/gopher-proto.svg (100%) rename {static => web/static}/icon/http-proto.svg (100%) rename {static => web/static}/icon/mailto-proto.svg (100%) rename {static => web/static}/icon/mushroom.png (100%) rename {static => web/static}/icon/x.svg (100%) rename {static => web/static}/robots.txt (100%) rename {static => web/static}/shortcuts.js (100%) rename {static => web/static}/static.go (100%) rename {static => web/static}/toolbar.js (100%) rename {static => web/static}/view.js (100%) rename admin/view_delete_user.html => web/views/admin-delete-user.html (100%) rename admin/view_edit_user.html => web/views/admin-edit-user.html (100%) rename admin/view_new_user.html => web/views/admin-new-user.html (100%) rename admin/view_panel.html => web/views/admin-panel.html (93%) rename admin/view_user_list.html => web/views/admin-user-list.html (100%) create mode 100644 web/views/auth-lock.html create mode 100644 web/views/auth-login.html create mode 100644 web/views/auth-logout.html create mode 100644 web/views/auth-register.html create mode 100644 web/views/auth-telegram.html rename backlinks/view_backlinks.html => web/views/backlinks.html (100%) create mode 100644 web/views/cat-edit.html rename categories/view_list.html => web/views/cat-list.html (100%) rename categories/view_page.html => web/views/cat-page.html (100%) rename settings/view_change_password.html => web/views/change-password.html (100%) rename hypview/view_delete.html => web/views/hypha-delete.html (100%) rename hypview/view_edit.html => web/views/hypha-edit.html (100%) create mode 100644 web/views/hypha-media.html create mode 100644 web/views/hypha-revision.html create mode 100644 web/views/hypha.html rename backlinks/view_orphans.html => web/views/orphans.html (100%) create mode 100644 web/views/user-list.html create mode 100644 web/viewutil/base.html rename {viewutil => web/viewutil}/chain.go (100%) rename {viewutil => web/viewutil}/err.go (100%) rename {viewutil => web/viewutil}/meta.go (70%) rename {viewutil => web/viewutil}/viewutil.go (99%) diff --git a/admin/view.go b/admin/view.go deleted file mode 100644 index 26c1ce2..0000000 --- a/admin/view.go +++ /dev/null @@ -1,126 +0,0 @@ -package admin - -import ( - "embed" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/viewutil" - "github.com/gorilla/mux" - "net/http" -) - -const adminTranslationRu = ` -{{define "panel title"}}Панель админстратора{{end}} -{{define "panel safe section title"}}Безопасная секция{{end}} -{{define "panel link about"}}Об этой вики{{end}} -{{define "panel update header"}}Обновить ссылки в верхней панели{{end}} -{{define "panel link user list"}}Список пользователей{{end}} -{{define "panel users"}}Управление пользователями{{end}} -{{define "panel unsafe section title"}}Опасная секция{{end}} -{{define "panel shutdown"}}Выключить вики{{end}} -{{define "panel reindex hyphae"}}Переиндексировать гифы{{end}} -{{define "panel interwiki"}}Интервики{{end}} - -{{define "manage users"}}Управление пользователями{{end}} -{{define "create user"}}Создать пользователя{{end}} -{{define "reindex users"}}Переиндексировать пользователей{{end}} -{{define "name"}}Имя{{end}} -{{define "group"}}Группа{{end}} -{{define "registered at"}}Зарегистрирован{{end}} -{{define "actions"}}Действия{{end}} -{{define "edit"}}Изменить{{end}} - -{{define "new user"}}Новый пользователь{{end}} -{{define "password"}}Пароль{{end}} -{{define "confirm password"}}Подтвердить пароль{{end}} -{{define "change password"}}Изменить пароль{{end}} -{{define "non local password change"}}Поменять пароль можно только у локальных пользователей.{{end}} -{{define "create"}}Создать{{end}} - -{{define "change group"}}Изменить группу{{end}} -{{define "user x"}}Пользователь {{.}}{{end}} -{{define "update"}}Обновить{{end}} -{{define "delete user"}}Удалить пользователя{{end}} -{{define "delete user tip"}}Удаляет пользователя из базы данных. Правки пользователя будут сохранены. Имя пользователя освободится для повторной регистрации.{{end}} - -{{define "delete user?"}}Удалить пользователя {{.}}?{{end}} -{{define "delete user warning"}}Вы уверены, что хотите удалить этого пользователя из базы данных? Это действие нельзя отменить.{{end}} -` - -var ( - //go:embed *.html - fs embed.FS - panelChain, listChain, newUserChain, editUserChain, deleteUserChain viewutil.Chain -) - -func Init(rtr *mux.Router) { - rtr.HandleFunc("/shutdown", handlerAdminShutdown).Methods(http.MethodPost) - rtr.HandleFunc("/reindex-users", handlerAdminReindexUsers).Methods(http.MethodPost) - - rtr.HandleFunc("/new-user", handlerAdminUserNew).Methods(http.MethodGet, http.MethodPost) - rtr.HandleFunc("/users/{username}/edit", handlerAdminUserEdit).Methods(http.MethodGet, http.MethodPost) - rtr.HandleFunc("/users/{username}/change-password", handlerAdminUserChangePassword).Methods(http.MethodPost) - rtr.HandleFunc("/users/{username}/delete", handlerAdminUserDelete).Methods(http.MethodGet, http.MethodPost) - rtr.HandleFunc("/users", handlerAdminUsers) - - rtr.HandleFunc("/", handlerAdmin) - - panelChain = viewutil.CopyEnRuWith(fs, "view_panel.html", adminTranslationRu) - listChain = viewutil.CopyEnRuWith(fs, "view_user_list.html", adminTranslationRu) - newUserChain = viewutil.CopyEnRuWith(fs, "view_new_user.html", adminTranslationRu) - editUserChain = viewutil.CopyEnRuWith(fs, "view_edit_user.html", adminTranslationRu) - deleteUserChain = viewutil.CopyEnRuWith(fs, "view_delete_user.html", adminTranslationRu) -} - -func viewPanel(meta viewutil.Meta) { - viewutil.ExecutePage(meta, panelChain, &viewutil.BaseData{}) -} - -type listData struct { - *viewutil.BaseData - UserHypha string - Users []*user.User -} - -func viewList(meta viewutil.Meta, users []*user.User) { - viewutil.ExecutePage(meta, listChain, listData{ - BaseData: &viewutil.BaseData{}, - UserHypha: cfg.UserHypha, - Users: users, - }) -} - -type newUserData struct { - *viewutil.BaseData - Form util.FormData -} - -func viewNewUser(meta viewutil.Meta, form util.FormData) { - viewutil.ExecutePage(meta, newUserChain, newUserData{ - BaseData: &viewutil.BaseData{}, - Form: form, - }) -} - -type editDeleteUserData struct { - *viewutil.BaseData - Form util.FormData - U *user.User -} - -func viewEditUser(meta viewutil.Meta, form util.FormData, u *user.User) { - viewutil.ExecutePage(meta, editUserChain, editDeleteUserData{ - BaseData: &viewutil.BaseData{}, - Form: form, - U: u, - }) -} - -func viewDeleteUser(meta viewutil.Meta, form util.FormData, u *user.User) { - viewutil.ExecutePage(meta, deleteUserChain, editDeleteUserData{ - BaseData: &viewutil.BaseData{}, - Form: form, - U: u, - }) -} diff --git a/auth/auth.qtpl b/auth/auth.qtpl deleted file mode 100644 index 2e34332..0000000 --- a/auth/auth.qtpl +++ /dev/null @@ -1,213 +0,0 @@ -{% import "net/http" %} -{% import "sort" %} -{% import "github.com/bouncepaw/mycorrhiza/cfg" %} -{% import "github.com/bouncepaw/mycorrhiza/l18n" %} -{% import "github.com/bouncepaw/mycorrhiza/user" %} - -{% func Register(rq *http.Request) %} -{% code - lc := l18n.FromRequest(rq) -%} -
    -
    - {% if cfg.AllowRegistration %} - - - - {%= telegramWidget(lc) %} - {% elseif cfg.UseAuth %} -

    {%s lc.Get("auth.noregister") %}

    -

    ← {%s lc.Get("auth.go_back") %}

    - {% else %} -

    {%s lc.Get("auth.noauth") %}

    -

    ← {%s lc.Get("auth.go_back") %}

    - {% endif %} -
    -
    -{% endfunc %} - -{% func Login(lc *l18n.Localizer) %} -
    -
    - {% if cfg.UseAuth %} - - {%= telegramWidget(lc) %} - {% else %} -

    {%s lc.Get("auth.noauth") %}

    -

    ← {%s lc.Get("auth.go_home") %}

    - {% endif %} -
    -
    -{% endfunc %} - -Telegram auth widget was requested by Yogurt. As you can see, we don't offer user administrators control over it. Of course we don't. -{% func telegramWidget(lc *l18n.Localizer) %} -{% if cfg.TelegramEnabled %} -

    {%s lc.Get("auth.telegram_tip") %}

    - -{% endif %} -{% endfunc %} - -{% func LoginError(err string, lc *l18n.Localizer) %} -
    -
    - {% switch err %} - {% case "unknown username" %} -

    {%s lc.Get("auth.error_username") %}

    - {% case "wrong password" %} -

    {%s lc.Get("auth.error_password") %}

    - {% default %} -

    {%s err %}

    - {% endswitch %} -

    ← {%s lc.Get("auth.try_again") %}

    -
    -
    -{% endfunc %} - -{% func Logout(can bool, lc *l18n.Localizer) %} -
    -
    - {% if can %} -

    {%s lc.Get("auth.logout_header") %}

    -
    - - {%s lc.Get("auth.go_home") %} -
    - {% else %} -

    {%s lc.Get("auth.logout_anon") %}

    -

    {%s lc.Get("auth.login_title") %}

    -

    ← {%s lc.Get("auth.go_home") %}

    - {% endif %} -
    -
    -{% endfunc %} - -{% func Lock(lc *l18n.Localizer) %} - - - - - - 🔒 {%s lc.Get("auth.lock_title") %} - - - - -
    -
    -

    🔒

    -

    {%s lc.Get("auth.lock_title") %}

    - - {%= telegramWidget(lc) %} -
    -
    - - -{% endfunc %} - -{% code -var userListL10n = map[string]L10nEntry{ - "heading": En("List of users").Ru("Список пользователей"), - "administrators": En("Administrators").Ru("Администраторы"), - "moderators": En("Moderators").Ru("Модераторы"), - "editors": En("Editors").Ru("Редакторы"), - "readers": En("Readers").Ru("Читатели"), -} -%} - -{% func UserList(lc *l18n.Localizer) %} -
    -{% code -var get = func(key string) string { - return userListL10n[key].Get(lc.Locale) -} - -var ( - admins = make([]string, 0) - moderators = make([]string, 0) - editors = make([]string, 0) - readers = make([]string, 0) -) -for u := range user.YieldUsers() { - switch u.Group { - // What if we place the users into sorted slices? - case "admin": - admins = append(admins, u.Name) - case "moderator": - moderators = append(moderators, u.Name) - case "editor", "trusted": - editors = append(editors, u.Name) - case "reader": - readers = append(readers, u.Name) - } -} -sort.Strings(admins) -sort.Strings(moderators) -sort.Strings(editors) -sort.Strings(readers) -%} -

    {%s get("heading") %}

    -
    -

    {%s get("administrators") %}

    -
      {% for _, name := range admins %} -
    1. {%s name %}
    2. - {% endfor %}
    -
    -
    -

    {%s get("moderators") %}

    -
      {% for _, name := range moderators %} -
    1. {%s name %}
    2. - {% endfor %}
    -
    -
    -

    {%s get("editors") %}

    -
      {% for _, name := range editors %} -
    1. {%s name %}
    2. - {% endfor %}
    -
    -
    -

    {%s get("readers") %}

    -
      {% for _, name := range readers %} -
    1. {%s name %}
    2. - {% endfor %}
    -
    -
    -{% endfunc %} diff --git a/auth/auth.qtpl.go b/auth/auth.qtpl.go deleted file mode 100644 index dfbbd48..0000000 --- a/auth/auth.qtpl.go +++ /dev/null @@ -1,805 +0,0 @@ -// Code generated by qtc from "auth.qtpl". DO NOT EDIT. -// See https://github.com/valyala/quicktemplate for details. - -//line auth/auth.qtpl:1 -package auth - -//line auth/auth.qtpl:1 -import "net/http" - -//line auth/auth.qtpl:2 -import "sort" - -//line auth/auth.qtpl:3 -import "github.com/bouncepaw/mycorrhiza/cfg" - -//line auth/auth.qtpl:4 -import "github.com/bouncepaw/mycorrhiza/l18n" - -//line auth/auth.qtpl:5 -import "github.com/bouncepaw/mycorrhiza/user" - -//line auth/auth.qtpl:7 -import ( - qtio422016 "io" - - qt422016 "github.com/valyala/quicktemplate" -) - -//line auth/auth.qtpl:7 -var ( - _ = qtio422016.Copy - _ = qt422016.AcquireByteBuffer -) - -//line auth/auth.qtpl:7 -func StreamRegister(qw422016 *qt422016.Writer, rq *http.Request) { -//line auth/auth.qtpl:7 - qw422016.N().S(` -`) -//line auth/auth.qtpl:9 - lc := l18n.FromRequest(rq) - -//line auth/auth.qtpl:10 - qw422016.N().S(` -
    -
    - `) -//line auth/auth.qtpl:13 - if cfg.AllowRegistration { -//line auth/auth.qtpl:13 - qw422016.N().S(` - - `) -//line auth/auth.qtpl:31 - streamtelegramWidget(qw422016, lc) -//line auth/auth.qtpl:31 - qw422016.N().S(` - `) -//line auth/auth.qtpl:32 - } else if cfg.UseAuth { -//line auth/auth.qtpl:32 - qw422016.N().S(` -

    `) -//line auth/auth.qtpl:33 - qw422016.E().S(lc.Get("auth.noregister")) -//line auth/auth.qtpl:33 - qw422016.N().S(`

    -

    ← `) -//line auth/auth.qtpl:34 - qw422016.E().S(lc.Get("auth.go_back")) -//line auth/auth.qtpl:34 - qw422016.N().S(`

    - `) -//line auth/auth.qtpl:35 - } else { -//line auth/auth.qtpl:35 - qw422016.N().S(` -

    `) -//line auth/auth.qtpl:36 - qw422016.E().S(lc.Get("auth.noauth")) -//line auth/auth.qtpl:36 - qw422016.N().S(`

    -

    ← `) -//line auth/auth.qtpl:37 - qw422016.E().S(lc.Get("auth.go_back")) -//line auth/auth.qtpl:37 - qw422016.N().S(`

    - `) -//line auth/auth.qtpl:38 - } -//line auth/auth.qtpl:38 - qw422016.N().S(` -
    -
    -`) -//line auth/auth.qtpl:41 -} - -//line auth/auth.qtpl:41 -func WriteRegister(qq422016 qtio422016.Writer, rq *http.Request) { -//line auth/auth.qtpl:41 - qw422016 := qt422016.AcquireWriter(qq422016) -//line auth/auth.qtpl:41 - StreamRegister(qw422016, rq) -//line auth/auth.qtpl:41 - qt422016.ReleaseWriter(qw422016) -//line auth/auth.qtpl:41 -} - -//line auth/auth.qtpl:41 -func Register(rq *http.Request) string { -//line auth/auth.qtpl:41 - qb422016 := qt422016.AcquireByteBuffer() -//line auth/auth.qtpl:41 - WriteRegister(qb422016, rq) -//line auth/auth.qtpl:41 - qs422016 := string(qb422016.B) -//line auth/auth.qtpl:41 - qt422016.ReleaseByteBuffer(qb422016) -//line auth/auth.qtpl:41 - return qs422016 -//line auth/auth.qtpl:41 -} - -//line auth/auth.qtpl:43 -func StreamLogin(qw422016 *qt422016.Writer, lc *l18n.Localizer) { -//line auth/auth.qtpl:43 - qw422016.N().S(` -
    -
    - `) -//line auth/auth.qtpl:46 - if cfg.UseAuth { -//line auth/auth.qtpl:46 - qw422016.N().S(` - - `) -//line auth/auth.qtpl:62 - streamtelegramWidget(qw422016, lc) -//line auth/auth.qtpl:62 - qw422016.N().S(` - `) -//line auth/auth.qtpl:63 - } else { -//line auth/auth.qtpl:63 - qw422016.N().S(` -

    `) -//line auth/auth.qtpl:64 - qw422016.E().S(lc.Get("auth.noauth")) -//line auth/auth.qtpl:64 - qw422016.N().S(`

    -

    ← `) -//line auth/auth.qtpl:65 - qw422016.E().S(lc.Get("auth.go_home")) -//line auth/auth.qtpl:65 - qw422016.N().S(`

    - `) -//line auth/auth.qtpl:66 - } -//line auth/auth.qtpl:66 - qw422016.N().S(` -
    -
    -`) -//line auth/auth.qtpl:69 -} - -//line auth/auth.qtpl:69 -func WriteLogin(qq422016 qtio422016.Writer, lc *l18n.Localizer) { -//line auth/auth.qtpl:69 - qw422016 := qt422016.AcquireWriter(qq422016) -//line auth/auth.qtpl:69 - StreamLogin(qw422016, lc) -//line auth/auth.qtpl:69 - qt422016.ReleaseWriter(qw422016) -//line auth/auth.qtpl:69 -} - -//line auth/auth.qtpl:69 -func Login(lc *l18n.Localizer) string { -//line auth/auth.qtpl:69 - qb422016 := qt422016.AcquireByteBuffer() -//line auth/auth.qtpl:69 - WriteLogin(qb422016, lc) -//line auth/auth.qtpl:69 - qs422016 := string(qb422016.B) -//line auth/auth.qtpl:69 - qt422016.ReleaseByteBuffer(qb422016) -//line auth/auth.qtpl:69 - return qs422016 -//line auth/auth.qtpl:69 -} - -// Telegram auth widget was requested by Yogurt. As you can see, we don't offer user administrators control over it. Of course we don't. - -//line auth/auth.qtpl:72 -func streamtelegramWidget(qw422016 *qt422016.Writer, lc *l18n.Localizer) { -//line auth/auth.qtpl:72 - qw422016.N().S(` -`) -//line auth/auth.qtpl:73 - if cfg.TelegramEnabled { -//line auth/auth.qtpl:73 - qw422016.N().S(` -

    `) -//line auth/auth.qtpl:74 - qw422016.E().S(lc.Get("auth.telegram_tip")) -//line auth/auth.qtpl:74 - qw422016.N().S(`

    - -`) -//line auth/auth.qtpl:76 - } -//line auth/auth.qtpl:76 - qw422016.N().S(` -`) -//line auth/auth.qtpl:77 -} - -//line auth/auth.qtpl:77 -func writetelegramWidget(qq422016 qtio422016.Writer, lc *l18n.Localizer) { -//line auth/auth.qtpl:77 - qw422016 := qt422016.AcquireWriter(qq422016) -//line auth/auth.qtpl:77 - streamtelegramWidget(qw422016, lc) -//line auth/auth.qtpl:77 - qt422016.ReleaseWriter(qw422016) -//line auth/auth.qtpl:77 -} - -//line auth/auth.qtpl:77 -func telegramWidget(lc *l18n.Localizer) string { -//line auth/auth.qtpl:77 - qb422016 := qt422016.AcquireByteBuffer() -//line auth/auth.qtpl:77 - writetelegramWidget(qb422016, lc) -//line auth/auth.qtpl:77 - qs422016 := string(qb422016.B) -//line auth/auth.qtpl:77 - qt422016.ReleaseByteBuffer(qb422016) -//line auth/auth.qtpl:77 - return qs422016 -//line auth/auth.qtpl:77 -} - -//line auth/auth.qtpl:79 -func StreamLoginError(qw422016 *qt422016.Writer, err string, lc *l18n.Localizer) { -//line auth/auth.qtpl:79 - qw422016.N().S(` -
    -
    - `) -//line auth/auth.qtpl:82 - switch err { -//line auth/auth.qtpl:83 - case "unknown username": -//line auth/auth.qtpl:83 - qw422016.N().S(` -

    `) -//line auth/auth.qtpl:84 - qw422016.E().S(lc.Get("auth.error_username")) -//line auth/auth.qtpl:84 - qw422016.N().S(`

    - `) -//line auth/auth.qtpl:85 - case "wrong password": -//line auth/auth.qtpl:85 - qw422016.N().S(` -

    `) -//line auth/auth.qtpl:86 - qw422016.E().S(lc.Get("auth.error_password")) -//line auth/auth.qtpl:86 - qw422016.N().S(`

    - `) -//line auth/auth.qtpl:87 - default: -//line auth/auth.qtpl:87 - qw422016.N().S(` -

    `) -//line auth/auth.qtpl:88 - qw422016.E().S(err) -//line auth/auth.qtpl:88 - qw422016.N().S(`

    - `) -//line auth/auth.qtpl:89 - } -//line auth/auth.qtpl:89 - qw422016.N().S(` -

    ← `) -//line auth/auth.qtpl:90 - qw422016.E().S(lc.Get("auth.try_again")) -//line auth/auth.qtpl:90 - qw422016.N().S(`

    -
    -
    -`) -//line auth/auth.qtpl:93 -} - -//line auth/auth.qtpl:93 -func WriteLoginError(qq422016 qtio422016.Writer, err string, lc *l18n.Localizer) { -//line auth/auth.qtpl:93 - qw422016 := qt422016.AcquireWriter(qq422016) -//line auth/auth.qtpl:93 - StreamLoginError(qw422016, err, lc) -//line auth/auth.qtpl:93 - qt422016.ReleaseWriter(qw422016) -//line auth/auth.qtpl:93 -} - -//line auth/auth.qtpl:93 -func LoginError(err string, lc *l18n.Localizer) string { -//line auth/auth.qtpl:93 - qb422016 := qt422016.AcquireByteBuffer() -//line auth/auth.qtpl:93 - WriteLoginError(qb422016, err, lc) -//line auth/auth.qtpl:93 - qs422016 := string(qb422016.B) -//line auth/auth.qtpl:93 - qt422016.ReleaseByteBuffer(qb422016) -//line auth/auth.qtpl:93 - return qs422016 -//line auth/auth.qtpl:93 -} - -//line auth/auth.qtpl:95 -func StreamLogout(qw422016 *qt422016.Writer, can bool, lc *l18n.Localizer) { -//line auth/auth.qtpl:95 - qw422016.N().S(` -
    -
    - `) -//line auth/auth.qtpl:98 - if can { -//line auth/auth.qtpl:98 - qw422016.N().S(` -

    `) -//line auth/auth.qtpl:99 - qw422016.E().S(lc.Get("auth.logout_header")) -//line auth/auth.qtpl:99 - qw422016.N().S(`

    -
    - - `) -//line auth/auth.qtpl:102 - qw422016.E().S(lc.Get("auth.go_home")) -//line auth/auth.qtpl:102 - qw422016.N().S(` -
    - `) -//line auth/auth.qtpl:104 - } else { -//line auth/auth.qtpl:104 - qw422016.N().S(` -

    `) -//line auth/auth.qtpl:105 - qw422016.E().S(lc.Get("auth.logout_anon")) -//line auth/auth.qtpl:105 - qw422016.N().S(`

    -

    `) -//line auth/auth.qtpl:106 - qw422016.E().S(lc.Get("auth.login_title")) -//line auth/auth.qtpl:106 - qw422016.N().S(`

    -

    ← `) -//line auth/auth.qtpl:107 - qw422016.E().S(lc.Get("auth.go_home")) -//line auth/auth.qtpl:107 - qw422016.N().S(`

    - `) -//line auth/auth.qtpl:108 - } -//line auth/auth.qtpl:108 - qw422016.N().S(` -
    -
    -`) -//line auth/auth.qtpl:111 -} - -//line auth/auth.qtpl:111 -func WriteLogout(qq422016 qtio422016.Writer, can bool, lc *l18n.Localizer) { -//line auth/auth.qtpl:111 - qw422016 := qt422016.AcquireWriter(qq422016) -//line auth/auth.qtpl:111 - StreamLogout(qw422016, can, lc) -//line auth/auth.qtpl:111 - qt422016.ReleaseWriter(qw422016) -//line auth/auth.qtpl:111 -} - -//line auth/auth.qtpl:111 -func Logout(can bool, lc *l18n.Localizer) string { -//line auth/auth.qtpl:111 - qb422016 := qt422016.AcquireByteBuffer() -//line auth/auth.qtpl:111 - WriteLogout(qb422016, can, lc) -//line auth/auth.qtpl:111 - qs422016 := string(qb422016.B) -//line auth/auth.qtpl:111 - qt422016.ReleaseByteBuffer(qb422016) -//line auth/auth.qtpl:111 - return qs422016 -//line auth/auth.qtpl:111 -} - -//line auth/auth.qtpl:113 -func StreamLock(qw422016 *qt422016.Writer, lc *l18n.Localizer) { -//line auth/auth.qtpl:113 - qw422016.N().S(` - - - - - - 🔒 `) -//line auth/auth.qtpl:119 - qw422016.E().S(lc.Get("auth.lock_title")) -//line auth/auth.qtpl:119 - qw422016.N().S(` - - - - -
    -
    -

    🔒

    -

    `) -//line auth/auth.qtpl:127 - qw422016.E().S(lc.Get("auth.lock_title")) -//line auth/auth.qtpl:127 - qw422016.N().S(`

    - - `) -//line auth/auth.qtpl:139 - streamtelegramWidget(qw422016, lc) -//line auth/auth.qtpl:139 - qw422016.N().S(` -
    -
    - - -`) -//line auth/auth.qtpl:144 -} - -//line auth/auth.qtpl:144 -func WriteLock(qq422016 qtio422016.Writer, lc *l18n.Localizer) { -//line auth/auth.qtpl:144 - qw422016 := qt422016.AcquireWriter(qq422016) -//line auth/auth.qtpl:144 - StreamLock(qw422016, lc) -//line auth/auth.qtpl:144 - qt422016.ReleaseWriter(qw422016) -//line auth/auth.qtpl:144 -} - -//line auth/auth.qtpl:144 -func Lock(lc *l18n.Localizer) string { -//line auth/auth.qtpl:144 - qb422016 := qt422016.AcquireByteBuffer() -//line auth/auth.qtpl:144 - WriteLock(qb422016, lc) -//line auth/auth.qtpl:144 - qs422016 := string(qb422016.B) -//line auth/auth.qtpl:144 - qt422016.ReleaseByteBuffer(qb422016) -//line auth/auth.qtpl:144 - return qs422016 -//line auth/auth.qtpl:144 -} - -//line auth/auth.qtpl:147 -var userListL10n = map[string]L10nEntry{ - "heading": En("List of users").Ru("Список пользователей"), - "administrators": En("Administrators").Ru("Администраторы"), - "moderators": En("Moderators").Ru("Модераторы"), - "editors": En("Editors").Ru("Редакторы"), - "readers": En("Readers").Ru("Читатели"), -} - -//line auth/auth.qtpl:156 -func StreamUserList(qw422016 *qt422016.Writer, lc *l18n.Localizer) { -//line auth/auth.qtpl:156 - qw422016.N().S(` -
    -`) -//line auth/auth.qtpl:159 - var get = func(key string) string { - return userListL10n[key].Get(lc.Locale) - } - - var ( - admins = make([]string, 0) - moderators = make([]string, 0) - editors = make([]string, 0) - readers = make([]string, 0) - ) - for u := range user.YieldUsers() { - switch u.Group { - // What if we place the users into sorted slices? - case "admin": - admins = append(admins, u.Name) - case "moderator": - moderators = append(moderators, u.Name) - case "editor", "trusted": - editors = append(editors, u.Name) - case "reader": - readers = append(readers, u.Name) - } - } - sort.Strings(admins) - sort.Strings(moderators) - sort.Strings(editors) - sort.Strings(readers) - -//line auth/auth.qtpl:186 - qw422016.N().S(` -

    `) -//line auth/auth.qtpl:187 - qw422016.E().S(get("heading")) -//line auth/auth.qtpl:187 - qw422016.N().S(`

    -
    -

    `) -//line auth/auth.qtpl:189 - qw422016.E().S(get("administrators")) -//line auth/auth.qtpl:189 - qw422016.N().S(`

    -
      `) -//line auth/auth.qtpl:190 - for _, name := range admins { -//line auth/auth.qtpl:190 - qw422016.N().S(` -
    1. `) -//line auth/auth.qtpl:191 - qw422016.E().S(name) -//line auth/auth.qtpl:191 - qw422016.N().S(`
    2. - `) -//line auth/auth.qtpl:192 - } -//line auth/auth.qtpl:192 - qw422016.N().S(`
    -
    -
    -

    `) -//line auth/auth.qtpl:195 - qw422016.E().S(get("moderators")) -//line auth/auth.qtpl:195 - qw422016.N().S(`

    -
      `) -//line auth/auth.qtpl:196 - for _, name := range moderators { -//line auth/auth.qtpl:196 - qw422016.N().S(` -
    1. `) -//line auth/auth.qtpl:197 - qw422016.E().S(name) -//line auth/auth.qtpl:197 - qw422016.N().S(`
    2. - `) -//line auth/auth.qtpl:198 - } -//line auth/auth.qtpl:198 - qw422016.N().S(`
    -
    -
    -

    `) -//line auth/auth.qtpl:201 - qw422016.E().S(get("editors")) -//line auth/auth.qtpl:201 - qw422016.N().S(`

    -
      `) -//line auth/auth.qtpl:202 - for _, name := range editors { -//line auth/auth.qtpl:202 - qw422016.N().S(` -
    1. `) -//line auth/auth.qtpl:203 - qw422016.E().S(name) -//line auth/auth.qtpl:203 - qw422016.N().S(`
    2. - `) -//line auth/auth.qtpl:204 - } -//line auth/auth.qtpl:204 - qw422016.N().S(`
    -
    -
    -

    `) -//line auth/auth.qtpl:207 - qw422016.E().S(get("readers")) -//line auth/auth.qtpl:207 - qw422016.N().S(`

    -
      `) -//line auth/auth.qtpl:208 - for _, name := range readers { -//line auth/auth.qtpl:208 - qw422016.N().S(` -
    1. `) -//line auth/auth.qtpl:209 - qw422016.E().S(name) -//line auth/auth.qtpl:209 - qw422016.N().S(`
    2. - `) -//line auth/auth.qtpl:210 - } -//line auth/auth.qtpl:210 - qw422016.N().S(`
    -
    -
    -`) -//line auth/auth.qtpl:213 -} - -//line auth/auth.qtpl:213 -func WriteUserList(qq422016 qtio422016.Writer, lc *l18n.Localizer) { -//line auth/auth.qtpl:213 - qw422016 := qt422016.AcquireWriter(qq422016) -//line auth/auth.qtpl:213 - StreamUserList(qw422016, lc) -//line auth/auth.qtpl:213 - qt422016.ReleaseWriter(qw422016) -//line auth/auth.qtpl:213 -} - -//line auth/auth.qtpl:213 -func UserList(lc *l18n.Localizer) string { -//line auth/auth.qtpl:213 - qb422016 := qt422016.AcquireByteBuffer() -//line auth/auth.qtpl:213 - WriteUserList(qb422016, lc) -//line auth/auth.qtpl:213 - qs422016 := string(qb422016.B) -//line auth/auth.qtpl:213 - qt422016.ReleaseByteBuffer(qb422016) -//line auth/auth.qtpl:213 - return qs422016 -//line auth/auth.qtpl:213 -} diff --git a/auth/get_rid_of_it.go b/auth/get_rid_of_it.go deleted file mode 100644 index a7b0c91..0000000 --- a/auth/get_rid_of_it.go +++ /dev/null @@ -1,22 +0,0 @@ -package auth - -type L10nEntry struct { - _en string - _ru string -} - -func En(v string) L10nEntry { - return L10nEntry{_en: v} -} - -func (e L10nEntry) Ru(v string) L10nEntry { - e._ru = v - return e -} - -func (e L10nEntry) Get(lang string) string { - if lang == "ru" && e._ru != "" { - return e._ru - } - return e._en -} diff --git a/auth/web.go b/auth/web.go deleted file mode 100644 index ee93bc9..0000000 --- a/auth/web.go +++ /dev/null @@ -1,225 +0,0 @@ -package auth - -import ( - "errors" - "fmt" - "io" - "log" - "mime" - "net/http" - "strings" - - "github.com/bouncepaw/mycorrhiza/viewutil" - - "github.com/gorilla/mux" - - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/util" -) - -func InitAuth(r *mux.Router) { - r.HandleFunc("/user-list", handlerUserList) - r.HandleFunc("/lock", handlerLock) - // The check below saves a lot of extra checks and lines of codes in other places in this file. - if !cfg.UseAuth { - return - } - if cfg.AllowRegistration { - r.HandleFunc("/register", handlerRegister).Methods(http.MethodPost, http.MethodGet) - } - if cfg.TelegramEnabled { - r.HandleFunc("/telegram-login", handlerTelegramLogin) - } - r.HandleFunc("/login", handlerLogin) - r.HandleFunc("/logout", handlerLogout) -} - -func handlerUserList(w http.ResponseWriter, rq *http.Request) { - lc := l18n.FromRequest(rq) - w.Header().Set("Content-Type", mime.TypeByExtension(".html")) - w.WriteHeader(http.StatusOK) - w.Write([]byte(viewutil.Base(viewutil.MetaFrom(w, rq), lc.Get("ui.users_title"), UserList(lc), map[string]string{}))) -} - -func handlerLock(w http.ResponseWriter, rq *http.Request) { - _, _ = io.WriteString(w, Lock(l18n.FromRequest(rq))) -} - -// handlerRegister displays the register form (GET) or registers the user (POST). -func handlerRegister(w http.ResponseWriter, rq *http.Request) { - lc := l18n.FromRequest(rq) - util.PrepareRq(rq) - if rq.Method == http.MethodGet { - _, _ = io.WriteString( - w, - viewutil.Base( - viewutil.MetaFrom(w, rq), - lc.Get("auth.register_title"), - Register(rq), - map[string]string{}, - ), - ) - return - } - - var ( - username = rq.PostFormValue("username") - password = rq.PostFormValue("password") - err = user.Register(username, password, "editor", "local", false) - ) - if err != nil { - log.Printf("Failed to register ‘%s’: %s", username, err.Error()) - w.Header().Set("Content-Type", mime.TypeByExtension(".html")) - w.WriteHeader(http.StatusBadRequest) - _, _ = io.WriteString( - w, - viewutil.Base( - viewutil.MetaFrom(w, rq), - lc.Get("auth.register_title"), - fmt.Sprintf( - `

    %s

    %s

    `, - err.Error(), - lc.Get("auth.try_again"), - ), - map[string]string{}, - ), - ) - return - } - - log.Printf("Successfully registered ‘%s’", username) - if err := user.LoginDataHTTP(w, username, password); err != nil { - return - } - http.Redirect(w, rq, "/"+rq.URL.RawQuery, http.StatusSeeOther) -} - -// handlerLogout shows the logout form (GET) or logs the user out (POST). -func handlerLogout(w http.ResponseWriter, rq *http.Request) { - if rq.Method == http.MethodGet { - var ( - u = user.FromRequest(rq) - can = u != nil - lc = l18n.FromRequest(rq) - ) - w.Header().Set("Content-Type", "text/html;charset=utf-8") - if can { - log.Println("User", u.Name, "tries to log out") - w.WriteHeader(http.StatusOK) - } else { - log.Println("Unknown user tries to log out") - w.WriteHeader(http.StatusForbidden) - } - _, _ = io.WriteString( - w, - viewutil.Base(viewutil.MetaFrom(w, rq), lc.Get("auth.logout_title"), Logout(can, lc), map[string]string{}), - ) - } else if rq.Method == http.MethodPost { - user.LogoutFromRequest(w, rq) - http.Redirect(w, rq, "/", http.StatusSeeOther) - } -} - -// handlerLogin shows the login form (GET) or logs the user in (POST). -func handlerLogin(w http.ResponseWriter, rq *http.Request) { - lc := l18n.FromRequest(rq) - if rq.Method == http.MethodGet { - w.Header().Set("Content-Type", "text/html;charset=utf-8") - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString( - w, - viewutil.Base( - viewutil.MetaFrom(w, rq), - lc.Get("auth.login_title"), - Login(lc), - map[string]string{}, - ), - ) - } else if rq.Method == http.MethodPost { - var ( - username = util.CanonicalName(rq.PostFormValue("username")) - password = rq.PostFormValue("password") - err = user.LoginDataHTTP(w, username, password) - ) - if err != nil { - w.Header().Set("Content-Type", "text/html;charset=utf-8") - w.WriteHeader(http.StatusInternalServerError) - _, _ = io.WriteString(w, viewutil.Base(viewutil.MetaFrom(w, rq), err.Error(), LoginError(err.Error(), lc), map[string]string{})) - return - } - http.Redirect(w, rq, "/", http.StatusSeeOther) - } -} - -func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) { - // Note there is no lock here. - lc := l18n.FromRequest(rq) - w.Header().Set("Content-Type", "text/html;charset=utf-8") - rq.ParseForm() - var ( - values = rq.URL.Query() - username = strings.ToLower(values.Get("username")) - seemsValid = user.TelegramAuthParamsAreValid(values) - err = user.Register( - username, - "", // Password matters not - "editor", - "telegram", - false, - ) - ) - // If registering a user via Telegram failed, because a Telegram user with this name - // has already registered, then everything is actually ok! - if user.HasUsername(username) && user.ByName(username).Source == "telegram" { - err = nil - } - - if !seemsValid { - err = errors.New("Wrong parameters") - } - - if err != nil { - log.Printf("Failed to register ‘%s’ using Telegram: %s", username, err.Error()) - w.WriteHeader(http.StatusBadRequest) - _, _ = io.WriteString( - w, - viewutil.Base( - viewutil.MetaFrom(w, rq), - lc.Get("ui.error"), - fmt.Sprintf( - `

    %s

    %s

    %s

    `, - lc.Get("auth.error_telegram"), - err.Error(), - lc.Get("auth.go_login"), - ), - map[string]string{}, - ), - ) - return - } - - errmsg := user.LoginDataHTTP(w, username, "") - if errmsg != nil { - log.Printf("Failed to login ‘%s’ using Telegram: %s", username, err.Error()) - w.WriteHeader(http.StatusBadRequest) - _, _ = io.WriteString( - w, - viewutil.Base( - viewutil.MetaFrom(w, rq), - "Error", - fmt.Sprintf( - `

    %s

    %s

    %s

    `, - lc.Get("auth.error_telegram"), - err.Error(), - lc.Get("auth.go_login"), - ), - map[string]string{}, - ), - ) - return - } - log.Printf("Authorize ‘%s’ from Telegram", username) - http.Redirect(w, rq, "/", http.StatusSeeOther) -} diff --git a/backlinks/web.go b/backlinks/web.go deleted file mode 100644 index ff5cb75..0000000 --- a/backlinks/web.go +++ /dev/null @@ -1,85 +0,0 @@ -package backlinks - -import ( - "embed" - "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/viewutil" - "github.com/gorilla/mux" - "net/http" - "sort" -) - -func InitHandlers(rtr *mux.Router) { - rtr.PathPrefix("/backlinks/").HandlerFunc(handlerBacklinks) - rtr.PathPrefix("/orphans").HandlerFunc(handlerOrphans) - chainBacklinks = viewutil.CopyEnRuWith(fs, "view_backlinks.html", ruTranslation) - chainOrphans = viewutil.CopyEnRuWith(fs, "view_orphans.html", ruTranslation) -} - -// handlerBacklinks lists all backlinks to a hypha. -func handlerBacklinks(w http.ResponseWriter, rq *http.Request) { - var ( - hyphaName = util.HyphaNameFromRq(rq, "backlinks") - backlinks []string - ) - for b := range yieldHyphaBacklinks(hyphaName) { - backlinks = append(backlinks, b) - } - viewBacklinks(viewutil.MetaFrom(w, rq), hyphaName, backlinks) -} - -func handlerOrphans(w http.ResponseWriter, rq *http.Request) { - var orphans []string - for h := range hyphae.YieldExistingHyphae() { - if BacklinksCount(h.CanonicalName()) == 0 { - orphans = append(orphans, h.CanonicalName()) - } - } - sort.Strings(orphans) - viewOrphans(viewutil.MetaFrom(w, rq), orphans) -} - -var ( - //go:embed *.html - fs embed.FS - ruTranslation = ` -{{define "backlinks to text"}}Обратные ссылки на {{.}}{{end}} -{{define "backlinks to link"}}Обратные ссылки на {{beautifulName .}}{{end}} -{{define "description"}}Ниже перечислены гифы, на которых есть ссылка на эту гифу, трансклюзия этой гифы или эта гифа вставлена как изображение.{{end}} -{{define "orphaned hyphae"}}Гифы-сироты{{end}} -{{define "orphan description"}}Ниже перечислены гифы без ссылок на них.{{end}} -` - chainBacklinks viewutil.Chain - chainOrphans viewutil.Chain -) - -type backlinksData struct { - *viewutil.BaseData - HyphaName string - Backlinks []string -} - -func viewBacklinks(meta viewutil.Meta, hyphaName string, backlinks []string) { - viewutil.ExecutePage(meta, chainBacklinks, backlinksData{ - BaseData: &viewutil.BaseData{ - Addr: "/backlinks/" + hyphaName, - }, - HyphaName: hyphaName, - Backlinks: backlinks, - }) -} - -type orphansData struct { - *viewutil.BaseData - Orphans []string -} - -func viewOrphans(meta viewutil.Meta, orphans []string) { - viewutil.ExecutePage(meta, chainOrphans, orphansData{ - BaseData: &viewutil.BaseData{ - Addr: "/orphans", - }, - Orphans: orphans, - }) -} diff --git a/categories/view_card.html b/categories/view_card.html deleted file mode 100644 index 3262154..0000000 --- a/categories/view_card.html +++ /dev/null @@ -1,37 +0,0 @@ -{{define "category card"}} -{{if or .GivenPermissionToModify (len .Categories)}} - {{$hyphaName := .HyphaName}} - {{$givenPermission := .GivenPermissionToModify}} - -{{end}}{{end}} \ No newline at end of file diff --git a/categories/view_edit.html b/categories/view_edit.html deleted file mode 100644 index b8e93d6..0000000 --- a/categories/view_edit.html +++ /dev/null @@ -1,37 +0,0 @@ -{{define "edit category x"}}Edit category {{beautifulName .}}{{end}} -{{define "title"}}{{template "edit category x" .CatName}}{{end}} -{{define "body"}} -
    -

    {{block "edit category heading" .CatName}}Edit category {{beautifulName .}}{{end}}

    - {{if len .Hyphae | not}} -

    {{block "empty cat" .}}This category is empty{{end}}

    - {{end}} - - {{if .GivenPermissionToModify}} -

    {{block "add to category title" .}}Add a hypha to the category{{end}}

    -
    - - - - -
    - - {{if len .Hyphae}} -

    {{block "remove hyphae" .}}Remove hyphae from the category{{end}}

    -
    -
      - {{range .Hyphae}} -
    1. - - -
    2. - {{end}} -
    - - - -
    - {{end}}{{end}} -
    -{{end}} diff --git a/categories/views.go b/categories/views.go deleted file mode 100644 index 9292ede..0000000 --- a/categories/views.go +++ /dev/null @@ -1,107 +0,0 @@ -package categories - -import ( - "embed" - "github.com/bouncepaw/mycorrhiza/viewutil" - "log" - "sort" - "strings" -) - -const ruTranslation = ` -{{define "empty cat"}}Эта категория пуста.{{end}} -{{define "cat"}}Категория{{end}} -{{define "hypha name"}}Название гифы{{end}} -{{define "categories"}}Категории{{end}} -{{define "placeholder"}}Название категории...{{end}} -{{define "remove from category title"}}Убрать гифу из этой категории{{end}} -{{define "add to category title"}}Добавить гифу в эту категорию{{end}} -{{define "category list"}}Список категорий{{end}} -{{define "no categories"}}В этой вики нет категорий.{{end}} -{{define "category x"}}Категория {{. | beautifulName}}{{end}} - -{{define "edit category x"}}Редактирование категории {{beautifulName .}}{{end}} -{{define "edit category heading"}}Редактирование категории {{beautifulName .}}{{end}} -{{define "add"}}Добавить{{end}} -{{define "remove hyphae"}}Убрать гифы из этой категории{{end}} -{{define "remove"}}Убрать{{end}} -{{define "edit"}}Редактировать{{end}} -` - -var ( - //go:embed *.html - fs embed.FS - viewListChain, viewPageChain, viewCardChain, viewEditChain viewutil.Chain -) - -func prepareViews() { - viewCardChain = viewutil.CopyEnRuWith(fs, "view_card.html", ruTranslation) - viewListChain = viewutil.CopyEnRuWith(fs, "view_list.html", ruTranslation) - viewPageChain = viewutil.CopyEnRuWith(fs, "view_page.html", ruTranslation) - viewEditChain = viewutil.CopyEnRuWith(fs, "view_edit.html", ruTranslation) -} - -type cardData struct { - HyphaName string - Categories []string - GivenPermissionToModify bool -} - -// CategoryCard is the little sidebar that is shown nearby the hypha view. -func CategoryCard(meta viewutil.Meta, hyphaName string) string { - var buf strings.Builder - err := viewCardChain.Get(meta).ExecuteTemplate(&buf, "category card", cardData{ - HyphaName: hyphaName, - Categories: CategoriesWithHypha(hyphaName), - GivenPermissionToModify: meta.U.CanProceed("add-to-category"), - }) - if err != nil { - log.Println(err) - } - return buf.String() -} - -type catData struct { - *viewutil.BaseData - CatName string - Hyphae []string - GivenPermissionToModify bool -} - -func categoryEdit(meta viewutil.Meta, catName string) { - viewutil.ExecutePage(meta, viewEditChain, catData{ - BaseData: &viewutil.BaseData{ - Addr: "/edit-category/" + catName, - }, - CatName: catName, - Hyphae: hyphaeInCategory(catName), - GivenPermissionToModify: meta.U.CanProceed("add-to-category"), - }) -} - -func categoryPage(meta viewutil.Meta, catName string) { - viewutil.ExecutePage(meta, viewPageChain, catData{ - BaseData: &viewutil.BaseData{ - Addr: "/category/" + catName, - }, - CatName: catName, - Hyphae: hyphaeInCategory(catName), - GivenPermissionToModify: meta.U.CanProceed("add-to-category"), - }) -} - -type listData struct { - *viewutil.BaseData - Categories []string -} - -func categoryList(meta viewutil.Meta) { - cats := listOfCategories() - sort.Strings(cats) - viewutil.ExecutePage(meta, viewListChain, listData{ - BaseData: &viewutil.BaseData{ - Addr: "/category", - }, - Categories: cats, - }) -} diff --git a/flag.go b/flag.go index 8253b84..70646bf 100644 --- a/flag.go +++ b/flag.go @@ -12,17 +12,17 @@ import ( "golang.org/x/term" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/files" - "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/version" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/files" + user2 "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/internal/version" ) // CLI options are read and parsed here. // printHelp prints the help message. func printHelp() { - fmt.Fprintf( + _, _ = fmt.Fprintf( flag.CommandLine.Output(), "Usage: %s WIKI_PATH\n", os.Args[0], @@ -70,13 +70,13 @@ func createAdminCommand(name string) { } cfg.UseAuth = true cfg.AllowRegistration = true - user.InitUserDatabase() + user2.InitUserDatabase() password, err := askPass("Password") if err != nil { log.Fatal(err) } - if err := user.Register(name, password, "admin", "local", true); err != nil { + if err := user2.Register(name, password, "admin", "local", true); err != nil { log.Fatal(err) } } diff --git a/help/web.go b/help/web.go index c6b3516..2259fac 100644 --- a/help/web.go +++ b/help/web.go @@ -2,15 +2,17 @@ package help // stuff.go is used for meta stuff about the wiki or all hyphae at once. import ( - "git.sr.ht/~bouncepaw/mycomarkup/v5" - "github.com/bouncepaw/mycorrhiza/mycoopts" - "github.com/bouncepaw/mycorrhiza/viewutil" - "github.com/gorilla/mux" "io" "net/http" "strings" + "github.com/bouncepaw/mycorrhiza/mycoopts" + "github.com/bouncepaw/mycorrhiza/web/viewutil" + + "git.sr.ht/~bouncepaw/mycomarkup/v5" "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" + + "github.com/gorilla/mux" ) var ( diff --git a/history/feed.go b/history/feed.go index 65a88e7..4906816 100644 --- a/history/feed.go +++ b/history/feed.go @@ -3,12 +3,11 @@ package history import ( "errors" "fmt" + "github.com/bouncepaw/mycorrhiza/internal/cfg" "net/url" "strings" "time" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/gorilla/feeds" ) diff --git a/history/history.go b/history/history.go index 571477c..b889e80 100644 --- a/history/history.go +++ b/history/history.go @@ -9,7 +9,7 @@ import ( "path/filepath" "regexp" - "github.com/bouncepaw/mycorrhiza/files" + "github.com/bouncepaw/mycorrhiza/internal/files" "github.com/bouncepaw/mycorrhiza/util" ) diff --git a/history/histweb/histview.go b/history/histweb/histview.go index a761d43..196b998 100644 --- a/history/histweb/histview.go +++ b/history/histweb/histview.go @@ -4,12 +4,12 @@ package histweb import ( "embed" "fmt" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/files" + hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/viewutil" + viewutil2 "github.com/bouncepaw/mycorrhiza/web/viewutil" "github.com/gorilla/mux" "html/template" "log" @@ -30,9 +30,9 @@ func InitHandlers(rtr *mux.Router) { rtr.HandleFunc("/recent-changes-atom", handlerRecentChangesAtom) rtr.HandleFunc("/recent-changes-json", handlerRecentChangesJSON) - chainPrimitiveDiff = viewutil.CopyEnRuWith(fs, "view_primitive_diff.html", ruTranslation) - chainRecentChanges = viewutil.CopyEnRuWith(fs, "view_recent_changes.html", ruTranslation) - chainHistory = viewutil.CopyEnRuWith(fs, "view_history.html", ruTranslation) + chainPrimitiveDiff = viewutil2.CopyEnRuWith(fs, "view_primitive_diff.html", ruTranslation) + chainRecentChanges = viewutil2.CopyEnRuWith(fs, "view_recent_changes.html", ruTranslation) + chainHistory = viewutil2.CopyEnRuWith(fs, "view_history.html", ruTranslation) } func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { @@ -45,12 +45,12 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { } var ( mycoFilePath string - h = hyphae.ByName(util.CanonicalName(slug)) + h = hyphae2.ByName(util.CanonicalName(slug)) ) switch h := h.(type) { - case hyphae.ExistingHypha: + case hyphae2.ExistingHypha: mycoFilePath = h.TextFilePath() - case *hyphae.EmptyHypha: + case *hyphae2.EmptyHypha: mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco") } text, err := history.PrimitiveDiffAtRevision(mycoFilePath, revHash) @@ -58,7 +58,7 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - primitiveDiff(viewutil.MetaFrom(w, rq), h, revHash, text) + primitiveDiff(viewutil2.MetaFrom(w, rq), h, revHash, text) } // handlerRecentChanges displays the /recent-changes/ page. @@ -68,7 +68,7 @@ func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { if editCount > 100 { return } - recentChanges(viewutil.MetaFrom(w, rq), editCount, history.RecentChanges(editCount)) + recentChanges(viewutil2.MetaFrom(w, rq), editCount, history.RecentChanges(editCount)) } // handlerHistory lists all revisions of a hypha. @@ -83,7 +83,7 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) { } log.Println("Found", len(revs), "revisions for", hyphaName) - historyView(viewutil.MetaFrom(w, rq), hyphaName, list) + historyView(viewutil2.MetaFrom(w, rq), hyphaName, list) } // genericHandlerOfFeeds is a helper function for the web feed handlers. @@ -135,20 +135,20 @@ var ( {{define "n recent changes"}}{{.}} свеж{{if eq . 1}}ая правка{{else if le . 4}}их правок{{else}}их правок{{end}}{{end}} {{define "recent empty"}}Правки не найдены.{{end}} ` - chainPrimitiveDiff, chainRecentChanges, chainHistory viewutil.Chain + chainPrimitiveDiff, chainRecentChanges, chainHistory viewutil2.Chain ) type recentChangesData struct { - *viewutil.BaseData + *viewutil2.BaseData EditCount int Changes []history.Revision UserHypha string Stops []int } -func recentChanges(meta viewutil.Meta, editCount int, changes []history.Revision) { - viewutil.ExecutePage(meta, chainRecentChanges, recentChangesData{ - BaseData: &viewutil.BaseData{}, +func recentChanges(meta viewutil2.Meta, editCount int, changes []history.Revision) { + viewutil2.ExecutePage(meta, chainRecentChanges, recentChangesData{ + BaseData: &viewutil2.BaseData{}, EditCount: editCount, Changes: changes, UserHypha: cfg.UserHypha, @@ -157,13 +157,13 @@ func recentChanges(meta viewutil.Meta, editCount int, changes []history.Revision } type primitiveDiffData struct { - *viewutil.BaseData + *viewutil2.BaseData HyphaName string Hash string Text template.HTML } -func primitiveDiff(meta viewutil.Meta, h hyphae.Hypha, hash, text string) { +func primitiveDiff(meta viewutil2.Meta, h hyphae2.Hypha, hash, text string) { hunks := history.SplitPrimitiveDiff(text) if len(hunks) > 0 { var buf strings.Builder @@ -198,8 +198,8 @@ func primitiveDiff(meta viewutil.Meta, h hyphae.Hypha, hash, text string) { text = fmt.Sprintf( `
    %s
    `, text) } - viewutil.ExecutePage(meta, chainPrimitiveDiff, primitiveDiffData{ - BaseData: &viewutil.BaseData{}, + viewutil2.ExecutePage(meta, chainPrimitiveDiff, primitiveDiffData{ + BaseData: &viewutil2.BaseData{}, HyphaName: h.CanonicalName(), Hash: hash, Text: template.HTML(text), @@ -207,14 +207,14 @@ func primitiveDiff(meta viewutil.Meta, h hyphae.Hypha, hash, text string) { } type historyData struct { - *viewutil.BaseData + *viewutil2.BaseData HyphaName string Contents string } -func historyView(meta viewutil.Meta, hyphaName, contents string) { - viewutil.ExecutePage(meta, chainHistory, historyData{ - BaseData: &viewutil.BaseData{ +func historyView(meta viewutil2.Meta, hyphaName, contents string) { + viewutil2.ExecutePage(meta, chainHistory, historyData{ + BaseData: &viewutil2.BaseData{ Addr: "/history/" + util.CanonicalName(hyphaName), }, HyphaName: hyphaName, diff --git a/history/operations.go b/history/operations.go index 66aa21f..0f9eb80 100644 --- a/history/operations.go +++ b/history/operations.go @@ -4,11 +4,11 @@ package history // Things related to writing history. import ( "fmt" + "github.com/bouncepaw/mycorrhiza/internal/user" "os" "path/filepath" "sync" - "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" ) diff --git a/history/revision.go b/history/revision.go index e3aa853..7f04309 100644 --- a/history/revision.go +++ b/history/revision.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "github.com/bouncepaw/mycorrhiza/files" + "github.com/bouncepaw/mycorrhiza/internal/files" ) // Revision represents a revision, duh. Hash is usually short. Username is extracted from email. diff --git a/history/view.qtpl b/history/view.qtpl index 150ba9f..dfba661 100644 --- a/history/view.qtpl +++ b/history/view.qtpl @@ -1,5 +1,5 @@ {% import "fmt" %} -{% import "github.com/bouncepaw/mycorrhiza/cfg" %} +{% import "github.com/bouncepaw/mycorrhiza/internal/cfg" %} HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string. {% func (rev Revision) HyphaeLinksHTML() %} @@ -56,22 +56,18 @@ WithRevisions returns an html representation of `revs` that is meant to be inser {% endfor %} {% endfunc %} - -{% func (rev *Revision) asHistoryEntry(hyphaName string) %} -
  • - - - - {%s rev.Hash %} - {%s rev.Message %} - {% if rev.Username != "anon" %} - - {% endif %} -
  • -{% endfunc %} \ No newline at end of file diff --git a/history/view.qtpl.go b/history/view.qtpl.go index 796a527..7708c28 100644 --- a/history/view.qtpl.go +++ b/history/view.qtpl.go @@ -8,7 +8,7 @@ package history import "fmt" //line history/view.qtpl:2 -import "github.com/bouncepaw/mycorrhiza/cfg" +import "github.com/bouncepaw/mycorrhiza/internal/cfg" // HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string. @@ -283,141 +283,102 @@ func StreamWithRevisions(qw422016 *qt422016.Writer, hyphaName string, revs []Rev for _, rev := range grp { //line history/view.qtpl:58 qw422016.N().S(` - `) -//line history/view.qtpl:59 - rev.streamasHistoryEntry(qw422016, hyphaName) -//line history/view.qtpl:59 +
  • + + + + `) +//line history/view.qtpl:63 + qw422016.E().S(rev.Hash) +//line history/view.qtpl:63 + qw422016.N().S(` + `) +//line history/view.qtpl:64 + qw422016.E().S(rev.Message) +//line history/view.qtpl:64 + qw422016.N().S(` + `) +//line history/view.qtpl:65 + if rev.Username != "anon" { +//line history/view.qtpl:65 + qw422016.N().S(` + + `) +//line history/view.qtpl:67 + } +//line history/view.qtpl:67 qw422016.N().S(` +
  • `) -//line history/view.qtpl:60 +//line history/view.qtpl:69 } -//line history/view.qtpl:60 +//line history/view.qtpl:69 qw422016.N().S(` `) -//line history/view.qtpl:63 +//line history/view.qtpl:72 } -//line history/view.qtpl:63 +//line history/view.qtpl:72 qw422016.N().S(` `) -//line history/view.qtpl:64 +//line history/view.qtpl:73 } -//line history/view.qtpl:64 +//line history/view.qtpl:73 func WriteWithRevisions(qq422016 qtio422016.Writer, hyphaName string, revs []Revision) { -//line history/view.qtpl:64 +//line history/view.qtpl:73 qw422016 := qt422016.AcquireWriter(qq422016) -//line history/view.qtpl:64 +//line history/view.qtpl:73 StreamWithRevisions(qw422016, hyphaName, revs) -//line history/view.qtpl:64 +//line history/view.qtpl:73 qt422016.ReleaseWriter(qw422016) -//line history/view.qtpl:64 +//line history/view.qtpl:73 } -//line history/view.qtpl:64 +//line history/view.qtpl:73 func WithRevisions(hyphaName string, revs []Revision) string { -//line history/view.qtpl:64 +//line history/view.qtpl:73 qb422016 := qt422016.AcquireByteBuffer() -//line history/view.qtpl:64 +//line history/view.qtpl:73 WriteWithRevisions(qb422016, hyphaName, revs) -//line history/view.qtpl:64 - qs422016 := string(qb422016.B) -//line history/view.qtpl:64 - qt422016.ReleaseByteBuffer(qb422016) -//line history/view.qtpl:64 - return qs422016 -//line history/view.qtpl:64 -} - -//line history/view.qtpl:66 -func (rev *Revision) streamasHistoryEntry(qw422016 *qt422016.Writer, hyphaName string) { -//line history/view.qtpl:66 - qw422016.N().S(` -
  • - - - - `) -//line history/view.qtpl:71 - qw422016.E().S(rev.Hash) -//line history/view.qtpl:71 - qw422016.N().S(` - `) -//line history/view.qtpl:72 - qw422016.E().S(rev.Message) -//line history/view.qtpl:72 - qw422016.N().S(` - `) //line history/view.qtpl:73 - if rev.Username != "anon" { -//line history/view.qtpl:73 - qw422016.N().S(` - - `) -//line history/view.qtpl:75 - } -//line history/view.qtpl:75 - qw422016.N().S(` -
  • -`) -//line history/view.qtpl:77 -} - -//line history/view.qtpl:77 -func (rev *Revision) writeasHistoryEntry(qq422016 qtio422016.Writer, hyphaName string) { -//line history/view.qtpl:77 - qw422016 := qt422016.AcquireWriter(qq422016) -//line history/view.qtpl:77 - rev.streamasHistoryEntry(qw422016, hyphaName) -//line history/view.qtpl:77 - qt422016.ReleaseWriter(qw422016) -//line history/view.qtpl:77 -} - -//line history/view.qtpl:77 -func (rev *Revision) asHistoryEntry(hyphaName string) string { -//line history/view.qtpl:77 - qb422016 := qt422016.AcquireByteBuffer() -//line history/view.qtpl:77 - rev.writeasHistoryEntry(qb422016, hyphaName) -//line history/view.qtpl:77 qs422016 := string(qb422016.B) -//line history/view.qtpl:77 +//line history/view.qtpl:73 qt422016.ReleaseByteBuffer(qb422016) -//line history/view.qtpl:77 +//line history/view.qtpl:73 return qs422016 -//line history/view.qtpl:77 +//line history/view.qtpl:73 } diff --git a/httpd.go b/httpd.go index 7ee56e7..ee2af1e 100644 --- a/httpd.go +++ b/httpd.go @@ -1,14 +1,13 @@ package main import ( + "github.com/bouncepaw/mycorrhiza/internal/cfg" "log" "net" "net/http" "os" "strings" "time" - - "github.com/bouncepaw/mycorrhiza/cfg" ) func serveHTTP(handler http.Handler) { diff --git a/hypview/hypview.go b/hypview/hypview.go index 337b054..6881768 100644 --- a/hypview/hypview.go +++ b/hypview/hypview.go @@ -2,72 +2,19 @@ package hypview import ( "embed" - "github.com/bouncepaw/mycorrhiza/backlinks" "html/template" "log" "strings" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/viewutil" + "github.com/bouncepaw/mycorrhiza/internal/backlinks" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/web/viewutil" ) var ( //go:embed *.html fs embed.FS ruTranslation = ` -{{define "editing hypha"}}Редактирование {{beautifulName .}}{{end}} -{{define "editing [[hypha]]"}}Редактирование {{beautifulName .}}{{end}} -{{define "creating [[hypha]]"}}Создание {{beautifulName .}}{{end}} -{{define "you're creating a new hypha"}}Вы создаёте новую гифу.{{end}} -{{define "describe your changes"}}Опишите ваши правки{{end}} -{{define "save"}}Сохранить{{end}} -{{define "preview"}}Предпросмотр{{end}} -{{define "previewing hypha"}}Предпросмотр «{{beautifulName .}}»{{end}} -{{define "preview tip"}}Заметьте, эта гифа ещё не сохранена. Вот её предпросмотр:{{end}} - -{{define "markup"}}Разметка{{end}} -{{define "link"}}Ссылка{{end}} -{{define "link title"}}Текст{{end}} -{{define "heading"}}Заголовок{{end}} -{{define "bold"}}Жирный{{end}} -{{define "italic"}}Курсив{{end}} -{{define "highlight"}}Выделение{{end}} -{{define "underline"}}Подчеркивание{{end}} -{{define "mono"}}Моноширинный{{end}} -{{define "super"}}Надстрочный{{end}} -{{define "sub"}}Подстрочный{{end}} -{{define "strike"}}Зачёркнутый{{end}} -{{define "rocket"}}Ссылка-ракета{{end}} -{{define "transclude"}}Трансклюзия{{end}} -{{define "hr"}}Гориз. черта{{end}} -{{define "code"}}Код-блок{{end}} -{{define "bullets"}}Маркир. список{{end}} -{{define "numbers"}}Нумер. список{{end}} -{{define "mycomarkup help"}}Подробнее о Микоразметке{{end}} -{{define "actions"}}Действия{{end}} -{{define "current date utc"}}Дата UTC{{end}} -{{define "current time utc"}}Время UTC{{end}} -{{define "current date local"}}Местная дата{{end}} -{{define "current time local"}}Местное время{{end}} -{{define "selflink"}}Ссылка на вас{{end}} - -{{define "empty heading"}}Эта гифа не существует{{end}} -{{define "empty no rights"}}У вас нет прав для создания новых гиф. Вы можете:{{end}} -{{define "empty log in"}}Войти в свою учётную запись, если она у вас есть{{end}} -{{define "empty register"}}Создать новую учётную запись{{end}} -{{define "write a text"}}Написать текст{{end}} -{{define "write a text tip"}}Напишите заметку, дневник, статью, рассказ или иной текст с помощью Микоразметки. Сохраняется полная история правок документа.{{end}} -{{define "write a text writing conventions"}}Не забывайте следовать правилам оформления этой вики, если они имеются.{{end}} -{{define "write a text btn"}}Создать{{end}} -{{define "upload a media"}}Загрузить медиа{{end}} -{{define "upload a media tip"}}Загрузите изображение, видео или аудио. Распространённые форматы можно просматривать из браузера, остальные можно только скачать и просмотреть локально. Позже вы можете дописать пояснение к этому медиа.{{end}} -{{define "upload a media btn"}}Загрузить{{end}} - -{{define "delete hypha?"}}Удалить {{beautifulName .}}?{{end}} -{{define "delete [[hypha]]?"}}Удалить {{beautifulName .}}?{{end}} -{{define "want to delete?"}}Вы действительно хотите удалить эту гифу?{{end}} -{{define "delete tip"}}Нельзя отменить удаление гифы, но её история останется доступной.{{end}} - {{define "rename hypha?"}}Переименовать {{beautifulName .}}?{{end}} {{define "rename [[hypha]]?"}}Переименовать {{beautifulName .}}?{{end}} {{define "new name"}}Новое название:{{end}} @@ -75,48 +22,15 @@ var ( {{define "rename tip"}}Переименовывайте аккуратно. Документация на английском.{{end}} {{define "leave redirection"}}Оставить перенаправление{{end}} -{{define "remove media from x?"}}Убрать медиа у {{beautifulName .}}?{{end}} -{{define "remove media from [[x]]?"}}Убрать медиа у {{beautifulName .MatchedHyphaName}}?{{end}} -{{define "remove media for real?"}}Вы точно хотите убрать медиа у гифы «{{beautifulName .MatchedHyphaName}}»?{{end}} + ` chainNaviTitle viewutil.Chain - chainEditHypha viewutil.Chain - chainEmptyHypha viewutil.Chain - chainDeleteHypha viewutil.Chain chainRenameHypha viewutil.Chain - chainRemoveMedia viewutil.Chain ) func Init() { chainNaviTitle = viewutil.CopyEnRuWith(fs, "view_navititle.html", "") - chainEditHypha = viewutil.CopyEnRuWith(fs, "view_edit.html", ruTranslation) - chainEmptyHypha = viewutil.CopyEnRuWith(fs, "view_empty_hypha.html", ruTranslation) - chainDeleteHypha = viewutil.CopyEnRuWith(fs, "view_delete.html", ruTranslation) chainRenameHypha = viewutil.CopyEnRuWith(fs, "view_rename.html", ruTranslation) - chainRemoveMedia = viewutil.CopyEnRuWith(fs, "view_remove_media.html", ruTranslation) -} - -type editData struct { - *viewutil.BaseData - HyphaName string - IsNew bool - Content string - Message string - Preview template.HTML -} - -func EditHypha(meta viewutil.Meta, hyphaName string, isNew bool, content string, message string, preview template.HTML) { - viewutil.ExecutePage(meta, chainEditHypha, editData{ - BaseData: &viewutil.BaseData{ - Addr: "/edit/" + hyphaName, - EditScripts: cfg.EditScripts, - }, - HyphaName: hyphaName, - IsNew: isNew, - Content: content, - Message: message, - Preview: preview, - }) } type renameData struct { @@ -135,49 +49,6 @@ func RenameHypha(meta viewutil.Meta, hyphaName string) { }) } -type deleteRemoveMediaData struct { - *viewutil.BaseData - HyphaName string -} - -func DeleteHypha(meta viewutil.Meta, hyphaName string) { - viewutil.ExecutePage(meta, chainDeleteHypha, deleteRemoveMediaData{ - BaseData: &viewutil.BaseData{ - Addr: "/delete/" + hyphaName, - }, - HyphaName: hyphaName, - }) -} - -func RemoveMedia(meta viewutil.Meta, hyphaName string) { - viewutil.ExecutePage(meta, chainRemoveMedia, deleteRemoveMediaData{ - BaseData: &viewutil.BaseData{ - Addr: "/remove-media/" + hyphaName, - }, - HyphaName: hyphaName, - }) -} - -type emptyHyphaData struct { - Meta viewutil.Meta - HyphaName string - AllowRegistration bool - UseAuth bool -} - -func EmptyHypha(meta viewutil.Meta, hyphaName string) string { - var buf strings.Builder - if err := chainEmptyHypha.Get(meta).ExecuteTemplate(&buf, "empty hypha card", emptyHyphaData{ - Meta: meta, - HyphaName: hyphaName, - AllowRegistration: cfg.AllowRegistration, - UseAuth: cfg.UseAuth, - }); err != nil { - log.Println(err) - } - return buf.String() -} - type naviTitleData struct { HyphaNameParts []string HyphaNamePartsWithParents []string @@ -185,7 +56,7 @@ type naviTitleData struct { HomeHypha string } -func NaviTitle(meta viewutil.Meta, hyphaName string) string { +func NaviTitle(meta viewutil.Meta, hyphaName string) template.HTML { parts, partsWithParents := naviTitleify(hyphaName) var buf strings.Builder err := chainNaviTitle.Get(meta).ExecuteTemplate(&buf, "navititle", naviTitleData{ @@ -197,7 +68,7 @@ func NaviTitle(meta viewutil.Meta, hyphaName string) string { if err != nil { log.Println(err) } - return buf.String() + return template.HTML(buf.String()) } func naviTitleify(hyphaName string) ([]string, []string) { diff --git a/hypview/nav.qtpl b/hypview/nav.qtpl deleted file mode 100644 index 3f6a39d..0000000 --- a/hypview/nav.qtpl +++ /dev/null @@ -1,50 +0,0 @@ -{% import "github.com/bouncepaw/mycorrhiza/backlinks" %} -{% import "github.com/bouncepaw/mycorrhiza/cfg" %} -{% import "github.com/bouncepaw/mycorrhiza/hyphae" %} -{% import "github.com/bouncepaw/mycorrhiza/user" %} -{% import "github.com/bouncepaw/mycorrhiza/util" %} -{% import "github.com/bouncepaw/mycorrhiza/viewutil" %} - -{% func hyphaInfoEntry(h hyphae.Hypha, u *user.User, action string, hasToExist bool, displayText string) %} -{% code flag := true %} -{% switch h.(type) %} -{% case *hyphae.EmptyHypha %} - {% code flag = !hasToExist %} -{% endswitch %} -{% if u.CanProceed(action) && flag %} -
  • - {%s displayText %} -
  • -{% endif %} -{% endfunc %} - -{% func hyphaInfo(meta viewutil.Meta, h hyphae.Hypha) %} -{% code - u := meta.U - lc := meta.Lc - backs := backlinks.BacklinksCount(h.CanonicalName()) -%} - -{% endfunc %} - -{% func commonScripts() %} -{% for _, scriptPath := range cfg.CommonScripts %} - -{% endfor %} -{% endfunc %} - -{% func beautifulLink(hyphaName string) %}{%s util.BeautifulName(hyphaName) %}{% endfunc %} diff --git a/hypview/nav.qtpl.go b/hypview/nav.qtpl.go deleted file mode 100644 index d808df9..0000000 --- a/hypview/nav.qtpl.go +++ /dev/null @@ -1,311 +0,0 @@ -// Code generated by qtc from "nav.qtpl". DO NOT EDIT. -// See https://github.com/valyala/quicktemplate for details. - -//line hypview/nav.qtpl:1 -package hypview - -//line hypview/nav.qtpl:1 -import "github.com/bouncepaw/mycorrhiza/backlinks" - -//line hypview/nav.qtpl:2 -import "github.com/bouncepaw/mycorrhiza/cfg" - -//line hypview/nav.qtpl:3 -import "github.com/bouncepaw/mycorrhiza/hyphae" - -//line hypview/nav.qtpl:4 -import "github.com/bouncepaw/mycorrhiza/user" - -//line hypview/nav.qtpl:5 -import "github.com/bouncepaw/mycorrhiza/util" - -//line hypview/nav.qtpl:6 -import "github.com/bouncepaw/mycorrhiza/viewutil" - -//line hypview/nav.qtpl:8 -import ( - qtio422016 "io" - - qt422016 "github.com/valyala/quicktemplate" -) - -//line hypview/nav.qtpl:8 -var ( - _ = qtio422016.Copy - _ = qt422016.AcquireByteBuffer -) - -//line hypview/nav.qtpl:8 -func streamhyphaInfoEntry(qw422016 *qt422016.Writer, h hyphae.Hypha, u *user.User, action string, hasToExist bool, displayText string) { -//line hypview/nav.qtpl:8 - qw422016.N().S(` -`) -//line hypview/nav.qtpl:9 - flag := true - -//line hypview/nav.qtpl:9 - qw422016.N().S(` -`) -//line hypview/nav.qtpl:10 - switch h.(type) { -//line hypview/nav.qtpl:11 - case *hyphae.EmptyHypha: -//line hypview/nav.qtpl:11 - qw422016.N().S(` - `) -//line hypview/nav.qtpl:12 - flag = !hasToExist - -//line hypview/nav.qtpl:12 - qw422016.N().S(` -`) -//line hypview/nav.qtpl:13 - } -//line hypview/nav.qtpl:13 - qw422016.N().S(` -`) -//line hypview/nav.qtpl:14 - if u.CanProceed(action) && flag { -//line hypview/nav.qtpl:14 - qw422016.N().S(` -
  • - `) -//line hypview/nav.qtpl:16 - qw422016.E().S(displayText) -//line hypview/nav.qtpl:16 - qw422016.N().S(` -
  • -`) -//line hypview/nav.qtpl:18 - } -//line hypview/nav.qtpl:18 - qw422016.N().S(` -`) -//line hypview/nav.qtpl:19 -} - -//line hypview/nav.qtpl:19 -func writehyphaInfoEntry(qq422016 qtio422016.Writer, h hyphae.Hypha, u *user.User, action string, hasToExist bool, displayText string) { -//line hypview/nav.qtpl:19 - qw422016 := qt422016.AcquireWriter(qq422016) -//line hypview/nav.qtpl:19 - streamhyphaInfoEntry(qw422016, h, u, action, hasToExist, displayText) -//line hypview/nav.qtpl:19 - qt422016.ReleaseWriter(qw422016) -//line hypview/nav.qtpl:19 -} - -//line hypview/nav.qtpl:19 -func hyphaInfoEntry(h hyphae.Hypha, u *user.User, action string, hasToExist bool, displayText string) string { -//line hypview/nav.qtpl:19 - qb422016 := qt422016.AcquireByteBuffer() -//line hypview/nav.qtpl:19 - writehyphaInfoEntry(qb422016, h, u, action, hasToExist, displayText) -//line hypview/nav.qtpl:19 - qs422016 := string(qb422016.B) -//line hypview/nav.qtpl:19 - qt422016.ReleaseByteBuffer(qb422016) -//line hypview/nav.qtpl:19 - return qs422016 -//line hypview/nav.qtpl:19 -} - -//line hypview/nav.qtpl:21 -func streamhyphaInfo(qw422016 *qt422016.Writer, meta viewutil.Meta, h hyphae.Hypha) { -//line hypview/nav.qtpl:21 - qw422016.N().S(` -`) -//line hypview/nav.qtpl:23 - u := meta.U - lc := meta.Lc - backs := backlinks.BacklinksCount(h.CanonicalName()) - -//line hypview/nav.qtpl:26 - qw422016.N().S(` - -`) -//line hypview/nav.qtpl:42 -} - -//line hypview/nav.qtpl:42 -func writehyphaInfo(qq422016 qtio422016.Writer, meta viewutil.Meta, h hyphae.Hypha) { -//line hypview/nav.qtpl:42 - qw422016 := qt422016.AcquireWriter(qq422016) -//line hypview/nav.qtpl:42 - streamhyphaInfo(qw422016, meta, h) -//line hypview/nav.qtpl:42 - qt422016.ReleaseWriter(qw422016) -//line hypview/nav.qtpl:42 -} - -//line hypview/nav.qtpl:42 -func hyphaInfo(meta viewutil.Meta, h hyphae.Hypha) string { -//line hypview/nav.qtpl:42 - qb422016 := qt422016.AcquireByteBuffer() -//line hypview/nav.qtpl:42 - writehyphaInfo(qb422016, meta, h) -//line hypview/nav.qtpl:42 - qs422016 := string(qb422016.B) -//line hypview/nav.qtpl:42 - qt422016.ReleaseByteBuffer(qb422016) -//line hypview/nav.qtpl:42 - return qs422016 -//line hypview/nav.qtpl:42 -} - -//line hypview/nav.qtpl:44 -func streamcommonScripts(qw422016 *qt422016.Writer) { -//line hypview/nav.qtpl:44 - qw422016.N().S(` -`) -//line hypview/nav.qtpl:45 - for _, scriptPath := range cfg.CommonScripts { -//line hypview/nav.qtpl:45 - qw422016.N().S(` - -`) -//line hypview/nav.qtpl:47 - } -//line hypview/nav.qtpl:47 - qw422016.N().S(` -`) -//line hypview/nav.qtpl:48 -} - -//line hypview/nav.qtpl:48 -func writecommonScripts(qq422016 qtio422016.Writer) { -//line hypview/nav.qtpl:48 - qw422016 := qt422016.AcquireWriter(qq422016) -//line hypview/nav.qtpl:48 - streamcommonScripts(qw422016) -//line hypview/nav.qtpl:48 - qt422016.ReleaseWriter(qw422016) -//line hypview/nav.qtpl:48 -} - -//line hypview/nav.qtpl:48 -func commonScripts() string { -//line hypview/nav.qtpl:48 - qb422016 := qt422016.AcquireByteBuffer() -//line hypview/nav.qtpl:48 - writecommonScripts(qb422016) -//line hypview/nav.qtpl:48 - qs422016 := string(qb422016.B) -//line hypview/nav.qtpl:48 - qt422016.ReleaseByteBuffer(qb422016) -//line hypview/nav.qtpl:48 - return qs422016 -//line hypview/nav.qtpl:48 -} - -//line hypview/nav.qtpl:50 -func streambeautifulLink(qw422016 *qt422016.Writer, hyphaName string) { -//line hypview/nav.qtpl:50 - qw422016.N().S(``) -//line hypview/nav.qtpl:50 - qw422016.E().S(util.BeautifulName(hyphaName)) -//line hypview/nav.qtpl:50 - qw422016.N().S(``) -//line hypview/nav.qtpl:50 -} - -//line hypview/nav.qtpl:50 -func writebeautifulLink(qq422016 qtio422016.Writer, hyphaName string) { -//line hypview/nav.qtpl:50 - qw422016 := qt422016.AcquireWriter(qq422016) -//line hypview/nav.qtpl:50 - streambeautifulLink(qw422016, hyphaName) -//line hypview/nav.qtpl:50 - qt422016.ReleaseWriter(qw422016) -//line hypview/nav.qtpl:50 -} - -//line hypview/nav.qtpl:50 -func beautifulLink(hyphaName string) string { -//line hypview/nav.qtpl:50 - qb422016 := qt422016.AcquireByteBuffer() -//line hypview/nav.qtpl:50 - writebeautifulLink(qb422016, hyphaName) -//line hypview/nav.qtpl:50 - qs422016 := string(qb422016.B) -//line hypview/nav.qtpl:50 - qt422016.ReleaseByteBuffer(qb422016) -//line hypview/nav.qtpl:50 - return qs422016 -//line hypview/nav.qtpl:50 -} diff --git a/hypview/readers.qtpl b/hypview/readers.qtpl deleted file mode 100644 index 7662730..0000000 --- a/hypview/readers.qtpl +++ /dev/null @@ -1,161 +0,0 @@ -{% import "net/http" %} -{% import "strings" %} -{% import "path" %} -{% import "os" %} - -{% import "github.com/bouncepaw/mycorrhiza/cfg" %} -{% import "github.com/bouncepaw/mycorrhiza/hyphae" %} -{% import "github.com/bouncepaw/mycorrhiza/categories" %} -{% import "github.com/bouncepaw/mycorrhiza/l18n" %} -{% import "github.com/bouncepaw/mycorrhiza/mimetype" %} -{% import "github.com/bouncepaw/mycorrhiza/tree" %} -{% import "github.com/bouncepaw/mycorrhiza/user" %} -{% import "github.com/bouncepaw/mycorrhiza/util" %} -{% import "github.com/bouncepaw/mycorrhiza/viewutil" %} - -{% func MediaMenu(rq *http.Request, h hyphae.Hypha, u *user.User) %} -{% code - lc := l18n.FromRequest(rq) -%} -
    -

    {%s= lc.Get("ui.media_title", &l18n.Replacements{"name": beautifulLink(h.CanonicalName())}) %}

    - {% switch h.(type) %} - {% case *hyphae.MediaHypha %} -

    {%s lc.Get("ui.media_tip") %} {%s lc.Get("ui.media_what_is") %}

    - {% default %} -

    {%s lc.Get("ui.media_empty") %} {%s lc.Get("ui.media_what_is") %}

    - {% endswitch %} - -
    - {% switch h := h.(type) %} - {% case *hyphae.MediaHypha %} - {% code - mime := mimetype.FromExtension(path.Ext(h.MediaFilePath())) - fileinfo, err := os.Stat(h.MediaFilePath()) %} - {% if err == nil %} -
    - {%s lc.Get("ui.media_stat") %} - -

    {%s lc.Get("ui.media_stat_mime") %} {%s mime %}

    -
    - {% endif %} - - {% if strings.HasPrefix(mime, "image/") %} -
    - {%s lc.Get("ui.media_include") %} - -
    img { {%s h.CanonicalName() %} }
    -
    - {% endif %} - {% endswitch %} - - {% if u.CanProceed("upload-binary") %} - - {% endif %} - - - {% switch h := h.(type) %} - {% case *hyphae.MediaHypha %} - {% if u.CanProceed("remove-media") %} - - {% endif %} - {% endswitch %} - -
    -
    -{% endfunc %} - -If `contents` == "", a helpful message is shown instead. - -If you rename .prevnext, change the docs too. -{% func Hypha(meta viewutil.Meta, h hyphae.Hypha, contents string) %} -{% code - subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.CanonicalName()) - lc := meta.Lc -%} -
    -
    - {% if meta.U.CanProceed("edit") %} - - {% endif %} - - {% if cfg.UseAuth && util.IsProfileName(h.CanonicalName()) && meta.U.Name == strings.TrimPrefix(h.CanonicalName(), cfg.UserHypha + "/") %} - - {% if meta.U.Group == "admin" %} - - {% endif %} - {% endif %} - - {%s= NaviTitle(meta, h.CanonicalName()) %} - {% switch h.(type) %} - {% case *hyphae.EmptyHypha %} - {%s= EmptyHypha(meta, h.CanonicalName()) %} - {% default %} - {%s= contents %} - {% endswitch %} -
    -
    - {% if prevHyphaName != "" %} - - {% endif %} - {% if nextHyphaName != "" %} - - {% endif %} -
    -{% if strings.TrimSpace(subhyphae) != "" %} -
    -

    {%s lc.Get("ui.subhyphae") %}

    - -
    -{% endif %} -
    - {%= hyphaInfo(meta, h) %} -
    -
    -{%s= categories.CategoryCard(meta, h.CanonicalName()) %} -{%= viewScripts() %} -{% endfunc %} - -{% func Revision(meta viewutil.Meta, h hyphae.Hypha, contents, revHash string) %} -
    -
    -

    {%s meta.Lc.Get("ui.revision_warning") %} {%s meta.Lc.Get("ui.revision_link") %}

    - {%s= NaviTitle(meta, h.CanonicalName()) %} - {%s= contents %} -
    -
    -{%= viewScripts() %} -{% endfunc %} - -{% func viewScripts() %} -{% for _, scriptPath := range cfg.ViewScripts %} - -{% endfor %} -{% endfunc %} diff --git a/hypview/readers.qtpl.go b/hypview/readers.qtpl.go deleted file mode 100644 index 5c12150..0000000 --- a/hypview/readers.qtpl.go +++ /dev/null @@ -1,651 +0,0 @@ -// Code generated by qtc from "readers.qtpl". DO NOT EDIT. -// See https://github.com/valyala/quicktemplate for details. - -//line hypview/readers.qtpl:1 -package hypview - -//line hypview/readers.qtpl:1 -import "net/http" - -//line hypview/readers.qtpl:2 -import "strings" - -//line hypview/readers.qtpl:3 -import "path" - -//line hypview/readers.qtpl:4 -import "os" - -//line hypview/readers.qtpl:6 -import "github.com/bouncepaw/mycorrhiza/cfg" - -//line hypview/readers.qtpl:7 -import "github.com/bouncepaw/mycorrhiza/hyphae" - -//line hypview/readers.qtpl:8 -import "github.com/bouncepaw/mycorrhiza/categories" - -//line hypview/readers.qtpl:9 -import "github.com/bouncepaw/mycorrhiza/l18n" - -//line hypview/readers.qtpl:10 -import "github.com/bouncepaw/mycorrhiza/mimetype" - -//line hypview/readers.qtpl:11 -import "github.com/bouncepaw/mycorrhiza/tree" - -//line hypview/readers.qtpl:12 -import "github.com/bouncepaw/mycorrhiza/user" - -//line hypview/readers.qtpl:13 -import "github.com/bouncepaw/mycorrhiza/util" - -//line hypview/readers.qtpl:14 -import "github.com/bouncepaw/mycorrhiza/viewutil" - -//line hypview/readers.qtpl:16 -import ( - qtio422016 "io" - - qt422016 "github.com/valyala/quicktemplate" -) - -//line hypview/readers.qtpl:16 -var ( - _ = qtio422016.Copy - _ = qt422016.AcquireByteBuffer -) - -//line hypview/readers.qtpl:16 -func StreamMediaMenu(qw422016 *qt422016.Writer, rq *http.Request, h hyphae.Hypha, u *user.User) { -//line hypview/readers.qtpl:16 - qw422016.N().S(` -`) -//line hypview/readers.qtpl:18 - lc := l18n.FromRequest(rq) - -//line hypview/readers.qtpl:19 - qw422016.N().S(` -
    -

    `) -//line hypview/readers.qtpl:21 - qw422016.N().S(lc.Get("ui.media_title", &l18n.Replacements{"name": beautifulLink(h.CanonicalName())})) -//line hypview/readers.qtpl:21 - qw422016.N().S(`

    - `) -//line hypview/readers.qtpl:22 - switch h.(type) { -//line hypview/readers.qtpl:23 - case *hyphae.MediaHypha: -//line hypview/readers.qtpl:23 - qw422016.N().S(` -

    `) -//line hypview/readers.qtpl:24 - qw422016.E().S(lc.Get("ui.media_tip")) -//line hypview/readers.qtpl:24 - qw422016.N().S(` `) -//line hypview/readers.qtpl:24 - qw422016.E().S(lc.Get("ui.media_what_is")) -//line hypview/readers.qtpl:24 - qw422016.N().S(`

    - `) -//line hypview/readers.qtpl:25 - default: -//line hypview/readers.qtpl:25 - qw422016.N().S(` -

    `) -//line hypview/readers.qtpl:26 - qw422016.E().S(lc.Get("ui.media_empty")) -//line hypview/readers.qtpl:26 - qw422016.N().S(` `) -//line hypview/readers.qtpl:26 - qw422016.E().S(lc.Get("ui.media_what_is")) -//line hypview/readers.qtpl:26 - qw422016.N().S(`

    - `) -//line hypview/readers.qtpl:27 - } -//line hypview/readers.qtpl:27 - qw422016.N().S(` - -
    - `) -//line hypview/readers.qtpl:30 - switch h := h.(type) { -//line hypview/readers.qtpl:31 - case *hyphae.MediaHypha: -//line hypview/readers.qtpl:31 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:33 - mime := mimetype.FromExtension(path.Ext(h.MediaFilePath())) - fileinfo, err := os.Stat(h.MediaFilePath()) - -//line hypview/readers.qtpl:34 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:35 - if err == nil { -//line hypview/readers.qtpl:35 - qw422016.N().S(` -
    - `) -//line hypview/readers.qtpl:37 - qw422016.E().S(lc.Get("ui.media_stat")) -//line hypview/readers.qtpl:37 - qw422016.N().S(` - -

    `) -//line hypview/readers.qtpl:39 - qw422016.E().S(lc.Get("ui.media_stat_mime")) -//line hypview/readers.qtpl:39 - qw422016.N().S(` `) -//line hypview/readers.qtpl:39 - qw422016.E().S(mime) -//line hypview/readers.qtpl:39 - qw422016.N().S(`

    -
    - `) -//line hypview/readers.qtpl:41 - } -//line hypview/readers.qtpl:41 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:43 - if strings.HasPrefix(mime, "image/") { -//line hypview/readers.qtpl:43 - qw422016.N().S(` -
    - `) -//line hypview/readers.qtpl:45 - qw422016.E().S(lc.Get("ui.media_include")) -//line hypview/readers.qtpl:45 - qw422016.N().S(` - -
    img { `)
    -//line hypview/readers.qtpl:47
    -			qw422016.E().S(h.CanonicalName())
    -//line hypview/readers.qtpl:47
    -			qw422016.N().S(` }
    -
    - `) -//line hypview/readers.qtpl:49 - } -//line hypview/readers.qtpl:49 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:50 - } -//line hypview/readers.qtpl:50 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:52 - if u.CanProceed("upload-binary") { -//line hypview/readers.qtpl:52 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:65 - } -//line hypview/readers.qtpl:65 - qw422016.N().S(` - - - `) -//line hypview/readers.qtpl:68 - switch h := h.(type) { -//line hypview/readers.qtpl:69 - case *hyphae.MediaHypha: -//line hypview/readers.qtpl:69 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:70 - if u.CanProceed("remove-media") { -//line hypview/readers.qtpl:70 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:78 - } -//line hypview/readers.qtpl:78 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:79 - } -//line hypview/readers.qtpl:79 - qw422016.N().S(` - -
    -
    -`) -//line hypview/readers.qtpl:83 -} - -//line hypview/readers.qtpl:83 -func WriteMediaMenu(qq422016 qtio422016.Writer, rq *http.Request, h hyphae.Hypha, u *user.User) { -//line hypview/readers.qtpl:83 - qw422016 := qt422016.AcquireWriter(qq422016) -//line hypview/readers.qtpl:83 - StreamMediaMenu(qw422016, rq, h, u) -//line hypview/readers.qtpl:83 - qt422016.ReleaseWriter(qw422016) -//line hypview/readers.qtpl:83 -} - -//line hypview/readers.qtpl:83 -func MediaMenu(rq *http.Request, h hyphae.Hypha, u *user.User) string { -//line hypview/readers.qtpl:83 - qb422016 := qt422016.AcquireByteBuffer() -//line hypview/readers.qtpl:83 - WriteMediaMenu(qb422016, rq, h, u) -//line hypview/readers.qtpl:83 - qs422016 := string(qb422016.B) -//line hypview/readers.qtpl:83 - qt422016.ReleaseByteBuffer(qb422016) -//line hypview/readers.qtpl:83 - return qs422016 -//line hypview/readers.qtpl:83 -} - -// If `contents` == "", a helpful message is shown instead. -// -// If you rename .prevnext, change the docs too. - -//line hypview/readers.qtpl:88 -func StreamHypha(qw422016 *qt422016.Writer, meta viewutil.Meta, h hyphae.Hypha, contents string) { -//line hypview/readers.qtpl:88 - qw422016.N().S(` -`) -//line hypview/readers.qtpl:90 - subhyphae, prevHyphaName, nextHyphaName := tree.Tree(h.CanonicalName()) - lc := meta.Lc - -//line hypview/readers.qtpl:92 - qw422016.N().S(` -
    -
    - `) -//line hypview/readers.qtpl:95 - if meta.U.CanProceed("edit") { -//line hypview/readers.qtpl:95 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:99 - } -//line hypview/readers.qtpl:99 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:101 - if cfg.UseAuth && util.IsProfileName(h.CanonicalName()) && meta.U.Name == strings.TrimPrefix(h.CanonicalName(), cfg.UserHypha+"/") { -//line hypview/readers.qtpl:101 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:105 - if meta.U.Group == "admin" { -//line hypview/readers.qtpl:105 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:109 - } -//line hypview/readers.qtpl:109 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:110 - } -//line hypview/readers.qtpl:110 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:112 - qw422016.N().S(NaviTitle(meta, h.CanonicalName())) -//line hypview/readers.qtpl:112 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:113 - switch h.(type) { -//line hypview/readers.qtpl:114 - case *hyphae.EmptyHypha: -//line hypview/readers.qtpl:114 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:115 - qw422016.N().S(EmptyHypha(meta, h.CanonicalName())) -//line hypview/readers.qtpl:115 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:116 - default: -//line hypview/readers.qtpl:116 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:117 - qw422016.N().S(contents) -//line hypview/readers.qtpl:117 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:118 - } -//line hypview/readers.qtpl:118 - qw422016.N().S(` -
    -
    - `) -//line hypview/readers.qtpl:121 - if prevHyphaName != "" { -//line hypview/readers.qtpl:121 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:123 - } -//line hypview/readers.qtpl:123 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:124 - if nextHyphaName != "" { -//line hypview/readers.qtpl:124 - qw422016.N().S(` - - `) -//line hypview/readers.qtpl:126 - } -//line hypview/readers.qtpl:126 - qw422016.N().S(` -
    -`) -//line hypview/readers.qtpl:128 - if strings.TrimSpace(subhyphae) != "" { -//line hypview/readers.qtpl:128 - qw422016.N().S(` -
    -

    `) -//line hypview/readers.qtpl:130 - qw422016.E().S(lc.Get("ui.subhyphae")) -//line hypview/readers.qtpl:130 - qw422016.N().S(`

    - -
    -`) -//line hypview/readers.qtpl:137 - } -//line hypview/readers.qtpl:137 - qw422016.N().S(` -
    - `) -//line hypview/readers.qtpl:139 - streamhyphaInfo(qw422016, meta, h) -//line hypview/readers.qtpl:139 - qw422016.N().S(` -
    -
    -`) -//line hypview/readers.qtpl:142 - qw422016.N().S(categories.CategoryCard(meta, h.CanonicalName())) -//line hypview/readers.qtpl:142 - qw422016.N().S(` -`) -//line hypview/readers.qtpl:143 - streamviewScripts(qw422016) -//line hypview/readers.qtpl:143 - qw422016.N().S(` -`) -//line hypview/readers.qtpl:144 -} - -//line hypview/readers.qtpl:144 -func WriteHypha(qq422016 qtio422016.Writer, meta viewutil.Meta, h hyphae.Hypha, contents string) { -//line hypview/readers.qtpl:144 - qw422016 := qt422016.AcquireWriter(qq422016) -//line hypview/readers.qtpl:144 - StreamHypha(qw422016, meta, h, contents) -//line hypview/readers.qtpl:144 - qt422016.ReleaseWriter(qw422016) -//line hypview/readers.qtpl:144 -} - -//line hypview/readers.qtpl:144 -func Hypha(meta viewutil.Meta, h hyphae.Hypha, contents string) string { -//line hypview/readers.qtpl:144 - qb422016 := qt422016.AcquireByteBuffer() -//line hypview/readers.qtpl:144 - WriteHypha(qb422016, meta, h, contents) -//line hypview/readers.qtpl:144 - qs422016 := string(qb422016.B) -//line hypview/readers.qtpl:144 - qt422016.ReleaseByteBuffer(qb422016) -//line hypview/readers.qtpl:144 - return qs422016 -//line hypview/readers.qtpl:144 -} - -//line hypview/readers.qtpl:146 -func StreamRevision(qw422016 *qt422016.Writer, meta viewutil.Meta, h hyphae.Hypha, contents, revHash string) { -//line hypview/readers.qtpl:146 - qw422016.N().S(` -
    -
    -

    `) -//line hypview/readers.qtpl:149 - qw422016.E().S(meta.Lc.Get("ui.revision_warning")) -//line hypview/readers.qtpl:149 - qw422016.N().S(` `) -//line hypview/readers.qtpl:149 - qw422016.E().S(meta.Lc.Get("ui.revision_link")) -//line hypview/readers.qtpl:149 - qw422016.N().S(`

    - `) -//line hypview/readers.qtpl:150 - qw422016.N().S(NaviTitle(meta, h.CanonicalName())) -//line hypview/readers.qtpl:150 - qw422016.N().S(` - `) -//line hypview/readers.qtpl:151 - qw422016.N().S(contents) -//line hypview/readers.qtpl:151 - qw422016.N().S(` -
    -
    -`) -//line hypview/readers.qtpl:154 - streamviewScripts(qw422016) -//line hypview/readers.qtpl:154 - qw422016.N().S(` -`) -//line hypview/readers.qtpl:155 -} - -//line hypview/readers.qtpl:155 -func WriteRevision(qq422016 qtio422016.Writer, meta viewutil.Meta, h hyphae.Hypha, contents, revHash string) { -//line hypview/readers.qtpl:155 - qw422016 := qt422016.AcquireWriter(qq422016) -//line hypview/readers.qtpl:155 - StreamRevision(qw422016, meta, h, contents, revHash) -//line hypview/readers.qtpl:155 - qt422016.ReleaseWriter(qw422016) -//line hypview/readers.qtpl:155 -} - -//line hypview/readers.qtpl:155 -func Revision(meta viewutil.Meta, h hyphae.Hypha, contents, revHash string) string { -//line hypview/readers.qtpl:155 - qb422016 := qt422016.AcquireByteBuffer() -//line hypview/readers.qtpl:155 - WriteRevision(qb422016, meta, h, contents, revHash) -//line hypview/readers.qtpl:155 - qs422016 := string(qb422016.B) -//line hypview/readers.qtpl:155 - qt422016.ReleaseByteBuffer(qb422016) -//line hypview/readers.qtpl:155 - return qs422016 -//line hypview/readers.qtpl:155 -} - -//line hypview/readers.qtpl:157 -func streamviewScripts(qw422016 *qt422016.Writer) { -//line hypview/readers.qtpl:157 - qw422016.N().S(` -`) -//line hypview/readers.qtpl:158 - for _, scriptPath := range cfg.ViewScripts { -//line hypview/readers.qtpl:158 - qw422016.N().S(` - -`) -//line hypview/readers.qtpl:160 - } -//line hypview/readers.qtpl:160 - qw422016.N().S(` -`) -//line hypview/readers.qtpl:161 -} - -//line hypview/readers.qtpl:161 -func writeviewScripts(qq422016 qtio422016.Writer) { -//line hypview/readers.qtpl:161 - qw422016 := qt422016.AcquireWriter(qq422016) -//line hypview/readers.qtpl:161 - streamviewScripts(qw422016) -//line hypview/readers.qtpl:161 - qt422016.ReleaseWriter(qw422016) -//line hypview/readers.qtpl:161 -} - -//line hypview/readers.qtpl:161 -func viewScripts() string { -//line hypview/readers.qtpl:161 - qb422016 := qt422016.AcquireByteBuffer() -//line hypview/readers.qtpl:161 - writeviewScripts(qb422016) -//line hypview/readers.qtpl:161 - qs422016 := string(qb422016.B) -//line hypview/readers.qtpl:161 - qt422016.ReleaseByteBuffer(qb422016) -//line hypview/readers.qtpl:161 - return qs422016 -//line hypview/readers.qtpl:161 -} diff --git a/hypview/view_empty_hypha.html b/hypview/view_empty_hypha.html deleted file mode 100644 index 2af3638..0000000 --- a/hypview/view_empty_hypha.html +++ /dev/null @@ -1,32 +0,0 @@ -{{define "empty hypha card"}} -
    -

    {{block "empty heading" .}}This hypha does not exist{{end}}

    - {{if and .UseAuth (eq .Meta.U.Group "anon")}} -

    {{block "empty no rights" .}}You are not authorized to create new hyphae. Here is what you can do:{{end}}

    - - {{else}} -
    -
    -

    📝 {{block "write a text" .}}Write a text{{end}}

    -

    {{block "write a text tip" .}}Write a note, a diary, an article, a story or anything textual using Mycomarkup. Full history of edits to the document will be saved.{{end}}

    -

    {{block "write a text writing conventions" .}}Make sure to follow this wiki's writing conventions if there are any.{{end}}

    - {{block "write a text btn" .}}Create{{end}} -
    - -
    -

    🖼 {{block "upload a media" .}}Upload a media{{end}}

    -

    {{block "upload a media tip" .}}Upload a picture, a video or an audio. Most common formats can be viewed from the browser, others can only be downloaded and viewed locally. You can write a description for the media later.{{end}}

    -
    - - -
    -
    -
    - {{end}} -
    -{{end}} \ No newline at end of file diff --git a/hypview/view_remove_media.html b/hypview/view_remove_media.html deleted file mode 100644 index 3edd8d8..0000000 --- a/hypview/view_remove_media.html +++ /dev/null @@ -1,22 +0,0 @@ -{{define "remove media from x?"}}Remove media from {{beautifulName .}}?{{end}} -{{define "title"}}{{template "remove media from x?" .HyphaName}}{{end}} -{{define "body"}} -
    - -
    -{{end}} \ No newline at end of file diff --git a/backlinks/backlinks.go b/internal/backlinks/backlinks.go similarity index 84% rename from backlinks/backlinks.go rename to internal/backlinks/backlinks.go index 3660580..f2e235a 100644 --- a/backlinks/backlinks.go +++ b/internal/backlinks/backlinks.go @@ -2,10 +2,11 @@ package backlinks import ( + hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" "log" "os" + "sort" - "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/util" ) @@ -13,7 +14,7 @@ import ( func yieldHyphaBacklinks(hyphaName string) <-chan string { hyphaName = util.CanonicalName(hyphaName) out := make(chan string) - sorted := hyphae.PathographicSort(out) + sorted := hyphae2.PathographicSort(out) go func() { backlinks, exists := backlinkIndex[hyphaName] if exists { @@ -42,7 +43,7 @@ var backlinkIndex = make(map[string]linkSet) // IndexBacklinks traverses all text hyphae, extracts links from them and forms an initial index. Call it when indexing and reindexing hyphae. func IndexBacklinks() { // It is safe to ignore the mutex, because there is only one worker. - for h := range hyphae.FilterHyphaeWithText(hyphae.YieldExistingHyphae()) { + for h := range hyphae2.FilterHyphaeWithText(hyphae2.YieldExistingHyphae()) { foundLinks := extractHyphaLinksFromContent(h.CanonicalName(), fetchText(h)) for _, link := range foundLinks { if _, exists := backlinkIndex[link]; !exists { @@ -61,6 +62,25 @@ func BacklinksCount(hyphaName string) int { return 0 } +func BacklinksFor(hyphaName string) []string { + var backlinks []string + for b := range yieldHyphaBacklinks(hyphaName) { + backlinks = append(backlinks, b) + } + return backlinks +} + +func Orphans() []string { + var orphans []string + for h := range hyphae2.YieldExistingHyphae() { + if BacklinksCount(h.CanonicalName()) == 0 { + orphans = append(orphans, h.CanonicalName()) + } + } + sort.Strings(orphans) + return orphans +} + // Using set here seems like the most appropriate solution type linkSet map[string]struct{} @@ -72,14 +92,14 @@ func toLinkSet(xs []string) linkSet { return result } -func fetchText(h hyphae.Hypha) string { +func fetchText(h hyphae2.Hypha) string { var path string switch h := h.(type) { - case *hyphae.EmptyHypha: + case *hyphae2.EmptyHypha: return "" - case *hyphae.TextualHypha: + case *hyphae2.TextualHypha: path = h.TextFilePath() - case *hyphae.MediaHypha: + case *hyphae2.MediaHypha: if !h.HasTextFile() { return "" } diff --git a/backlinks/hooks.go b/internal/backlinks/hooks.go similarity index 97% rename from backlinks/hooks.go rename to internal/backlinks/hooks.go index 41084f6..ef65952 100644 --- a/backlinks/hooks.go +++ b/internal/backlinks/hooks.go @@ -5,7 +5,7 @@ import ( "git.sr.ht/~bouncepaw/mycomarkup/v5/links" "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" "git.sr.ht/~bouncepaw/mycomarkup/v5/tools" - "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" "github.com/bouncepaw/mycorrhiza/mycoopts" ) diff --git a/categories/categories.go b/internal/categories/categories.go similarity index 91% rename from categories/categories.go rename to internal/categories/categories.go index 75d3482..86faa01 100644 --- a/categories/categories.go +++ b/internal/categories/categories.go @@ -23,8 +23,8 @@ package categories import "sync" -// listOfCategories returns unsorted names of all categories. -func listOfCategories() (categoryList []string) { +// ListOfCategories returns unsorted names of all categories. +func ListOfCategories() (categoryList []string) { mutex.RLock() for cat, _ := range categoryToHyphae { categoryList = append(categoryList, cat) @@ -44,8 +44,8 @@ func CategoriesWithHypha(hyphaName string) (categoryList []string) { } } -// hyphaeInCategory returns what hyphae are in the category. If the returned slice is empty, the category does not exist, and vice versa. The category name must be canonical. -func hyphaeInCategory(catName string) (hyphaList []string) { +// HyphaeInCategory returns what hyphae are in the category. If the returned slice is empty, the category does not exist, and vice versa. The category name must be canonical. +func HyphaeInCategory(catName string) (hyphaList []string) { mutex.RLock() defer mutex.RUnlock() if node, ok := categoryToHyphae[catName]; ok { @@ -75,8 +75,8 @@ func AddHyphaToCategory(hyphaName, catName string) { go saveToDisk() } -// removeHyphaFromCategory removes the hypha from the category and updates the records on the disk. If the hypha is not in the category, nothing happens. Pass canonical names. -func removeHyphaFromCategory(hyphaName, catName string) { +// RemoveHyphaFromCategory removes the hypha from the category and updates the records on the disk. If the hypha is not in the category, nothing happens. Pass canonical names. +func RemoveHyphaFromCategory(hyphaName, catName string) { mutex.Lock() if node, ok := hyphaToCategories[hyphaName]; ok { node.removeCategory(catName) diff --git a/categories/files.go b/internal/categories/files.go similarity index 98% rename from categories/files.go rename to internal/categories/files.go index 9bac8ac..237a5fd 100644 --- a/categories/files.go +++ b/internal/categories/files.go @@ -2,13 +2,14 @@ package categories import ( "encoding/json" - "github.com/bouncepaw/mycorrhiza/files" - "github.com/bouncepaw/mycorrhiza/util" "golang.org/x/exp/slices" "log" "os" "sort" "sync" + + "github.com/bouncepaw/mycorrhiza/internal/files" + "github.com/bouncepaw/mycorrhiza/util" ) var categoryToHyphae = map[string]*categoryNode{} diff --git a/cfg/config.go b/internal/cfg/config.go similarity index 100% rename from cfg/config.go rename to internal/cfg/config.go diff --git a/files/files.go b/internal/files/files.go similarity index 97% rename from files/files.go rename to internal/files/files.go index b549e83..bb82cff 100644 --- a/files/files.go +++ b/internal/files/files.go @@ -2,12 +2,11 @@ package files import ( + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/web/static" "io" "os" "path/filepath" - - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/static" ) var paths struct { diff --git a/hyphae/count.go b/internal/hyphae/count.go similarity index 100% rename from hyphae/count.go rename to internal/hyphae/count.go diff --git a/hyphae/deprecated.go b/internal/hyphae/deprecated.go similarity index 100% rename from hyphae/deprecated.go rename to internal/hyphae/deprecated.go diff --git a/hyphae/empty_hypha.go b/internal/hyphae/empty_hypha.go similarity index 100% rename from hyphae/empty_hypha.go rename to internal/hyphae/empty_hypha.go diff --git a/hyphae/existing_hypha.go b/internal/hyphae/existing_hypha.go similarity index 100% rename from hyphae/existing_hypha.go rename to internal/hyphae/existing_hypha.go diff --git a/hyphae/files.go b/internal/hyphae/files.go similarity index 97% rename from hyphae/files.go rename to internal/hyphae/files.go index a0ee9cc..9be35a2 100644 --- a/hyphae/files.go +++ b/internal/hyphae/files.go @@ -1,12 +1,11 @@ package hyphae import ( + "github.com/bouncepaw/mycorrhiza/internal/mimetype" "log" "log/slog" "os" "path/filepath" - - "github.com/bouncepaw/mycorrhiza/mimetype" ) // Index finds all hypha files in the full `path` and saves them to the hypha storage. diff --git a/hyphae/hypha.go b/internal/hyphae/hypha.go similarity index 100% rename from hyphae/hypha.go rename to internal/hyphae/hypha.go diff --git a/hyphae/iterators.go b/internal/hyphae/iterators.go similarity index 100% rename from hyphae/iterators.go rename to internal/hyphae/iterators.go diff --git a/hyphae/media_hypha.go b/internal/hyphae/media_hypha.go similarity index 93% rename from hyphae/media_hypha.go rename to internal/hyphae/media_hypha.go index 60e3505..581d271 100644 --- a/hyphae/media_hypha.go +++ b/internal/hyphae/media_hypha.go @@ -1,9 +1,10 @@ package hyphae import ( - "github.com/bouncepaw/mycorrhiza/files" "path/filepath" "sync" + + "github.com/bouncepaw/mycorrhiza/internal/files" ) type MediaHypha struct { diff --git a/hyphae/textual_hypha.go b/internal/hyphae/textual_hypha.go similarity index 100% rename from hyphae/textual_hypha.go rename to internal/hyphae/textual_hypha.go diff --git a/migration/headings.go b/internal/migration/headings.go similarity index 95% rename from migration/headings.go rename to internal/migration/headings.go index 14fb2aa..fb8f3cd 100644 --- a/migration/headings.go +++ b/internal/migration/headings.go @@ -2,7 +2,7 @@ package migration import ( "git.sr.ht/~bouncepaw/mycomarkup/v5/tools" - "github.com/bouncepaw/mycorrhiza/files" + "github.com/bouncepaw/mycorrhiza/internal/files" "io/ioutil" "log" "os" diff --git a/migration/migration.go b/internal/migration/migration.go similarity index 95% rename from migration/migration.go rename to internal/migration/migration.go index 00bf1ff..d3f945f 100644 --- a/migration/migration.go +++ b/internal/migration/migration.go @@ -8,13 +8,14 @@ package migration import ( - "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/user" + "github.com/bouncepaw/mycorrhiza/internal/user" "io" "log" "os" "strings" + + "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" ) func genericLineMigrator( diff --git a/migration/rockets.go b/internal/migration/rockets.go similarity index 96% rename from migration/rockets.go rename to internal/migration/rockets.go index 9787eba..5dc050b 100644 --- a/migration/rockets.go +++ b/internal/migration/rockets.go @@ -2,7 +2,7 @@ package migration import ( "git.sr.ht/~bouncepaw/mycomarkup/v5/tools" - "github.com/bouncepaw/mycorrhiza/files" + "github.com/bouncepaw/mycorrhiza/internal/files" "io/ioutil" "log" "os" diff --git a/mimetype/mime.go b/internal/mimetype/mime.go similarity index 100% rename from mimetype/mime.go rename to internal/mimetype/mime.go diff --git a/shroom/can.go b/internal/shroom/can.go similarity index 73% rename from shroom/can.go rename to internal/shroom/can.go index b3885e7..c9d1f3d 100644 --- a/shroom/can.go +++ b/internal/shroom/can.go @@ -2,23 +2,23 @@ package shroom import ( "errors" + hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/user" - "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/user" ) // TODO: get rid of this abomination func canFactory( - rejectLogger func(hyphae.Hypha, *user.User, string), + rejectLogger func(hyphae2.Hypha, *user.User, string), action string, - dispatcher func(hyphae.Hypha, *user.User, *l18n.Localizer) (string, string), + dispatcher func(hyphae2.Hypha, *user.User, *l18n.Localizer) (string, string), noRightsMsg string, notExistsMsg string, mustExist bool, -) func(*user.User, hyphae.Hypha, *l18n.Localizer) error { - return func(u *user.User, h hyphae.Hypha, lc *l18n.Localizer) error { +) func(*user.User, hyphae2.Hypha, *l18n.Localizer) error { + return func(u *user.User, h hyphae2.Hypha, lc *l18n.Localizer) error { if !u.CanProceed(action) { rejectLogger(h, u, "no rights") return errors.New(noRightsMsg) @@ -26,7 +26,7 @@ func canFactory( if mustExist { switch h.(type) { - case *hyphae.EmptyHypha: + case *hyphae2.EmptyHypha: rejectLogger(h, u, "does not exist") return errors.New(notExistsMsg) } diff --git a/shroom/delete.go b/internal/shroom/delete.go similarity index 62% rename from shroom/delete.go rename to internal/shroom/delete.go index d63703f..701fd0c 100644 --- a/shroom/delete.go +++ b/internal/shroom/delete.go @@ -2,29 +2,29 @@ package shroom import ( "fmt" - "github.com/bouncepaw/mycorrhiza/backlinks" - "github.com/bouncepaw/mycorrhiza/categories" "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/user" + "github.com/bouncepaw/mycorrhiza/internal/backlinks" + "github.com/bouncepaw/mycorrhiza/internal/categories" + hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/user" ) // Delete deletes the hypha and makes a history record about that. -func Delete(u *user.User, h hyphae.ExistingHypha) error { +func Delete(u *user.User, h hyphae2.ExistingHypha) error { hop := history. Operation(history.TypeDeleteHypha). WithMsg(fmt.Sprintf("Delete ‘%s’", h.CanonicalName())). WithUser(u) - originalText, _ := hyphae.FetchMycomarkupFile(h) + originalText, _ := hyphae2.FetchMycomarkupFile(h) switch h := h.(type) { - case *hyphae.MediaHypha: + case *hyphae2.MediaHypha: if h.HasTextFile() { hop.WithFilesRemoved(h.MediaFilePath(), h.TextFilePath()) } else { hop.WithFilesRemoved(h.MediaFilePath()) } - case *hyphae.TextualHypha: + case *hyphae2.TextualHypha: hop.WithFilesRemoved(h.TextFilePath()) } if hop.Apply().HasErrors() { @@ -32,6 +32,6 @@ func Delete(u *user.User, h hyphae.ExistingHypha) error { } backlinks.UpdateBacklinksAfterDelete(h, originalText) categories.RemoveHyphaFromAllCategories(h.CanonicalName()) - hyphae.DeleteHypha(h) + hyphae2.DeleteHypha(h) return nil } diff --git a/shroom/header_links.go b/internal/shroom/header_links.go similarity index 84% rename from shroom/header_links.go rename to internal/shroom/header_links.go index 0d2be2d..dd3ff1b 100644 --- a/shroom/header_links.go +++ b/internal/shroom/header_links.go @@ -4,19 +4,19 @@ import ( "git.sr.ht/~bouncepaw/mycomarkup/v5" "git.sr.ht/~bouncepaw/mycomarkup/v5/blocks" "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" "github.com/bouncepaw/mycorrhiza/mycoopts" - "github.com/bouncepaw/mycorrhiza/viewutil" + "github.com/bouncepaw/mycorrhiza/web/viewutil" "os" ) // SetHeaderLinks initializes header links by reading the configured hypha, if there is any, or resorting to default values. func SetHeaderLinks() { - switch userLinksHypha := hyphae.ByName(cfg.HeaderLinksHypha).(type) { - case *hyphae.EmptyHypha: + switch userLinksHypha := hyphae2.ByName(cfg.HeaderLinksHypha).(type) { + case *hyphae2.EmptyHypha: setDefaultHeaderLinks() - case hyphae.ExistingHypha: + case hyphae2.ExistingHypha: contents, err := os.ReadFile(userLinksHypha.TextFilePath()) if err != nil || len(contents) == 0 { setDefaultHeaderLinks() diff --git a/shroom/log.go b/internal/shroom/log.go similarity index 87% rename from shroom/log.go rename to internal/shroom/log.go index 2be9a70..2aef825 100644 --- a/shroom/log.go +++ b/internal/shroom/log.go @@ -1,10 +1,9 @@ package shroom import ( + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/user" "log" - - "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/user" ) func rejectRenameLog(h hyphae.Hypha, u *user.User, errmsg string) { diff --git a/shroom/rename.go b/internal/shroom/rename.go similarity index 73% rename from shroom/rename.go rename to internal/shroom/rename.go index 22bdfb6..3aeeeb7 100644 --- a/shroom/rename.go +++ b/internal/shroom/rename.go @@ -3,36 +3,36 @@ package shroom import ( "errors" "fmt" - "github.com/bouncepaw/mycorrhiza/backlinks" - "github.com/bouncepaw/mycorrhiza/categories" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/files" + "github.com/bouncepaw/mycorrhiza/internal/backlinks" + "github.com/bouncepaw/mycorrhiza/internal/categories" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/files" + hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/user" "path" "path/filepath" "regexp" "strings" "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" ) // Rename renames the old hypha to the new name and makes a history record about that. Call if and only if the user has the permission to rename. -func Rename(oldHypha hyphae.ExistingHypha, newName string, recursive bool, leaveRedirections bool, u *user.User) error { +func Rename(oldHypha hyphae2.ExistingHypha, newName string, recursive bool, leaveRedirections bool, u *user.User) error { // * bouncepaw hates this function and related renaming functions if newName == "" { rejectRenameLog(oldHypha, u, "no new name given") return errors.New("ui.rename_noname_tip") } - if !hyphae.IsValidName(newName) { + if !hyphae2.IsValidName(newName) { rejectRenameLog(oldHypha, u, fmt.Sprintf("new name ‘%s’ invalid", newName)) return errors.New("ui.rename_badname_tip") // FIXME: There is a bug related to this. } - switch targetHypha := hyphae.ByName(newName); targetHypha.(type) { - case hyphae.ExistingHypha: + switch targetHypha := hyphae2.ByName(newName); targetHypha.(type) { + case hyphae2.ExistingHypha: if targetHypha.CanonicalName() == oldHypha.CanonicalName() { return nil } @@ -81,7 +81,7 @@ func Rename(oldHypha hyphae.ExistingHypha, newName string, recursive bool, leave oldName = h.CanonicalName() newName = re.ReplaceAllString(oldName, newName) ) - hyphae.RenameHyphaTo(h, newName, replaceName) + hyphae2.RenameHyphaTo(h, newName, replaceName) backlinks.UpdateBacklinksAfterRename(h, oldName) categories.RenameHyphaInAllCategories(oldName, newName) if leaveRedirections { @@ -104,12 +104,12 @@ const redirectionTemplate = `=> %[1]s | 👁️➡️ %[2]s func leaveRedirection(oldName, newName string, hop *history.Op) error { var ( text = fmt.Sprintf(redirectionTemplate, newName, util.BeautifulName(newName)) - emptyHypha = hyphae.ByName(oldName) + emptyHypha = hyphae2.ByName(oldName) ) switch emptyHypha := emptyHypha.(type) { - case *hyphae.EmptyHypha: - h := hyphae.ExtendEmptyToTextual(emptyHypha, filepath.Join(files.HyphaeDir(), oldName+".myco")) - hyphae.Insert(h) + case *hyphae2.EmptyHypha: + h := hyphae2.ExtendEmptyToTextual(emptyHypha, filepath.Join(files.HyphaeDir(), oldName+".myco")) + hyphae2.Insert(h) categories.AddHyphaToCategory(oldName, cfg.RedirectionCategory) defer backlinks.UpdateBacklinksAfterEdit(h, "") return writeTextToDisk(h, []byte(text), hop) @@ -118,15 +118,15 @@ func leaveRedirection(oldName, newName string, hop *history.Op) error { } } -func findHyphaeToRename(superhypha hyphae.ExistingHypha, recursive bool) []hyphae.ExistingHypha { - hyphaList := []hyphae.ExistingHypha{superhypha} +func findHyphaeToRename(superhypha hyphae2.ExistingHypha, recursive bool) []hyphae2.ExistingHypha { + hyphaList := []hyphae2.ExistingHypha{superhypha} if recursive { - hyphaList = append(hyphaList, hyphae.Subhyphae(superhypha)...) + hyphaList = append(hyphaList, hyphae2.Subhyphae(superhypha)...) } return hyphaList } -func renamingPairs(hyphaeToRename []hyphae.ExistingHypha, replaceName func(string) string) (map[string]string, error) { +func renamingPairs(hyphaeToRename []hyphae2.ExistingHypha, replaceName func(string) string) (map[string]string, error) { var ( renameMap = make(map[string]string) newNames = make([]string, len(hyphaeToRename)) @@ -138,12 +138,12 @@ func renamingPairs(hyphaeToRename []hyphae.ExistingHypha, replaceName func(strin renameMap[h.TextFilePath()] = replaceName(h.TextFilePath()) } switch h := h.(type) { - case *hyphae.MediaHypha: + case *hyphae2.MediaHypha: renameMap[h.MediaFilePath()] = replaceName(h.MediaFilePath()) } h.Unlock() } - if firstFailure, ok := hyphae.AreFreeNames(newNames...); !ok { + if firstFailure, ok := hyphae2.AreFreeNames(newNames...); !ok { return nil, errors.New("Hypha " + firstFailure + " already exists") } return renameMap, nil diff --git a/shroom/search.go b/internal/shroom/search.go similarity index 94% rename from shroom/search.go rename to internal/shroom/search.go index 4387abf..57efad0 100644 --- a/shroom/search.go +++ b/internal/shroom/search.go @@ -1,9 +1,9 @@ package shroom import ( + "github.com/bouncepaw/mycorrhiza/internal/hyphae" "strings" - "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/util" ) diff --git a/shroom/shroom.go b/internal/shroom/shroom.go similarity index 100% rename from shroom/shroom.go rename to internal/shroom/shroom.go diff --git a/shroom/unattach.go b/internal/shroom/unattach.go similarity index 74% rename from shroom/unattach.go rename to internal/shroom/unattach.go index e62ce02..74b76b4 100644 --- a/shroom/unattach.go +++ b/internal/shroom/unattach.go @@ -2,14 +2,14 @@ package shroom import ( "fmt" + hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/user" ) // RemoveMedia removes media from the media hypha and makes a history record about that. If it only had media, the hypha will be deleted. If it also had text, the hypha will become textual. -func RemoveMedia(u *user.User, h *hyphae.MediaHypha) error { +func RemoveMedia(u *user.User, h *hyphae2.MediaHypha) error { hop := history. Operation(history.TypeRemoveMedia). WithFilesRemoved(h.MediaFilePath()). @@ -24,9 +24,9 @@ func RemoveMedia(u *user.User, h *hyphae.MediaHypha) error { } if h.HasTextFile() { - hyphae.Insert(hyphae.ShrinkMediaToTextual(h)) + hyphae2.Insert(hyphae2.ShrinkMediaToTextual(h)) } else { - hyphae.DeleteHypha(h) + hyphae2.DeleteHypha(h) } return nil } diff --git a/shroom/upload.go b/internal/shroom/upload.go similarity index 95% rename from shroom/upload.go rename to internal/shroom/upload.go index 8750ca9..a63c1ec 100644 --- a/shroom/upload.go +++ b/internal/shroom/upload.go @@ -4,18 +4,19 @@ import ( "bytes" "errors" "fmt" - "github.com/bouncepaw/mycorrhiza/backlinks" - "github.com/bouncepaw/mycorrhiza/files" - "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/mimetype" - "github.com/bouncepaw/mycorrhiza/user" "io" "log" "mime/multipart" "os" "path/filepath" "strings" + + "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/internal/backlinks" + "github.com/bouncepaw/mycorrhiza/internal/files" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/mimetype" + "github.com/bouncepaw/mycorrhiza/internal/user" ) func historyMessageForTextUpload(h hyphae.Hypha, userMessage string) string { diff --git a/tree/tree.go b/internal/tree/tree.go similarity index 64% rename from tree/tree.go rename to internal/tree/tree.go index 7f00a3c..6dc171e 100644 --- a/tree/tree.go +++ b/internal/tree/tree.go @@ -1,14 +1,18 @@ package tree import ( - "github.com/bouncepaw/mycorrhiza/hyphae" + "fmt" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/util" + "html/template" + "io" "path" "sort" "strings" ) // Tree returns the subhypha matrix as HTML and names of the next and previous hyphae (or empty strings). -func Tree(hyphaName string) (childrenHTML, prev, next string) { +func Tree(hyphaName string) (childrenHTML template.HTML, prev, next string) { var ( root = child{hyphaName, true, make([]child, 0)} descendantPrefix = hyphaName + "/" @@ -44,6 +48,41 @@ type child struct { children []child } +/* +Subhyphae links are recursive. It may end up looking like that if drawn with +pseudographics: +╔══════════════╗ +║Foo ║ The presented hyphae are ./foo and ./foo/bar +║╔════════════╗║ +║║Bar ║║ +║╚════════════╝║ +╚══════════════╝ +*/ +func childHTML(c *child, w io.Writer) { + sort.Slice(c.children, func(i, j int) bool { + return c.children[i].name < c.children[j].name + }) + + _, _ = io.WriteString(w, "
  • \n%s\n", + c.name, + util.BeautifulName(path.Base(c.name)), + )) + + if len(c.children) > 0 { + _, _ = io.WriteString(w, "
      \n") + for _, child := range c.children { + childHTML(&child, w) + } + _, _ = io.WriteString(w, "
    \n") + } + _, _ = io.WriteString(w, "
  • \n") +} + func addHyphaToChild(hyphaName, subPath string, child *child) { // when hyphaName = "root/a/b", subPath = "a/b", and child.name = "root" // addHyphaToChild("root/a/b", "b", child{"root/a"}) @@ -78,12 +117,13 @@ func findOrCreateSubchild(name string, baseChild *child) *child { return &baseChild.children[len(baseChild.children)-1] } -func subhyphaeMatrix(children []child) (html string) { +func subhyphaeMatrix(children []child) template.HTML { sort.Slice(children, func(i, j int) bool { return children[i].name < children[j].name }) + var buf strings.Builder for _, child := range children { - html += childHTML(&child) + childHTML(&child, &buf) } - return html + return template.HTML(buf.String()) } diff --git a/user/files.go b/internal/user/files.go similarity index 96% rename from user/files.go rename to internal/user/files.go index 2139d83..d48d8a9 100644 --- a/user/files.go +++ b/internal/user/files.go @@ -3,11 +3,11 @@ package user import ( "encoding/json" "errors" + "github.com/bouncepaw/mycorrhiza/internal/cfg" "log" "os" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/files" + "github.com/bouncepaw/mycorrhiza/internal/files" "github.com/bouncepaw/mycorrhiza/util" ) diff --git a/user/net.go b/internal/user/net.go similarity index 95% rename from user/net.go rename to internal/user/net.go index a97503b..00f60c2 100644 --- a/user/net.go +++ b/internal/user/net.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "errors" "fmt" + "github.com/bouncepaw/mycorrhiza/internal/cfg" "log" "net/http" "sort" @@ -14,7 +15,6 @@ import ( "golang.org/x/crypto/bcrypt" - "github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/util" ) @@ -79,6 +79,11 @@ func Register(username, password, group, source string, force bool) error { return SaveUserDatabase() } +var ( + ErrUnknownUsername = errors.New("unknown username") + ErrWrongPassword = errors.New("wrong password") +) + // LoginDataHTTP logs such user in and returns string representation of an error if there is any. // // The HTTP parameters are used for setting header status (bad request, if it is bad) and saving a cookie. @@ -87,12 +92,12 @@ func LoginDataHTTP(w http.ResponseWriter, username, password string) error { if !HasUsername(username) { w.WriteHeader(http.StatusBadRequest) log.Println("Unknown username", username, "was entered") - return errors.New("unknown username") + return ErrUnknownUsername } if !CredentialsOK(username, password) { w.WriteHeader(http.StatusBadRequest) log.Println("A wrong password was entered for username", username) - return errors.New("wrong password") + return ErrWrongPassword } token, err := AddSession(username) if err != nil { diff --git a/user/user.go b/internal/user/user.go similarity index 97% rename from user/user.go rename to internal/user/user.go index 72a2c25..6aa500a 100644 --- a/user/user.go +++ b/internal/user/user.go @@ -2,12 +2,12 @@ package user import ( "fmt" + "github.com/bouncepaw/mycorrhiza/internal/cfg" "net/http" "strings" "sync" "time" - "github.com/bouncepaw/mycorrhiza/cfg" "golang.org/x/crypto/bcrypt" ) @@ -37,8 +37,8 @@ var minimalRights = map[string]int{ "upload-binary": 1, "rename": 1, "upload-text": 1, - "add-to-category": 2, - "remove-from-category": 2, + "add-to-category": 1, + "remove-from-category": 1, "remove-media": 2, "update-header-links": 3, "delete": 3, diff --git a/user/users.go b/internal/user/users.go similarity index 78% rename from user/users.go rename to internal/user/users.go index 41f4135..d2ca5ef 100644 --- a/user/users.go +++ b/internal/user/users.go @@ -1,6 +1,9 @@ package user -import "sync" +import ( + "sort" + "sync" +) var users sync.Map var tokens sync.Map @@ -99,3 +102,24 @@ func terminateSession(token string) { tokens.Delete(token) dumpTokens() } + +func UsersInGroups() (admins []string, moderators []string, editors []string, readers []string) { + for u := range YieldUsers() { + switch u.Group { + // What if we place the users into sorted slices? + case "admin": + admins = append(admins, u.Name) + case "moderator": + moderators = append(moderators, u.Name) + case "editor", "trusted": + editors = append(editors, u.Name) + case "reader": + readers = append(readers, u.Name) + } + } + sort.Strings(admins) + sort.Strings(moderators) + sort.Strings(editors) + sort.Strings(readers) + return +} diff --git a/version/version.go b/internal/version/version.go similarity index 100% rename from version/version.go rename to internal/version/version.go diff --git a/interwiki/interwiki.go b/interwiki/interwiki.go index 5925db3..de4a532 100644 --- a/interwiki/interwiki.go +++ b/interwiki/interwiki.go @@ -5,7 +5,7 @@ import ( "encoding/json" "errors" "git.sr.ht/~bouncepaw/mycomarkup/v5/options" - "github.com/bouncepaw/mycorrhiza/files" + "github.com/bouncepaw/mycorrhiza/internal/files" "github.com/bouncepaw/mycorrhiza/util" "log" "os" diff --git a/interwiki/web.go b/interwiki/web.go index 7e2420c..58ba51d 100644 --- a/interwiki/web.go +++ b/interwiki/web.go @@ -2,7 +2,7 @@ package interwiki import ( "embed" - "github.com/bouncepaw/mycorrhiza/viewutil" + "github.com/bouncepaw/mycorrhiza/web/viewutil" "github.com/gorilla/mux" "log" "net/http" diff --git a/l18n/en/auth.json b/l18n/en/auth.json index 307420a..3ead3dd 100644 --- a/l18n/en/auth.json +++ b/l18n/en/auth.json @@ -3,33 +3,29 @@ "password": "Password", "register_title": "Register", - "register_header": "Register on {{.name}}", + "register_header": "", "register_button": "Register", - - "login_title": "Login", - "login_header": "Log in to {{.name}}", - "login_button": "Log in", - "logout_title": "Logout?", + "logout_title": "", "logout_header": "Log out?", "logout_button": "Confirm", - "logout_anon": "You cannot log out because you are not logged in.", + "logout_anon": "", "lock_title": "Locked", - "password_tip": "The server stores your password in an encrypted form; even administrators cannot read it.", - "cookie_tip": "By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.", - "telegram_tip": "You can log in using Telegram. It only works if you have set your @username in Telegram and this username is free on this wiki.", + "password_tip": "", + "cookie_tip": "", + "telegram_tip": "", - "noauth": "Authentication is disabled. You can make edits anonymously.", + "noauth": "", "noregister": "Registrations are currently closed. Administrators can make an account for you by hand; contact them.", - "error_username": "Unknown username.", - "error_password": "Wrong password.", - "error_telegram": "Could not authorize using Telegram.", + "error_username": "", + "error_password": "", + "error_telegram": "", "go_back": "Go back", - "go_home": "Go home", + "go_home": "", "go_login": "Go to the login page", "try_again": "Try again" } diff --git a/l18n/en/ui.json b/l18n/en/ui.json index 8350b05..23f9e55 100644 --- a/l18n/en/ui.json +++ b/l18n/en/ui.json @@ -4,18 +4,6 @@ "title_search": "Search by title", "admin_panel": "Admin panel", - "edit_link": "Edit text", - "logout_link": "Log out", - "history_link": "View history", - "rename_link": "Rename", - "delete_link": "Delete", - "text_link": "View markup", - "media_link": "Manage media", - "media_link_for_textual": "Turn to media hypha", - "backlinks_link": "{{.n}} backlink%s", - "backlinks_link+one": "", - "backlinks_link+other": "s", - "subhyphae": "Subhyphae", "random_no_hyphae": "There are no hyphae", @@ -57,9 +45,9 @@ "diff_title": "Diff of {{.name}} at {{.rev}}", - "revision_title": "{{.name}} at {{.rev}}", - "revision_warning": "Please note that viewing media is not supported in history for now.", - "revision_link": "Get Mycomarkup source of this revision", + "revision_title": "", + "revision_warning": "", + "revision_link": "", "revision_no_text": "This hypha had no text at this revision.", "about_title": "About {{.name}}", @@ -76,25 +64,6 @@ "media_noaudio": "Your browser does not support audio.", "media_noaudio_link": "Download audio", - "media_title": "Media of {{.name}}", - "media_empty": "This hypha has no media, you can upload it here.", - "media_tip": "You can manage the hypha's media on this page.", - "media_what_is": "What is media?", - "media_upload": "Upload", - "media_stat": "Stat", - "media_stat_size": "File size:", - "media_size_value": "{{.n}} byte%s", - "media_size_value+one": "", - "media_size_value+other": "s", - "media_stat_mime": "MIME type:", - "media_include": "Include", - "media_include_tip": "This media is an image. To include it in a hypha, use a syntax like this:", - "media_new": "media", - "media_new_tip": "You can upload a new media. Please do not upload too big pictures unless you need to because may not want to wait for big pictures to load.", - "media_remove": "Remove media", - "media_remove_tip": "Please note that you don't have to remove media before uploading a new media.", - "media_remove_button": "Remove media", - "confirm": "Confirm", "cancel": "Cancel" } diff --git a/l18n/ru/auth.json b/l18n/ru/auth.json index 008bb35..1b1943a 100644 --- a/l18n/ru/auth.json +++ b/l18n/ru/auth.json @@ -2,31 +2,31 @@ "username": "Логин", "password": "Пароль", - "register_title": "Регистрация", - "register_header": "Регистрация на «{{.name}}»", - "register_button": "Зарегистрироваться", + "register_title": "", + "register_header": "", + "register_button": "", "login_title": "Вход", - "login_header": "Вход в «{{.name}}»", - "login_button": "Войти", + "login_header": "", + "login_button": "", - "logout_title": "Выйти?", - "logout_header": "Выйти?", + "logout_title": "", + "logout_header": "?", "logout_button": "Подтвердить", - "logout_anon": "Вы не можете выйти, потому что ещё не вошли.", + "logout_anon": "", - "lock_title": "Доступ закрыт", + "lock_title": "", - "password_tip": "Сервер хранит ваш пароль в зашифрованном виде, даже администраторы не смогут его прочесть.", - "cookie_tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.", + "password_tip": "", + "cookie_tip": "", "telegram_tip": "Вы можете войти с помощью Телеграм. Это сработает, если у вашего профиля есть @имя, и оно не занято в этой вики.", - "noauth": "Аутентификация отключена. Вы можете делать правки анонимно.", + "noauth": "", "noregister": "Регистрация в текущее время недоступна. Администраторы могут вручную создать вам учётную запись, свяжитесь с ними.", - "error_username": "Неизвестное имя пользователя.", + "error_username": "", "error_password": "Неверный пароль.", - "error_telegram": "Не удалось авторизоваться через Телеграм.", + "error_telegram": "", "go_back": "Назад", "go_home": "Домой", diff --git a/l18n/ru/ui.json b/l18n/ru/ui.json index 05b3c3a..c4006ba 100644 --- a/l18n/ru/ui.json +++ b/l18n/ru/ui.json @@ -8,14 +8,14 @@ "backlinks_heading": "Обратные ссылки на {{.hypha_link}}", "backlinks_desc": "Ниже перечислены гифы, на которых есть ссылка на эту гифу, трансклюзия этой гифы или эта гифа вставлена как изображение.", - "edit_link": "Редактировать", - "logout_link": "Выйти", - "history_link": "История", - "rename_link": "Переименовать", - "delete_link": "Удалить", - "text_link": "Посмотреть разметку", - "media_link": "Медиа", - "media_link_for_textual": "Превратить в медиа-гифу", + "edit_link": "", + "logout_link": "", + "history_link": "", + "rename_link": "", + "delete_link": "", + "text_link": "", + "media_link": "", + "media_link_for_textual": "", "backlinks_link": "{{.n}} %s сюда", "backlinks_link+one": "ссылка", "backlinks_link+few": "ссылки", @@ -59,9 +59,9 @@ "ask_really": "Вы действительно хотите {{.verb}} гифу «{{.name}}»?", "ask_remove_media_verb": "убрать медиа", - "revision_title": "{{.name}} из {{.rev}}", - "revision_warning": "Обратите внимание, просмотр медиа в истории пока что недоступен.", - "revision_link": "Посмотреть Микоразметку для этой ревизии", + "revision_title": "", + "revision_warning": "", + "revision_link": "", "revision_no_text": "В этой ревизии гифы не было текста.", "about_title": "О {{.name}}", @@ -78,26 +78,6 @@ "media_noaudio": "Ваш браузер не поддерживает аудио.", "media_noaudio_link": "Скачать аудио", - "media_title": "Медиа «{{.name}}»", - "media_empty": "Эта гифа не имеет медиа, здесь вы можете его загрузить.", - "media_tip": "На этой странице вы можете управлять медиа.", - "media_what_is": "Что такое медиа?", - "media_upload": "Загрузить", - "media_stat": "Свойства", - "media_stat_size": "Размер файла:", - "media_size_value": "{{.n}} %s", - "media_size_value+one": "байт", - "media_size_value+few": "байта", - "media_size_value+many": "байт", - "media_stat_mime": "MIME-тип:", - "media_include": "Добавление", - "media_include_tip": "Это медиа – изображение. Чтобы добавить его в текст гифы, используйте синтаксис ниже:", - "media_new": "Прикрепить", - "media_new_tip": "Вы можете загрузить новое медиа. Пожалуйста, не загружайте слишком большие изображения без необходимости, чтобы впоследствии не ждать её долгую загрузку.", - "media_remove": "Открепить", - "media_remove_tip": "Заметьте, чтобы заменить медиа, вам не нужно его перед этим откреплять.", - "media_remove_button": "Открепить", - "confirm": "Применить", "cancel": "Отмена" } diff --git a/main.go b/main.go index 5988df3..4028464 100644 --- a/main.go +++ b/main.go @@ -1,30 +1,27 @@ // Command mycorrhiza is a program that runs a mycorrhiza wiki. // -//go:generate go run github.com/valyala/quicktemplate/qtc -dir=tree //go:generate go run github.com/valyala/quicktemplate/qtc -dir=history //go:generate go run github.com/valyala/quicktemplate/qtc -dir=mycoopts -//go:generate go run github.com/valyala/quicktemplate/qtc -dir=auth -//go:generate go run github.com/valyala/quicktemplate/qtc -dir=hypview package main import ( - "github.com/bouncepaw/mycorrhiza/backlinks" - "github.com/bouncepaw/mycorrhiza/categories" - "github.com/bouncepaw/mycorrhiza/interwiki" - "github.com/bouncepaw/mycorrhiza/migration" - "github.com/bouncepaw/mycorrhiza/version" - "github.com/bouncepaw/mycorrhiza/viewutil" + "github.com/bouncepaw/mycorrhiza/internal/categories" "log" "os" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/files" "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/shroom" - "github.com/bouncepaw/mycorrhiza/static" - "github.com/bouncepaw/mycorrhiza/user" + "github.com/bouncepaw/mycorrhiza/internal/backlinks" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/files" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/migration" + "github.com/bouncepaw/mycorrhiza/internal/shroom" + "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/internal/version" + "github.com/bouncepaw/mycorrhiza/interwiki" "github.com/bouncepaw/mycorrhiza/web" + "github.com/bouncepaw/mycorrhiza/web/static" + "github.com/bouncepaw/mycorrhiza/web/viewutil" ) func main() { diff --git a/misc/about.go b/misc/about.go index cd348f6..36c392a 100644 --- a/misc/about.go +++ b/misc/about.go @@ -1,10 +1,10 @@ package misc import ( - "github.com/bouncepaw/mycorrhiza/cfg" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/internal/version" "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/version" "log" "strings" "text/template" // sic! diff --git a/misc/handlers.go b/misc/handlers.go index d097763..e282ee4 100644 --- a/misc/handlers.go +++ b/misc/handlers.go @@ -11,16 +11,16 @@ import ( "github.com/gorilla/mux" - "github.com/bouncepaw/mycorrhiza/backlinks" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/files" - "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/backlinks" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/files" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/shroom" + "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/shroom" - "github.com/bouncepaw/mycorrhiza/static" - "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/viewutil" + "github.com/bouncepaw/mycorrhiza/web/static" + "github.com/bouncepaw/mycorrhiza/web/viewutil" ) func InitAssetHandlers(rtr *mux.Router) { diff --git a/misc/views.go b/misc/views.go index e8533dc..99f83ff 100644 --- a/misc/views.go +++ b/misc/views.go @@ -2,8 +2,8 @@ package misc import ( "embed" - "github.com/bouncepaw/mycorrhiza/hyphae" - "github.com/bouncepaw/mycorrhiza/viewutil" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/web/viewutil" ) var ( diff --git a/mycoopts/mycoopts.go b/mycoopts/mycoopts.go index 2d0696f..bd1d9f2 100644 --- a/mycoopts/mycoopts.go +++ b/mycoopts/mycoopts.go @@ -3,8 +3,8 @@ package mycoopts import ( "errors" "git.sr.ht/~bouncepaw/mycomarkup/v5/options" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" "github.com/bouncepaw/mycorrhiza/interwiki" "github.com/bouncepaw/mycorrhiza/util" ) diff --git a/mycoopts/view.qtpl b/mycoopts/view.qtpl index 935e4de..362089c 100644 --- a/mycoopts/view.qtpl +++ b/mycoopts/view.qtpl @@ -1,6 +1,6 @@ {% import "path/filepath" %} -{% import "github.com/bouncepaw/mycorrhiza/hyphae" %} +{% import "github.com/bouncepaw/mycorrhiza/internal/hyphae" %} {% import "github.com/bouncepaw/mycorrhiza/l18n" %} {% func mediaRaw(h *hyphae.MediaHypha) %}{%= Media(h, l18n.New("en", "en")) %}{% endfunc %} diff --git a/mycoopts/view.qtpl.go b/mycoopts/view.qtpl.go index ddb1450..7cb2926 100644 --- a/mycoopts/view.qtpl.go +++ b/mycoopts/view.qtpl.go @@ -8,7 +8,7 @@ package mycoopts import "path/filepath" //line mycoopts/view.qtpl:3 -import "github.com/bouncepaw/mycorrhiza/hyphae" +import "github.com/bouncepaw/mycorrhiza/internal/hyphae" //line mycoopts/view.qtpl:4 import "github.com/bouncepaw/mycorrhiza/l18n" diff --git a/settings/view.go b/settings/view.go deleted file mode 100644 index 338447c..0000000 --- a/settings/view.go +++ /dev/null @@ -1,47 +0,0 @@ -package settings - -import ( - "embed" - "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/viewutil" - "github.com/gorilla/mux" - "net/http" - - "github.com/bouncepaw/mycorrhiza/user" -) - -// TODO: translate untranslated strings -const settingsTranslationRu = ` -{{define "change password"}}Change password{{end}} -{{define "confirm password"}}Confirm password{{end}} -{{define "current password"}}Current password{{end}} -{{define "non local password change"}}Non-local accounts cannot have their passwords changed.{{end}} -{{define "password"}}Password{{end}} -{{define "submit"}}Submit{{end}} -` - -var ( - //go:embed *.html - fs embed.FS - changePassowrdChain viewutil.Chain -) - -func Init(rtr *mux.Router) { - rtr.HandleFunc("/change-password", handlerUserChangePassword).Methods(http.MethodGet, http.MethodPost) - - changePassowrdChain = viewutil.CopyEnRuWith(fs, "view_change_password.html", settingsTranslationRu) -} - -func changePasswordPage(meta viewutil.Meta, form util.FormData, u *user.User) { - viewutil.ExecutePage(meta, changePassowrdChain, changePasswordData{ - BaseData: &viewutil.BaseData{}, - Form: form, - U: u, - }) -} - -type changePasswordData struct { - *viewutil.BaseData - Form util.FormData - U *user.User -} diff --git a/tree/view.qtpl b/tree/view.qtpl deleted file mode 100644 index f6fe3ef..0000000 --- a/tree/view.qtpl +++ /dev/null @@ -1,32 +0,0 @@ -{% import "sort" %} -{% import "path" %} -{% import "github.com/bouncepaw/mycorrhiza/util" %} - -Subhyphae links are recursive. It may end up looking like that if drawn with -pseudographics: -╔══════════════╗ -║Foo ║ The presented hyphae are ./foo and ./foo/bar -║╔════════════╗║ -║║Bar ║║ -║╚════════════╝║ -╚══════════════╝ -{% func childHTML(c *child) %} -{% code - sort.Slice(c.children, func(i, j int) bool { - return c.children[i].name < c.children[j].name - }) -%} -
  • - - {%s util.BeautifulName(path.Base(c.name)) %} - -{% if len(c.children) > 0 %} -
      - {% for _, child := range c.children %} - {%s= childHTML(&child) %} - {% endfor %} -
    -{% endif %} -
  • -{% endfunc %} - diff --git a/tree/view.qtpl.go b/tree/view.qtpl.go deleted file mode 100644 index 1c1c89a..0000000 --- a/tree/view.qtpl.go +++ /dev/null @@ -1,126 +0,0 @@ -// Code generated by qtc from "view.qtpl". DO NOT EDIT. -// See https://github.com/valyala/quicktemplate for details. - -//line tree/view.qtpl:1 -package tree - -//line tree/view.qtpl:1 -import "sort" - -//line tree/view.qtpl:2 -import "path" - -//line tree/view.qtpl:3 -import "github.com/bouncepaw/mycorrhiza/util" - -// Subhyphae links are recursive. It may end up looking like that if drawn with -// pseudographics: -// ╔══════════════╗ -// ║Foo ║ The presented hyphae are ./foo and ./foo/bar -// ║╔════════════╗║ -// ║║Bar ║║ -// ║╚════════════╝║ -// ╚══════════════╝ - -//line tree/view.qtpl:13 -import ( - qtio422016 "io" - - qt422016 "github.com/valyala/quicktemplate" -) - -//line tree/view.qtpl:13 -var ( - _ = qtio422016.Copy - _ = qt422016.AcquireByteBuffer -) - -//line tree/view.qtpl:13 -func streamchildHTML(qw422016 *qt422016.Writer, c *child) { -//line tree/view.qtpl:13 - qw422016.N().S(` -`) -//line tree/view.qtpl:15 - sort.Slice(c.children, func(i, j int) bool { - return c.children[i].name < c.children[j].name - }) - -//line tree/view.qtpl:18 - qw422016.N().S(` -
  • - - `) -//line tree/view.qtpl:21 - qw422016.E().S(util.BeautifulName(path.Base(c.name))) -//line tree/view.qtpl:21 - qw422016.N().S(` - -`) -//line tree/view.qtpl:23 - if len(c.children) > 0 { -//line tree/view.qtpl:23 - qw422016.N().S(` -
      - `) -//line tree/view.qtpl:25 - for _, child := range c.children { -//line tree/view.qtpl:25 - qw422016.N().S(` - `) -//line tree/view.qtpl:26 - qw422016.N().S(childHTML(&child)) -//line tree/view.qtpl:26 - qw422016.N().S(` - `) -//line tree/view.qtpl:27 - } -//line tree/view.qtpl:27 - qw422016.N().S(` -
    -`) -//line tree/view.qtpl:29 - } -//line tree/view.qtpl:29 - qw422016.N().S(` -
  • -`) -//line tree/view.qtpl:31 -} - -//line tree/view.qtpl:31 -func writechildHTML(qq422016 qtio422016.Writer, c *child) { -//line tree/view.qtpl:31 - qw422016 := qt422016.AcquireWriter(qq422016) -//line tree/view.qtpl:31 - streamchildHTML(qw422016, c) -//line tree/view.qtpl:31 - qt422016.ReleaseWriter(qw422016) -//line tree/view.qtpl:31 -} - -//line tree/view.qtpl:31 -func childHTML(c *child) string { -//line tree/view.qtpl:31 - qb422016 := qt422016.AcquireByteBuffer() -//line tree/view.qtpl:31 - writechildHTML(qb422016, c) -//line tree/view.qtpl:31 - qs422016 := string(qb422016.B) -//line tree/view.qtpl:31 - qt422016.ReleaseByteBuffer(qb422016) -//line tree/view.qtpl:31 - return qs422016 -//line tree/view.qtpl:31 -} diff --git a/util/util.go b/util/util.go index a00230c..499697b 100644 --- a/util/util.go +++ b/util/util.go @@ -3,13 +3,14 @@ package util import ( "crypto/rand" "encoding/hex" - "github.com/bouncepaw/mycorrhiza/files" "log" "net/http" "strings" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/files" + "git.sr.ht/~bouncepaw/mycomarkup/v5/util" - "github.com/bouncepaw/mycorrhiza/cfg" ) // PrepareRq strips the trailing / in rq.URL.Path. In the future it might do more stuff for making all request structs uniform. @@ -31,6 +32,7 @@ func ShorterPath(path string) string { } // HTTP404Page writes a 404 error in the status, needed when no content is found on the page. +// TODO: demolish func HTTP404Page(w http.ResponseWriter, page string) { w.Header().Set("Content-Type", "text/html;charset=utf-8") w.WriteHeader(http.StatusNotFound) @@ -38,6 +40,7 @@ func HTTP404Page(w http.ResponseWriter, page string) { } // HTTP200Page wraps some frequently used things for successful 200 responses. +// TODO: demolish func HTTP200Page(w http.ResponseWriter, page string) { w.Header().Set("Content-Type", "text/html;charset=utf-8") w.WriteHeader(http.StatusOK) diff --git a/admin/admin.go b/web/admin.go similarity index 54% rename from admin/admin.go rename to web/admin.go index ef5ac3d..c01abce 100644 --- a/admin/admin.go +++ b/web/admin.go @@ -1,20 +1,110 @@ -package admin +package web import ( "fmt" - "github.com/bouncepaw/mycorrhiza/viewutil" - "github.com/gorilla/mux" "log" "mime" "net/http" "os" "sort" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/user" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/web/viewutil" + "github.com/gorilla/mux" ) +const adminTranslationRu = ` +{{define "panel title"}}Панель админстратора{{end}} +{{define "panel safe section title"}}Безопасная секция{{end}} +{{define "panel link about"}}Об этой вики{{end}} +{{define "panel update header"}}Обновить ссылки в верхней панели{{end}} +{{define "panel link user list"}}Список пользователей{{end}} +{{define "panel users"}}Управление пользователями{{end}} +{{define "panel unsafe section title"}}Опасная секция{{end}} +{{define "panel shutdown"}}Выключить вики{{end}} +{{define "panel reindex hyphae"}}Переиндексировать гифы{{end}} +{{define "panel interwiki"}}Интервики{{end}} + +{{define "manage users"}}Управление пользователями{{end}} +{{define "create user"}}Создать пользователя{{end}} +{{define "reindex users"}}Переиндексировать пользователей{{end}} +{{define "name"}}Имя{{end}} +{{define "group"}}Группа{{end}} +{{define "registered at"}}Зарегистрирован{{end}} +{{define "actions"}}Действия{{end}} +{{define "edit"}}Изменить{{end}} + +{{define "new user"}}Новый пользователь{{end}} +{{define "password"}}Пароль{{end}} +{{define "confirm password"}}Подтвердить пароль{{end}} +{{define "change password"}}Изменить пароль{{end}} +{{define "non local password change"}}Поменять пароль можно только у локальных пользователей.{{end}} +{{define "create"}}Создать{{end}} + +{{define "change group"}}Изменить группу{{end}} +{{define "user x"}}Пользователь {{.}}{{end}} +{{define "update"}}Обновить{{end}} +{{define "delete user"}}Удалить пользователя{{end}} +{{define "delete user tip"}}Удаляет пользователя из базы данных. Правки пользователя будут сохранены. Имя пользователя освободится для повторной регистрации.{{end}} + +{{define "delete user?"}}Удалить пользователя {{.}}?{{end}} +{{define "delete user warning"}}Вы уверены, что хотите удалить этого пользователя из базы данных? Это действие нельзя отменить.{{end}} +` + +func viewPanel(meta viewutil.Meta) { + viewutil.ExecutePage(meta, panelChain, &viewutil.BaseData{}) +} + +type listData struct { + *viewutil.BaseData + UserHypha string + Users []*user.User +} + +func viewList(meta viewutil.Meta, users []*user.User) { + viewutil.ExecutePage(meta, listChain, listData{ + BaseData: &viewutil.BaseData{}, + UserHypha: cfg.UserHypha, + Users: users, + }) +} + +type newUserData struct { + *viewutil.BaseData + Form util.FormData +} + +func viewNewUser(meta viewutil.Meta, form util.FormData) { + viewutil.ExecutePage(meta, newUserChain, newUserData{ + BaseData: &viewutil.BaseData{}, + Form: form, + }) +} + +type editDeleteUserData struct { + *viewutil.BaseData + Form util.FormData + U *user.User +} + +func viewEditUser(meta viewutil.Meta, form util.FormData, u *user.User) { + viewutil.ExecutePage(meta, editUserChain, editDeleteUserData{ + BaseData: &viewutil.BaseData{}, + Form: form, + U: u, + }) +} + +func viewDeleteUser(meta viewutil.Meta, form util.FormData, u *user.User) { + viewutil.ExecutePage(meta, deleteUserChain, editDeleteUserData{ + BaseData: &viewutil.BaseData{}, + Form: form, + U: u, + }) +} + // handlerAdmin provides the admin panel. func handlerAdmin(w http.ResponseWriter, rq *http.Request) { w.Header().Set("Content-Type", "text/html;charset=utf-8") diff --git a/categories/handlers.go b/web/cats.go similarity index 70% rename from categories/handlers.go rename to web/cats.go index db016ce..47b1d7f 100644 --- a/categories/handlers.go +++ b/web/cats.go @@ -1,40 +1,46 @@ -package categories +package web import ( - "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/viewutil" - "github.com/gorilla/mux" + "github.com/bouncepaw/mycorrhiza/internal/categories" "io" "log" + "log/slog" "net/http" + "sort" "strings" -) -// InitHandlers initializes HTTP handlers for the given router. Call somewhere in package web. -func InitHandlers(r *mux.Router) { - r.PathPrefix("/add-to-category").HandlerFunc(handlerAddToCategory).Methods("POST") - r.PathPrefix("/remove-from-category").HandlerFunc(handlerRemoveFromCategory).Methods("POST") - r.PathPrefix("/category/").HandlerFunc(handlerCategory).Methods("GET") - r.PathPrefix("/edit-category/").HandlerFunc(handlerEditCategory).Methods("GET") - r.PathPrefix("/category").HandlerFunc(handlerListCategory).Methods("GET") - prepareViews() -} + "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/web/viewutil" +) func handlerEditCategory(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) + meta := viewutil.MetaFrom(w, rq) catName := util.CanonicalName(strings.TrimPrefix(strings.TrimPrefix(rq.URL.Path, "/edit-category"), "/")) if catName == "" { - http.Error(w, "No category name", http.StatusBadRequest) + viewutil.HandlerNotFound(w, rq) return } - log.Println("Editing category", catName) - categoryEdit(viewutil.MetaFrom(w, rq), catName) + + slog.Info("Editing category", "name", catName) + _ = pageCatEdit.RenderTo(meta, map[string]any{ + "Addr": "/edit-category/" + catName, + "CatName": catName, + "Hyphae": categories.HyphaeInCategory(catName), + "GivenPermissionToModify": meta.U.CanProceed("add-to-category"), + }) } func handlerListCategory(w http.ResponseWriter, rq *http.Request) { - log.Println("Viewing list of categories") - categoryList(viewutil.MetaFrom(w, rq)) + slog.Info("Viewing list of categories") + cats := categories.ListOfCategories() + sort.Strings(cats) + + _ = pageCatList.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{ + "Addr": "/category", + "Categories": cats, + }) } func handlerCategory(w http.ResponseWriter, rq *http.Request) { @@ -44,8 +50,15 @@ func handlerCategory(w http.ResponseWriter, rq *http.Request) { handlerListCategory(w, rq) return } - log.Println("Viewing category", catName) - categoryPage(viewutil.MetaFrom(w, rq), catName) + + meta := viewutil.MetaFrom(w, rq) + slog.Info("Viewing category", "name", catName) + _ = pageCatPage.RenderTo(meta, map[string]any{ + "Addr": "/category/" + catName, + "CatName": catName, + "Hyphae": categories.HyphaeInCategory(catName), + "GivenPermissionToModify": meta.U.CanProceed("add-to-category"), + }) } // A request for removal of hyphae can either remove one hypha (used in the card on /hypha) or many hyphae (used in /edit-category). Both approaches are handled by /remove-from-category. This function finds all passed hyphae. @@ -93,7 +106,7 @@ func handlerRemoveFromCategory(w http.ResponseWriter, rq *http.Request) { } for _, hyphaName := range hyphaNames { // TODO: Make it more effective. - removeHyphaFromCategory(hyphaName, catName) + categories.RemoveHyphaFromCategory(hyphaName, catName) } log.Printf("%s removed %q from category %s\n", u.Name, hyphaNames, catName) http.Redirect(w, rq, redirectTo, http.StatusSeeOther) @@ -115,7 +128,7 @@ func handlerAddToCategory(w http.ResponseWriter, rq *http.Request) { http.Redirect(w, rq, redirectTo, http.StatusSeeOther) return } - log.Println(user.FromRequest(rq).Name, "added", hyphaName, "to", catName) - AddHyphaToCategory(hyphaName, catName) + slog.Info(user.FromRequest(rq).Name, "added", hyphaName, "to", catName) + categories.AddHyphaToCategory(hyphaName, catName) http.Redirect(w, rq, redirectTo, http.StatusSeeOther) } diff --git a/web/mutators.go b/web/mutators.go index e85ddda..75e13ae 100644 --- a/web/mutators.go +++ b/web/mutators.go @@ -2,22 +2,21 @@ package web import ( "git.sr.ht/~bouncepaw/mycomarkup/v5" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/shroom" + "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/web/viewutil" "html/template" "log" "net/http" + "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" "github.com/bouncepaw/mycorrhiza/hypview" "github.com/bouncepaw/mycorrhiza/mycoopts" - "github.com/bouncepaw/mycorrhiza/viewutil" - - "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" "github.com/gorilla/mux" - "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/shroom" - "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" ) @@ -25,7 +24,7 @@ func initMutators(r *mux.Router) { r.PathPrefix("/edit/").HandlerFunc(handlerEdit) r.PathPrefix("/rename/").HandlerFunc(handlerRename).Methods("GET", "POST") r.PathPrefix("/delete/").HandlerFunc(handlerDelete).Methods("GET", "POST") - r.PathPrefix("/remove-media/").HandlerFunc(handlerRemoveMedia).Methods("GET", "POST") + r.PathPrefix("/remove-media/").HandlerFunc(handlerRemoveMedia).Methods("POST") r.PathPrefix("/upload-binary/").HandlerFunc(handlerUploadBinary) r.PathPrefix("/upload-text/").HandlerFunc(handlerUploadText) } @@ -43,10 +42,6 @@ func handlerRemoveMedia(w http.ResponseWriter, rq *http.Request) { viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no rights") return } - if rq.Method == "GET" { - hypview.RemoveMedia(viewutil.MetaFrom(w, rq), h.CanonicalName()) - return - } switch h := h.(type) { case *hyphae.EmptyHypha, *hyphae.TextualHypha: viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "no media to remove") @@ -83,7 +78,11 @@ func handlerDelete(w http.ResponseWriter, rq *http.Request) { } if rq.Method == "GET" { - hypview.DeleteHypha(meta, h.CanonicalName()) + _ = pageHyphaDelete.RenderTo( + viewutil.MetaFrom(w, rq), + map[string]any{ + "HyphaName": h.CanonicalName(), + }) return } @@ -169,7 +168,15 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { return } } - hypview.EditHypha(meta, hyphaName, isNew, content, "", "") + _ = pageHyphaEdit.RenderTo( + viewutil.MetaFrom(w, rq), + map[string]any{ + "HyphaName": hyphaName, + "Content": content, + "IsNew": isNew, + "Message": "", + "Preview": "", + }) } // handlerUploadText uploads a new text part for the hypha. @@ -191,7 +198,16 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) { if action == "preview" { ctx, _ := mycocontext.ContextFromStringInput(textData, mycoopts.MarkupOptions(hyphaName)) preview := template.HTML(mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx))) - hypview.EditHypha(meta, hyphaName, isNew, textData, message, preview) + + _ = pageHyphaEdit.RenderTo( + viewutil.MetaFrom(w, rq), + map[string]any{ + "HyphaName": hyphaName, + "Content": textData, + "IsNew": isNew, + "Message": message, + "Preview": preview, + }) return } diff --git a/viewutil/base.html b/web/newtmpl/base.html similarity index 100% rename from viewutil/base.html rename to web/newtmpl/base.html diff --git a/web/newtmpl/newtmpl.go b/web/newtmpl/newtmpl.go new file mode 100644 index 0000000..b0184c5 --- /dev/null +++ b/web/newtmpl/newtmpl.go @@ -0,0 +1,118 @@ +package newtmpl + +import ( + "embed" + "fmt" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/web/viewutil" + "html/template" + "strings" +) + +//go:embed *.html +var fs embed.FS + +var base = template.Must(template.ParseFS(fs, "base.html")) + +type Page struct { + TemplateEnglish *template.Template + TemplateRussian *template.Template +} + +func NewPage(fs embed.FS, russianTranslation map[string]string, tmpls ...string) *Page { + must := template.Must + en := must(must(must( + base.Clone()). + Funcs(template.FuncMap{ + "beautifulName": util.BeautifulName, + "inc": func(i int) int { return i + 1 }, + "base": func(hyphaName string) string { + parts := strings.Split(hyphaName, "/") + return parts[len(parts)-1] + }, + "beautifulLink": func(hyphaName string) template.HTML { + return template.HTML( + fmt.Sprintf( + `%s`, hyphaName, hyphaName)) + }, + }). + Parse(fmt.Sprintf(` +{{define "wiki name"}}%s{{end}} +{{define "user hypha"}}%s{{end}} +`, cfg.WikiName, cfg.UserHypha))). + ParseFS(fs, tmpls...)) + + if cfg.UseAuth { + en = must(en.Parse(` +{{define "auth"}} + +{{end}} +`)) + } + if cfg.AllowRegistration { + must(en.Parse(`{{define "registration"}} +{{if .Meta.U.Group | eq "anon"}} + +{{end}} +{{end}}`)) + } + + russianTranslation["search by title"] = "Поиск по названию" + russianTranslation["login"] = "Войти" + russianTranslation["register"] = "Регистрация" + russianTranslation["cancel"] = "Отмена" + russianTranslation["categories"] = "Категории" + russianTranslation["remove from category title"] = "Убрать гифу из этой категории" + russianTranslation["placeholder"] = "Название категории..." + russianTranslation["add to category title"] = "Добавить гифу в эту категорию" + + return &Page{ + TemplateEnglish: en, + TemplateRussian: must(must(en.Clone()). + Parse(translationsIntoTemplates(russianTranslation))), + } +} + +func translationsIntoTemplates(m map[string]string) string { + var sb strings.Builder + for k, v := range m { + sb.WriteString(fmt.Sprintf(`{{define "%s"}}%s{{end}} +`, k, v)) + } + return sb.String() +} + +func (p *Page) RenderTo(meta viewutil.Meta, data map[string]any) error { + data["Meta"] = meta + data["HeadElements"] = meta.HeadElements + data["BodyAttributes"] = meta.BodyAttributes + data["CommonScripts"] = cfg.CommonScripts + data["EditScripts"] = cfg.EditScripts + data["HeaderLinks"] = viewutil.HeaderLinks + data["UseAuth"] = cfg.UseAuth + + tmpl := p.TemplateEnglish + if meta.LocaleIsRussian() { + tmpl = p.TemplateRussian + } + + return tmpl.ExecuteTemplate(meta.W, "page", data) +} diff --git a/web/pages.go b/web/pages.go new file mode 100644 index 0000000..38cf691 --- /dev/null +++ b/web/pages.go @@ -0,0 +1,202 @@ +package web + +import ( + "embed" + "github.com/bouncepaw/mycorrhiza/web/newtmpl" + "github.com/bouncepaw/mycorrhiza/web/viewutil" +) + +//go:embed views/*.html +var fs embed.FS + +var pageOrphans, pageBacklinks, pageUserList, pageChangePassword *newtmpl.Page +var pageHyphaDelete, pageHyphaEdit, pageHyphaEmpty, pageHypha *newtmpl.Page +var pageRevision, pageMedia *newtmpl.Page +var pageAuthLock, pageAuthLogin, pageAuthLogout, pageAuthRegister *newtmpl.Page +var pageCatPage, pageCatList, pageCatEdit *newtmpl.Page + +var panelChain, listChain, newUserChain, editUserChain, deleteUserChain viewutil.Chain + +func initPages() { + + panelChain = viewutil.CopyEnRuWith(fs, "views/admin-panel.html", adminTranslationRu) + listChain = viewutil.CopyEnRuWith(fs, "views/admin-user-list.html", adminTranslationRu) + newUserChain = viewutil.CopyEnRuWith(fs, "views/admin-new-user.html", adminTranslationRu) + editUserChain = viewutil.CopyEnRuWith(fs, "views/admin-edit-user.html", adminTranslationRu) + deleteUserChain = viewutil.CopyEnRuWith(fs, "views/admin-delete-user.html", adminTranslationRu) + + pageOrphans = newtmpl.NewPage(fs, map[string]string{ + "orphaned hyphae": "Гифы-сироты", + "orphan description": "Ниже перечислены гифы без ссылок на них.", + }, "views/orphans.html") + pageBacklinks = newtmpl.NewPage(fs, map[string]string{ + "backlinks to text": `Обратные ссылки на {{.}}`, + "backlinks to link": `Обратные ссылки на {{beautifulName .}}`, + "description": `Ниже перечислены гифы, на которых есть ссылка на эту гифу, трансклюзия этой гифы или эта гифа вставлена как изображение.`, + }, "views/backlinks.html") + pageUserList = newtmpl.NewPage(fs, map[string]string{ + "title": "Список пользователей", + "administrators": "Администраторы", + "moderators": "Модераторы", + "editors": "Редакторы", + "readers": "Читатели", + }, "views/user-list.html") + pageChangePassword = newtmpl.NewPage(fs, map[string]string{ + "change password": "Сменить пароль", + "confirm password": "Повторите пароль", + "current password": "Текущий пароль", + "non local password change": "Пароль можно поменять только местным аккаунтам. Telegram-аккаунтам нельзя.", + "password": "Пароль", + "submit": "Поменять", + }, "views/change-password.html") + pageHyphaDelete = newtmpl.NewPage(fs, map[string]string{ + "delete hypha?": "Удалить {{beautifulName .}}?", + "delete [[hypha]]?": "Удалить {{beautifulName .}}?", + "want to delete?": "Вы действительно хотите удалить эту гифу?", + "delete tip": "Нельзя отменить удаление гифы, но её история останется доступной.", + }, "views/hypha-delete.html") + pageHyphaEdit = newtmpl.NewPage(fs, map[string]string{ + "editing hypha": `Редактирование {{beautifulName .}}`, + "editing [[hypha]]": `Редактирование {{beautifulName .}}`, + "creating [[hypha]]": `Создание {{beautifulName .}}`, + "you're creating a new hypha": `Вы создаёте новую гифу.`, + "describe your changes": `Опишите ваши правки`, + "save": `Сохранить`, + "preview": `Предпросмотр`, + "previewing hypha": `Предпросмотр {{beautifulName .}}`, + "preview tip": `Заметьте, эта гифа ещё не сохранена. Вот её предпросмотр:`, + + "markup": `Разметка`, + "link": `Ссылка`, + "link title": `Текст`, + "heading": `Заголовок`, + "bold": `Жирный`, + "italic": `Курсив`, + "highlight": `Выделение`, + "underline": `Подчеркивание`, + "mono": `Моноширинный`, + "super": `Надстрочный`, + "sub": `Подстрочный`, + "strike": `Зачёркнутый`, + "rocket": `Ссылка-ракета`, + "transclude": `Трансклюзия`, + "hr": `Гориз. черта`, + "code": `Код-блок`, + "bullets": `Маркир. список`, + "numbers": `Нумер. список`, + "mycomarkup help": `Подробнее о Микоразметке`, + "actions": `Действия`, + "current date local": `Местная дата`, + "current time local": `Местное время`, + "current date utc": "Дата UTC", + "current time utc": "Время UTC", + "selflink": `Ссылка на вас`, + }, "views/hypha-edit.html") + pageHypha = newtmpl.NewPage(fs, map[string]string{ + "edit text": "Редактировать", + "log out": "Выйти", + "admin panel": "Админка", + "subhyphae": "Подгифы", + "history": "История", + "rename": "Переименовать", + "delete": "Удалить", + "view markup": "Посмотреть разметку", + "manage media": "Медиа", + "turn to media": "Превратить в медиа-гифу", + "backlinks": "{{.BacklinkCount}} обратн{{if eq .BacklinkCount 1}}ая ссылка{{else if and (le .BacklinkCount 4) (gt .BacklinkCount 1)}}ые ссылки{{else}}ых ссылок{{end}}", + + "empty heading": `Эта гифа не существует`, + "empty no rights": `У вас нет прав для создания новых гиф. Вы можете:`, + "empty log in": `Войти в свою учётную запись, если она у вас есть`, + "empty register": `Создать новую учётную запись`, + "write a text": `Написать текст`, + "write a text tip": `Напишите заметку, дневник, статью, рассказ или иной текст с помощью Микоразметки. Сохраняется полная история правок документа.`, + "write a text writing conventions": `Не забывайте следовать правилам оформления этой вики, если они имеются.`, + "write a text btn": `Создать`, + "upload a media": `Загрузить медиа`, + "upload a media tip": `Загрузите изображение, видео или аудио. Распространённые форматы можно просматривать из браузера, остальные можно только скачать и просмотреть локально. Позже вы можете дописать пояснение к этому медиа.`, + "upload a media btn": `Загрузить`, + }, "views/hypha.html") + pageRevision = newtmpl.NewPage(fs, map[string]string{ + "revision warning": "Обратите внимание, просмотр медиа в истории пока что недоступен.", + "revision link": "Посмотреть Микоразметку для этой ревизии", + "hypha at rev": "{{.HyphaName}} на {{.RevHash}}", + }, "views/hypha-revision.html") + pageMedia = newtmpl.NewPage(fs, map[string]string{ // TODO: сделать новый перевод + "media title": "Медиа «{{.HyphaName | beautifulLink}}»", + "tip": "На этой странице вы можете управлять медиа.", + "empty": "Эта гифа не имеет медиа, здесь вы можете его загрузить.", + "what is media?": "Что такое медиа?", + "stat": "Свойства", + "stat size": "Размер файла:", + "stat mime": "MIME-тип:", + + "upload title": "Прикрепить", + "upload tip": "Вы можете загрузить новое медиа. Пожалуйста, не загружайте слишком большие изображения без необходимости, чтобы впоследствии не ждать её долгую загрузку.", + "upload btn": "Загрузить", + + "remove title": "Открепить", + "remove tip": "Заметьте, чтобы заменить медиа, вам не нужно его перед этим откреплять.", + "remove btn": "Открепить", + }, "views/hypha-media.html") + + pageAuthLock = newtmpl.NewPage(fs, map[string]string{ + "lock title": "Доступ закрыт", + "username": "Логин", + "password": "Пароль", + "log in": "Войти", + }, "views/auth-telegram.html", "views/auth-lock.html") + + pageAuthLogin = newtmpl.NewPage(fs, map[string]string{ + "username": "Логин", + "password": "Пароль", + "log in": "Войти", + "cookie tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.", + "log in to x": "Войти в {{.}}", + "auth disabled": "Аутентификация отключена. Вы можете делать правки анонимно.", + "error username": "Неизвестное имя пользователя.", + "error password": "Неправильный пароль.", + "error telegram": "Не удалось войти через Телеграм.", + "go home": "Домой", + }, "views/auth-telegram.html", "views/auth-login.html") + + pageAuthLogout = newtmpl.NewPage(fs, map[string]string{ + "log out?": "Выйти?", + "log out": "Выйти", + "cannot log out anon": "Вы не можете выйти, потому что ещё не вошли.", + "log in": "Войти", + "go home": "Домой", + }, "views/auth-logout.html") + + pageAuthRegister = newtmpl.NewPage(fs, map[string]string{ + "username": "Логин", + "password": "Пароль", + "cookie tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.", + "password tip": "Сервер хранит ваш пароль в зашифрованном виде, даже администраторы не смогут его прочесть.", + "register btn": "Зарегистрироваться", + "register on x": "Регистрация на {{.}}", + }, "views/auth-telegram.html", "views/auth-register.html") + + pageCatPage = newtmpl.NewPage(fs, map[string]string{ + "category x": "Категория {{. | beautifulName}}", + "edit": "Редактировать", + "cat": "Категория", + "empty cat": "Эта категория пуста.", + }, "views/cat-page.html") + + pageCatEdit = newtmpl.NewPage(fs, map[string]string{ + "edit category x": "Редактирование категории {{beautifulName .}}", + "edit category heading": "Редактирование категории {{beautifulName .}}", + "empty cat": "Эта категория пуста.", + "add to category title": "Добавить гифу в эту категорию", + "hypha name": "Название гифы", + "add": "Добавить", + "remove hyphae": "Убрать гифы из этой категории", + "remove": "Убрать", + }, "views/cat-edit.html") + + pageCatList = newtmpl.NewPage(fs, map[string]string{ + "category list": "Список категорий", + "no categories": "В этой вики нет категорий.", + }, "views/cat-list.html") +} diff --git a/settings/settings.go b/web/password.go similarity index 84% rename from settings/settings.go rename to web/password.go index fbb3eca..4a74a7b 100644 --- a/settings/settings.go +++ b/web/password.go @@ -1,15 +1,13 @@ -package settings +package web import ( "fmt" + "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/web/viewutil" "mime" "net/http" "reflect" - - "github.com/bouncepaw/mycorrhiza/viewutil" - - "github.com/bouncepaw/mycorrhiza/user" - "github.com/bouncepaw/mycorrhiza/util" ) func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) { @@ -49,6 +47,7 @@ func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) { f = f.WithError(err) } } else { + // TODO: handle first attempt different err := fmt.Errorf("incorrect password") f = f.WithError(err) } @@ -58,5 +57,11 @@ func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) { } w.Header().Set("Content-Type", mime.TypeByExtension(".html")) - changePasswordPage(viewutil.MetaFrom(w, rq), f, u) + _ = pageChangePassword.RenderTo( + viewutil.MetaFrom(w, rq), + map[string]any{ + "Form": f, + "U": u, + }, + ) } diff --git a/web/readers.go b/web/readers.go index 0482a9a..0f8363c 100644 --- a/web/readers.go +++ b/web/readers.go @@ -3,15 +3,24 @@ package web import ( "fmt" "git.sr.ht/~bouncepaw/mycomarkup/v5" - "github.com/bouncepaw/mycorrhiza/categories" - "github.com/bouncepaw/mycorrhiza/files" - views2 "github.com/bouncepaw/mycorrhiza/hypview" + "github.com/bouncepaw/mycorrhiza/hypview" + "github.com/bouncepaw/mycorrhiza/internal/backlinks" + "github.com/bouncepaw/mycorrhiza/internal/categories" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/files" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/mimetype" + "github.com/bouncepaw/mycorrhiza/internal/tree" + "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/mycoopts" - "github.com/bouncepaw/mycorrhiza/viewutil" + "github.com/bouncepaw/mycorrhiza/web/viewutil" + "html/template" "io" "log" + "log/slog" "net/http" "os" + "path" "path/filepath" "strings" "time" @@ -21,10 +30,7 @@ import ( "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" "git.sr.ht/~bouncepaw/mycomarkup/v5/tools" "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/hyphae" "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/mimetype" - "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" ) @@ -38,6 +44,10 @@ func initReaders(r *mux.Router) { r.PathPrefix("/media/").HandlerFunc(handlerMedia) r.Path("/today").HandlerFunc(handlerToday) r.Path("/edit-today").HandlerFunc(handlerEditToday) + + // Backlinks + r.PathPrefix("/backlinks/").HandlerFunc(handlerBacklinks) + r.PathPrefix("/orphans").HandlerFunc(handlerOrphans) } func handlerEditToday(w http.ResponseWriter, rq *http.Request) { @@ -56,15 +66,31 @@ func handlerMedia(w http.ResponseWriter, rq *http.Request) { hyphaName = util.HyphaNameFromRq(rq, "media") h = hyphae.ByName(hyphaName) u = user.FromRequest(rq) - lc = l18n.FromRequest(rq) + isMedia = false + + mime string + fileSize int64 ) - util.HTTP200Page(w, - viewutil.Base( - viewutil.MetaFrom(w, rq), - lc.Get("ui.media_title", &l18n.Replacements{"name": util.BeautifulName(hyphaName)}), - views2.MediaMenu(rq, h, u), - map[string]string{}, - )) + switch h := h.(type) { + case *hyphae.MediaHypha: + isMedia = true + mime = mimetype.FromExtension(path.Ext(h.MediaFilePath())) + + fileinfo, err := os.Stat(h.MediaFilePath()) + if err != nil { + slog.Error("failed to stat media file", "err", err) + // no return + } + + fileSize = fileinfo.Size() + } + _ = pageMedia.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{ + "HyphaName": h.CanonicalName(), + "U": u, + "IsMediaHypha": isMedia, + "MimeType": mime, + "FileSize": fileSize, + }) } // handlerRevisionText sends Mycomarkup text of the hypha at the given revision. See also: handlerRevision, handlerText. @@ -129,7 +155,7 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { var ( hyphaName = util.CanonicalName(slug) h = hyphae.ByName(hyphaName) - contents = fmt.Sprintf(`

    %s

    `, lc.Get("ui.revision_no_text")) + contents = template.HTML(fmt.Sprintf(`

    %s

    `, lc.Get("ui.revision_no_text"))) textContents string err error mycoFilePath string @@ -143,26 +169,17 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) { textContents, err = history.FileAtRevision(mycoFilePath, revHash) if err == nil { ctx, _ := mycocontext.ContextFromStringInput(textContents, mycoopts.MarkupOptions(hyphaName)) - contents = mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx)) + contents = template.HTML(mycomarkup.BlocksToHTML(ctx, mycomarkup.BlockTree(ctx))) } - page := views2.Revision( - viewutil.MetaFrom(w, rq), - h, - contents, - revHash, - ) - w.Header().Set("Content-Type", "text/html;charset=utf-8") - w.WriteHeader(http.StatusOK) - _, _ = fmt.Fprint( - w, - viewutil.Base( - viewutil.MetaFrom(w, rq), - lc.Get("ui.revision_title", &l18n.Replacements{"name": util.BeautifulName(hyphaName), "rev": revHash}), - page, - map[string]string{}, - ), - ) + meta := viewutil.MetaFrom(w, rq) + _ = pageRevision.RenderTo(meta, map[string]any{ + "ViewScripts": cfg.ViewScripts, + "Contents": contents, + "RevHash": revHash, + "NaviTitle": hypview.NaviTitle(meta, h.CanonicalName()), + "HyphaName": h.CanonicalName(), + }) } // handlerText serves raw source text of the hypha. @@ -182,8 +199,7 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) hyphaName := util.HyphaNameFromRq(rq, "binary") switch h := hyphae.ByName(hyphaName).(type) { - case *hyphae.EmptyHypha: - case *hyphae.TextualHypha: + case *hyphae.EmptyHypha, *hyphae.TextualHypha: w.WriteHeader(http.StatusNotFound) log.Printf("Textual hypha ‘%s’ has no media, cannot serve\n", h.CanonicalName()) case *hyphae.MediaHypha: @@ -197,44 +213,80 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) { func handlerHypha(w http.ResponseWriter, rq *http.Request) { util.PrepareRq(rq) var ( - hyphaName = util.HyphaNameFromRq(rq, "page", "hypha") - h = hyphae.ByName(hyphaName) - contents string - openGraph string - lc = l18n.FromRequest(rq) + hyphaName = util.HyphaNameFromRq(rq, "page", "hypha") + h = hyphae.ByName(hyphaName) + contents template.HTML + openGraph template.HTML + lc = l18n.FromRequest(rq) + meta = viewutil.MetaFrom(w, rq) + subhyphae, prevHyphaName, nextHyphaName = tree.Tree(h.CanonicalName()) + cats = categories.CategoriesWithHypha(h.CanonicalName()) + category_list = ":" + strings.Join(cats, ":") + ":" + isMyProfile = cfg.UseAuth && util.IsProfileName(h.CanonicalName()) && meta.U.Name == strings.TrimPrefix(h.CanonicalName(), cfg.UserHypha+"/") + + data = map[string]any{ + "HyphaName": h.CanonicalName(), + "SubhyphaeHTML": subhyphae, + "PrevHyphaName": prevHyphaName, + "NextHyphaName": nextHyphaName, + "IsMyProfile": isMyProfile, + "NaviTitle": hypview.NaviTitle(meta, h.CanonicalName()), + "BacklinkCount": backlinks.BacklinksCount(h.CanonicalName()), + "GivenPermissionToModify": user.CanProceed(rq, "edit"), + "Categories": cats, + "IsMediaHypha": false, + } ) + slog.Info("reading hypha", "name", h.CanonicalName(), "can edit", data["GivenPermissionToModify"]) + meta.BodyAttributes = map[string]string{ + "cats": category_list, + } switch h := h.(type) { case *hyphae.EmptyHypha: - util.HTTP404Page(w, - viewutil.Base( - viewutil.MetaFrom(w, rq), - util.BeautifulName(hyphaName), - views2.Hypha(viewutil.MetaFrom(w, rq), h, contents), - map[string]string{}, - openGraph)) + w.WriteHeader(http.StatusNotFound) + data["Contents"] = "" + _ = pageHypha.RenderTo(meta, data) case hyphae.ExistingHypha: - fileContentsT, errT := os.ReadFile(h.TextFilePath()) - if errT == nil { + fileContentsT, err := os.ReadFile(h.TextFilePath()) + if err == nil { ctx, _ := mycocontext.ContextFromStringInput(string(fileContentsT), mycoopts.MarkupOptions(hyphaName)) getOpenGraph, descVisitor, imgVisitor := tools.OpenGraphVisitors(ctx) + openGraph = template.HTML(getOpenGraph()) ast := mycomarkup.BlockTree(ctx, descVisitor, imgVisitor) - contents = mycomarkup.BlocksToHTML(ctx, ast) - openGraph = getOpenGraph() + contents = template.HTML(mycomarkup.BlocksToHTML(ctx, ast)) } switch h := h.(type) { case *hyphae.MediaHypha: - contents = mycoopts.Media(h, lc) + contents + contents = template.HTML(mycoopts.Media(h, lc)) + contents + data["IsMediaHypha"] = true } - category_list := ":" + strings.Join(categories.CategoriesWithHypha(h.CanonicalName()), ":") + ":" + data["Contents"] = contents + meta.HeadElements = append(meta.HeadElements, openGraph) + _ = pageHypha.RenderTo(meta, data) - util.HTTP200Page(w, - viewutil.Base( - viewutil.MetaFrom(w, rq), - util.BeautifulName(hyphaName), - views2.Hypha(viewutil.MetaFrom(w, rq), h, contents), - map[string]string{"cats": category_list}, - openGraph)) + // TODO: check head cats + // TODO: check opengraph } } + +// handlerBacklinks lists all backlinks to a hypha. +func handlerBacklinks(w http.ResponseWriter, rq *http.Request) { + hyphaName := util.HyphaNameFromRq(rq, "backlinks") + + _ = pageBacklinks.RenderTo(viewutil.MetaFrom(w, rq), + map[string]any{ + "Addr": "/backlinks/" + hyphaName, + "HyphaName": hyphaName, + "Backlinks": backlinks.BacklinksFor(hyphaName), + }) +} + +func handlerOrphans(w http.ResponseWriter, rq *http.Request) { + _ = pageOrphans.RenderTo(viewutil.MetaFrom(w, rq), + map[string]any{ + "Addr": "/orphans", + "Orphans": backlinks.Orphans(), + }) +} diff --git a/static/common.js b/web/static/common.js similarity index 100% rename from static/common.js rename to web/static/common.js diff --git a/static/default.css b/web/static/default.css similarity index 99% rename from static/default.css rename to web/static/default.css index f13975f..36c683f 100644 --- a/static/default.css +++ b/web/static/default.css @@ -354,7 +354,7 @@ kbd { margin: 0; padding: 8px; border: none; - background: url(/static/icon/x.svg) no-repeat 8px 8px / 16px 16px; + background: url(/web/static/icon/x.svg) no-repeat 8px 8px / 16px 16px; width: 32px; height: 32px; cursor: pointer; diff --git a/static/editor.js b/web/static/editor.js similarity index 100% rename from static/editor.js rename to web/static/editor.js diff --git a/static/icon/README.md b/web/static/icon/README.md similarity index 100% rename from static/icon/README.md rename to web/static/icon/README.md diff --git a/static/icon/feed.svg b/web/static/icon/feed.svg similarity index 100% rename from static/icon/feed.svg rename to web/static/icon/feed.svg diff --git a/static/icon/gemini-proto.svg b/web/static/icon/gemini-proto.svg similarity index 100% rename from static/icon/gemini-proto.svg rename to web/static/icon/gemini-proto.svg diff --git a/static/icon/gopher-proto.svg b/web/static/icon/gopher-proto.svg similarity index 100% rename from static/icon/gopher-proto.svg rename to web/static/icon/gopher-proto.svg diff --git a/static/icon/http-proto.svg b/web/static/icon/http-proto.svg similarity index 100% rename from static/icon/http-proto.svg rename to web/static/icon/http-proto.svg diff --git a/static/icon/mailto-proto.svg b/web/static/icon/mailto-proto.svg similarity index 100% rename from static/icon/mailto-proto.svg rename to web/static/icon/mailto-proto.svg diff --git a/static/icon/mushroom.png b/web/static/icon/mushroom.png similarity index 100% rename from static/icon/mushroom.png rename to web/static/icon/mushroom.png diff --git a/static/icon/x.svg b/web/static/icon/x.svg similarity index 100% rename from static/icon/x.svg rename to web/static/icon/x.svg diff --git a/static/robots.txt b/web/static/robots.txt similarity index 100% rename from static/robots.txt rename to web/static/robots.txt diff --git a/static/shortcuts.js b/web/static/shortcuts.js similarity index 100% rename from static/shortcuts.js rename to web/static/shortcuts.js diff --git a/static/static.go b/web/static/static.go similarity index 100% rename from static/static.go rename to web/static/static.go diff --git a/static/toolbar.js b/web/static/toolbar.js similarity index 100% rename from static/toolbar.js rename to web/static/toolbar.js diff --git a/static/view.js b/web/static/view.js similarity index 100% rename from static/view.js rename to web/static/view.js diff --git a/admin/view_delete_user.html b/web/views/admin-delete-user.html similarity index 100% rename from admin/view_delete_user.html rename to web/views/admin-delete-user.html diff --git a/admin/view_edit_user.html b/web/views/admin-edit-user.html similarity index 100% rename from admin/view_edit_user.html rename to web/views/admin-edit-user.html diff --git a/admin/view_new_user.html b/web/views/admin-new-user.html similarity index 100% rename from admin/view_new_user.html rename to web/views/admin-new-user.html diff --git a/admin/view_panel.html b/web/views/admin-panel.html similarity index 93% rename from admin/view_panel.html rename to web/views/admin-panel.html index a850107..fdd99d4 100644 --- a/admin/view_panel.html +++ b/web/views/admin-panel.html @@ -11,6 +11,7 @@
  • {{block "panel link user list" .}}User list{{end}}
  • {{block "panel users" .}}Manage users{{end}}
  • {{block "panel interwiki" .}}Interwiki{{end}}
  • +
  • {{block "panel/orphans" .}}Orphaned hyphae{{end}}
  • diff --git a/admin/view_user_list.html b/web/views/admin-user-list.html similarity index 100% rename from admin/view_user_list.html rename to web/views/admin-user-list.html diff --git a/web/views/auth-lock.html b/web/views/auth-lock.html new file mode 100644 index 0000000..0784400 --- /dev/null +++ b/web/views/auth-lock.html @@ -0,0 +1,33 @@ +{{define "title"}}{{block "lock title" .}}Locked{{end}}{{end}} +{{define "page"}} + + + + + + 🔒 {{template "lock title" .}} + + + + +
    +
    +

    🔒

    +

    {{template "lock title" .}}

    + + {{template "telegram widget"}} +
    +
    + + +{{end}} \ No newline at end of file diff --git a/web/views/auth-login.html b/web/views/auth-login.html new file mode 100644 index 0000000..73ecb8a --- /dev/null +++ b/web/views/auth-login.html @@ -0,0 +1,39 @@ +{{define "log in to x"}}Log in to {{.}}{{end}} +{{define "title"}}{{template "log in to x" .WikiName}}{{end}} +{{define "body"}} +
    +
    + {{if .UseAuth}} + {{if .ErrUnknownUsername}} +

    {{block "error username" .}}Unknown username.{{end}}

    + {{else if .ErrWrongPassword}} +

    {{block "error password" .}}Wrong password.{{end}}

    + {{else if .ErrTelegram}} +

    {{block "error telegram" .}}Could not authorize using Telegram.{{end}}

    + {{else if .Err}} +

    {{.Err}}

    + {{end}} + + + {{template "telegram widget" .}} + {{else}} +

    {{block "auth disabled" .}}Authentication is disabled. You can make edits anonymously.{{end}}

    +

    ← {{block "go home" .}}Go home{{end}}

    + {{end}} +
    +
    +{{end}} \ No newline at end of file diff --git a/web/views/auth-logout.html b/web/views/auth-logout.html new file mode 100644 index 0000000..7a40f6c --- /dev/null +++ b/web/views/auth-logout.html @@ -0,0 +1,18 @@ +{{define "title"}}{{end}} +{{define "body"}} +
    +
    + {{if .CanLogout}} +

    {{block "log out?" .}}Log out?{{end}}

    +
    + +

    ← {{block "go home" .}}Go home{{end}}

    +
    + {{else}} +

    {{block "cannot log out anon" .}}You cannot log out because you are not logged in.{{end}}

    +

    {{block "log in" .}}Log in{{end}}

    +

    ← {{template "go home"}}

    + {{end}} +
    +
    +{{end}} \ No newline at end of file diff --git a/web/views/auth-register.html b/web/views/auth-register.html new file mode 100644 index 0000000..b2c44fa --- /dev/null +++ b/web/views/auth-register.html @@ -0,0 +1,35 @@ +{{define "register on x"}}Register on {{.}}{{end}} +{{define "title"}}{{template "register on x" .WikiName}}{{end}} +{{define "body"}} +
    +
    + {{if .AllowRegistration}} + + {{template "telegram widget" .}} + {{else if .UseAuth}} +

    {%s lc.Get("auth.noregister") %}

    +

    ← {%s lc.Get("auth.go_back") %}

    + {{else}} +

    {%s lc.Get("auth.noauth") %}

    +

    ← {%s lc.Get("auth.go_back") %}

    + {{end}} +
    +
    +{{end}} \ No newline at end of file diff --git a/web/views/auth-telegram.html b/web/views/auth-telegram.html new file mode 100644 index 0000000..432f5f7 --- /dev/null +++ b/web/views/auth-telegram.html @@ -0,0 +1,11 @@ +{{define "telegram widget"}} + {{if .TelegramEnabled}} +

    {{block "telegram tip" .}}You can log in using Telegram. It only works if you have set your @username in Telegram and this username is free on this wiki.{{end}}

    + + {{end}} +{{end}} diff --git a/backlinks/view_backlinks.html b/web/views/backlinks.html similarity index 100% rename from backlinks/view_backlinks.html rename to web/views/backlinks.html diff --git a/web/views/cat-edit.html b/web/views/cat-edit.html new file mode 100644 index 0000000..6fd61bb --- /dev/null +++ b/web/views/cat-edit.html @@ -0,0 +1,37 @@ +{{define "edit category x"}}Edit category {{beautifulName .}}{{end}} +{{define "title"}}{{template "edit category x" .CatName}}{{end}} +{{define "body"}} +
    +

    {{block "edit category heading" .CatName}}Edit category {{beautifulName .}}{{end}}

    + {{if len .Hyphae | not}} +

    {{block "empty cat" .}}This category is empty{{end}}

    + {{end}} + + {{if .GivenPermissionToModify}} +

    {{block "add to category title" .}}Add a hypha to the category{{end}}

    +
    + + + + +
    + + {{if len .Hyphae}} +

    {{block "remove hyphae" .}}Remove hyphae from the category{{end}}

    +
    +
      + {{range .Hyphae}} +
    1. + + +
    2. + {{end}} +
    + + + +
    + {{end}}{{end}} +
    +{{end}} diff --git a/categories/view_list.html b/web/views/cat-list.html similarity index 100% rename from categories/view_list.html rename to web/views/cat-list.html diff --git a/categories/view_page.html b/web/views/cat-page.html similarity index 100% rename from categories/view_page.html rename to web/views/cat-page.html diff --git a/settings/view_change_password.html b/web/views/change-password.html similarity index 100% rename from settings/view_change_password.html rename to web/views/change-password.html diff --git a/hypview/view_delete.html b/web/views/hypha-delete.html similarity index 100% rename from hypview/view_delete.html rename to web/views/hypha-delete.html diff --git a/hypview/view_edit.html b/web/views/hypha-edit.html similarity index 100% rename from hypview/view_edit.html rename to web/views/hypha-edit.html diff --git a/web/views/hypha-media.html b/web/views/hypha-media.html new file mode 100644 index 0000000..c3c4d49 --- /dev/null +++ b/web/views/hypha-media.html @@ -0,0 +1,57 @@ +{{define "title"}}{{end}} +{{define "body"}} +
    +

    {{block "media title" .}}Media of {{.HyphaName | beautifulLink }}{{end}}

    +

    + {{if .IsMediaHypha}} + {{block "tip" .}}You can manage the hypha's media on this page.{{end}} + {{else}} + {{block "empty" .}}This hypha has no media, you can upload it here.{{end}} + {{end}} + + {{block "what is media?" .}}What is media?{{end}} + +

    + +
    + {{if .IsMediaHypha}} +
    + {{block "stat" .}}Stat{{end}} +

    {{block "stat size" .}}File size:{{end}} {{.FileSize}}

    +

    {{block "stat mime" .}}MIME type:{{end}} {{.MimeType}}

    +
    + {{end}} + + {{if .U.CanProceed "upload-binary" }} + + {{end}} + + {{if .IsMediaHypha | and (.U.CanProceed "remove-media")}} + + {{end}} +
    +
    +{{end}} \ No newline at end of file diff --git a/web/views/hypha-revision.html b/web/views/hypha-revision.html new file mode 100644 index 0000000..d17bbee --- /dev/null +++ b/web/views/hypha-revision.html @@ -0,0 +1,18 @@ +{{define "hypha at rev"}}{{.HyphaName}} at {{.RevHash}}{{end}} +{{define "title"}}{{template "hypha at rev" .}}{{end}} +{{define "body"}} +
    +
    +

    + {{block "revision warning" .}}Please note that viewing media is not supported in history for now.{{end}} + + {{block "revision link" .}}Get Mycomarkup source of this revision{{end}} + +

    + {{.NaviTitle}} + {{.Contents}} +
    +
    + {{range .ViewScripts}} + {{end}} +{{end}} \ No newline at end of file diff --git a/web/views/hypha.html b/web/views/hypha.html new file mode 100644 index 0000000..3c2188d --- /dev/null +++ b/web/views/hypha.html @@ -0,0 +1,160 @@ +{{define "title"}}{{.HyphaName | beautifulName}}{{end}} + +{{define "body"}} +
    +
    + {{if .Meta.U.CanProceed "edit"}} + + {{end}} + + {{if .IsMyProfile}} + + {{if eq .Meta.U.Group "admin"}} + + {{end}} + {{end}} + + {{.NaviTitle}} + + {{if .Contents}}{{.Contents}}{{else}}{{template "empty hypha card" .}}{{end}} +
    + +
    + {{if .PrevHyphaName}} + + {{end}} + {{if .NextHyphaName}} + + {{end}} +
    + + {{ if .SubhyphaeHTML }} +
    +

    {{block "subhyphae" .}}Subhyphae{{end}}

    + +
    + {{end}} + +
    + +
    +
    + {{template "category card" .}} + {{range .ViewScripts}}{{end}} +{{end}} + +{{define "category card"}} + {{if or .GivenPermissionToModify (len .Categories)}} + {{$hyphaName := .HyphaName}} + {{$givenPermission := .GivenPermissionToModify}} + + {{end}} +{{end}} + + +{{define "empty hypha card"}} +
    +

    {{block "empty heading" .}}This hypha does not exist{{end}}

    + {{if and .UseAuth (eq .Meta.U.Group "anon")}} +

    {{block "empty no rights" .}}You are not authorized to create new hyphae. Here is what you can do:{{end}}

    + + {{else}} +
    +
    +

    📝 {{block "write a text" .}}Write a text{{end}}

    +

    {{block "write a text tip" .}}Write a note, a diary, an article, a story or anything textual using Mycomarkup. Full history of edits to the document will be saved.{{end}}

    +

    {{block "write a text writing conventions" .}}Make sure to follow this wiki's writing conventions if there are any.{{end}}

    + {{block "write a text btn" .}}Create{{end}} +
    + +
    +

    🖼 {{block "upload a media" .}}Upload a media{{end}}

    +

    {{block "upload a media tip" .}}Upload a picture, a video or an audio. Most common formats can be viewed from the browser, others can only be downloaded and viewed locally. You can write a description for the media later.{{end}}

    +
    + + +
    +
    +
    + {{end}} +
    +{{end}} \ No newline at end of file diff --git a/backlinks/view_orphans.html b/web/views/orphans.html similarity index 100% rename from backlinks/view_orphans.html rename to web/views/orphans.html diff --git a/web/views/user-list.html b/web/views/user-list.html new file mode 100644 index 0000000..1ae4984 --- /dev/null +++ b/web/views/user-list.html @@ -0,0 +1,30 @@ +{{define "title"}}List of users{{end}} +{{define "body"}} +
    +

    {{template "title"}}

    +
    {{$u := .UserHypha}} +

    {{block "administrators" .}}Administrators{{end}}

    +
      + {{range .Admins}}
    1. {{.}}
    2. {{end}} +
    +
    +
    +

    {{block "moderators" .}}Moderators{{end}}

    +
      + {{range .Moderators}}
    1. {{.}}
    2. {{end}} +
    +
    +
    +

    {{block "editors" .}}Editors{{end}}

    +
      + {{range .Editors}}
    1. {{.}}
    2. {{end}} +
    +
    +
    +

    {{block "readers" .}}Readers{{end}}

    +
      + {{range .Readers}}
    1. {{.}}
    2. {{end}} +
    +
    +
    + {{end}} \ No newline at end of file diff --git a/web/viewutil/base.html b/web/viewutil/base.html new file mode 100644 index 0000000..48ac707 --- /dev/null +++ b/web/viewutil/base.html @@ -0,0 +1,56 @@ +{{define "confirm"}}Confirm{{end}} +{{define "cancel"}}Cancel{{end}} +{{define "save"}}Save{{end}} +{{define "error"}}Error{{end}} +{{define "delete"}}Delete{{end}} +{{define "page"}} + + + + + + {{block "title" .}}{{end}} + + + {{range .HeadElements}}{{.}}{{end}} + + +
    + +
    +{{block "body" .}}{{end}} + + + +{{range .CommonScripts}} + +{{end}} + + +{{end}} diff --git a/viewutil/chain.go b/web/viewutil/chain.go similarity index 100% rename from viewutil/chain.go rename to web/viewutil/chain.go diff --git a/viewutil/err.go b/web/viewutil/err.go similarity index 100% rename from viewutil/err.go rename to web/viewutil/err.go diff --git a/viewutil/meta.go b/web/viewutil/meta.go similarity index 70% rename from viewutil/meta.go rename to web/viewutil/meta.go index d69b35f..d47dc02 100644 --- a/viewutil/meta.go +++ b/web/viewutil/meta.go @@ -1,8 +1,9 @@ package viewutil import ( + "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/user" + "html/template" "io" "net/http" ) @@ -13,6 +14,10 @@ type Meta struct { U *user.User W io.Writer Addr string + + // New template additions + HeadElements []template.HTML + BodyAttributes map[string]string } // MetaFrom makes a Meta from the given data. You are meant to further modify it. @@ -28,3 +33,7 @@ func MetaFrom(w http.ResponseWriter, rq *http.Request) Meta { func (m Meta) Locale() string { return m.Lc.Locale } + +func (m Meta) LocaleIsRussian() bool { + return m.Locale() == "ru" +} diff --git a/viewutil/viewutil.go b/web/viewutil/viewutil.go similarity index 99% rename from viewutil/viewutil.go rename to web/viewutil/viewutil.go index 25de03b..f49de2d 100644 --- a/viewutil/viewutil.go +++ b/web/viewutil/viewutil.go @@ -4,12 +4,12 @@ package viewutil import ( "embed" "fmt" + "github.com/bouncepaw/mycorrhiza/internal/cfg" "io/fs" "log" "strings" "text/template" // TODO: save the world - "github.com/bouncepaw/mycorrhiza/cfg" "github.com/bouncepaw/mycorrhiza/util" ) diff --git a/web/web.go b/web/web.go index 3ebfc14..01ba4e6 100644 --- a/web/web.go +++ b/web/web.go @@ -2,25 +2,27 @@ package web import ( + "errors" + "fmt" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/l18n" + "github.com/bouncepaw/mycorrhiza/web/viewutil" "io" + "log" + "log/slog" + "mime" "net/http" "net/url" + "strings" - "github.com/bouncepaw/mycorrhiza/admin" - "github.com/bouncepaw/mycorrhiza/settings" - "github.com/bouncepaw/mycorrhiza/auth" - "github.com/bouncepaw/mycorrhiza/backlinks" - "github.com/bouncepaw/mycorrhiza/categories" "github.com/bouncepaw/mycorrhiza/help" "github.com/bouncepaw/mycorrhiza/history/histweb" "github.com/bouncepaw/mycorrhiza/hypview" "github.com/bouncepaw/mycorrhiza/interwiki" "github.com/bouncepaw/mycorrhiza/misc" - "github.com/gorilla/mux" - "github.com/bouncepaw/mycorrhiza/cfg" - "github.com/bouncepaw/mycorrhiza/user" "github.com/bouncepaw/mycorrhiza/util" ) @@ -40,11 +42,25 @@ func Handler() http.Handler { // Public routes. They're always accessible regardless of the user status. misc.InitAssetHandlers(router) - auth.InitAuth(router) + + // Auth + router.HandleFunc("/user-list", handlerUserList) + router.HandleFunc("/lock", handlerLock) + // The check below saves a lot of extra checks and lines of codes in other places in this file. + if cfg.UseAuth { + if cfg.AllowRegistration { + router.HandleFunc("/register", handlerRegister).Methods(http.MethodPost, http.MethodGet) + } + if cfg.TelegramEnabled { + router.HandleFunc("/telegram-login", handlerTelegramLogin) + } + router.HandleFunc("/login", handlerLogin) + router.HandleFunc("/logout", handlerLogout) + } // Wiki routes. They may be locked or restricted. - wikiRouter := router.PathPrefix("").Subrouter() - wikiRouter.Use(func(next http.Handler) http.Handler { + r := router.PathPrefix("").Subrouter() + r.Use(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, rq *http.Request) { user := user.FromRequest(rq) if !user.ShowLockMaybe(w, rq) { @@ -53,36 +69,52 @@ func Handler() http.Handler { }) }) - initReaders(wikiRouter) - initMutators(wikiRouter) - help.InitHandlers(wikiRouter) - backlinks.InitHandlers(wikiRouter) - categories.InitHandlers(wikiRouter) - misc.InitHandlers(wikiRouter) + initReaders(r) + initMutators(r) + help.InitHandlers(r) + misc.InitHandlers(r) hypview.Init() - histweb.InitHandlers(wikiRouter) - interwiki.InitHandlers(wikiRouter) + histweb.InitHandlers(r) + interwiki.InitHandlers(r) - // Admin routes. + r.PathPrefix("/add-to-category").HandlerFunc(handlerAddToCategory).Methods("POST") + r.PathPrefix("/remove-from-category").HandlerFunc(handlerRemoveFromCategory).Methods("POST") + r.PathPrefix("/category/").HandlerFunc(handlerCategory).Methods("GET") + r.PathPrefix("/edit-category/").HandlerFunc(handlerEditCategory).Methods("GET") + r.PathPrefix("/category").HandlerFunc(handlerListCategory).Methods("GET") + + // Admin routes if cfg.UseAuth { - adminRouter := wikiRouter.PathPrefix("/admin").Subrouter() + adminRouter := r.PathPrefix("/admin").Subrouter() adminRouter.Use(groupMiddleware("admin")) - admin.Init(adminRouter) - settingsRouter := wikiRouter.PathPrefix("/settings").Subrouter() + adminRouter.HandleFunc("/shutdown", handlerAdminShutdown).Methods(http.MethodPost) + adminRouter.HandleFunc("/reindex-users", handlerAdminReindexUsers).Methods(http.MethodPost) + + adminRouter.HandleFunc("/new-user", handlerAdminUserNew).Methods(http.MethodGet, http.MethodPost) + adminRouter.HandleFunc("/users/{username}/edit", handlerAdminUserEdit).Methods(http.MethodGet, http.MethodPost) + adminRouter.HandleFunc("/users/{username}/change-password", handlerAdminUserChangePassword).Methods(http.MethodPost) + adminRouter.HandleFunc("/users/{username}/delete", handlerAdminUserDelete).Methods(http.MethodGet, http.MethodPost) + adminRouter.HandleFunc("/users", handlerAdminUsers) + + adminRouter.HandleFunc("/", handlerAdmin) + + settingsRouter := r.PathPrefix("/settings").Subrouter() // TODO: check if necessary? //settingsRouter.Use(groupMiddleware("settings")) - settings.Init(settingsRouter) + settingsRouter.HandleFunc("/change-password", handlerUserChangePassword).Methods(http.MethodGet, http.MethodPost) } // Index page - wikiRouter.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) { + r.HandleFunc("/", func(w http.ResponseWriter, rq *http.Request) { // Let's pray it never fails addr, _ := url.Parse("/hypha/" + cfg.HomeHypha) rq.URL = addr handlerHypha(w, rq) }) + initPages() + return router } @@ -102,3 +134,198 @@ func groupMiddleware(group string) func(http.Handler) http.Handler { }) } } + +// Auth +func handlerUserList(w http.ResponseWriter, rq *http.Request) { + admins, moderators, editors, readers := user.UsersInGroups() + _ = pageUserList.RenderTo(viewutil.MetaFrom(w, rq), + map[string]any{ + "Admins": admins, + "Moderators": moderators, + "Editors": editors, + "Readers": readers, + }) +} + +func handlerLock(w http.ResponseWriter, rq *http.Request) { + _ = pageAuthLock.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{}) +} + +// handlerRegister displays the register form (GET) or registers the user (POST). +func handlerRegister(w http.ResponseWriter, rq *http.Request) { + util.PrepareRq(rq) + if rq.Method == http.MethodGet { + slog.Info("Showing registration form") + _ = pageAuthRegister.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{ + "UseAuth": cfg.UseAuth, + "AllowRegistration": cfg.AllowRegistration, + "RawQuery": rq.URL.RawQuery, + "WikiName": cfg.WikiName, + }) + return + } + + var ( + username = rq.PostFormValue("username") + password = rq.PostFormValue("password") + err = user.Register(username, password, "editor", "local", false) + ) + if err != nil { + slog.Info("Failed to register", "username", username, "err", err.Error()) + w.Header().Set("Content-Type", mime.TypeByExtension(".html")) + w.WriteHeader(http.StatusBadRequest) + _ = pageAuthRegister.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{ + "UseAuth": cfg.UseAuth, + "AllowRegistration": cfg.AllowRegistration, + "RawQuery": rq.URL.RawQuery, + "WikiName": cfg.WikiName, + + "Err": err, + "Username": username, + "Password": password, + }) + return + } + + slog.Info("Registered user", "username", username) + if err := user.LoginDataHTTP(w, username, password); err != nil { + return + } + http.Redirect(w, rq, "/"+rq.URL.RawQuery, http.StatusSeeOther) +} + +// handlerLogout shows the logout form (GET) or logs the user out (POST). +func handlerLogout(w http.ResponseWriter, rq *http.Request) { + if rq.Method == http.MethodPost { + slog.Info("Somebody logged out") + user.LogoutFromRequest(w, rq) + http.Redirect(w, rq, "/", http.StatusSeeOther) + return + } + + var ( + u = user.FromRequest(rq) + can = u != nil + ) + w.Header().Set("Content-Type", "text/html;charset=utf-8") + if can { + slog.Info("Logging out", "username", u.Name) + w.WriteHeader(http.StatusOK) + } else { + slog.Info("Unknown user logging out") + w.WriteHeader(http.StatusForbidden) + } + _ = pageAuthLogout.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{ + "CanLogout": can, + }) +} + +// handlerLogin shows the login form (GET) or logs the user in (POST). +func handlerLogin(w http.ResponseWriter, rq *http.Request) { + if rq.Method == http.MethodGet { + w.WriteHeader(http.StatusOK) + _ = pageAuthLogin.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{ + "UseAuth": cfg.UseAuth, + "ErrUnknownUsername": false, + "ErrWrongPassword": false, + "ErrTelegram": false, + "Err": nil, + "WikiName": cfg.WikiName, + }) + slog.Info("Somebody logging in") + return + } + + var ( + username = util.CanonicalName(rq.PostFormValue("username")) + password = rq.PostFormValue("password") + err = user.LoginDataHTTP(w, username, password) + ) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + _ = pageAuthLogin.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{ + "UseAuth": cfg.UseAuth, + "ErrUnknownUsername": errors.Is(err, user.ErrUnknownUsername), + "ErrWrongPassword": errors.Is(err, user.ErrWrongPassword), + "ErrTelegram": false, // TODO: ? + "Err": err.Error(), + "WikiName": cfg.WikiName, + "Username": username, + }) + slog.Info("Failed to log in", "username", username, "err", err.Error()) + return + } + http.Redirect(w, rq, "/", http.StatusSeeOther) + slog.Info("Logged in", "username", username) +} + +func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) { + // Note there is no lock here. + lc := l18n.FromRequest(rq) + w.Header().Set("Content-Type", "text/html;charset=utf-8") + _ = rq.ParseForm() + var ( + values = rq.URL.Query() + username = strings.ToLower(values.Get("username")) + seemsValid = user.TelegramAuthParamsAreValid(values) + err = user.Register( + username, + "", // Password matters not + "editor", + "telegram", + false, + ) + ) + // If registering a user via Telegram failed, because a Telegram user with this name + // has already registered, then everything is actually ok! + if user.HasUsername(username) && user.ByName(username).Source == "telegram" { + err = nil + } + + if !seemsValid { + err = errors.New("Wrong parameters") + } + + if err != nil { + slog.Info("Failed to register", "username", username, "err", err.Error(), "method", "telegram") + w.WriteHeader(http.StatusBadRequest) + _, _ = io.WriteString( + w, + viewutil.Base( + viewutil.MetaFrom(w, rq), + lc.Get("ui.error"), + fmt.Sprintf( + `

    %s

    %s

    %s

    `, + lc.Get("auth.error_telegram"), + err.Error(), + lc.Get("auth.go_login"), + ), + map[string]string{}, + ), + ) + return + } + + errmsg := user.LoginDataHTTP(w, username, "") + if errmsg != nil { + log.Printf("Failed to login ‘%s’ using Telegram: %s", username, err.Error()) + w.WriteHeader(http.StatusBadRequest) + _, _ = io.WriteString( + w, + viewutil.Base( + viewutil.MetaFrom(w, rq), + "Error", + fmt.Sprintf( + `

    %s

    %s

    %s

    `, + lc.Get("auth.error_telegram"), + err.Error(), + lc.Get("auth.go_login"), + ), + map[string]string{}, + ), + ) + return + } + http.Redirect(w, rq, "/", http.StatusSeeOther) + slog.Info("Logged in", "username", username, "method", "telegram") +} From 380a8c321a5ece865baa9b9009ef4c6185fec0a2 Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 7 Sep 2024 21:32:45 +0300 Subject: [PATCH 11/19] Drop Go version to 1.21 and update dependencies --- go.mod | 18 +++++++++--------- go.sum | 26 ++++++++++++++++---------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 67e03a5..3220ce8 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,23 @@ module github.com/bouncepaw/mycorrhiza -go 1.22 +go 1.21 require ( git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0 - github.com/go-ini/ini v1.63.2 - github.com/gorilla/feeds v1.1.2 - github.com/gorilla/mux v1.8.0 + github.com/go-ini/ini v1.67.0 + github.com/gorilla/feeds v1.2.0 + github.com/gorilla/mux v1.8.1 github.com/valyala/quicktemplate v1.7.0 - golang.org/x/crypto v0.17.0 - golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd - golang.org/x/term v0.15.0 - golang.org/x/text v0.14.0 + golang.org/x/crypto v0.27.0 + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e + golang.org/x/term v0.24.0 + golang.org/x/text v0.18.0 ) require ( github.com/stretchr/testify v1.7.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/sys v0.25.0 // indirect ) // Use this trick to test local Mycomarkup changes, replace the path with yours, diff --git a/go.sum b/go.sum index 49029fd..1a60a89 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -git.sr.ht/~bouncepaw/mycomarkup/v5 v5.5.0 h1:0Ycy67Leh4E7HGw/Z2xs/VEw6BH68QIpQdpXcJooX7w= -git.sr.ht/~bouncepaw/mycomarkup/v5 v5.5.0/go.mod h1:TCzFBqW11En4EjLfcQtJu8C/Ro7FIFR8vZ+nM9f6Q28= git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0 h1:zAZwMF+6x8U/nunpqPRVYoDiqVUMBHI04PG8GsDrFOk= git.sr.ht/~bouncepaw/mycomarkup/v5 v5.6.0/go.mod h1:TCzFBqW11En4EjLfcQtJu8C/Ro7FIFR8vZ+nM9f6Q28= github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= @@ -8,11 +6,17 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-ini/ini v1.63.2 h1:kwN3umicd2HF3Tgvap4um1ZG52/WyKT9GGdPx0CJk6Y= github.com/go-ini/ini v1.63.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw= github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= +github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc= +github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -33,24 +37,26 @@ github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTc github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd h1:zVFyTKZN/Q7mNRWSs1GOYnHM9NiFSJ54YVRsD0rNWT4= golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= From d70d8aa99072afa75f9480cee59badee327fb5da Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 7 Sep 2024 21:35:22 +0300 Subject: [PATCH 12/19] Revert "Make shortcuts work outside of English layout (#227)" This reverts commit b43f2836c12d54a60f6250ad7d77659d29841492. --- web/static/shortcuts.js | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/web/static/shortcuts.js b/web/static/shortcuts.js index 9dcd577..99073e3 100644 --- a/web/static/shortcuts.js +++ b/web/static/shortcuts.js @@ -57,23 +57,9 @@ rrh.shortcuts = { if ((!event.ctrlKey && !event.metaKey && !event.altKey) && event.target instanceof Node && isTextField(event.target)) return - let possibleShortcuts = [keyEventToShortcut(event)] - if (event.code.startsWith('Key')) { - possibleShortcuts.push(keyEventToShortcut({ - ...event, - key: event.code.replace(/^Key/, '').toLowerCase(), - })) - } + let shortcut = keyEventToShortcut(event) - let shortcut = null - for (let possibleShortcut of possibleShortcuts) { - if (possibleShortcut in this.active) { - shortcut = possibleShortcut - break - } - } - - if (shortcut === null) { + if (!this.active[shortcut]) { this._resetActive() return } From 4c5f385afdfb55191472a5b75ec21d6c73a7559e Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 7 Sep 2024 21:39:59 +0300 Subject: [PATCH 13/19] Drop exp/slices dependency and use the built-in slices This is the future --- go.mod | 1 - go.sum | 10 ---------- internal/categories/files.go | 2 +- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 3220ce8..398d62d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/gorilla/mux v1.8.1 github.com/valyala/quicktemplate v1.7.0 golang.org/x/crypto v0.27.0 - golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e golang.org/x/term v0.24.0 golang.org/x/text v0.18.0 ) diff --git a/go.sum b/go.sum index 1a60a89..0ad0006 100644 --- a/go.sum +++ b/go.sum @@ -4,17 +4,11 @@ github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu github.com/andybalholm/brotli v1.0.3/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-ini/ini v1.63.2 h1:kwN3umicd2HF3Tgvap4um1ZG52/WyKT9GGdPx0CJk6Y= -github.com/go-ini/ini v1.63.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw= -github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= github.com/gorilla/feeds v1.2.0 h1:O6pBiXJ5JHhPvqy53NsjKOThq+dNFm8+DFrxBEdzSCc= github.com/gorilla/feeds v1.2.0/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= @@ -39,10 +33,6 @@ github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7Fw golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd h1:zVFyTKZN/Q7mNRWSs1GOYnHM9NiFSJ54YVRsD0rNWT4= -golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= -golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/categories/files.go b/internal/categories/files.go index 237a5fd..f4e4fb3 100644 --- a/internal/categories/files.go +++ b/internal/categories/files.go @@ -2,9 +2,9 @@ package categories import ( "encoding/json" - "golang.org/x/exp/slices" "log" "os" + "slices" "sort" "sync" From a4cc67cd746baa824594d888ec0bfd132f596cd0 Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 7 Sep 2024 23:55:39 +0300 Subject: [PATCH 14/19] Migrate from log to slog #109 (#255) * Migrate httpd.go * Migrate history and main * Migrate hypview * Migrate interwiki * Migrate misc * Migrate utils * Migrate backlinks * Migrate categories * Reformat some imports * Migrate hyphae * Migrate migration * Reformat more imports * Migrate user * Migrate shroom * Migrate viewutil * Migrate web * Migrate others * Migrate main * Wording concerns --- flag.go | 41 ++++++++++++------- history/feed.go | 3 +- history/history.go | 16 +++++--- history/histweb/histview.go | 66 ++++++++++++++++--------------- history/operations.go | 2 +- history/revision.go | 17 +++++--- httpd.go | 62 ++++++++++++++++++----------- hypview/hypview.go | 4 +- internal/backlinks/backlinks.go | 20 +++++----- internal/backlinks/hooks.go | 5 ++- internal/categories/files.go | 25 ++++++------ internal/files/files.go | 5 ++- internal/hyphae/existing_hypha.go | 3 +- internal/hyphae/files.go | 9 +++-- internal/migration/headings.go | 14 ++++--- internal/migration/migration.go | 22 ++++++----- internal/migration/rockets.go | 14 ++++--- internal/shroom/can.go | 14 +++---- internal/shroom/delete.go | 13 +++--- internal/shroom/header_links.go | 18 +++++---- internal/shroom/log.go | 26 +++++++++--- internal/shroom/rename.go | 42 ++++++++++---------- internal/shroom/search.go | 2 +- internal/shroom/unattach.go | 10 ++--- internal/shroom/upload.go | 4 +- internal/tree/tree.go | 5 ++- internal/user/files.go | 30 ++++++++------ internal/user/net.go | 16 ++++---- internal/user/user.go | 3 +- interwiki/interwiki.go | 56 +++++++++++++++----------- interwiki/web.go | 19 ++++++--- interwiki/wiki.go | 20 +++++++--- l18n/l18n.go | 4 +- main.go | 45 ++++++++++++++------- misc/about.go | 14 ++++--- misc/handlers.go | 15 +++---- misc/views.go | 1 + mycoopts/mycoopts.go | 4 +- util/util.go | 4 +- web/admin.go | 8 ++-- web/cats.go | 11 +++--- web/mutators.go | 42 +++++++++++--------- web/newtmpl/newtmpl.go | 5 ++- web/pages.go | 1 + web/password.go | 7 ++-- web/readers.go | 54 ++++++++++++++----------- web/viewutil/meta.go | 5 ++- web/viewutil/viewutil.go | 6 +-- web/web.go | 15 ++++--- 49 files changed, 499 insertions(+), 348 deletions(-) diff --git a/flag.go b/flag.go index 70646bf..82a2627 100644 --- a/flag.go +++ b/flag.go @@ -3,19 +3,20 @@ package main import ( "bufio" _ "embed" + "errors" "flag" "fmt" "io" - "log" + "log/slog" "os" "path/filepath" - "golang.org/x/term" - "github.com/bouncepaw/mycorrhiza/internal/cfg" "github.com/bouncepaw/mycorrhiza/internal/files" - user2 "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/internal/version" + + "golang.org/x/term" ) // CLI options are read and parsed here. @@ -31,7 +32,7 @@ func printHelp() { } // parseCliArgs parses CLI options and sets several important global variables. Call it early. -func parseCliArgs() { +func parseCliArgs() error { var createAdminName string var versionFlag bool @@ -42,43 +43,53 @@ func parseCliArgs() { flag.Parse() if versionFlag { - fmt.Println("Mycorrhiza Wiki", version.Long) + slog.Info("Running Mycorrhiza Wiki", "version", version.Long) os.Exit(0) } args := flag.Args() if len(args) == 0 { - log.Fatal("error: pass a wiki directory") + slog.Error("Pass a wiki directory") + return errors.New("wiki directory not passed") } wikiDir, err := filepath.Abs(args[0]) if err != nil { - log.Fatal(err) + slog.Error("Failed to take absolute filepath of wiki directory", + "path", args[0], "err", err) + return err } cfg.WikiDir = wikiDir if createAdminName != "" { - createAdminCommand(createAdminName) + if err := createAdminCommand(createAdminName); err != nil { + os.Exit(1) + } os.Exit(0) } + return nil } -func createAdminCommand(name string) { +func createAdminCommand(name string) error { if err := files.PrepareWikiRoot(); err != nil { - log.Fatal(err) + slog.Error("Failed to prepare wiki root", "err", err) + return err } cfg.UseAuth = true cfg.AllowRegistration = true - user2.InitUserDatabase() + user.InitUserDatabase() password, err := askPass("Password") if err != nil { - log.Fatal(err) + slog.Error("Failed to prompt password", "err", err) + return err } - if err := user2.Register(name, password, "admin", "local", true); err != nil { - log.Fatal(err) + if err := user.Register(name, password, "admin", "local", true); err != nil { + slog.Error("Failed to register admin", "err", err) + return err } + return nil } func askPass(prompt string) (string, error) { diff --git a/history/feed.go b/history/feed.go index 4906816..edd0dda 100644 --- a/history/feed.go +++ b/history/feed.go @@ -3,11 +3,12 @@ package history import ( "errors" "fmt" - "github.com/bouncepaw/mycorrhiza/internal/cfg" "net/url" "strings" "time" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/gorilla/feeds" ) diff --git a/history/history.go b/history/history.go index b889e80..0e77786 100644 --- a/history/history.go +++ b/history/history.go @@ -4,7 +4,7 @@ package history import ( "bytes" "fmt" - "log" + "log/slog" "os/exec" "path/filepath" "regexp" @@ -21,12 +21,14 @@ var renameMsgPattern = regexp.MustCompile(`^Rename ‘(.*)’ to ‘.*’`) var gitEnv = []string{"GIT_COMMITTER_NAME=wikimind", "GIT_COMMITTER_EMAIL=wikimind@mycorrhiza"} // Start finds git and initializes git credentials. -func Start() { +func Start() error { path, err := exec.LookPath("git") if err != nil { - log.Fatal("Could not find the git executable. Check your $PATH.") + slog.Error("Could not find the Git executable. Check your $PATH.") + return err } gitpath = path + return nil } // InitGitRepo checks a Git repository and initializes it if necessary. @@ -44,7 +46,7 @@ func InitGitRepo() { } } if !isGitRepo { - log.Println("Initializing Git repo at", files.HyphaeDir()) + slog.Info("Initializing Git repo", "path", files.HyphaeDir()) gitsh("init") gitsh("config", "core.quotePath", "false") } @@ -60,7 +62,7 @@ func gitsh(args ...string) (out bytes.Buffer, err error) { b, err := cmd.CombinedOutput() if err != nil { - log.Println("gitsh:", err) + slog.Info("Git command failed", "err", err, "output", string(b)) } return *bytes.NewBuffer(b), err } @@ -77,7 +79,9 @@ func silentGitsh(args ...string) (out bytes.Buffer, err error) { // Rename renames from `from` to `to` using `git mv`. func Rename(from, to string) error { - log.Println(util.ShorterPath(from), util.ShorterPath(to)) + slog.Info("Renaming file with git mv", + "from", util.ShorterPath(from), + "to", util.ShorterPath(to)) _, err := gitsh("mv", "--force", from, to) return err } diff --git a/history/histweb/histview.go b/history/histweb/histview.go index 196b998..7c7b3a4 100644 --- a/history/histweb/histview.go +++ b/history/histweb/histview.go @@ -4,19 +4,21 @@ package histweb import ( "embed" "fmt" - "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/internal/cfg" - "github.com/bouncepaw/mycorrhiza/internal/files" - hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" - "github.com/bouncepaw/mycorrhiza/util" - viewutil2 "github.com/bouncepaw/mycorrhiza/web/viewutil" - "github.com/gorilla/mux" "html/template" - "log" + "log/slog" "net/http" "path/filepath" "strconv" "strings" + + "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/files" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/web/viewutil" + + "github.com/gorilla/mux" ) func InitHandlers(rtr *mux.Router) { @@ -30,9 +32,9 @@ func InitHandlers(rtr *mux.Router) { rtr.HandleFunc("/recent-changes-atom", handlerRecentChangesAtom) rtr.HandleFunc("/recent-changes-json", handlerRecentChangesJSON) - chainPrimitiveDiff = viewutil2.CopyEnRuWith(fs, "view_primitive_diff.html", ruTranslation) - chainRecentChanges = viewutil2.CopyEnRuWith(fs, "view_recent_changes.html", ruTranslation) - chainHistory = viewutil2.CopyEnRuWith(fs, "view_history.html", ruTranslation) + chainPrimitiveDiff = viewutil.CopyEnRuWith(fs, "view_primitive_diff.html", ruTranslation) + chainRecentChanges = viewutil.CopyEnRuWith(fs, "view_recent_changes.html", ruTranslation) + chainHistory = viewutil.CopyEnRuWith(fs, "view_history.html", ruTranslation) } func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { @@ -45,12 +47,12 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { } var ( mycoFilePath string - h = hyphae2.ByName(util.CanonicalName(slug)) + h = hyphae.ByName(util.CanonicalName(slug)) ) switch h := h.(type) { - case hyphae2.ExistingHypha: + case hyphae.ExistingHypha: mycoFilePath = h.TextFilePath() - case *hyphae2.EmptyHypha: + case *hyphae.EmptyHypha: mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco") } text, err := history.PrimitiveDiffAtRevision(mycoFilePath, revHash) @@ -58,7 +60,7 @@ func handlerPrimitiveDiff(w http.ResponseWriter, rq *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } - primitiveDiff(viewutil2.MetaFrom(w, rq), h, revHash, text) + primitiveDiff(viewutil.MetaFrom(w, rq), h, revHash, text) } // handlerRecentChanges displays the /recent-changes/ page. @@ -68,7 +70,7 @@ func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) { if editCount > 100 { return } - recentChanges(viewutil2.MetaFrom(w, rq), editCount, history.RecentChanges(editCount)) + recentChanges(viewutil.MetaFrom(w, rq), editCount, history.RecentChanges(editCount)) } // handlerHistory lists all revisions of a hypha. @@ -81,9 +83,11 @@ func handlerHistory(w http.ResponseWriter, rq *http.Request) { if err == nil { list = history.WithRevisions(hyphaName, revs) } - log.Println("Found", len(revs), "revisions for", hyphaName) - historyView(viewutil2.MetaFrom(w, rq), hyphaName, list) + // TODO: extra log, not needed? + slog.Info("Found revisions", "hyphaName", hyphaName, "n", len(revs), "err", err) + + historyView(viewutil.MetaFrom(w, rq), hyphaName, list) } // genericHandlerOfFeeds is a helper function for the web feed handlers. @@ -135,20 +139,20 @@ var ( {{define "n recent changes"}}{{.}} свеж{{if eq . 1}}ая правка{{else if le . 4}}их правок{{else}}их правок{{end}}{{end}} {{define "recent empty"}}Правки не найдены.{{end}} ` - chainPrimitiveDiff, chainRecentChanges, chainHistory viewutil2.Chain + chainPrimitiveDiff, chainRecentChanges, chainHistory viewutil.Chain ) type recentChangesData struct { - *viewutil2.BaseData + *viewutil.BaseData EditCount int Changes []history.Revision UserHypha string Stops []int } -func recentChanges(meta viewutil2.Meta, editCount int, changes []history.Revision) { - viewutil2.ExecutePage(meta, chainRecentChanges, recentChangesData{ - BaseData: &viewutil2.BaseData{}, +func recentChanges(meta viewutil.Meta, editCount int, changes []history.Revision) { + viewutil.ExecutePage(meta, chainRecentChanges, recentChangesData{ + BaseData: &viewutil.BaseData{}, EditCount: editCount, Changes: changes, UserHypha: cfg.UserHypha, @@ -157,13 +161,13 @@ func recentChanges(meta viewutil2.Meta, editCount int, changes []history.Revisio } type primitiveDiffData struct { - *viewutil2.BaseData + *viewutil.BaseData HyphaName string Hash string Text template.HTML } -func primitiveDiff(meta viewutil2.Meta, h hyphae2.Hypha, hash, text string) { +func primitiveDiff(meta viewutil.Meta, h hyphae.Hypha, hash, text string) { hunks := history.SplitPrimitiveDiff(text) if len(hunks) > 0 { var buf strings.Builder @@ -198,8 +202,8 @@ func primitiveDiff(meta viewutil2.Meta, h hyphae2.Hypha, hash, text string) { text = fmt.Sprintf( `
    %s
    `, text) } - viewutil2.ExecutePage(meta, chainPrimitiveDiff, primitiveDiffData{ - BaseData: &viewutil2.BaseData{}, + viewutil.ExecutePage(meta, chainPrimitiveDiff, primitiveDiffData{ + BaseData: &viewutil.BaseData{}, HyphaName: h.CanonicalName(), Hash: hash, Text: template.HTML(text), @@ -207,14 +211,14 @@ func primitiveDiff(meta viewutil2.Meta, h hyphae2.Hypha, hash, text string) { } type historyData struct { - *viewutil2.BaseData + *viewutil.BaseData HyphaName string Contents string } -func historyView(meta viewutil2.Meta, hyphaName, contents string) { - viewutil2.ExecutePage(meta, chainHistory, historyData{ - BaseData: &viewutil2.BaseData{ +func historyView(meta viewutil.Meta, hyphaName, contents string) { + viewutil.ExecutePage(meta, chainHistory, historyData{ + BaseData: &viewutil.BaseData{ Addr: "/history/" + util.CanonicalName(hyphaName), }, HyphaName: hyphaName, diff --git a/history/operations.go b/history/operations.go index 0f9eb80..71cfbd5 100644 --- a/history/operations.go +++ b/history/operations.go @@ -4,11 +4,11 @@ package history // Things related to writing history. import ( "fmt" - "github.com/bouncepaw/mycorrhiza/internal/user" "os" "path/filepath" "sync" + "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/util" ) diff --git a/history/revision.go b/history/revision.go index 7f04309..9f5becb 100644 --- a/history/revision.go +++ b/history/revision.go @@ -2,7 +2,8 @@ package history import ( "fmt" - "log" + "log/slog" + "os" "regexp" "strconv" "strings" @@ -11,9 +12,11 @@ import ( "github.com/bouncepaw/mycorrhiza/internal/files" ) -// Revision represents a revision, duh. Hash is usually short. Username is extracted from email. +// Revision represents a revision of a hypha. type Revision struct { - Hash string + // Hash is usually short. + Hash string + // Username is extracted from email. Username string Time time.Time Message string @@ -71,7 +74,9 @@ func (stream *recentChangesStream) next(n int) []Revision { res, err := gitLog(args...) if err != nil { - log.Fatal(err) + // TODO: return error + slog.Error("Failed to git log", "err", err) + os.Exit(1) } if len(res) != 0 { stream.currHash = res[len(res)-1].Hash @@ -103,14 +108,14 @@ func (stream recentChangesStream) iterator() func() (Revision, bool) { func RecentChanges(n int) []Revision { stream := newRecentChangesStream() revs := stream.next(n) - log.Printf("Found %d recent changes", len(revs)) + slog.Info("Found recent changes", "n", len(revs)) return revs } // Revisions returns slice of revisions for the given hypha name, ordered most recent first. func Revisions(hyphaName string) ([]Revision, error) { revs, err := gitLog("--", hyphaName+".*") - log.Printf("Found %d revisions for ‘%s’\n", len(revs), hyphaName) + slog.Info("Found revisions", "hyphaName", hyphaName, "n", len(revs), "err", err) return revs, err } diff --git a/httpd.go b/httpd.go index ee2af1e..71bf815 100644 --- a/httpd.go +++ b/httpd.go @@ -1,16 +1,18 @@ package main import ( - "github.com/bouncepaw/mycorrhiza/internal/cfg" - "log" + "errors" + "log/slog" "net" "net/http" "os" "strings" "time" + + "github.com/bouncepaw/mycorrhiza/internal/cfg" ) -func serveHTTP(handler http.Handler) { +func serveHTTP(handler http.Handler) (err error) { server := &http.Server{ ReadTimeout: 300 * time.Second, WriteTimeout: 300 * time.Second, @@ -19,35 +21,51 @@ func serveHTTP(handler http.Handler) { } if strings.HasPrefix(cfg.ListenAddr, "/") { - startUnixSocketServer(server, cfg.ListenAddr) + err = startUnixSocketServer(server, cfg.ListenAddr) } else { server.Addr = cfg.ListenAddr - startHTTPServer(server) + err = startHTTPServer(server) } + return err } -func startUnixSocketServer(server *http.Server, socketFile string) { - os.Remove(socketFile) - - listener, err := net.Listen("unix", socketFile) +func startUnixSocketServer(server *http.Server, socketPath string) error { + err := os.Remove(socketPath) if err != nil { - log.Fatalf("Failed to start a server: %v", err) - } - defer listener.Close() - - if err := os.Chmod(socketFile, 0666); err != nil { - log.Fatalf("Failed to set socket permissions: %v", err) + return err } - log.Printf("Listening on Unix socket %s", cfg.ListenAddr) - if err := server.Serve(listener); err != http.ErrServerClosed { - log.Fatalf("Failed to start a server: %v", err) + listener, err := net.Listen("unix", socketPath) + if err != nil { + slog.Error("Failed to start the server", "err", err) + return err } + defer func(listener net.Listener) { + _ = listener.Close() + }(listener) + + if err := os.Chmod(socketPath, 0666); err != nil { + slog.Error("Failed to set socket permissions", "err", err) + return err + } + + slog.Info("Listening Unix socket", "addr", socketPath) + + if err := server.Serve(listener); !errors.Is(err, http.ErrServerClosed) { + slog.Error("Failed to start the server", "err", err) + return err + } + + return nil } -func startHTTPServer(server *http.Server) { - log.Printf("Listening on %s", server.Addr) - if err := server.ListenAndServe(); err != http.ErrServerClosed { - log.Fatalf("Failed to start a server: %v", err) +func startHTTPServer(server *http.Server) error { + slog.Info("Listening over HTTP", "addr", server.Addr) + + if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + slog.Error("Failed to start the server", "err", err) + return err } + + return nil } diff --git a/hypview/hypview.go b/hypview/hypview.go index 6881768..0843534 100644 --- a/hypview/hypview.go +++ b/hypview/hypview.go @@ -3,7 +3,7 @@ package hypview import ( "embed" "html/template" - "log" + "log/slog" "strings" "github.com/bouncepaw/mycorrhiza/internal/backlinks" @@ -66,7 +66,7 @@ func NaviTitle(meta viewutil.Meta, hyphaName string) template.HTML { HomeHypha: cfg.HomeHypha, }) if err != nil { - log.Println(err) + slog.Error("Failed to render NaviTitle properly; using nevertheless", "err", err) } return template.HTML(buf.String()) } diff --git a/internal/backlinks/backlinks.go b/internal/backlinks/backlinks.go index f2e235a..ec9fe9d 100644 --- a/internal/backlinks/backlinks.go +++ b/internal/backlinks/backlinks.go @@ -2,8 +2,8 @@ package backlinks import ( - hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" - "log" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "log/slog" "os" "sort" @@ -14,7 +14,7 @@ import ( func yieldHyphaBacklinks(hyphaName string) <-chan string { hyphaName = util.CanonicalName(hyphaName) out := make(chan string) - sorted := hyphae2.PathographicSort(out) + sorted := hyphae.PathographicSort(out) go func() { backlinks, exists := backlinkIndex[hyphaName] if exists { @@ -43,7 +43,7 @@ var backlinkIndex = make(map[string]linkSet) // IndexBacklinks traverses all text hyphae, extracts links from them and forms an initial index. Call it when indexing and reindexing hyphae. func IndexBacklinks() { // It is safe to ignore the mutex, because there is only one worker. - for h := range hyphae2.FilterHyphaeWithText(hyphae2.YieldExistingHyphae()) { + for h := range hyphae.FilterHyphaeWithText(hyphae.YieldExistingHyphae()) { foundLinks := extractHyphaLinksFromContent(h.CanonicalName(), fetchText(h)) for _, link := range foundLinks { if _, exists := backlinkIndex[link]; !exists { @@ -72,7 +72,7 @@ func BacklinksFor(hyphaName string) []string { func Orphans() []string { var orphans []string - for h := range hyphae2.YieldExistingHyphae() { + for h := range hyphae.YieldExistingHyphae() { if BacklinksCount(h.CanonicalName()) == 0 { orphans = append(orphans, h.CanonicalName()) } @@ -92,14 +92,14 @@ func toLinkSet(xs []string) linkSet { return result } -func fetchText(h hyphae2.Hypha) string { +func fetchText(h hyphae.Hypha) string { var path string switch h := h.(type) { - case *hyphae2.EmptyHypha: + case *hyphae.EmptyHypha: return "" - case *hyphae2.TextualHypha: + case *hyphae.TextualHypha: path = h.TextFilePath() - case *hyphae2.MediaHypha: + case *hyphae.MediaHypha: if !h.HasTextFile() { return "" } @@ -108,7 +108,7 @@ func fetchText(h hyphae2.Hypha) string { text, err := os.ReadFile(path) if err != nil { - log.Println(err) + slog.Error("Failed to read file", "path", path, "err", err, "hyphaName", h.CanonicalName()) return "" } return string(text) diff --git a/internal/backlinks/hooks.go b/internal/backlinks/hooks.go index ef65952..60a3c50 100644 --- a/internal/backlinks/hooks.go +++ b/internal/backlinks/hooks.go @@ -1,12 +1,13 @@ package backlinks import ( + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/mycoopts" + "git.sr.ht/~bouncepaw/mycomarkup/v5" "git.sr.ht/~bouncepaw/mycomarkup/v5/links" "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" "git.sr.ht/~bouncepaw/mycomarkup/v5/tools" - "github.com/bouncepaw/mycorrhiza/internal/hyphae" - "github.com/bouncepaw/mycorrhiza/mycoopts" ) // UpdateBacklinksAfterEdit is a creation/editing hook for backlinks index diff --git a/internal/categories/files.go b/internal/categories/files.go index f4e4fb3..7c80546 100644 --- a/internal/categories/files.go +++ b/internal/categories/files.go @@ -2,7 +2,7 @@ package categories import ( "encoding/json" - "log" + "log/slog" "os" "slices" "sort" @@ -16,12 +16,11 @@ var categoryToHyphae = map[string]*categoryNode{} var hyphaToCategories = map[string]*hyphaNode{} // Init initializes the category system. Call it after the Structure is initialized. This function might terminate the program in case of a bad mood or filesystem faults. -func Init() { - var ( - record, err = readCategoriesFromDisk() - ) +func Init() error { + record, err := readCategoriesFromDisk() if err != nil { - log.Fatalln(err) + slog.Error("Failed to read categories from disk", "err", err) + return err } for _, cat := range record.Categories { @@ -46,7 +45,8 @@ func Init() { } } - log.Println("Found", len(categoryToHyphae), "categories") + slog.Info("Indexed categories", "n", len(categoryToHyphae)) + return nil } type categoryNode struct { @@ -123,9 +123,7 @@ func readCategoriesFromDisk() (catFileRecord, error) { var fileMutex sync.Mutex func saveToDisk() { - var ( - record catFileRecord - ) + var record catFileRecord for name, node := range categoryToHyphae { record.Categories = append(record.Categories, catRecord{ Name: name, @@ -134,13 +132,16 @@ func saveToDisk() { } data, err := json.MarshalIndent(record, "", "\t") if err != nil { - log.Fatalln(err) // Better fail now, than later + slog.Error("Failed to marshal categories record", "err", err) + os.Exit(1) // Better fail now, than later } + // TODO: make the data safer somehow?? Back it up before overwriting? fileMutex.Lock() err = os.WriteFile(files.CategoriesJSON(), data, 0666) if err != nil { - log.Fatalln(err) + slog.Error("Failed to write categories.json", "err", err) + os.Exit(1) } fileMutex.Unlock() } diff --git a/internal/files/files.go b/internal/files/files.go index bb82cff..e6fa383 100644 --- a/internal/files/files.go +++ b/internal/files/files.go @@ -2,11 +2,12 @@ package files import ( - "github.com/bouncepaw/mycorrhiza/internal/cfg" - "github.com/bouncepaw/mycorrhiza/web/static" "io" "os" "path/filepath" + + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/web/static" ) var paths struct { diff --git a/internal/hyphae/existing_hypha.go b/internal/hyphae/existing_hypha.go index 8b6cf7f..0301137 100644 --- a/internal/hyphae/existing_hypha.go +++ b/internal/hyphae/existing_hypha.go @@ -1,9 +1,10 @@ package hyphae import ( - "github.com/bouncepaw/mycorrhiza/util" "os" "path/filepath" + + "github.com/bouncepaw/mycorrhiza/util" ) // ExistingHypha is not EmptyHypha. *MediaHypha and *TextualHypha implement this interface. diff --git a/internal/hyphae/files.go b/internal/hyphae/files.go index 9be35a2..3c499bf 100644 --- a/internal/hyphae/files.go +++ b/internal/hyphae/files.go @@ -1,11 +1,11 @@ package hyphae import ( - "github.com/bouncepaw/mycorrhiza/internal/mimetype" - "log" "log/slog" "os" "path/filepath" + + "github.com/bouncepaw/mycorrhiza/internal/mimetype" ) // Index finds all hypha files in the full `path` and saves them to the hypha storage. @@ -51,7 +51,7 @@ func Index(path string) { } } } - log.Println("Indexed", Count(), "hyphae") + slog.Info("Indexed hyphae", "n", Count()) } // indexHelper finds all hypha files in the full `path` and sends them to the @@ -60,7 +60,8 @@ func Index(path string) { func indexHelper(path string, nestLevel uint, ch chan ExistingHypha) { nodes, err := os.ReadDir(path) if err != nil { - log.Fatal(err) + slog.Error("Failed to read directory", "path", path, "err", err) + os.Exit(1) } for _, node := range nodes { diff --git a/internal/migration/headings.go b/internal/migration/headings.go index fb8f3cd..1de4a35 100644 --- a/internal/migration/headings.go +++ b/internal/migration/headings.go @@ -1,11 +1,13 @@ package migration import ( - "git.sr.ht/~bouncepaw/mycomarkup/v5/tools" - "github.com/bouncepaw/mycorrhiza/internal/files" "io/ioutil" - "log" + "log/slog" "os" + + "github.com/bouncepaw/mycorrhiza/internal/files" + + "git.sr.ht/~bouncepaw/mycomarkup/v5/tools" ) var headingMarkerPath string @@ -29,7 +31,8 @@ func shouldMigrateHeadings() bool { return true } if err != nil { - log.Fatalln("When checking if heading migration is needed:", err.Error()) + slog.Error("Failed to check if heading migration is needed", "err", err) + os.Exit(1) } _ = file.Close() return false @@ -42,6 +45,7 @@ func createHeadingMarker() { 0766, ) if err != nil { - log.Fatalln(err) + slog.Error("Failed to create heading migration marker", "err", err) + os.Exit(1) } } diff --git a/internal/migration/migration.go b/internal/migration/migration.go index d3f945f..0bcad88 100644 --- a/internal/migration/migration.go +++ b/internal/migration/migration.go @@ -8,14 +8,14 @@ package migration import ( - "github.com/bouncepaw/mycorrhiza/internal/user" "io" - "log" + "log/slog" "os" "strings" "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/user" ) func genericLineMigrator( @@ -37,7 +37,8 @@ func genericLineMigrator( file, err := os.OpenFile(hypha.TextFilePath(), os.O_RDWR, 0766) if err != nil { hop.WithErrAbort(err) - log.Fatal("Something went wrong when opening ", hypha.TextFilePath(), ": ", err.Error()) + slog.Error("Failed to open text part file", "path", hypha.TextFilePath(), "err", err) + os.Exit(1) } var buf strings.Builder @@ -45,7 +46,7 @@ func genericLineMigrator( if err != nil { hop.WithErrAbort(err) _ = file.Close() - log.Fatal("Something went wrong when reading ", hypha.TextFilePath(), ": ", err.Error()) + slog.Error("Failed to read text part file", "path", hypha.TextFilePath(), "err", err) } var ( @@ -59,21 +60,24 @@ func genericLineMigrator( if err != nil { hop.WithErrAbort(err) _ = file.Close() - log.Fatal("Something went wrong when truncating ", hypha.TextFilePath(), ": ", err.Error()) + slog.Error("Failed to truncate text part file", "path", hypha.TextFilePath(), "err", err) + os.Exit(1) } _, err = file.Seek(0, 0) if err != nil { hop.WithErrAbort(err) _ = file.Close() - log.Fatal("Something went wrong when seeking in ", hypha.TextFilePath(), ": ", err.Error()) + slog.Error("Failed to seek in text part file", "path", hypha.TextFilePath(), "err", err) + os.Exit(1) } _, err = file.WriteString(newText) if err != nil { hop.WithErrAbort(err) _ = file.Close() - log.Fatal("Something went wrong when writing to ", hypha.TextFilePath(), ": ", err.Error()) + slog.Error("Failed to write to text part file", "path", hypha.TextFilePath(), "err", err) + os.Exit(1) } } _ = file.Close() @@ -85,8 +89,8 @@ func genericLineMigrator( } if hop.WithFiles(mycoFiles...).Apply().HasErrors() { - log.Fatal(commitErrorMessage, hop.FirstErrorText()) + slog.Error(commitErrorMessage + hop.FirstErrorText()) } - log.Println("Migrated", len(mycoFiles), "Mycomarkup documents") + slog.Info("Migrated Mycomarkup documents", "n", len(mycoFiles)) } diff --git a/internal/migration/rockets.go b/internal/migration/rockets.go index 5dc050b..dc99fce 100644 --- a/internal/migration/rockets.go +++ b/internal/migration/rockets.go @@ -1,11 +1,13 @@ package migration import ( - "git.sr.ht/~bouncepaw/mycomarkup/v5/tools" - "github.com/bouncepaw/mycorrhiza/internal/files" "io/ioutil" - "log" + "log/slog" "os" + + "github.com/bouncepaw/mycorrhiza/internal/files" + + "git.sr.ht/~bouncepaw/mycomarkup/v5/tools" ) var rocketMarkerPath string @@ -33,7 +35,8 @@ func shouldMigrateRockets() bool { return true } if err != nil { - log.Fatalln("When checking if rocket migration is needed:", err.Error()) + slog.Error("Failed to check if rocket migration is needed", "err", err) + os.Exit(1) } _ = file.Close() return false @@ -46,6 +49,7 @@ func createRocketLinkMarker() { 0766, ) if err != nil { - log.Fatalln(err) + slog.Error("Failed to create rocket link migration marker") + os.Exit(1) } } diff --git a/internal/shroom/can.go b/internal/shroom/can.go index c9d1f3d..8a4ccc3 100644 --- a/internal/shroom/can.go +++ b/internal/shroom/can.go @@ -2,23 +2,23 @@ package shroom import ( "errors" - hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" - "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/l18n" ) // TODO: get rid of this abomination func canFactory( - rejectLogger func(hyphae2.Hypha, *user.User, string), + rejectLogger func(hyphae.Hypha, *user.User, string), action string, - dispatcher func(hyphae2.Hypha, *user.User, *l18n.Localizer) (string, string), + dispatcher func(hyphae.Hypha, *user.User, *l18n.Localizer) (string, string), noRightsMsg string, notExistsMsg string, mustExist bool, -) func(*user.User, hyphae2.Hypha, *l18n.Localizer) error { - return func(u *user.User, h hyphae2.Hypha, lc *l18n.Localizer) error { +) func(*user.User, hyphae.Hypha, *l18n.Localizer) error { + return func(u *user.User, h hyphae.Hypha, lc *l18n.Localizer) error { if !u.CanProceed(action) { rejectLogger(h, u, "no rights") return errors.New(noRightsMsg) @@ -26,7 +26,7 @@ func canFactory( if mustExist { switch h.(type) { - case *hyphae2.EmptyHypha: + case *hyphae.EmptyHypha: rejectLogger(h, u, "does not exist") return errors.New(notExistsMsg) } diff --git a/internal/shroom/delete.go b/internal/shroom/delete.go index 701fd0c..f07df9d 100644 --- a/internal/shroom/delete.go +++ b/internal/shroom/delete.go @@ -2,29 +2,30 @@ package shroom import ( "fmt" + "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/internal/backlinks" "github.com/bouncepaw/mycorrhiza/internal/categories" - hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" "github.com/bouncepaw/mycorrhiza/internal/user" ) // Delete deletes the hypha and makes a history record about that. -func Delete(u *user.User, h hyphae2.ExistingHypha) error { +func Delete(u *user.User, h hyphae.ExistingHypha) error { hop := history. Operation(history.TypeDeleteHypha). WithMsg(fmt.Sprintf("Delete ‘%s’", h.CanonicalName())). WithUser(u) - originalText, _ := hyphae2.FetchMycomarkupFile(h) + originalText, _ := hyphae.FetchMycomarkupFile(h) switch h := h.(type) { - case *hyphae2.MediaHypha: + case *hyphae.MediaHypha: if h.HasTextFile() { hop.WithFilesRemoved(h.MediaFilePath(), h.TextFilePath()) } else { hop.WithFilesRemoved(h.MediaFilePath()) } - case *hyphae2.TextualHypha: + case *hyphae.TextualHypha: hop.WithFilesRemoved(h.TextFilePath()) } if hop.Apply().HasErrors() { @@ -32,6 +33,6 @@ func Delete(u *user.User, h hyphae2.ExistingHypha) error { } backlinks.UpdateBacklinksAfterDelete(h, originalText) categories.RemoveHyphaFromAllCategories(h.CanonicalName()) - hyphae2.DeleteHypha(h) + hyphae.DeleteHypha(h) return nil } diff --git a/internal/shroom/header_links.go b/internal/shroom/header_links.go index dd3ff1b..af28f75 100644 --- a/internal/shroom/header_links.go +++ b/internal/shroom/header_links.go @@ -1,22 +1,24 @@ package shroom import ( + "os" + + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/mycoopts" + "github.com/bouncepaw/mycorrhiza/web/viewutil" + "git.sr.ht/~bouncepaw/mycomarkup/v5" "git.sr.ht/~bouncepaw/mycomarkup/v5/blocks" "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" - "github.com/bouncepaw/mycorrhiza/internal/cfg" - hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" - "github.com/bouncepaw/mycorrhiza/mycoopts" - "github.com/bouncepaw/mycorrhiza/web/viewutil" - "os" ) // SetHeaderLinks initializes header links by reading the configured hypha, if there is any, or resorting to default values. func SetHeaderLinks() { - switch userLinksHypha := hyphae2.ByName(cfg.HeaderLinksHypha).(type) { - case *hyphae2.EmptyHypha: + switch userLinksHypha := hyphae.ByName(cfg.HeaderLinksHypha).(type) { + case *hyphae.EmptyHypha: setDefaultHeaderLinks() - case hyphae2.ExistingHypha: + case hyphae.ExistingHypha: contents, err := os.ReadFile(userLinksHypha.TextFilePath()) if err != nil || len(contents) == 0 { setDefaultHeaderLinks() diff --git a/internal/shroom/log.go b/internal/shroom/log.go index 2aef825..535dcaf 100644 --- a/internal/shroom/log.go +++ b/internal/shroom/log.go @@ -1,20 +1,36 @@ package shroom import ( + "log/slog" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" "github.com/bouncepaw/mycorrhiza/internal/user" - "log" ) func rejectRenameLog(h hyphae.Hypha, u *user.User, errmsg string) { - log.Printf("Reject rename ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg) + slog.Info("Reject rename", + "hyphaName", h.CanonicalName(), + "username", u.Name, + "errmsg", errmsg) } + func rejectRemoveMediaLog(h hyphae.Hypha, u *user.User, errmsg string) { - log.Printf("Reject remove media ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg) + slog.Info("Reject remove media", + "hyphaName", h.CanonicalName(), + "username", u.Name, + "errmsg", errmsg) } + func rejectEditLog(h hyphae.Hypha, u *user.User, errmsg string) { - log.Printf("Reject edit ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg) + slog.Info("Reject edit", + "hyphaName", h.CanonicalName(), + "username", u.Name, + "errmsg", errmsg) } + func rejectUploadMediaLog(h hyphae.Hypha, u *user.User, errmsg string) { - log.Printf("Reject upload media ‘%s’ by @%s: %s\n", h.CanonicalName(), u.Name, errmsg) + slog.Info("Reject upload media", + "hyphaName", h.CanonicalName(), + "username", u.Name, + "errmsg", errmsg) } diff --git a/internal/shroom/rename.go b/internal/shroom/rename.go index 3aeeeb7..6150c92 100644 --- a/internal/shroom/rename.go +++ b/internal/shroom/rename.go @@ -3,36 +3,36 @@ package shroom import ( "errors" "fmt" - "github.com/bouncepaw/mycorrhiza/internal/backlinks" - "github.com/bouncepaw/mycorrhiza/internal/categories" - "github.com/bouncepaw/mycorrhiza/internal/cfg" - "github.com/bouncepaw/mycorrhiza/internal/files" - hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" - "github.com/bouncepaw/mycorrhiza/internal/user" "path" "path/filepath" "regexp" "strings" "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/internal/backlinks" + "github.com/bouncepaw/mycorrhiza/internal/categories" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/files" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/util" ) // Rename renames the old hypha to the new name and makes a history record about that. Call if and only if the user has the permission to rename. -func Rename(oldHypha hyphae2.ExistingHypha, newName string, recursive bool, leaveRedirections bool, u *user.User) error { +func Rename(oldHypha hyphae.ExistingHypha, newName string, recursive bool, leaveRedirections bool, u *user.User) error { // * bouncepaw hates this function and related renaming functions if newName == "" { rejectRenameLog(oldHypha, u, "no new name given") return errors.New("ui.rename_noname_tip") } - if !hyphae2.IsValidName(newName) { + if !hyphae.IsValidName(newName) { rejectRenameLog(oldHypha, u, fmt.Sprintf("new name ‘%s’ invalid", newName)) return errors.New("ui.rename_badname_tip") // FIXME: There is a bug related to this. } - switch targetHypha := hyphae2.ByName(newName); targetHypha.(type) { - case hyphae2.ExistingHypha: + switch targetHypha := hyphae.ByName(newName); targetHypha.(type) { + case hyphae.ExistingHypha: if targetHypha.CanonicalName() == oldHypha.CanonicalName() { return nil } @@ -81,7 +81,7 @@ func Rename(oldHypha hyphae2.ExistingHypha, newName string, recursive bool, leav oldName = h.CanonicalName() newName = re.ReplaceAllString(oldName, newName) ) - hyphae2.RenameHyphaTo(h, newName, replaceName) + hyphae.RenameHyphaTo(h, newName, replaceName) backlinks.UpdateBacklinksAfterRename(h, oldName) categories.RenameHyphaInAllCategories(oldName, newName) if leaveRedirections { @@ -104,12 +104,12 @@ const redirectionTemplate = `=> %[1]s | 👁️➡️ %[2]s func leaveRedirection(oldName, newName string, hop *history.Op) error { var ( text = fmt.Sprintf(redirectionTemplate, newName, util.BeautifulName(newName)) - emptyHypha = hyphae2.ByName(oldName) + emptyHypha = hyphae.ByName(oldName) ) switch emptyHypha := emptyHypha.(type) { - case *hyphae2.EmptyHypha: - h := hyphae2.ExtendEmptyToTextual(emptyHypha, filepath.Join(files.HyphaeDir(), oldName+".myco")) - hyphae2.Insert(h) + case *hyphae.EmptyHypha: + h := hyphae.ExtendEmptyToTextual(emptyHypha, filepath.Join(files.HyphaeDir(), oldName+".myco")) + hyphae.Insert(h) categories.AddHyphaToCategory(oldName, cfg.RedirectionCategory) defer backlinks.UpdateBacklinksAfterEdit(h, "") return writeTextToDisk(h, []byte(text), hop) @@ -118,15 +118,15 @@ func leaveRedirection(oldName, newName string, hop *history.Op) error { } } -func findHyphaeToRename(superhypha hyphae2.ExistingHypha, recursive bool) []hyphae2.ExistingHypha { - hyphaList := []hyphae2.ExistingHypha{superhypha} +func findHyphaeToRename(superhypha hyphae.ExistingHypha, recursive bool) []hyphae.ExistingHypha { + hyphaList := []hyphae.ExistingHypha{superhypha} if recursive { - hyphaList = append(hyphaList, hyphae2.Subhyphae(superhypha)...) + hyphaList = append(hyphaList, hyphae.Subhyphae(superhypha)...) } return hyphaList } -func renamingPairs(hyphaeToRename []hyphae2.ExistingHypha, replaceName func(string) string) (map[string]string, error) { +func renamingPairs(hyphaeToRename []hyphae.ExistingHypha, replaceName func(string) string) (map[string]string, error) { var ( renameMap = make(map[string]string) newNames = make([]string, len(hyphaeToRename)) @@ -138,12 +138,12 @@ func renamingPairs(hyphaeToRename []hyphae2.ExistingHypha, replaceName func(stri renameMap[h.TextFilePath()] = replaceName(h.TextFilePath()) } switch h := h.(type) { - case *hyphae2.MediaHypha: + case *hyphae.MediaHypha: renameMap[h.MediaFilePath()] = replaceName(h.MediaFilePath()) } h.Unlock() } - if firstFailure, ok := hyphae2.AreFreeNames(newNames...); !ok { + if firstFailure, ok := hyphae.AreFreeNames(newNames...); !ok { return nil, errors.New("Hypha " + firstFailure + " already exists") } return renameMap, nil diff --git a/internal/shroom/search.go b/internal/shroom/search.go index 57efad0..666752d 100644 --- a/internal/shroom/search.go +++ b/internal/shroom/search.go @@ -1,9 +1,9 @@ package shroom import ( - "github.com/bouncepaw/mycorrhiza/internal/hyphae" "strings" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" "github.com/bouncepaw/mycorrhiza/util" ) diff --git a/internal/shroom/unattach.go b/internal/shroom/unattach.go index 74b76b4..613cae3 100644 --- a/internal/shroom/unattach.go +++ b/internal/shroom/unattach.go @@ -2,14 +2,14 @@ package shroom import ( "fmt" - hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae" - "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/history" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/internal/user" ) // RemoveMedia removes media from the media hypha and makes a history record about that. If it only had media, the hypha will be deleted. If it also had text, the hypha will become textual. -func RemoveMedia(u *user.User, h *hyphae2.MediaHypha) error { +func RemoveMedia(u *user.User, h *hyphae.MediaHypha) error { hop := history. Operation(history.TypeRemoveMedia). WithFilesRemoved(h.MediaFilePath()). @@ -24,9 +24,9 @@ func RemoveMedia(u *user.User, h *hyphae2.MediaHypha) error { } if h.HasTextFile() { - hyphae2.Insert(hyphae2.ShrinkMediaToTextual(h)) + hyphae.Insert(hyphae.ShrinkMediaToTextual(h)) } else { - hyphae2.DeleteHypha(h) + hyphae.DeleteHypha(h) } return nil } diff --git a/internal/shroom/upload.go b/internal/shroom/upload.go index a63c1ec..ff438a6 100644 --- a/internal/shroom/upload.go +++ b/internal/shroom/upload.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" "io" - "log" + "log/slog" "mime/multipart" "os" "path/filepath" @@ -201,7 +201,7 @@ func UploadBinary(h hyphae.Hypha, mime string, file multipart.File, u *user.User if err := history.Rename(prevFilePath, uploadedFilePath); err != nil { return err } - log.Printf("Move ‘%s’ to ‘%s’\n", prevFilePath, uploadedFilePath) + slog.Info("Move file", "from", prevFilePath, "to", uploadedFilePath) h.SetMediaFilePath(uploadedFilePath) } } diff --git a/internal/tree/tree.go b/internal/tree/tree.go index 6dc171e..44f4dac 100644 --- a/internal/tree/tree.go +++ b/internal/tree/tree.go @@ -2,13 +2,14 @@ package tree import ( "fmt" - "github.com/bouncepaw/mycorrhiza/internal/hyphae" - "github.com/bouncepaw/mycorrhiza/util" "html/template" "io" "path" "sort" "strings" + + "github.com/bouncepaw/mycorrhiza/internal/hyphae" + "github.com/bouncepaw/mycorrhiza/util" ) // Tree returns the subhypha matrix as HTML and names of the next and previous hyphae (or empty strings). diff --git a/internal/user/files.go b/internal/user/files.go index d48d8a9..7d836a6 100644 --- a/internal/user/files.go +++ b/internal/user/files.go @@ -3,10 +3,10 @@ package user import ( "encoding/json" "errors" - "github.com/bouncepaw/mycorrhiza/internal/cfg" - "log" + "log/slog" "os" + "github.com/bouncepaw/mycorrhiza/internal/cfg" "github.com/bouncepaw/mycorrhiza/internal/files" "github.com/bouncepaw/mycorrhiza/util" ) @@ -32,19 +32,23 @@ func usersFromFile() []*User { return users } if err != nil { - log.Fatal(err) + slog.Error("Failed to read users.json", "err", err) + os.Exit(1) } + err = json.Unmarshal(contents, &users) if err != nil { - log.Fatal(err) + slog.Error("Failed to unmarshal users.json contents", "err", err) + os.Exit(1) } + for _, u := range users { u.Name = util.CanonicalName(u.Name) if u.Source == "" { u.Source = "local" } } - log.Println("Found", len(users), "users") + slog.Info("Indexed users", "n", len(users)) return users } @@ -63,20 +67,22 @@ func readTokensToUsers() { return } if err != nil { - log.Fatal(err) + slog.Error("Failed to read tokens.json", "err", err) + os.Exit(1) } var tmp map[string]string err = json.Unmarshal(contents, &tmp) if err != nil { - log.Fatal(err) + slog.Error("Failed to unmarshal tokens.json contents", "err", err) + os.Exit(1) } for token, username := range tmp { tokens.Store(token, username) // commenceSession(username, token) } - log.Println("Found", len(tmp), "active sessions") + slog.Info("Indexed active sessions", "n", len(tmp)) } // SaveUserDatabase stores current user credentials into JSON file by configured path. @@ -94,13 +100,13 @@ func dumpUserCredentials() error { blob, err := json.MarshalIndent(userList, "", "\t") if err != nil { - log.Println(err) + slog.Error("Failed to marshal users.json", "err", err) return err } err = os.WriteFile(files.UserCredentialsJSON(), blob, 0666) if err != nil { - log.Println(err) + slog.Error("Failed to write users.json", "err", err) return err } @@ -119,11 +125,11 @@ func dumpTokens() { blob, err := json.MarshalIndent(tmp, "", "\t") if err != nil { - log.Println(err) + slog.Error("Failed to marshal tokens.json", "err", err) return } err = os.WriteFile(files.TokensJSON(), blob, 0666) if err != nil { - log.Println("an error occurred in dumpTokens function:", err) + slog.Error("Failed to write tokens.json", "err", err) } } diff --git a/internal/user/net.go b/internal/user/net.go index 00f60c2..7066d28 100644 --- a/internal/user/net.go +++ b/internal/user/net.go @@ -6,16 +6,16 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/bouncepaw/mycorrhiza/internal/cfg" - "log" + "log/slog" "net/http" "sort" "strings" "time" - "golang.org/x/crypto/bcrypt" - + "github.com/bouncepaw/mycorrhiza/internal/cfg" "github.com/bouncepaw/mycorrhiza/util" + + "golang.org/x/crypto/bcrypt" ) // CanProceed returns `true` if the user in `rq` has enough rights to access `route`. @@ -91,17 +91,17 @@ func LoginDataHTTP(w http.ResponseWriter, username, password string) error { w.Header().Set("Content-Type", "text/html;charset=utf-8") if !HasUsername(username) { w.WriteHeader(http.StatusBadRequest) - log.Println("Unknown username", username, "was entered") + slog.Info("Unknown username entered", "username", username) return ErrUnknownUsername } if !CredentialsOK(username, password) { w.WriteHeader(http.StatusBadRequest) - log.Println("A wrong password was entered for username", username) + slog.Info("Wrong password entered", "username", username) return ErrWrongPassword } token, err := AddSession(username) if err != nil { - log.Println(err) + slog.Error("Failed to add session", "username", username, "err", err) w.WriteHeader(http.StatusBadRequest) return err } @@ -114,7 +114,7 @@ func AddSession(username string) (string, error) { token, err := util.RandomString(16) if err == nil { commenceSession(username, token) - log.Println("New token for", username, "is", token) + slog.Info("Added session", "username", username) } return token, err } diff --git a/internal/user/user.go b/internal/user/user.go index 6aa500a..eec2ff7 100644 --- a/internal/user/user.go +++ b/internal/user/user.go @@ -2,12 +2,13 @@ package user import ( "fmt" - "github.com/bouncepaw/mycorrhiza/internal/cfg" "net/http" "strings" "sync" "time" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "golang.org/x/crypto/bcrypt" ) diff --git a/interwiki/interwiki.go b/interwiki/interwiki.go index de4a532..5020afc 100644 --- a/interwiki/interwiki.go +++ b/interwiki/interwiki.go @@ -4,29 +4,36 @@ package interwiki import ( "encoding/json" "errors" - "git.sr.ht/~bouncepaw/mycomarkup/v5/options" - "github.com/bouncepaw/mycorrhiza/internal/files" - "github.com/bouncepaw/mycorrhiza/util" - "log" + "log/slog" "os" "sync" + + "github.com/bouncepaw/mycorrhiza/internal/files" + "github.com/bouncepaw/mycorrhiza/util" + + "git.sr.ht/~bouncepaw/mycomarkup/v5/options" ) -func Init() { - var ( - record, err = readInterwiki() - ) +func Init() error { + record, err := readInterwiki() if err != nil { - log.Fatalln(err) + slog.Error("Failed to read interwiki", "err", err) + return err } + for _, wiki := range record { wiki := wiki // This line is required - wiki.canonize() + if err := wiki.canonize(); err != nil { + return err + } if err := addEntry(&wiki); err != nil { - log.Fatalln(err.Error()) + slog.Error("Failed to add interwiki entry", "err", err) + return err } } - log.Printf("Loaded %d interwiki entries\n", len(listOfEntries)) + + slog.Info("Indexed interwiki map", "n", len(listOfEntries)) + return nil } func dropEmptyStrings(ss []string) (clean []string) { @@ -100,7 +107,6 @@ func deleteEntry(wiki *Wiki) { for i, w := range listOfEntries { i, w := i, w if w.Name == wiki.Name { - log.Println("It came to delete") // Drop ith element. listOfEntries[i] = listOfEntries[len(listOfEntries)-1] listOfEntries = listOfEntries[:len(listOfEntries)-1] @@ -113,21 +119,22 @@ func deleteEntry(wiki *Wiki) { wg.Wait() } +// TODO: There is something clearly wrong with error-returning in this function. func addEntry(wiki *Wiki) error { mutex.Lock() defer mutex.Unlock() wiki.Aliases = dropEmptyStrings(wiki.Aliases) + var ( names = append(wiki.Aliases, wiki.Name) ok, name = areNamesFree(names) ) - if !ok { - log.Printf("There are multiple uses of the same name ‘%s’\n", name) + switch { + case !ok: + slog.Error("There are multiple uses of the same name", "name", name) return errors.New(name) - } - if len(names) == 0 { - log.Println("No names passed for a new interwiki entry") - // There is something clearly wrong with error-returning in this function. + case len(names) == 0: + slog.Error("No names passed for a new interwiki entry") return errors.New("") } @@ -176,10 +183,13 @@ func readInterwiki() ([]Wiki, error) { func saveInterwikiJson() { // Trust me, wiki crashing when an admin takes an administrative action totally makes sense. if data, err := json.MarshalIndent(listOfEntries, "", "\t"); err != nil { - log.Fatalln(err) + slog.Error("Failed to marshal interwiki entries", "err", err) + os.Exit(1) } else if err = os.WriteFile(files.InterwikiJSON(), data, 0666); err != nil { - log.Fatalln(err) - } else { - log.Println("Saved interwiki.json") + slog.Error("Failed to write interwiki.json", "err", err) + os.Exit(1) } + + slog.Info("Saved interwiki.json") + } diff --git a/interwiki/web.go b/interwiki/web.go index 58ba51d..b8c62f8 100644 --- a/interwiki/web.go +++ b/interwiki/web.go @@ -2,11 +2,13 @@ package interwiki import ( "embed" - "github.com/bouncepaw/mycorrhiza/web/viewutil" - "github.com/gorilla/mux" - "log" + "log/slog" "net/http" "strings" + + "github.com/bouncepaw/mycorrhiza/web/viewutil" + + "github.com/gorilla/mux" ) var ( @@ -63,19 +65,24 @@ func handlerModifyEntry(w http.ResponseWriter, rq *http.Request) { ) if oldData, ok = entriesByName[name]; !ok { - log.Printf("Could not modify interwiki entry ‘%s’ because it does not exist", name) + slog.Info("Could not modify entry", + "name", name, + "reason", "does not exist") viewutil.HandlerNotFound(w, rq) return } if err := replaceEntry(oldData, &newData); err != nil { - log.Printf("Could not modify interwiki entry ‘%s’ because one of the proposed aliases/name is taken\n", name) + slog.Info("Could not modify entry", + "name", name, + "reason", "one of the proposed aliases or the name is taken", + "err", err) viewNameTaken(viewutil.MetaFrom(w, rq), oldData, err.Error(), "modify-entry/"+name) return } saveInterwikiJson() - log.Printf("Modified interwiki entry ‘%s’\n", name) + slog.Info("Modified entry", "name", name) http.Redirect(w, rq, "/interwiki", http.StatusSeeOther) } diff --git a/interwiki/wiki.go b/interwiki/wiki.go index 4d0ef51..05df378 100644 --- a/interwiki/wiki.go +++ b/interwiki/wiki.go @@ -1,9 +1,11 @@ package interwiki import ( + "errors" "fmt" + "log/slog" + "github.com/bouncepaw/mycorrhiza/util" - "log" ) // WikiEngine is an enumeration of supported interwiki targets. @@ -47,14 +49,20 @@ type Wiki struct { Engine WikiEngine `json:"engine"` } -func (w *Wiki) canonize() { +func (w *Wiki) canonize() error { switch { case w.Name == "": - log.Fatalln("Cannot have a wiki in the interwiki map with no name") + slog.Error("A site in the interwiki map has no name") + return errors.New("site with no name") case w.URL == "": - log.Fatalf("Wiki ‘%s’ has no URL\n", w.Name) + slog.Error("Site in the interwiki map has no URL", "name", w.Name) + return errors.New("site with no URL") case !w.Engine.Valid(): - log.Fatalf("Unknown engine ‘%s’ for wiki ‘%s’\n", w.Engine, w.Name) + slog.Error("Site in the interwiki map has an unknown engine", + "siteName", w.Name, + "engine", w.Engine, + ) + return errors.New("unknown engine") } w.Name = util.CanonicalName(w.Name) @@ -83,4 +91,6 @@ func (w *Wiki) canonize() { w.ImgSrcFormat = fmt.Sprintf("%s/{NAME}", w.URL) } } + + return nil } diff --git a/l18n/l18n.go b/l18n/l18n.go index d352d61..ea5ae44 100644 --- a/l18n/l18n.go +++ b/l18n/l18n.go @@ -21,7 +21,7 @@ import ( "encoding/json" "fmt" "io/fs" - "log" + "log/slog" "net/http" "path/filepath" "strings" @@ -78,7 +78,7 @@ func init() { var strings map[string]string if err := json.Unmarshal(contents, &strings); err != nil { - log.Fatalf("error while parsing %s: %v", path, err) + slog.Error("Failed to unmarshal localization file", "path", path, "err", err) } for key, value := range strings { diff --git a/main.go b/main.go index 4028464..21dd33f 100644 --- a/main.go +++ b/main.go @@ -5,12 +5,9 @@ package main import ( - "github.com/bouncepaw/mycorrhiza/internal/categories" - "log" - "os" - "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/internal/backlinks" + "github.com/bouncepaw/mycorrhiza/internal/categories" "github.com/bouncepaw/mycorrhiza/internal/cfg" "github.com/bouncepaw/mycorrhiza/internal/files" "github.com/bouncepaw/mycorrhiza/internal/hyphae" @@ -22,45 +19,63 @@ import ( "github.com/bouncepaw/mycorrhiza/web" "github.com/bouncepaw/mycorrhiza/web/static" "github.com/bouncepaw/mycorrhiza/web/viewutil" + "log/slog" + "os" ) func main() { - parseCliArgs() + if err := parseCliArgs(); err != nil { + os.Exit(1) + } if err := files.PrepareWikiRoot(); err != nil { - log.Fatal(err) + slog.Error("Failed to prepare wiki root", "err", err) + os.Exit(1) } if err := cfg.ReadConfigFile(files.ConfigPath()); err != nil { - log.Fatal(err) + slog.Error("Failed to read config", "err", err) + os.Exit(1) } - log.Println("Running Mycorrhiza Wiki", version.Short) if err := os.Chdir(files.HyphaeDir()); err != nil { - log.Fatal(err) + slog.Error("Failed to chdir to hyphae dir", + "err", err, "hyphaeDir", files.HyphaeDir()) + os.Exit(1) } - log.Println("Wiki directory is", cfg.WikiDir) + slog.Info("Running Mycorrhiza Wiki", + "version", version.Short, "wikiDir", cfg.WikiDir) // Init the subsystems: + // TODO: keep all crashes in main rather than somewhere there viewutil.Init() hyphae.Index(files.HyphaeDir()) backlinks.IndexBacklinks() go backlinks.RunBacklinksConveyor() user.InitUserDatabase() - history.Start() + if err := history.Start(); err != nil { + os.Exit(1) + } history.InitGitRepo() migration.MigrateRocketsMaybe() migration.MigrateHeadingsMaybe() shroom.SetHeaderLinks() - categories.Init() - interwiki.Init() + if err := categories.Init(); err != nil { + os.Exit(1) + } + if err := interwiki.Init(); err != nil { + os.Exit(1) + } // Static files: static.InitFS(files.StaticFiles()) if !user.HasAnyAdmins() { - log.Println("Your wiki has no admin yet. Run Mycorrhiza with -create-admin option to create an admin.") + slog.Error("Your wiki has no admin yet. Run Mycorrhiza with -create-admin option to create an admin.") } - serveHTTP(web.Handler()) + err := serveHTTP(web.Handler()) + if err != nil { + os.Exit(1) + } } diff --git a/misc/about.go b/misc/about.go index 36c392a..16d1164 100644 --- a/misc/about.go +++ b/misc/about.go @@ -1,13 +1,15 @@ package misc import ( + "log/slog" + "os" + "strings" + "text/template" // sic! TODO: make it html/template after the template library migration + "github.com/bouncepaw/mycorrhiza/internal/cfg" "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/internal/version" "github.com/bouncepaw/mycorrhiza/l18n" - "log" - "strings" - "text/template" // sic! ) type L10nEntry struct { @@ -95,7 +97,8 @@ func AboutHTML(lc *l18n.Localizer) string { } temp, err := template.New("about wiki").Funcs(template.FuncMap{"get": get}).Parse(aboutTemplateString) if err != nil { - log.Fatalln(err) + slog.Error("Failed to parse About template", "err", err) + os.Exit(1) } data := aboutData data.Version = version.Short @@ -112,7 +115,8 @@ func AboutHTML(lc *l18n.Localizer) string { var out strings.Builder err = temp.Execute(&out, data) if err != nil { - log.Println(err) + slog.Error("Failed to execute About template", "err", err) + os.Exit(1) } return out.String() } diff --git a/misc/handlers.go b/misc/handlers.go index e282ee4..fc16802 100644 --- a/misc/handlers.go +++ b/misc/handlers.go @@ -3,7 +3,7 @@ package misc import ( "io" - "log" + "log/slog" "math/rand" "mime" "net/http" @@ -73,11 +73,11 @@ func handlerReindex(w http.ResponseWriter, rq *http.Request) { if ok := user.CanProceed(rq, "reindex"); !ok { var lc = l18n.FromRequest(rq) viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.reindex_no_rights")) - log.Println("Rejected", rq.URL) + slog.Info("No rights to reindex") return } hyphae.ResetCount() - log.Println("Reindexing hyphae in", files.HyphaeDir()) + slog.Info("Reindexing hyphae", "hyphaeDir", files.HyphaeDir()) hyphae.Index(files.HyphaeDir()) backlinks.IndexBacklinks() http.Redirect(w, rq, "/", http.StatusSeeOther) @@ -89,9 +89,10 @@ func handlerUpdateHeaderLinks(w http.ResponseWriter, rq *http.Request) { if ok := user.CanProceed(rq, "update-header-links"); !ok { var lc = l18n.FromRequest(rq) viewutil.HttpErr(viewutil.MetaFrom(w, rq), http.StatusForbidden, cfg.HomeHypha, lc.Get("ui.header_no_rights")) - log.Println("Rejected", rq.URL) + slog.Info("No rights to update header links") return } + slog.Info("Updated header links") shroom.SetHeaderLinks() http.Redirect(w, rq, "/", http.StatusSeeOther) } @@ -133,7 +134,7 @@ func handlerAbout(w http.ResponseWriter, rq *http.Request) { map[string]string{}, )) if err != nil { - log.Println(err) + slog.Error("Failed to write About template", "err", err) } } @@ -148,7 +149,7 @@ func handlerStyle(w http.ResponseWriter, rq *http.Request) { } _, err = io.Copy(w, file) if err != nil { - log.Println(err) + slog.Error("Failed to write stylesheet; proceeding anyway", "err", err) } _ = file.Close() } @@ -163,7 +164,7 @@ func handlerRobotsTxt(w http.ResponseWriter, rq *http.Request) { } _, err = io.Copy(w, file) if err != nil { - log.Println() + slog.Error("Failed to write robots.txt; proceeding anyway", "err", err) } _ = file.Close() } diff --git a/misc/views.go b/misc/views.go index 99f83ff..2cbd389 100644 --- a/misc/views.go +++ b/misc/views.go @@ -2,6 +2,7 @@ package misc import ( "embed" + "github.com/bouncepaw/mycorrhiza/internal/hyphae" "github.com/bouncepaw/mycorrhiza/web/viewutil" ) diff --git a/mycoopts/mycoopts.go b/mycoopts/mycoopts.go index bd1d9f2..7fe78a8 100644 --- a/mycoopts/mycoopts.go +++ b/mycoopts/mycoopts.go @@ -2,11 +2,13 @@ package mycoopts import ( "errors" - "git.sr.ht/~bouncepaw/mycomarkup/v5/options" + "github.com/bouncepaw/mycorrhiza/internal/cfg" "github.com/bouncepaw/mycorrhiza/internal/hyphae" "github.com/bouncepaw/mycorrhiza/interwiki" "github.com/bouncepaw/mycorrhiza/util" + + "git.sr.ht/~bouncepaw/mycomarkup/v5/options" ) func MarkupOptions(hyphaName string) options.Options { diff --git a/util/util.go b/util/util.go index 499697b..7cdd404 100644 --- a/util/util.go +++ b/util/util.go @@ -3,7 +3,7 @@ package util import ( "crypto/rand" "encoding/hex" - "log" + "log/slog" "net/http" "strings" @@ -82,7 +82,7 @@ func HyphaNameFromRq(rq *http.Request, actions ...string) string { return CanonicalName(strings.TrimPrefix(p, "/"+action+"/")) } } - log.Println("HyphaNameFromRq: this request is invalid, fall back to home hypha") + slog.Info("HyphaNameFromRq: this request is invalid, fall back to home hypha") return cfg.HomeHypha } diff --git a/web/admin.go b/web/admin.go index c01abce..d3c4b41 100644 --- a/web/admin.go +++ b/web/admin.go @@ -2,7 +2,7 @@ package web import ( "fmt" - "log" + "log/slog" "mime" "net/http" "os" @@ -115,7 +115,7 @@ func handlerAdmin(w http.ResponseWriter, rq *http.Request) { // handlerAdminShutdown kills the wiki. func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) { if user.CanProceed(rq, "admin/shutdown") { - log.Println("An admin commanded the wiki to shutdown") + slog.Info("An admin commanded the wiki to shutdown") os.Exit(0) } } @@ -162,7 +162,7 @@ func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) { u.Group = newGroup if err := user.SaveUserDatabase(); err != nil { u.Group = oldGroup - log.Println(err) + slog.Info("Failed to save user database", "err", err) f = f.WithError(err) } else { http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther) @@ -241,7 +241,7 @@ func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) { if !f.HasError() { http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther) } else { - log.Println(f.Error()) + slog.Info("Failed to delete user", "err", f.Error()) } } diff --git a/web/cats.go b/web/cats.go index 47b1d7f..251b905 100644 --- a/web/cats.go +++ b/web/cats.go @@ -1,14 +1,13 @@ package web import ( - "github.com/bouncepaw/mycorrhiza/internal/categories" "io" - "log" "log/slog" "net/http" "sort" "strings" + "github.com/bouncepaw/mycorrhiza/internal/categories" "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/web/viewutil" @@ -66,7 +65,7 @@ func handlerCategory(w http.ResponseWriter, rq *http.Request) { // There is one hypha from the hypha field. Then there are n hyphae in fields prefixed by _. It seems like I have to do it myself. Compare with PHP which handles it for you. I hope I am doing this wrong. func hyphaeFromRequest(rq *http.Request) (canonicalNames []string) { if err := rq.ParseForm(); err != nil { - log.Println(err) + slog.Info("Failed to parse form", "err", err) } if hyphaName := util.CanonicalName(rq.PostFormValue("hypha")); hyphaName != "" { canonicalNames = append(canonicalNames, hyphaName) @@ -100,7 +99,8 @@ func handlerRemoveFromCategory(w http.ResponseWriter, rq *http.Request) { return } if len(hyphaNames) == 0 || catName == "" { - log.Printf("%s passed no data for removal of hyphae from a category\n", u.Name) + slog.Info("No data for removal of hyphae from category passed", + "username", u.Name, "catName", catName) http.Redirect(w, rq, redirectTo, http.StatusSeeOther) return } @@ -108,7 +108,8 @@ func handlerRemoveFromCategory(w http.ResponseWriter, rq *http.Request) { // TODO: Make it more effective. categories.RemoveHyphaFromCategory(hyphaName, catName) } - log.Printf("%s removed %q from category %s\n", u.Name, hyphaNames, catName) + slog.Info("Remove hyphae from category", + "username", u.Name, "catName", catName, "hyphaNames", hyphaNames) http.Redirect(w, rq, redirectTo, http.StatusSeeOther) } diff --git a/web/mutators.go b/web/mutators.go index 75e13ae..1561514 100644 --- a/web/mutators.go +++ b/web/mutators.go @@ -1,23 +1,22 @@ package web import ( - "git.sr.ht/~bouncepaw/mycomarkup/v5" + "html/template" + "log/slog" + "net/http" + + "github.com/bouncepaw/mycorrhiza/hypview" "github.com/bouncepaw/mycorrhiza/internal/hyphae" "github.com/bouncepaw/mycorrhiza/internal/shroom" "github.com/bouncepaw/mycorrhiza/internal/user" - "github.com/bouncepaw/mycorrhiza/web/viewutil" - "html/template" - "log" - "net/http" - - "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" - "github.com/bouncepaw/mycorrhiza/hypview" - "github.com/bouncepaw/mycorrhiza/mycoopts" - - "github.com/gorilla/mux" - "github.com/bouncepaw/mycorrhiza/l18n" + "github.com/bouncepaw/mycorrhiza/mycoopts" "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/web/viewutil" + + "git.sr.ht/~bouncepaw/mycomarkup/v5" + "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" + "github.com/gorilla/mux" ) func initMutators(r *mux.Router) { @@ -64,14 +63,16 @@ func handlerDelete(w http.ResponseWriter, rq *http.Request) { ) if !u.CanProceed("delete") { - log.Printf("%s has no rights to delete ‘%s’\n", u.Name, h.CanonicalName()) + slog.Info("No rights to delete hypha", + "username", u.Name, "hyphaName", h.CanonicalName()) viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "No rights") return } switch h.(type) { case *hyphae.EmptyHypha: - log.Printf("%s tries to delete empty hypha ‘%s’\n", u.Name, h.CanonicalName()) + slog.Info("Trying to delete empty hyphae", + "username", u.Name, "hyphaName", h.CanonicalName()) // TODO: localize viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "Cannot delete an empty hypha") return @@ -87,7 +88,7 @@ func handlerDelete(w http.ResponseWriter, rq *http.Request) { } if err := shroom.Delete(u, h.(hyphae.ExistingHypha)); err != nil { - log.Println(err) + slog.Error("Failed to delete hypha", "err", err) viewutil.HttpErr(meta, http.StatusInternalServerError, h.CanonicalName(), err.Error()) return } @@ -105,13 +106,15 @@ func handlerRename(w http.ResponseWriter, rq *http.Request) { switch h.(type) { case *hyphae.EmptyHypha: - log.Printf("%s tries to rename empty hypha ‘%s’", u.Name, h.CanonicalName()) + slog.Info("Trying to rename empty hypha", + "username", u.Name, "hyphaName", h.CanonicalName()) viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "Cannot rename an empty hypha") // TODO: localize return } if !u.CanProceed("rename") { - log.Printf("%s has no rights to rename ‘%s’\n", u.Name, h.CanonicalName()) + slog.Info("No rights to rename hypha", + "username", u.Name, "hyphaName", h.CanonicalName()) viewutil.HttpErr(meta, http.StatusForbidden, h.CanonicalName(), "No rights") return } @@ -129,7 +132,8 @@ func handlerRename(w http.ResponseWriter, rq *http.Request) { } if err := shroom.Rename(oldHypha, newName, recursive, leaveRedirections, u); err != nil { - log.Printf("%s tries to rename ‘%s’: %s", u.Name, oldHypha.CanonicalName(), err.Error()) + slog.Error("Failed to rename hypha", + "err", err, "username", u.Name, "hyphaName", oldHypha.CanonicalName()) viewutil.HttpErr(meta, http.StatusForbidden, oldHypha.CanonicalName(), lc.Get(err.Error())) // TODO: localize return } @@ -163,7 +167,7 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) { default: content, err = hyphae.FetchMycomarkupFile(h) if err != nil { - log.Println(err) + slog.Error("Failed to fetch Mycomarkup file", "err", err) viewutil.HttpErr(meta, http.StatusInternalServerError, hyphaName, lc.Get("ui.error_text_fetch")) return } diff --git a/web/newtmpl/newtmpl.go b/web/newtmpl/newtmpl.go index b0184c5..d2bf77f 100644 --- a/web/newtmpl/newtmpl.go +++ b/web/newtmpl/newtmpl.go @@ -3,11 +3,12 @@ package newtmpl import ( "embed" "fmt" + "html/template" + "strings" + "github.com/bouncepaw/mycorrhiza/internal/cfg" "github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/web/viewutil" - "html/template" - "strings" ) //go:embed *.html diff --git a/web/pages.go b/web/pages.go index 38cf691..4f8f077 100644 --- a/web/pages.go +++ b/web/pages.go @@ -2,6 +2,7 @@ package web import ( "embed" + "github.com/bouncepaw/mycorrhiza/web/newtmpl" "github.com/bouncepaw/mycorrhiza/web/viewutil" ) diff --git a/web/password.go b/web/password.go index 4a74a7b..37e885d 100644 --- a/web/password.go +++ b/web/password.go @@ -2,12 +2,13 @@ package web import ( "fmt" - "github.com/bouncepaw/mycorrhiza/internal/user" - "github.com/bouncepaw/mycorrhiza/util" - "github.com/bouncepaw/mycorrhiza/web/viewutil" "mime" "net/http" "reflect" + + "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/web/viewutil" ) func handlerUserChangePassword(w http.ResponseWriter, rq *http.Request) { diff --git a/web/readers.go b/web/readers.go index 0f8363c..480c874 100644 --- a/web/readers.go +++ b/web/readers.go @@ -2,7 +2,17 @@ package web import ( "fmt" - "git.sr.ht/~bouncepaw/mycomarkup/v5" + "html/template" + "io" + "log/slog" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/bouncepaw/mycorrhiza/history" "github.com/bouncepaw/mycorrhiza/hypview" "github.com/bouncepaw/mycorrhiza/internal/backlinks" "github.com/bouncepaw/mycorrhiza/internal/categories" @@ -12,26 +22,15 @@ import ( "github.com/bouncepaw/mycorrhiza/internal/mimetype" "github.com/bouncepaw/mycorrhiza/internal/tree" "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/mycoopts" + "github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/web/viewutil" - "html/template" - "io" - "log" - "log/slog" - "net/http" - "os" - "path" - "path/filepath" - "strings" - "time" - - "github.com/gorilla/mux" + "git.sr.ht/~bouncepaw/mycomarkup/v5" "git.sr.ht/~bouncepaw/mycomarkup/v5/mycocontext" "git.sr.ht/~bouncepaw/mycomarkup/v5/tools" - "github.com/bouncepaw/mycorrhiza/history" - "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/util" + "github.com/gorilla/mux" ) func initReaders(r *mux.Router) { @@ -109,6 +108,7 @@ func handlerRevisionText(w http.ResponseWriter, rq *http.Request) { h = hyphae.ByName(hyphaName) ) w.Header().Set("Content-Type", "text/plain; charset=utf-8") + switch h := h.(type) { case *hyphae.EmptyHypha: var mycoFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco") @@ -116,27 +116,32 @@ func handlerRevisionText(w http.ResponseWriter, rq *http.Request) { if err != nil { w.WriteHeader(http.StatusNotFound) - log.Printf("While serving text of ‘%s’ at revision ‘%s’: %s\n", hyphaName, revHash, err.Error()) + slog.Error("Failed to serve text part", + "err", err, "hyphaName", hyphaName, "revHash", revHash) _, _ = io.WriteString(w, "Error: "+err.Error()) return } - log.Printf("Serving text of ‘%s’ from ‘%s’ at revision ‘%s’\n", hyphaName, mycoFilePath, revHash) + slog.Info("Serving text part", + "hyphaName", hyphaName, "revHash", revHash, "mycoFilePath", mycoFilePath) w.WriteHeader(http.StatusOK) _, _ = io.WriteString(w, textContents) + case hyphae.ExistingHypha: if !h.HasTextFile() { - log.Printf(`Media hypha ‘%s’ has no text`) + slog.Info("Media hypha has no text part; cannot serve it", + "hyphaName", h.CanonicalName()) w.WriteHeader(http.StatusNotFound) } var textContents, err = history.FileAtRevision(h.TextFilePath(), revHash) if err != nil { w.WriteHeader(http.StatusNotFound) - log.Printf("While serving text of ‘%s’ at revision ‘%s’: %s\n", hyphaName, revHash, err.Error()) + slog.Error("Failed to serve text part", + "err", err, "hyphaName", h.CanonicalName(), "revHash", revHash) _, _ = io.WriteString(w, "Error: "+err.Error()) return } - log.Printf("Serving text of ‘%s’ from ‘%s’ at revision ‘%s’\n", hyphaName, h.TextFilePath(), revHash) + slog.Info("Serving text part", "hyphaName", h.CanonicalName(), "revHash", revHash) w.WriteHeader(http.StatusOK) _, _ = io.WriteString(w, textContents) } @@ -188,7 +193,7 @@ func handlerText(w http.ResponseWriter, rq *http.Request) { hyphaName := util.HyphaNameFromRq(rq, "text") switch h := hyphae.ByName(hyphaName).(type) { case hyphae.ExistingHypha: - log.Println("Serving", h.TextFilePath()) + slog.Info("Serving text part", "path", h.TextFilePath()) w.Header().Set("Content-Type", "text/plain; charset=utf-8") http.ServeFile(w, rq, h.TextFilePath()) } @@ -201,9 +206,10 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) { switch h := hyphae.ByName(hyphaName).(type) { case *hyphae.EmptyHypha, *hyphae.TextualHypha: w.WriteHeader(http.StatusNotFound) - log.Printf("Textual hypha ‘%s’ has no media, cannot serve\n", h.CanonicalName()) + slog.Info("Textual hypha has no media file; cannot serve it", + "hyphaName", h.CanonicalName()) case *hyphae.MediaHypha: - log.Println("Serving", h.MediaFilePath()) + slog.Info("Serving media file", "path", h.MediaFilePath()) w.Header().Set("Content-Type", mimetype.FromExtension(filepath.Ext(h.MediaFilePath()))) http.ServeFile(w, rq, h.MediaFilePath()) } diff --git a/web/viewutil/meta.go b/web/viewutil/meta.go index d47dc02..4b8ab2d 100644 --- a/web/viewutil/meta.go +++ b/web/viewutil/meta.go @@ -1,11 +1,12 @@ package viewutil import ( - "github.com/bouncepaw/mycorrhiza/internal/user" - "github.com/bouncepaw/mycorrhiza/l18n" "html/template" "io" "net/http" + + "github.com/bouncepaw/mycorrhiza/internal/user" + "github.com/bouncepaw/mycorrhiza/l18n" ) // Meta is a bundle of common stuffs used by views, templates. diff --git a/web/viewutil/viewutil.go b/web/viewutil/viewutil.go index f49de2d..80fb2f8 100644 --- a/web/viewutil/viewutil.go +++ b/web/viewutil/viewutil.go @@ -6,7 +6,7 @@ import ( "fmt" "github.com/bouncepaw/mycorrhiza/internal/cfg" "io/fs" - "log" + "log/slog" "strings" "text/template" // TODO: save the world @@ -125,7 +125,7 @@ func Base(meta Meta, title, body string, bodyAttributes map[string]string, headE BodyAttributes: bodyAttributes, }) if err != nil { - log.Println(err) + slog.Info("Failed to execute the legacy Base template; proceeding anyway", "err", err) } return w.String() } @@ -149,7 +149,7 @@ func ExecutePage(meta Meta, chain Chain, data interface { }) { data.withBaseValues(meta, HeaderLinks, cfg.CommonScripts) if err := chain.Get(meta).ExecuteTemplate(meta.W, "page", data); err != nil { - log.Println(err) + slog.Info("Failed to execute page; proceeding anyway", "err", err) } } diff --git a/web/web.go b/web/web.go index 01ba4e6..fad97d6 100644 --- a/web/web.go +++ b/web/web.go @@ -4,12 +4,7 @@ package web import ( "errors" "fmt" - "github.com/bouncepaw/mycorrhiza/internal/cfg" - "github.com/bouncepaw/mycorrhiza/internal/user" - "github.com/bouncepaw/mycorrhiza/l18n" - "github.com/bouncepaw/mycorrhiza/web/viewutil" "io" - "log" "log/slog" "mime" "net/http" @@ -19,11 +14,15 @@ import ( "github.com/bouncepaw/mycorrhiza/help" "github.com/bouncepaw/mycorrhiza/history/histweb" "github.com/bouncepaw/mycorrhiza/hypview" + "github.com/bouncepaw/mycorrhiza/internal/cfg" + "github.com/bouncepaw/mycorrhiza/internal/user" "github.com/bouncepaw/mycorrhiza/interwiki" + "github.com/bouncepaw/mycorrhiza/l18n" "github.com/bouncepaw/mycorrhiza/misc" - "github.com/gorilla/mux" - "github.com/bouncepaw/mycorrhiza/util" + "github.com/bouncepaw/mycorrhiza/web/viewutil" + + "github.com/gorilla/mux" ) // Handler initializes and returns the HTTP router based on the configuration. @@ -308,7 +307,7 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) { errmsg := user.LoginDataHTTP(w, username, "") if errmsg != nil { - log.Printf("Failed to login ‘%s’ using Telegram: %s", username, err.Error()) + slog.Error("Failed to login using Telegram", "err", err, "username", username) w.WriteHeader(http.StatusBadRequest) _, _ = io.WriteString( w, From a3e8654c5b665935e496a4e9efeddd0cdc128bb1 Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 5 Oct 2024 22:03:54 +0300 Subject: [PATCH 15/19] Update README.md --- README.md | 6 ++++-- help/en.myco | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a282d28..69025fe 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Mycorrhiza Wiki** is a lightweight file-system wiki engine that uses Git for keeping history. [Main wiki](https://mycorrhiza.wiki) -A screenshot of mycorrhiza.wiki's home page in the Safari browser +A screenshot of mycorrhiza.wiki's home page in the Safari browser ## Features @@ -30,7 +30,7 @@ Compare Mycorrhiza Wiki with other engines on [WikiMatrix](https://www.wikimatri ## Installing -See [the deployment guide](https://mycorrhiza.wiki/hypha/deployment) on the wiki. Also, Mycorrhiza might be available in your repositories. +See [the deployment guide](https://mycorrhiza.wiki/hypha/deployment) on the wiki. Also, Mycorrhiza might be available in your repositories. See [Repology](https://repology.org/project/mycorrhiza/versions). ## Contributing and community @@ -46,3 +46,5 @@ If you want to contribute with code, open a pull request on GitHub or send a pat If you want to report an issue, open an issue on GitHub or contact us directly. Consider supporting the development on [Boosty](https://boosty.to/bouncepaw). + +Check out [Betula](https://betula.mycorrhiza.wiki) as well. \ No newline at end of file diff --git a/help/en.myco b/help/en.myco index f82a3b3..363ce6d 100644 --- a/help/en.myco +++ b/help/en.myco @@ -1,6 +1,6 @@ = Help -This is documentation for Mycorrhiza Wiki 1.15. Choose a topic from the list. +This is documentation for Mycorrhiza Wiki 1.15.1. Choose a topic from the list. The documentation is incomplete. If you want to contribute to the documentation, open a pull request or an issue on [[https://github.com/bouncepaw/mycorrhiza | GitHub]] or [[https://lists.sr.ht/~bouncepaw/mycorrhiza-devel | send a patch]]. From 727280147afd3d2bb6b38582a75cd5643166cff7 Mon Sep 17 00:00:00 2001 From: Timur Ismagilov Date: Sat, 5 Oct 2024 22:30:17 +0300 Subject: [PATCH 16/19] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69025fe..546a14c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Mycorrhiza Wiki** is a lightweight file-system wiki engine that uses Git for keeping history. [Main wiki](https://mycorrhiza.wiki) -A screenshot of mycorrhiza.wiki's home page in the Safari browser +A screenshot of mycorrhiza.wiki's home page in the Safari browser ## Features From 0f7525a23c18bf1882d5f29c0d39d3d9e1410c10 Mon Sep 17 00:00:00 2001 From: novenary <1155030+9ary@users.noreply.github.com> Date: Wed, 11 Dec 2024 23:20:24 +0200 Subject: [PATCH 17/19] Don't exit when removing socket fails (#260) Prior to migrating to slog, errors when removing the socket were ignored. This is fine and desirable: the most likely error condition is that the socket did not exist in the first place. Exiting on error in that case effectively prevents Mycorrhiza to be used with unix sockets. If the removal fails for another reason, then starting the server will fail, so logging a warning is sufficient for troubleshooting. --- httpd.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpd.go b/httpd.go index 71bf815..b838e40 100644 --- a/httpd.go +++ b/httpd.go @@ -32,7 +32,7 @@ func serveHTTP(handler http.Handler) (err error) { func startUnixSocketServer(server *http.Server, socketPath string) error { err := os.Remove(socketPath) if err != nil { - return err + slog.Warn("Failed to clean up old socket", "err", err) } listener, err := net.Listen("unix", socketPath) From d679eb46613dac9f9127425d3eee0359f1e636a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Dec 2024 06:41:34 +0300 Subject: [PATCH 18/19] Bump golang.org/x/crypto from 0.27.0 to 0.31.0 (#261) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.27.0 to 0.31.0. - [Commits](https://github.com/golang/crypto/compare/v0.27.0...v0.31.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 398d62d..2f990a2 100644 --- a/go.mod +++ b/go.mod @@ -8,15 +8,15 @@ require ( github.com/gorilla/feeds v1.2.0 github.com/gorilla/mux v1.8.1 github.com/valyala/quicktemplate v1.7.0 - golang.org/x/crypto v0.27.0 - golang.org/x/term v0.24.0 - golang.org/x/text v0.18.0 + golang.org/x/crypto v0.31.0 + golang.org/x/term v0.27.0 + golang.org/x/text v0.21.0 ) require ( github.com/stretchr/testify v1.7.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - golang.org/x/sys v0.25.0 // indirect + golang.org/x/sys v0.28.0 // indirect ) // Use this trick to test local Mycomarkup changes, replace the path with yours, diff --git a/go.sum b/go.sum index 0ad0006..6071d23 100644 --- a/go.sum +++ b/go.sum @@ -31,22 +31,22 @@ github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTc github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= From da84a76e792a2fb3fb187d9cb857223d8c365ad8 Mon Sep 17 00:00:00 2001 From: Chris Sexton <3216719+chrissexton@users.noreply.github.com> Date: Wed, 19 Feb 2025 18:02:07 -0500 Subject: [PATCH 19/19] Reorder OpenGraph search. (#265) The previous ordering prevented the visitors from finding a description or image. --- web/readers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/readers.go b/web/readers.go index 480c874..e99ce59 100644 --- a/web/readers.go +++ b/web/readers.go @@ -258,8 +258,8 @@ func handlerHypha(w http.ResponseWriter, rq *http.Request) { if err == nil { ctx, _ := mycocontext.ContextFromStringInput(string(fileContentsT), mycoopts.MarkupOptions(hyphaName)) getOpenGraph, descVisitor, imgVisitor := tools.OpenGraphVisitors(ctx) - openGraph = template.HTML(getOpenGraph()) ast := mycomarkup.BlockTree(ctx, descVisitor, imgVisitor) + openGraph = template.HTML(getOpenGraph()) contents = template.HTML(mycomarkup.BlocksToHTML(ctx, ast)) } switch h := h.(type) {