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


lib/tsv.js (3107 bytes)

1 // The bookmark file of sbm, as bm reads and writes it: one bookmark per line,
2 //
3 //     URL<tab>description<tab>tag tag tag
4 //
5 // Blank lines and lines that start with # are not bookmarks. This file has no
6 // browser code, so the tests run in node.
7 "use strict";
8 
9 const Tsv = (() => {
10   const scheme = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
11 
12   function parseLine(line) {
13     const fields = line.split("\t");
14     if (fields.length === 1) {
15       // A line of the old format: "URL description | tags". bm-migrate
16       // converts it. Until then the first word is the URL.
17       const words = line.trim().split(/\s+/);
18       return { url: words[0], desc: words.slice(1).join(" "), tags: [] };
19     }
20     const url = fields[0].trim();
21     if (url === "") return null;
22     const tags = (fields[2] || "").split(" ").filter((t) => t !== "");
23     return { url, desc: fields[1], tags };
24   }
25 
26   return {
27     /** The bookmarks in the text of a file. */
28     parse(text) {
29       const out = [];
30       for (const raw of text.split("\n")) {
31         const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
32         if (line.trim() === "" || line.startsWith("#")) continue;
33         const b = parseLine(line);
34         if (b) out.push(b);
35       }
36       return out;
37     },
38 
39     /** Text for one field: tabs and line breaks become spaces. */
40     clean(text) {
41       return text.replace(/[\t\r\n]/g, " ");
42     },
43 
44     /** The line of a bookmark, without the newline. */
45     format(b) {
46       const tags = b.tags.flatMap((t) => t.split(/\s+/)).filter((t) => t !== "");
47       return b.url.replace(/\s/g, "") + "\t" + Tsv.clean(b.desc) + "\t" + tags.join(" ");
48     },
49 
50     /**
51      * The same page for bm: two URLs match when they differ only in the
52      * scheme, a leading www., trailing slashes or the case of the host.
53      */
54     norm(url) {
55       const u = url.replace(scheme, "");
56       const slash = u.indexOf("/");
57       const host = (slash >= 0 ? u.slice(0, slash) : u).toLowerCase().replace(/^www\./, "");
58       const rest = slash >= 0 ? u.slice(slash).replace(/\/+$/, "") : "";
59       return host + rest;
60     },
61 
62     /** The bookmark for the same page as url, or undefined. */
63     find(bookmarks, url) {
64       const n = Tsv.norm(url);
65       return bookmarks.find((b) => Tsv.norm(b.url) === n);
66     },
67 
68     /** The text to add to a file that holds text, so that line is a line of its own. */
69     appendix(text, line) {
70       return text === "" || text.endsWith("\n") ? line + "\n" : "\n" + line + "\n";
71     },
72 
73     /** The host of a URL, for display. */
74     host(url) {
75       return url.replace(scheme, "").split(/[/?#]/)[0];
76     },
77 
78     /**
79      * Where text that is no bookmark goes, as in bm: one word with "://" or
80      * a dot is an address. Other text is a web search.
81      */
82     target(text) {
83       const t = text.trim();
84       if (!t.includes(" ")) {
85         if (t.includes("://")) return t;
86         if (t.includes(".")) return "https://" + t;
87       }
88       return "https://duckduckgo.com/?q=" + encodeURIComponent(t).replace(/%20/g, "+");
89     },
90   };
91 })();
92 
93 if (typeof module !== "undefined") module.exports = Tsv;