Merge branch 'refs/heads/master' into newtmpl

# Conflicts:
#	hypview/hypview.go
#	internal/shroom/upload.go
This commit is contained in:
Timur Ismagilov 2024-06-29 20:11:19 +03:00
commit 86a8091bf6
16 changed files with 95 additions and 97 deletions

View File

@ -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

View File

@ -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).

View File

@ -1,12 +1,12 @@
{{define "category list"}}Category list{{end}}
{{define "title"}}{{template "category list"}}{{end}}
{{define "body"}}
<main class="main-width mv-categories">
<main class="main-width mv-tags">
<h1 class="p-name">{{template "title"}}</h1>
{{if len .Categories}}
<ol>
{{range .Categories}}
<li class="mv-category">
<li class="mv-tag">
<a class="wikilink u-url p-name" href="/category/{{.}}">{{beautifulName .}}</a>
</li>
{{end}}

View File

@ -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]].

View File

@ -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.

View File

@ -3,6 +3,7 @@ package hyphae
import (
"github.com/bouncepaw/mycorrhiza/internal/mimetype"
"log"
"log/slog"
"os"
"path/filepath"
)
@ -26,11 +27,10 @@ func Index(path string) {
switch foundHypha := foundHypha.(type) {
case *TextualHypha: // conflict! overwrite
storedHypha.mycoFilePath = foundHypha.mycoFilePath
log.Printf(
"File collision for hypha %s, using %s rather than %s\n",
foundHypha.CanonicalName(),
foundHypha.TextFilePath(),
storedHypha.TextFilePath(),
slog.Info("File collision",
"hypha", foundHypha.CanonicalName(),
"usingFile", foundHypha.TextFilePath(),
"insteadOf", storedHypha.TextFilePath(),
)
case *MediaHypha: // no conflict
Insert(ExtendTextualToMedia(storedHypha, foundHypha.mediaFilePath))
@ -42,11 +42,11 @@ func Index(path string) {
storedHypha.mycoFilePath = foundHypha.mycoFilePath
case *MediaHypha: // conflict! overwrite
storedHypha.mediaFilePath = foundHypha.mediaFilePath
log.Printf(
"File collision for hypha %s, using %s rather than %s\n",
foundHypha.CanonicalName(),
foundHypha.MediaFilePath(),
storedHypha.MediaFilePath(),
slog.Info("File collision",
"hypha", foundHypha.CanonicalName(),
"usingFile", foundHypha.MediaFilePath(),
"insteadOf", storedHypha.MediaFilePath(),
)
}
}

View File

@ -4,23 +4,24 @@ import (
"bytes"
"errors"
"fmt"
"github.com/bouncepaw/mycorrhiza/backlinks"
"github.com/bouncepaw/mycorrhiza/files"
"github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/internal/backlinks"
"github.com/bouncepaw/mycorrhiza/internal/files"
hyphae2 "github.com/bouncepaw/mycorrhiza/internal/hyphae"
"github.com/bouncepaw/mycorrhiza/internal/mimetype"
"github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/hyphae"
"github.com/bouncepaw/mycorrhiza/mimetype"
"github.com/bouncepaw/mycorrhiza/user"
"io"
"log"
"mime/multipart"
"os"
"path/filepath"
"strings"
)
func historyMessageForTextUpload(h hyphae2.Hypha, userMessage string) string {
func historyMessageForTextUpload(h hyphae.Hypha, userMessage string) string {
var verb string
switch h.(type) {
case *hyphae2.EmptyHypha:
case *hyphae.EmptyHypha:
verb = "Create"
default:
verb = "Edit"
@ -32,8 +33,8 @@ func historyMessageForTextUpload(h hyphae2.Hypha, userMessage string) string {
return fmt.Sprintf("%s %s: %s", verb, h.CanonicalName(), userMessage)
}
func writeTextToDisk(h hyphae2.ExistingHypha, data []byte, hop *history.Op) error {
if err := hyphae2.WriteToMycoFile(h, data); err != nil {
func writeTextToDisk(h hyphae.ExistingHypha, data []byte, hop *history.Op) error {
if err := hyphae.WriteToMycoFile(h, data); err != nil {
return err
}
hop.WithFiles(h.TextFilePath())
@ -42,7 +43,7 @@ func writeTextToDisk(h hyphae2.ExistingHypha, data []byte, hop *history.Op) erro
}
// UploadText edits the hypha's text part and makes a history record about that.
func UploadText(h hyphae2.Hypha, data []byte, userMessage string, u *user.User) error {
func UploadText(h hyphae.Hypha, data []byte, userMessage string, u *user.User) error {
hop := history.
Operation(history.TypeEditText).
WithMsg(historyMessageForTextUpload(h, userMessage)).
@ -56,13 +57,13 @@ func UploadText(h hyphae2.Hypha, data []byte, userMessage string, u *user.User)
}
// Hypha name exploit check
if !hyphae2.IsValidName(h.CanonicalName()) {
if !hyphae.IsValidName(h.CanonicalName()) {
// We check for the name only. I suppose the filepath would be valid as well.
hop.Abort()
return errors.New("invalid hypha name")
}
oldText, err := hyphae2.FetchMycomarkupFile(h)
oldText, err := hyphae.FetchMycomarkupFile(h)
if err != nil {
hop.Abort()
return err
@ -77,8 +78,10 @@ func UploadText(h hyphae2.Hypha, data []byte, userMessage string, u *user.User)
// At this point, we have a savable user-generated Mycomarkup document. Gotta save it.
switch h := h.(type) {
case *hyphae2.EmptyHypha:
H := hyphae2.ExtendEmptyToTextual(h, filepath.Join(files.HyphaeDir(), h.CanonicalName()+".myco"))
case *hyphae.EmptyHypha:
parts := []string{files.HyphaeDir()}
parts = append(parts, strings.Split(h.CanonicalName()+".myco", "\\")...)
H := hyphae.ExtendEmptyToTextual(h, filepath.Join(parts...))
err := writeTextToDisk(H, data, hop)
if err != nil {
@ -86,9 +89,9 @@ func UploadText(h hyphae2.Hypha, data []byte, userMessage string, u *user.User)
return err
}
hyphae2.Insert(H)
hyphae.Insert(H)
backlinks.UpdateBacklinksAfterEdit(H, "")
case *hyphae2.MediaHypha:
case *hyphae.MediaHypha:
// TODO: that []byte(...) part should be removed
if bytes.Equal(data, []byte(oldText)) {
// No changes! Just like cancel button
@ -103,8 +106,8 @@ func UploadText(h hyphae2.Hypha, data []byte, userMessage string, u *user.User)
}
backlinks.UpdateBacklinksAfterEdit(h, oldText)
case *hyphae2.TextualHypha:
oldText, err := hyphae2.FetchMycomarkupFile(h)
case *hyphae.TextualHypha:
oldText, err := hyphae.FetchMycomarkupFile(h)
if err != nil {
hop.Abort()
return err
@ -130,16 +133,17 @@ func UploadText(h hyphae2.Hypha, data []byte, userMessage string, u *user.User)
return nil
}
func historyMessageForMediaUpload(h hyphae2.Hypha, mime string) string {
func historyMessageForMediaUpload(h hyphae.Hypha, mime string) string {
return fmt.Sprintf("Upload media for %s with type %s", h.CanonicalName(), mime)
}
// writeMediaToDisk saves the given data with the given mime type for the given hypha to the disk and returns the path to the saved file and an error, if any.
func writeMediaToDisk(h hyphae2.Hypha, mime string, data []byte) (string, error) {
func writeMediaToDisk(h hyphae.Hypha, mime string, data []byte) (string, error) {
var (
ext = mimetype.ToExtension(mime)
// That's where the file will go
uploadedFilePath = filepath.Join(files.HyphaeDir(), h.CanonicalName()+ext)
uploadedFilePath = filepath.Join(append([]string{files.HyphaeDir()}, strings.Split(h.CanonicalName()+ext, "\\")...)...)
)
if err := os.MkdirAll(filepath.Dir(uploadedFilePath), 0777); err != nil {
@ -153,7 +157,7 @@ func writeMediaToDisk(h hyphae2.Hypha, mime string, data []byte) (string, error)
}
// UploadBinary edits the hypha's media part and makes a history record about that.
func UploadBinary(h hyphae2.Hypha, mime string, file multipart.File, u *user.User) error {
func UploadBinary(h hyphae.Hypha, mime string, file multipart.File, u *user.User) error {
// Privilege check
if !u.CanProceed("upload-binary") {
@ -162,7 +166,7 @@ func UploadBinary(h hyphae2.Hypha, mime string, file multipart.File, u *user.Use
}
// Hypha name exploit check
if !hyphae2.IsValidName(h.CanonicalName()) {
if !hyphae.IsValidName(h.CanonicalName()) {
// We check for the name only. I suppose the filepath would be valid as well.
return errors.New("invalid hypha name")
}
@ -185,12 +189,12 @@ func UploadBinary(h hyphae2.Hypha, mime string, file multipart.File, u *user.Use
}
switch h := h.(type) {
case *hyphae2.EmptyHypha:
H := hyphae2.ExtendEmptyToMedia(h, uploadedFilePath)
hyphae2.Insert(H)
case *hyphae2.TextualHypha:
hyphae2.Insert(hyphae2.ExtendTextualToMedia(h, uploadedFilePath))
case *hyphae2.MediaHypha: // If this is not the first media the hypha gets
case *hyphae.EmptyHypha:
H := hyphae.ExtendEmptyToMedia(h, uploadedFilePath)
hyphae.Insert(H)
case *hyphae.TextualHypha:
hyphae.Insert(hyphae.ExtendTextualToMedia(h, uploadedFilePath))
case *hyphae.MediaHypha: // If this is not the first media the hypha gets
prevFilePath := h.MediaFilePath()
if prevFilePath != uploadedFilePath {
if err := history.Rename(prevFilePath, uploadedFilePath); err != nil {

View File

@ -106,13 +106,11 @@ textarea {font-size:16px; font-family: inherit; line-height: 150%; }
main h1:not(.navi-title) {font-size:1.7rem;}
blockquote { margin: 0; padding-left: .75rem; }
.wikilink_external::before { display: inline-block; width: 18px; height: 16px; vertical-align: sub; }
/* .wikilink_external { padding-left: 16px; } */
.wikilink_gopher::before { content: url("/static/icon/gopher-proto.svg"); }
.wikilink_http::before, .wikilink_https::before { content: url("/static/icon/http-proto.svg"); }
/* .wikilink_https { background: transparent url("/static/icon/http-proto.svg") center left no-repeat; } */
.wikilink_gemini::before { content: url("/static/icon/gemini-proto.svg"); }
.wikilink_mailto::before { content: url("/static/icon/mailto-proto.svg"); }
.wikilink_external { padding-left: 1.2rem; }
.wikilink_gopher { background: transparent left no-repeat url("/static/icon/gopher-proto.svg"); }
.wikilink_http, .wikilink_https { background: transparent left no-repeat url("/static/icon/http-proto.svg"); }
.wikilink_gemini { background: transparent left no-repeat url("/static/icon/gemini-proto.svg"); }
.wikilink_mailto { background: transparent left no-repeat url("/static/icon/mailto-proto.svg"); }
article { overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; line-height: 150%; }
main h1 { margin: .5rem 0 0 0; }
@ -124,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; }

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="#999" d="M15.502 20A6.523 6.523 0 0 1 12 23.502 6.523 6.523 0 0 1 8.498 20h2.26c.326.489.747.912 1.242 1.243.495-.33.916-.754 1.243-1.243h2.259zM18 14.805l2 2.268V19H4v-1.927l2-2.268V9c0-3.483 2.504-6.447 6-7.545C15.496 2.553 18 5.517 18 9v5.805zM17.27 17L16 15.56V9c0-2.318-1.57-4.43-4-5.42C9.57 4.57 8 6.681 8 9v6.56L6.73 17h10.54zM12 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="1rem" height="1rem"><path fill="#999" d="M15.502 20A6.523 6.523 0 0 1 12 23.502 6.523 6.523 0 0 1 8.498 20h2.26c.326.489.747.912 1.242 1.243.495-.33.916-.754 1.243-1.243h2.259zM18 14.805l2 2.268V19H4v-1.927l2-2.268V9c0-3.483 2.504-6.447 6-7.545C15.496 2.553 18 5.517 18 9v5.805zM17.27 17L16 15.56V9c0-2.318-1.57-4.43-4-5.42C9.57 4.57 8 6.681 8 9v6.56L6.73 17h10.54zM12 11a2 2 0 1 1 0-4 2 2 0 0 1 0 4z"/></svg>

Before

Width:  |  Height:  |  Size: 473 B

After

Width:  |  Height:  |  Size: 477 B

View File

@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1rem" height="1rem">
<path fill="#999" d="M447.238,204.944v-70.459c0-8.836-7.164-16-16-16c-34.051,0-64.414,21.118-75.079,55.286
C226.094,41.594,0,133.882,0,319.435c0,0.071,0.01,0.14,0.011,0.21c0.116,44.591,36.423,80.833,81.04,80.833h171.203
c8.836,0,16-7.164,16-16c0-8.836-7.164-16-16-16H81.051c-21.441,0-39.7-13.836-46.351-33.044H496c8.836,0,16-7.164,16-16

Before

Width:  |  Height:  |  Size: 951 B

After

Width:  |  Height:  |  Size: 955 B

View File

@ -1 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="#999" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.008 8.008 0 0 0 5.648 6.667zM10.03 13c.151 2.439.848 4.73 1.97 6.752A15.905 15.905 0 0 0 13.97 13h-3.94zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.008 8.008 0 0 0 19.938 13zM4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333 8.008 8.008 0 0 0 4.062 11zm5.969 0h3.938A15.905 15.905 0 0 0 12 4.248 15.905 15.905 0 0 0 10.03 11zm4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.008 8.008 0 0 0-5.648-6.667z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="1rem" height="1rem">
<path fill="#999" d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-2.29-2.333A17.9 17.9 0 0 1 8.027 13H4.062a8.008 8.008 0 0 0 5.648 6.667zM10.03 13c.151 2.439.848 4.73 1.97 6.752A15.905 15.905 0 0 0 13.97 13h-3.94zm9.908 0h-3.965a17.9 17.9 0 0 1-1.683 6.667A8.008 8.008 0 0 0 19.938 13zM4.062 11h3.965A17.9 17.9 0 0 1 9.71 4.333 8.008 8.008 0 0 0 4.062 11zm5.969 0h3.938A15.905 15.905 0 0 0 12 4.248 15.905 15.905 0 0 0 10.03 11zm4.259-6.667A17.9 17.9 0 0 1 15.973 11h3.965a8.008 8.008 0 0 0-5.648-6.667z"/></svg>

Before

Width:  |  Height:  |  Size: 627 B

After

Width:  |  Height:  |  Size: 636 B

View File

@ -1 +1,2 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16"><path fill="#999" d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm17 4.238l-7.928 7.1L4 7.216V19h16V7.238zM4.511 5l7.55 6.662L19.502 5H4.511z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="1rem" height="1rem">
<path fill="#999" d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm17 4.238l-7.928 7.1L4 7.216V19h16V7.238zM4.511 5l7.55 6.662L19.502 5H4.511z"/></svg>

Before

Width:  |  Height:  |  Size: 261 B

After

Width:  |  Height:  |  Size: 270 B

View File

@ -329,7 +329,8 @@ if (document.body.dataset.rrhAddr.startsWith('/edit')) {
new Shortcut(isMac ? 'Meta+Shift+x' : 'Ctrl+X', wrapStrikethrough, 'Strikethrough', { force: true }),
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+;', insertDate, 'Insert date UTC', { force: true }),
new Shortcut(isMac ? 'Meta+;' : 'Ctrl+;', insertDateUTC, 'Insert date UTC', { force: true }),
new Shortcut(isMac ? "Meta+'" : "Ctrl+'", insertTimeLocal, 'Insert local time', { force: true })
]))
}
}

View File

@ -87,16 +87,30 @@ const insertHorizontalBar = textInserter('\n----\n'),
insertBulletedList = textInserter('\n* '),
insertNumberedList = textInserter('\n*. ')
function insertDate() {
function insertDateUTC() {
let date = new Date().toISOString().split('T')[0]
textInserter(date)()
}
function insertTimeUTC() {
let time = new Date().toISOString().substring(11, 19) + " UTC"
let time = new Date().toISOString().substring(11, 16) + " UTC "
textInserter(time)()
}
function len2(n) {
return n < 10 ? `0${n}` : `${n}`
}
function insertDateLocal() {
let d = new Date()
textInserter(`${d.getFullYear()}-${len2(d.getMonth() + 1)}-${len2(d.getDate())}`)()
}
function insertTimeLocal() {
let d = new Date()
textInserter(`${len2(d.getHours())}:${len2(d.getMinutes())} `)()
}
function insertUserlink() {
const userlink = document.querySelector('.auth-links__user-link')
const userHypha = userlink.getAttribute('href').substring(7) // no /hypha/
@ -124,8 +138,10 @@ const buttonsHandlers = {
codeblock: insertCodeblock,
bulletedlist: insertBulletedList,
numberedlist: insertNumberedList,
date: insertDate,
time: insertTimeUTC,
'date-utc': insertDateUTC,
'time-utc': insertTimeUTC,
'date-local': insertDateLocal,
'time-local': insertTimeLocal,
'user-link': insertUserlink,
}
for (const key of Object.keys(buttonsHandlers)) {

View File

@ -35,7 +35,7 @@ wrapper.appendChild(hamburgerSection);
return Array
.from(new DOMParser()
.parseFromString(html, 'text/html')
.querySelectorAll('.mv-category .p-name'))
.querySelectorAll('.mv-tags .p-name'))
.map(a => a.innerText);
});

View File

@ -32,8 +32,10 @@
<aside class="edit-toolbar action-toolbar layout-card">
<h2 class="edit-toolbar__title layout-card__title">{{block "actions" .}}Actions{{end}}</h2>
<section class="edit-toolbar__buttons">
<button class="btn edit-toolbar__btn edit-toolbar__date">{{block "current date" .}}Insert current date{{end}}</button>
<button class="btn edit-toolbar__btn edit-toolbar__time">{{block "current time" .}}Insert current time{{end}}</button>
<button class="btn edit-toolbar__btn edit-toolbar__date-local">{{block "current date local" .}}Current local date{{end}}</button>
<button class="btn edit-toolbar__btn edit-toolbar__time-local">{{block "current time local" .}}Current local time{{end}}</button>
<button class="btn edit-toolbar__btn edit-toolbar__date-utc">{{block "current date utc" .}}Current date UTC{{end}}</button>
<button class="btn edit-toolbar__btn edit-toolbar__time-utc">{{block "current time utc" .}}Current time UTC{{end}}</button>
{{if .Meta.U.Group | ne "anon"}}
<button class="btn edit-toolbar__btn edit-toolbar__user-link">{{block "selflink" .}}Link yourself{{end}}</button>
{{end}}