v0.26.1 dfd6f0b

store.js RawHistoryFolder

1// store.js: window.ermiteStore, a small client for what the pages remember in
2// the browser (localStorage): the preferences. Loaded before ermite.js, which
3// builds on it (ermiteKeys, ermiteLayout).
4//
5// It never throws. Without storage (disabled, private mode, sandboxed frame)
6// or when the browser refuses a write (quota), the value is held in memory:
7// the choice lasts for this page, and the page works as if it were saved.
8// Values are strings, as in localStorage; null says "nothing stored".
9//
10//   get(key, fallback)   the value, or fallback (null) when there is none
11//   set(key, value)      stores it; null or undefined removes the key
12//   remove(key)          forgets the key
13//   watch(key, fn)       fn(value or null) when another tab changes the key
14//                        (the "storage" event); answers a function that stops
15//   pref(key, opts)      a preference among a closed list:
16//                        pref(key, { values, fallback }) answers
17//                        { get, set, watch }, where get is always one of
18//                        values (a stored value that is not falls back) and
19//                        set ignores what is not
20//
21// Self-contained: jstest/store.test.mjs evaluates this file alone.
22window.ermiteStore = (() => {
23  // What storage could not keep, by key. A key mapped to null is removed for
24  // this page although storage would not say so.
25  const held = new Map();
26
27  const area = () => {
28    try { return localStorage; } catch { return null; }
29  };
30
31  function get(key, fallback = null) {
32    if (held.has(key)) return held.get(key) ?? fallback;
33    try { return area().getItem(key) ?? fallback; } catch { return fallback; }
34  }
35
36  function set(key, value) {
37    if (value === null || value === undefined) return remove(key);
38    value = String(value);
39    try {
40      area().setItem(key, value);
41      held.delete(key);
42    } catch { held.set(key, value); }
43  }
44
45  function remove(key) {
46    try {
47      area().removeItem(key);
48      held.delete(key);
49    } catch { held.set(key, null); }
50  }
51
52  function watch(key, fn) {
53    const on = (e) => {
54      // key is null when another tab clears the whole storage.
55      if (e.key !== null && e.key !== key) return;
56      if (e.storageArea && e.storageArea !== area()) return;
57      held.delete(key); // the other tab's word is newer than ours
58      fn(e.key === null ? null : (e.newValue ?? null));
59    };
60    addEventListener('storage', on);
61    return () => removeEventListener('storage', on);
62  }
63
64  function pref(key, { values, fallback }) {
65    const valid = (v) => (values.includes(v) ? v : fallback);
66    return {
67      get: () => valid(get(key)),
68      set(v) {
69        if (values.includes(v)) set(key, v);
70      },
71      watch: (fn) => watch(key, (v) => fn(valid(v))),
72    };
73  }
74
75  return { get, set, remove, watch, pref };
76})();

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 45

↑↓ moveenter openesc close