Recently Written · git

sbm-extension

sbm bookmarks for Firefox and Chrome: fuzzy search, add the page, sync with bm and the sbm app

git clone https://github.com/equwal/sbm-extension

Log | Files | Refs


commit 4ed7bb20d16ada2ef368bb2b8cd26d2244cd9b18
equwal <13551856+equwal@users.noreply.github.com>
2026-09-26 12:55:37 -0700

Add a More apps section with links to the other apps

The options page shows the list in a "More apps" section below the sync
settings. The popup footer has one "More apps" link that opens this
section in a new tab. The README has a More projects list. The manifest
does not change. A new test checks the addresses, the link attributes
and the permissions.

 README.md              | 24 ++++++++++++++++++++++++
 options.html           | 21 +++++++++++++++++++++
 popup.html             |  2 +-
 style.css              |  1 +
 test/more-apps.test.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 97 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 4ad4d18..be63764 100644
--- a/README.md
+++ b/README.md
@@ -65,6 +65,30 @@ The two zips have the same files. Only the manifest.json of the Firefox zip
 has no `background.service_worker`: Firefox ignores it and warns about it,
 and Chrome needs it.
 
+## More projects
+
+- [SubRead](https://subread.space/): read along with an audiobook, in the browser.
+  Also [for Android](https://github.com/equwal/subread-android/releases/latest),
+  [for YouTube](https://github.com/equwal/subread-extension/releases/latest)
+  and [for KOReader](https://github.com/equwal/subread.koplugin).
+- [SubRead Overlay](https://github.com/equwal/subread-overlay/releases/latest): subtitle lines over any Android media player.
+- [SubRead Dictionary](https://github.com/equwal/subread-dictionary/releases/latest): a pop-up dictionary for Android that reads Yomitan dictionaries.
+- [SubRead Anki](https://github.com/equwal/subread-anki): one tap makes an Anki card from any Android app.
+- [Subrep](https://github.com/equwal/subrep-android/releases/latest): live captions of the sound of your phone.
+- [Book Simulator](https://booksimulator.com/): a reading room for Aozora Bunko and Project Gutenberg books.
+- [honjimaku.com](https://honjimaku.com/): subtitles for Japanese audiobooks.
+- [sbm Sync](https://sbmsync.com/): your bookmarks, the same on every device,
+  with [sbm](https://github.com/equwal/sbm) for dmenu,
+  [sbm for Android](https://github.com/equwal/sbm-android/releases/latest)
+  and the [sbm add-on](https://github.com/equwal/sbm-extension/releases/latest) for Firefox and Chrome.
+- [Rebind](https://github.com/equwal/rebind/releases): remap the hardware buttons of e-ink readers and Android,
+  with [Ink Recents](https://github.com/equwal/ink-recents/releases/latest),
+  [Ink Dim](https://github.com/equwal/ink-dim/releases/latest)
+  and [Ink Update](https://github.com/equwal/ink-update/releases/latest).
+- [dickt.store](https://dickt.store/): language-learning tools, flashcards and web toys.
+- [hentaibun.online](https://hentaibun.online/): learn kanbun and kobun.
+- [Recently Written](https://recentlywritten.com/): the blog, and a list of [all projects](https://recentlywritten.com/projects.html).
+
 ## License
 
 AGPL-3.0. If sbm is useful to you, you can support it on
diff --git a/options.html b/options.html
index 64ce1cb..8f10b4f 100644
--- a/options.html
+++ b/options.html
@@ -39,6 +39,27 @@ other browsers.</p>
 
 <p id="message" role="status"></p>
 
+<section id="more-apps">
+  <h2>More apps</h2>
+  <ul>
+    <li><a href="https://subread.space/" target="_blank" rel="noopener">SubRead</a><br>Read along with an audiobook.</li>
+    <li><a href="https://booksimulator.com/" target="_blank" rel="noopener">Book Simulator</a><br>A reading room for Aozora Bunko and Project Gutenberg books.</li>
+    <li><a href="https://honjimaku.com/" target="_blank" rel="noopener">honjimaku.com</a><br>Subtitles for Japanese audiobooks.</li>
+    <li><a href="https://sbmsync.com/" target="_blank" rel="noopener">sbm Sync</a><br>Your bookmarks, the same on every device.</li>
+    <li><a href="https://github.com/equwal/subread-android/releases/latest" target="_blank" rel="noopener">SubRead for Android</a><br>Times an audiobook against its ebook on the device.</li>
+    <li><a href="https://github.com/equwal/subread-overlay/releases/latest" target="_blank" rel="noopener">SubRead Overlay</a><br>Subtitle lines over any Android media player.</li>
+    <li><a href="https://github.com/equwal/subread-dictionary/releases/latest" target="_blank" rel="noopener">SubRead Dictionary</a><br>Pop-up dictionary that reads Yomitan dictionaries.</li>
+    <li><a href="https://github.com/equwal/subread-anki" target="_blank" rel="noopener">SubRead Anki</a><br>One tap makes an Anki card from any app.</li>
+    <li><a href="https://github.com/equwal/subrep-android/releases/latest" target="_blank" rel="noopener">Subrep</a><br>Live captions of the sound of your phone.</li>
+    <li><a href="https://github.com/equwal/sbm-android/releases/latest" target="_blank" rel="noopener">sbm for Android</a><br>Fuzzy search for your bookmarks.</li>
+    <li><a href="https://github.com/equwal/rebind/releases" target="_blank" rel="noopener">Rebind</a><br>Remap the hardware buttons of e-ink readers and Android.</li>
+    <li><a href="https://github.com/equwal/ink-recents/releases/latest" target="_blank" rel="noopener">Ink Recents</a><br>A recent-apps switcher for e-ink.</li>
+    <li><a href="https://github.com/equwal/ink-dim/releases/latest" target="_blank" rel="noopener">Ink Dim</a><br>Frontlight below the lowest system level.</li>
+    <li><a href="https://github.com/equwal/ink-update/releases/latest" target="_blank" rel="noopener">Ink Update</a><br>Tells you when Rebind and its extensions update.</li>
+    <li><a href="https://recentlywritten.com/projects.html" target="_blank" rel="noopener">All projects</a><br>Everything, with source code.</li>
+  </ul>
+</section>
+
 <footer>
   <a href="https://github.com/equwal/sbm-extension" target="_blank" rel="noopener">Source code</a> ·
   <a href="https://sbmsync.com/privacy" target="_blank" rel="noopener">Privacy</a> ·
diff --git a/popup.html b/popup.html
index 1c570a7..3975674 100644
--- a/popup.html
+++ b/popup.html
@@ -21,7 +21,7 @@
 <footer>
   <button id="add-page" type="button">Add this page</button>
   <span id="status" class="note"></span>
-  <nav><a href="#" id="options">Options</a> · <a href="https://ko-fi.com/truex" target="_blank" rel="noopener">Support sbm on Ko-fi</a></nav>
+  <nav><a href="#" id="options">Options</a> · <a href="options.html#more-apps" target="_blank" rel="noopener">More apps</a> · <a href="https://ko-fi.com/truex" target="_blank" rel="noopener">Support sbm on Ko-fi</a></nav>
 </footer>
 <script src="lib/tsv.js"></script>
 <script src="lib/fuzzy.js"></script>
diff --git a/style.css b/style.css
index 50adfe8..7b2ecb7 100644
--- a/style.css
+++ b/style.css
@@ -20,4 +20,5 @@ label { display: block; }
 .options h1 { color: var(--accent); }
 .options section { margin: 1.5em 0; }
 .options #billing button { margin: 0 6px 6px 0; }
+.options #more-apps li { margin-bottom: 6px; color: var(--muted); }
 .options footer { margin-top: 3em; font-size: 13px; color: var(--muted); }
diff --git a/test/more-apps.test.js b/test/more-apps.test.js
new file mode 100644
index 0000000..4168883
--- /dev/null
+++ b/test/more-apps.test.js
@@ -0,0 +1,50 @@
+"use strict";
+const test = require("node:test");
+const assert = require("node:assert/strict");
+const fs = require("node:fs");
+const path = require("node:path");
+
+const read = (file) => fs.readFileSync(path.join(__dirname, "..", file), "utf8");
+
+// The links in the "More apps" section of the options page: the name, the
+// address and the attributes of each.
+function moreApps() {
+  const section = read("options.html").match(/<section id="more-apps">([\s\S]*?)<\/section>/);
+  assert.ok(section, "options.html has no More apps section");
+  return [...section[1].matchAll(/<a ([^>]*)>([^<]*)<\/a>/g)].map(([, attrs, name]) => ({
+    name,
+    attrs,
+    href: (attrs.match(/href="([^"]*)"/) || [])[1],
+  }));
+}
+
+test("More apps links to the other apps with https, each in a new tab", () => {
+  const apps = moreApps();
+  assert.ok(apps.length > 1);
+  for (const app of apps) {
+    assert.match(app.href, /^https:\/\/[^?#]+$/, app.name); // no tracking parameters
+    assert.match(app.attrs, /target="_blank"/, app.name);
+    assert.match(app.attrs, /rel="noopener"/, app.name);
+  }
+  assert.equal(new Set(apps.map((app) => app.href)).size, apps.length);
+  assert.equal(apps.at(-1).href, "https://recentlywritten.com/projects.html");
+});
+
+test("More apps does not list the add-on itself", () => {
+  for (const app of moreApps()) assert.doesNotMatch(app.href, /sbm-extension/, app.name);
+});
+
+test("the popup has one More apps link, to the section of the options page", () => {
+  const links = read("popup.html").match(/<a [^>]*>More apps<\/a>/g) || [];
+  assert.equal(links.length, 1);
+  assert.match(links[0], /href="options\.html#more-apps"/);
+  assert.match(links[0], /target="_blank"/);
+  assert.match(links[0], /rel="noopener"/);
+});
+
+test("the add-on asks for no new permission and puts nothing into web pages", () => {
+  const manifest = JSON.parse(read("manifest.json"));
+  assert.deepEqual(manifest.permissions, ["storage", "activeTab", "alarms"]);
+  assert.deepEqual(manifest.optional_host_permissions, ["https://*/*", "http://*/*"]);
+  assert.equal(manifest.content_scripts, undefined);
+});