~bouncepaw/betula

betula: Add bookmarklet v3 APPLIED

~danilax86: 1
 Add bookmarklet

 7 files changed, 146 insertions(+), 4 deletions(-)
#1026423 .build.yml success
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~bouncepaw/betula/patches/42827/mbox | git am -3
Learn more about email & git

[PATCH betula v3] Add bookmarklet Export this patch

From: Danila Gorelko <danila@danilax86.space>

Thanks to Umar, https://handlerug.me/betula-save-bookmarklet
Fixes: https://todo.sr.ht/~bouncepaw/betula/59
---
 web/bookmarklet.gohtml | 20 ++++++++++
 web/bookmarklet.js     | 91 ++++++++++++++++++++++++++++++++++++++++++
 web/handlers.go        | 19 ++++++++-
 web/settings.gohtml    |  2 +-
 web/skeleton.gohtml    |  5 ++-
 web/style.css          | 12 ++++++
 web/templates.go       |  1 +
 7 files changed, 146 insertions(+), 4 deletions(-)
 create mode 100644 web/bookmarklet.gohtml
 create mode 100644 web/bookmarklet.js

diff --git a/web/bookmarklet.gohtml b/web/bookmarklet.gohtml
new file mode 100644
index 0000000..5b24386
--- /dev/null
+++ b/web/bookmarklet.gohtml
@@ -0,0 +1,20 @@
{{define "title"}}Bookmarklet{{end}}
{{define "body"}}
    <main>
        <article>
            <h2>Bookmarklet</h2>
            <p>
                This special link allows you to add a link to your betula directly by using a bookmark in your web
                browser.
            </p>
            <div class="bookmarklet">
                <a href="javascript:{{.Script}}">
                    Add to Betula
                </a>
            </div>
            <p>
                Drag and drop this link to your bookmarks.
            </p>
        </article>
    </main>
{{end}}
diff --git a/web/bookmarklet.js b/web/bookmarklet.js
new file mode 100644
index 0000000..08f4b84
--- /dev/null
+++ b/web/bookmarklet.js
@@ -0,0 +1,91 @@
// Save link bookmarklet for Betula
// 2023 Umar Getagazov <umar@handlerug.me>
// Public domain, but attribution appreciated.
// https://handlerug.me/betula-save-bookmarklet

(($) => {
    function getSelectionInMycomarkup() {
        function convert(node, parentNodeName = '') {
            if (node instanceof Text) {
                if (node.textContent.trim() === '') {
                    return '';
                }

                return node.textContent
                    .replace(/\\/g,   '\\\\')
                    .replace(/\*\*/g, '\\**')
                    .replace(/\/\//g, '\\//')
                    .replace(/\+\+/g, '\\++');
            }

            let nodeName = node.nodeName.toLowerCase();

            let result = '';
            for (const child of node.childNodes) {
                result += convert(child, nodeName);
            }

            if (nodeName === 'p') {
                return `\n\n${result.trim()}\n\n`;
            } else if (nodeName === 'br') {
                return '\n';
            } else if (nodeName === 'a') {
                return `[[${decodeURI(node.href)} | ${result}]]`;
            } else if (nodeName === 'b' || nodeName === 'strong') {
                return `**${result}**`;
            } else if (nodeName === 'i' || nodeName === 'em') {
                return `//${result}//`;
            } else if (nodeName === 'h1') {
                return `\n\n${result}\n\n`;
            } else if (nodeName === 'h2') {
                return `= ${result}\n\n`;
            } else if (nodeName === 'h3') {
                return `== ${result}\n\n`;
            } else if (nodeName === 'h4') {
                return `=== ${result}\n\n`;
            } else if (nodeName === 'h5') {
                return `==== ${result}\n\n`;
            } else if (nodeName === 'li') {
                if (node.children.length === 1) {
                    let link = node.children[0];
                    if (link.nodeName.toLowerCase() === 'a') {
                        if (link.href === link.innerText || decodeURI(link.href) === link.innerText) {
                            return `=> ${decodeURI(link.href)}\n`;
                        } else {
                            return `=> ${decodeURI(link.href)} | ${link.innerText}\n`;
                        }
                    }
                }
                return parentNodeName === 'ol'
                    ? `*. ${result}\n`
                    : `* ${result}\n`;
            } else {
                return result;
            }
        }

        let selection = window.getSelection();
        if (selection.rangeCount === 0) {
            return '';
        }
        let range = selection.getRangeAt(0);
        let contents = range.cloneContents();
        return convert(contents).replace(/\n\n+/g, '\n\n');
    }

    let u = '%s/save-link?' + new URLSearchParams({
        url: ($('link[rel=canonical]') || location).href,
        title: $('meta[property="og:title"]')?.content || document.title,
        description: (
            getSelectionInMycomarkup() ||
            $('meta[property="og:description"]')?.content ||
            $('meta[name=description]')?.content
        )?.trim().replace(/^/gm, '> ') || ''
    });

    try {
        window.open(u, '_blank', 'location=yes,width=600,height=800,scrollbars=yes,status=yes,noopener,noreferrer');
    } catch {
        location.href = u;
    }
})(document.querySelector.bind(document));
diff --git a/web/handlers.go b/web/handlers.go
index 50a3a9c..bf94f00 100644
--- a/web/handlers.go
+++ b/web/handlers.go
@@ -26,8 +26,10 @@ import (

var (
	//go:embed *.gohtml *.css *.js
	fs  embed.FS
	mux = http.NewServeMux()
	fs embed.FS
	//go:embed bookmarklet.js
	bookmarkletScript string
	mux               = http.NewServeMux()
)

// Wrap handlers that only make sense for the admin with this thingy in init().
@@ -66,10 +68,23 @@ func init() {
	mux.HandleFunc("/login", handlerLogin)
	mux.HandleFunc("/logout", handlerLogout)
	mux.HandleFunc("/settings", adminOnly(handlerSettings))
	mux.HandleFunc("/bookmarklet", adminOnly(handlerBookmarklet))
	mux.HandleFunc("/static/style.css", handlerStyle)
	mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(fs))))
}

