Merge branch 'refs/heads/master' into newtmpl
# Conflicts: # hypview/hypview.go # internal/shroom/upload.go
28
.github/workflows/release.yaml
vendored
@ -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
|
||||
16
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).
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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]].
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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 |
@ -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 |
@ -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 |
@ -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 |
@ -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 })
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
@ -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}}
|
||||
|
||||