type dataBookmarklet struct {
	*dataCommon
	Script string
}

func handlerBookmarklet(w http.ResponseWriter, rq *http.Request) {
	templateExec(w, templateBookmarklet, dataBookmarklet{
		dataCommon: emptyCommon(),
		Script:     fmt.Sprintf(bookmarkletScript, settings.SiteURL()),
	}, rq)
}

func handlerHelp(w http.ResponseWriter, rq *http.Request) {
	http.Redirect(w, rq, "/help/en/index", http.StatusSeeOther)
}
diff --git a/web/settings.gohtml b/web/settings.gohtml
index c692111..27b6964 100644
--- a/web/settings.gohtml
+++ b/web/settings.gohtml
@@ -18,7 +18,7 @@
					<p class="input-caption">
						The address at which your Betula is hosted.
						Type out the protocol (http or https).
						This information is used for RSS feed generation.</p>
						This information is used for RSS feed and bookmarklet generation.</p>
				</div>

				<div>
diff --git a/web/skeleton.gohtml b/web/skeleton.gohtml
index 6576546..04c9cd0 100644
--- a/web/skeleton.gohtml
+++ b/web/skeleton.gohtml
@@ -31,8 +31,11 @@
		<li>
			<a href="/digest-rss">Site RSS</a>
		</li>
		{{if .Authorized}}
			<li><a href="/bookmarklet">Bookmarklet</a></li>
		{{end}}
	</ul>
</nav>
{{template "body" .}}
</body>
</html>
\ No newline at end of file
</html>
diff --git a/web/style.css b/web/style.css
index 47bd7ae..eafeb9b 100644
--- a/web/style.css
+++ b/web/style.css
@@ -391,6 +391,18 @@ a.btn_destructive:visited,
    color: white;
}

.bookmarklet {
    border: 1px dashed #999;
    border-radius: 5px;
    padding: 15px;
    text-align: center;
}

.bookmarklet a {
    text-decoration: none;
    font-weight: bold;
}

@media (prefers-color-scheme: dark) {
    .btn {
        border: #444 solid 1px;
diff --git a/web/templates.go b/web/templates.go
index c2633ed..5b00c27 100644
--- a/web/templates.go
+++ b/web/templates.go
@@ -68,6 +68,7 @@ var templateDay = templateFrom(funcMapForPosts, "post-fragment", "day")
var templateEditTag = templateFrom(funcMapForForm, "edit-tag")
var templateHelp = templateFrom(nil, "help")
var templateAbout = templateFrom(funcMapForTime, "about")
var templateBookmarklet = templateFrom(nil, "bookmarklet")

var funcMapForPosts = template.FuncMap{
	"randomGlobe": func() string {
-- 
2.38.5
betula/patches/.build.yml: SUCCESS in 2m16s

[Add bookmarklet][0] v3 from [~danilax86][1]

[0]: https://lists.sr.ht/~bouncepaw/betula/patches/42827
[1]: mailto:danila@danilax86.space

✓ #1026423 SUCCESS betula/patches/.build.yml https://builds.sr.ht/~bouncepaw/job/1026423
LGTM. Applied. Thanks!