@ok-ggwp/git-gloss 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -4
- package/dist/ary/browse.js +4 -61
- package/dist/ary/picker.js +163 -0
- package/dist/ary/server.js +87 -37
- package/dist/attention.js +34 -0
- package/dist/carry.js +4 -1
- package/dist/cli.js +2 -1
- package/dist/commands/add.js +17 -1
- package/dist/commands/ary.js +3 -3
- package/dist/commands/hunks.js +8 -1
- package/dist/commands/review.js +5 -4
- package/dist/commands/reviews.js +58 -5
- package/dist/commands/show.js +56 -20
- package/dist/commands/signals.js +89 -0
- package/dist/commands/sync.js +14 -2
- package/dist/commands/why.js +3 -2
- package/dist/config.js +248 -0
- package/dist/floor.js +148 -0
- package/dist/git.js +29 -3
- package/dist/glob.js +76 -0
- package/dist/html/client.js +1032 -296
- package/dist/html/data.js +26 -1
- package/dist/html/page.js +1 -1
- package/dist/html/style.css +310 -164
- package/dist/loader.js +7 -3
- package/dist/merge.js +5 -0
- package/dist/plain.js +9 -0
- package/dist/render.js +55 -5
- package/dist/reviews.js +98 -4
- package/dist/safe-match.js +30 -0
- package/dist/schema.js +55 -4
- package/dist/selection.js +21 -0
- package/dist/signals.js +302 -0
- package/dist/validate.js +10 -1
- package/package.json +1 -1
- package/skills/gloss/SKILL.md +42 -2
- package/dist/ary/client.js +0 -401
- package/dist/ary/index.html +0 -13
- package/dist/ary/style.css +0 -115
package/dist/ary/client.js
DELETED
|
@@ -1,401 +0,0 @@
|
|
|
1
|
-
// The ary shell: pick a branch, a base to compare it with, and a commit (or the
|
|
2
|
-
// whole branch), and the chosen view loads in the frame. Text only goes
|
|
3
|
-
// through textContent; nothing from the repo is parsed as HTML.
|
|
4
|
-
(function () {
|
|
5
|
-
'use strict'
|
|
6
|
-
var app = document.getElementById('app')
|
|
7
|
-
var PAGE = 100
|
|
8
|
-
|
|
9
|
-
// What is selected. head/base choose which commits are listed; rev is what
|
|
10
|
-
// the frame shows (a sha, "wip", a range, or anything typed in).
|
|
11
|
-
var state = { head: null, base: null, rev: null, q: '' }
|
|
12
|
-
var refs = null
|
|
13
|
-
var log = { commits: [], more: false, total: null }
|
|
14
|
-
var logError = null
|
|
15
|
-
var parts = {}
|
|
16
|
-
|
|
17
|
-
function el(tag, cls, text) {
|
|
18
|
-
var e = document.createElement(tag)
|
|
19
|
-
if (cls) e.className = cls
|
|
20
|
-
if (text !== undefined && text !== null) e.textContent = String(text)
|
|
21
|
-
return e
|
|
22
|
-
}
|
|
23
|
-
function add(parent) {
|
|
24
|
-
for (var i = 1; i < arguments.length; i++) if (arguments[i]) parent.appendChild(arguments[i])
|
|
25
|
-
return parent
|
|
26
|
-
}
|
|
27
|
-
function plural(n, word) { return n + ' ' + word + (n === 1 ? '' : 's') }
|
|
28
|
-
function api(path) {
|
|
29
|
-
return fetch(path).then(function (r) {
|
|
30
|
-
return r.json().then(function (body) {
|
|
31
|
-
if (!r.ok) throw new Error(body.error || r.statusText)
|
|
32
|
-
return body
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ---- location: the selection is kept in the hash, so reload and links work ----
|
|
38
|
-
function saveHash() {
|
|
39
|
-
var p = new URLSearchParams()
|
|
40
|
-
if (state.rev) p.set('rev', state.rev)
|
|
41
|
-
if (state.head) p.set('head', state.head)
|
|
42
|
-
if (state.base) p.set('base', state.base)
|
|
43
|
-
history.replaceState(null, '', '#' + p.toString())
|
|
44
|
-
}
|
|
45
|
-
function readHash() {
|
|
46
|
-
var p = new URLSearchParams(location.hash.slice(1))
|
|
47
|
-
return { rev: p.get('rev'), head: p.get('head'), base: p.get('base') }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ---- sidebar ----
|
|
51
|
-
// A branch picker with a search box: a repo can have hundreds of branches,
|
|
52
|
-
// too many to scan in a <select>. Matching is case-insensitive and every
|
|
53
|
-
// space-separated word must appear in the name.
|
|
54
|
-
var SHOWN = 200
|
|
55
|
-
function picker(onPick) {
|
|
56
|
-
var root = el('div', 'combo')
|
|
57
|
-
var button = el('button', 'combo-btn')
|
|
58
|
-
button.type = 'button'
|
|
59
|
-
button.setAttribute('aria-haspopup', 'listbox')
|
|
60
|
-
var panel = el('div', 'combo-panel')
|
|
61
|
-
panel.hidden = true
|
|
62
|
-
var input = el('input')
|
|
63
|
-
input.type = 'search'
|
|
64
|
-
input.spellcheck = false
|
|
65
|
-
input.placeholder = 'Search branches…'
|
|
66
|
-
input.setAttribute('aria-label', 'Search branches')
|
|
67
|
-
var list = el('div', 'combo-list')
|
|
68
|
-
list.setAttribute('role', 'listbox')
|
|
69
|
-
add(panel, input, list)
|
|
70
|
-
add(root, button, panel)
|
|
71
|
-
|
|
72
|
-
var groups = []
|
|
73
|
-
var noneLabel = null
|
|
74
|
-
var value = null
|
|
75
|
-
var items = [] // the rows now shown: { value, node }
|
|
76
|
-
var active = 0
|
|
77
|
-
|
|
78
|
-
function matches(name, words) {
|
|
79
|
-
var n = name.toLowerCase()
|
|
80
|
-
return words.every(function (w) { return n.indexOf(w) >= 0 })
|
|
81
|
-
}
|
|
82
|
-
function paint() {
|
|
83
|
-
items.forEach(function (it, i) { it.node.classList.toggle('active', i === active) })
|
|
84
|
-
if (items[active]) items[active].node.scrollIntoView({ block: 'nearest' })
|
|
85
|
-
}
|
|
86
|
-
function render() {
|
|
87
|
-
var words = input.value.toLowerCase().split(/\s+/).filter(Boolean)
|
|
88
|
-
list.textContent = ''
|
|
89
|
-
items = []
|
|
90
|
-
var shown = 0
|
|
91
|
-
var hidden = 0
|
|
92
|
-
function item(label, v) {
|
|
93
|
-
var node = el('button', 'combo-item', label)
|
|
94
|
-
node.type = 'button'
|
|
95
|
-
node.setAttribute('role', 'option')
|
|
96
|
-
node.setAttribute('aria-selected', String(v === value))
|
|
97
|
-
node.addEventListener('click', function () { pick(v) })
|
|
98
|
-
items.push({ value: v, node: node })
|
|
99
|
-
return node
|
|
100
|
-
}
|
|
101
|
-
if (noneLabel && !words.length) list.appendChild(item(noneLabel, null))
|
|
102
|
-
groups.forEach(function (g) {
|
|
103
|
-
var found = g.names.filter(function (n) { return matches(n, words) })
|
|
104
|
-
var room = found.slice(0, Math.max(0, SHOWN - shown))
|
|
105
|
-
hidden += found.length - room.length
|
|
106
|
-
shown += room.length
|
|
107
|
-
if (!room.length) return
|
|
108
|
-
add(list, add(el('div', 'combo-group', g.label), el('span', 'count', found.length)))
|
|
109
|
-
room.forEach(function (n) { list.appendChild(item(n, n)) })
|
|
110
|
-
})
|
|
111
|
-
if (hidden) add(list, el('div', 'combo-more', hidden + ' more: keep typing to narrow the list'))
|
|
112
|
-
if (!items.length) add(list, el('div', 'combo-more', 'No branches match.'))
|
|
113
|
-
// Start on the current value, or on the first match when searching.
|
|
114
|
-
var at = words.length ? -1 : items.findIndex(function (it) { return it.value === value })
|
|
115
|
-
active = at >= 0 ? at : 0
|
|
116
|
-
paint()
|
|
117
|
-
}
|
|
118
|
-
function open() {
|
|
119
|
-
panel.hidden = false
|
|
120
|
-
button.setAttribute('aria-expanded', 'true')
|
|
121
|
-
input.value = ''
|
|
122
|
-
render()
|
|
123
|
-
input.focus()
|
|
124
|
-
}
|
|
125
|
-
function close() {
|
|
126
|
-
panel.hidden = true
|
|
127
|
-
button.setAttribute('aria-expanded', 'false')
|
|
128
|
-
}
|
|
129
|
-
function pick(v) {
|
|
130
|
-
close()
|
|
131
|
-
button.focus()
|
|
132
|
-
if (v !== value) onPick(v)
|
|
133
|
-
}
|
|
134
|
-
button.addEventListener('click', function () { if (panel.hidden) open(); else close() })
|
|
135
|
-
input.addEventListener('input', render)
|
|
136
|
-
input.addEventListener('keydown', function (ev) {
|
|
137
|
-
if (ev.key === 'ArrowDown' || ev.key === 'ArrowUp') {
|
|
138
|
-
ev.preventDefault()
|
|
139
|
-
if (items.length) active = (active + (ev.key === 'ArrowDown' ? 1 : items.length - 1)) % items.length
|
|
140
|
-
paint()
|
|
141
|
-
} else if (ev.key === 'Enter') {
|
|
142
|
-
ev.preventDefault()
|
|
143
|
-
if (items[active]) pick(items[active].value)
|
|
144
|
-
} else if (ev.key === 'Escape') {
|
|
145
|
-
ev.preventDefault()
|
|
146
|
-
close()
|
|
147
|
-
button.focus()
|
|
148
|
-
}
|
|
149
|
-
})
|
|
150
|
-
document.addEventListener('mousedown', function (ev) { if (!panel.hidden && !root.contains(ev.target)) close() })
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
root: root,
|
|
154
|
-
// Local and remote branches, and what choosing "no branch" is called (if allowed).
|
|
155
|
-
setOptions: function (g, none) { groups = g; noneLabel = none },
|
|
156
|
-
setValue: function (v) {
|
|
157
|
-
value = v
|
|
158
|
-
button.textContent = v || noneLabel || 'choose a branch'
|
|
159
|
-
button.title = button.textContent
|
|
160
|
-
},
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function branchGroups() {
|
|
165
|
-
return [
|
|
166
|
-
{ label: 'Local branches', names: refs.branches.map(function (b) { return b.name }) },
|
|
167
|
-
{ label: 'Remote branches', names: refs.remotes.map(function (b) { return b.name }) },
|
|
168
|
-
]
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// One entry in the list: the title gets the full width (two lines at most),
|
|
172
|
-
// with the details and status marks on a line below it.
|
|
173
|
-
function row(title, details, marks, current, onClick) {
|
|
174
|
-
var b = el('button', 'row')
|
|
175
|
-
b.type = 'button'
|
|
176
|
-
b.setAttribute('aria-current', String(current))
|
|
177
|
-
b.title = title
|
|
178
|
-
add(b, el('div', 'subject', title))
|
|
179
|
-
var meta = el('div', 'meta')
|
|
180
|
-
details.forEach(function (d) { meta.appendChild(d) })
|
|
181
|
-
add(meta, add(el('span', 'marks'), marks[0], marks[1], marks[2]))
|
|
182
|
-
add(b, meta)
|
|
183
|
-
b.addEventListener('click', onClick)
|
|
184
|
-
return b
|
|
185
|
-
}
|
|
186
|
-
function mark(kind, text, tip) {
|
|
187
|
-
var m = el('span', 'mark ' + kind, text)
|
|
188
|
-
if (tip) m.title = tip
|
|
189
|
-
return m
|
|
190
|
-
}
|
|
191
|
-
function glossMark(glossed) {
|
|
192
|
-
return glossed ? mark('glossed', '● glossed', 'This commit has a gloss') : mark('none', '○ no gloss', 'This commit has no gloss')
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function renderList() {
|
|
196
|
-
var list = parts.list
|
|
197
|
-
list.textContent = ''
|
|
198
|
-
if (refs.wip) {
|
|
199
|
-
add(list, el('h2', null, 'Uncommitted work'))
|
|
200
|
-
var w = refs.wip
|
|
201
|
-
add(list, row('wip snapshot', [el('span', null, w.stale || 'the latest snapshot of your working tree')],
|
|
202
|
-
[glossMark(w.glossed), w.stale ? mark('warn', 'stale', w.stale) : null], state.rev === 'wip', function () { select('wip') }))
|
|
203
|
-
}
|
|
204
|
-
add(list, el('h2', null, state.head ? 'Commits on ' + state.head : 'Commits'))
|
|
205
|
-
if (logError) { add(list, el('p', 'error', logError)); return }
|
|
206
|
-
// A search narrows the list to some commits, so it isn't the whole branch.
|
|
207
|
-
if (state.base && state.head && log.commits.length && !state.q) {
|
|
208
|
-
var range = state.base + '..' + state.head
|
|
209
|
-
add(list, row('Whole branch', [el('span', null, range + ' · ' + plural(log.total === null ? log.commits.length : log.total, 'commit'))], [], state.rev === range, function () { select(range) }))
|
|
210
|
-
}
|
|
211
|
-
if (!log.commits.length) {
|
|
212
|
-
add(list, el('p', 'empty', state.q ? 'No commits match “' + state.q + '”.' : state.base ? 'No commits beyond ' + state.base + '.' : 'No commits.'))
|
|
213
|
-
}
|
|
214
|
-
log.commits.forEach(function (c) {
|
|
215
|
-
add(list, row(c.subject,
|
|
216
|
-
[el('span', 'sha', c.sha.slice(0, 7)), el('span', null, ' · ' + c.author + ' · ' + new Date(c.date).toLocaleDateString())],
|
|
217
|
-
c.merge ? [mark('none', 'merge')] : [glossMark(c.glossed), c.reviewed ? mark('reviewed', '✓ reviewed', 'This commit has a recorded review') : null],
|
|
218
|
-
state.rev === c.sha, function () { select(c.sha) }))
|
|
219
|
-
})
|
|
220
|
-
if (log.more) {
|
|
221
|
-
var more = el('button', 'btn more', 'Load more')
|
|
222
|
-
more.type = 'button'
|
|
223
|
-
more.addEventListener('click', function () { loadLog(true) })
|
|
224
|
-
add(list, more)
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function renderSide() {
|
|
229
|
-
parts.head.setOptions(branchGroups(), null)
|
|
230
|
-
parts.head.setValue(state.head)
|
|
231
|
-
parts.base.setOptions(branchGroups(), '(none: all of the branch’s history)')
|
|
232
|
-
parts.base.setValue(state.base)
|
|
233
|
-
parts.rev.value = state.rev && state.rev !== 'wip' ? state.rev : ''
|
|
234
|
-
renderList()
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// ---- viewer ----
|
|
238
|
-
function renderViewer() {
|
|
239
|
-
var v = parts.viewer
|
|
240
|
-
v.textContent = ''
|
|
241
|
-
if (!state.rev) {
|
|
242
|
-
add(v, el('div', 'placeholder', 'Pick a commit, a whole branch, or type any revision or range.'))
|
|
243
|
-
return
|
|
244
|
-
}
|
|
245
|
-
var src = '/view?rev=' + encodeURIComponent(state.rev)
|
|
246
|
-
var open = el('a', null, 'open in a tab ↗')
|
|
247
|
-
open.href = src
|
|
248
|
-
open.target = '_blank'
|
|
249
|
-
open.rel = 'noopener'
|
|
250
|
-
var reload = el('button', 'btn', 'Reload')
|
|
251
|
-
reload.type = 'button'
|
|
252
|
-
var frame = el('iframe')
|
|
253
|
-
frame.title = 'gloss of ' + state.rev
|
|
254
|
-
frame.src = src
|
|
255
|
-
// So the page's keys (j, k, o, e, c) work without clicking into it first.
|
|
256
|
-
frame.addEventListener('load', function () { frame.contentWindow.focus() })
|
|
257
|
-
reload.addEventListener('click', function () { frame.src = src })
|
|
258
|
-
add(v, add(el('div', 'viewer-bar'), el('span', 'what', state.rev === 'wip' ? 'uncommitted work (wip)' : state.rev), reload, open), frame)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// ---- actions ----
|
|
262
|
-
function select(rev) {
|
|
263
|
-
state.rev = rev
|
|
264
|
-
saveHash()
|
|
265
|
-
renderList()
|
|
266
|
-
parts.rev.value = rev === 'wip' ? '' : rev
|
|
267
|
-
renderViewer()
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function loadLog(append) {
|
|
271
|
-
if (!state.head) { log = { commits: [], more: false, total: null }; renderList(); return Promise.resolve() }
|
|
272
|
-
var q = new URLSearchParams({ head: state.head, limit: PAGE, skip: append ? log.commits.length : 0 })
|
|
273
|
-
if (state.base) q.set('base', state.base)
|
|
274
|
-
if (state.q) q.set('q', state.q)
|
|
275
|
-
var asked = state.head + '..' + state.base + '?' + state.q
|
|
276
|
-
return api('/api/log?' + q).then(function (data) {
|
|
277
|
-
if (asked !== state.head + '..' + state.base + '?' + state.q) return // the selection moved on
|
|
278
|
-
logError = null
|
|
279
|
-
log = append ? { commits: log.commits.concat(data.commits), more: data.more, total: data.total } : data
|
|
280
|
-
renderList()
|
|
281
|
-
}).catch(function (err) {
|
|
282
|
-
logError = err.message
|
|
283
|
-
log = { commits: [], more: false, total: null }
|
|
284
|
-
renderList()
|
|
285
|
-
})
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function pickHead(name) {
|
|
289
|
-
state.head = name
|
|
290
|
-
// A feature branch is most useful against the usual base; the base itself
|
|
291
|
-
// is listed whole.
|
|
292
|
-
state.base = name === refs.defaultBase ? null : refs.defaultBase
|
|
293
|
-
saveHash()
|
|
294
|
-
renderSide()
|
|
295
|
-
loadLog(false)
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function load() {
|
|
299
|
-
return api('/api/refs').then(function (data) {
|
|
300
|
-
parts.error.hidden = true
|
|
301
|
-
refs = data
|
|
302
|
-
var h = readHash()
|
|
303
|
-
// A link or bookmark may name a branch that has since been deleted or
|
|
304
|
-
// renamed: use the usual choice instead, and say so.
|
|
305
|
-
var known = {}
|
|
306
|
-
refs.branches.concat(refs.remotes).forEach(function (b) { known[b.name] = true })
|
|
307
|
-
var head = h.head && known[h.head] ? h.head : null
|
|
308
|
-
state.head = head || refs.current || refs.defaultBase || (refs.branches[0] && refs.branches[0].name) || null
|
|
309
|
-
var notice = null
|
|
310
|
-
if (h.head && !head) {
|
|
311
|
-
// The default base goes with the default branch.
|
|
312
|
-
state.base = state.head === refs.defaultBase ? null : refs.defaultBase
|
|
313
|
-
notice = 'Branch “' + h.head + '” no longer exists; showing ' + (state.head || 'the default') + ' instead.'
|
|
314
|
-
} else if (h.base && !known[h.base]) {
|
|
315
|
-
state.base = null
|
|
316
|
-
notice = 'The branch to compare with, “' + h.base + '”, no longer exists; showing all of ' + state.head + '’s history.'
|
|
317
|
-
} else if (head) {
|
|
318
|
-
state.base = h.base || null
|
|
319
|
-
} else {
|
|
320
|
-
state.base = state.head === refs.defaultBase ? null : refs.defaultBase
|
|
321
|
-
}
|
|
322
|
-
if (notice) {
|
|
323
|
-
parts.error.textContent = notice
|
|
324
|
-
parts.error.hidden = false
|
|
325
|
-
}
|
|
326
|
-
state.rev = h.rev || (refs.wip ? 'wip' : null)
|
|
327
|
-
parts.title.textContent = refs.repo
|
|
328
|
-
renderSide()
|
|
329
|
-
renderViewer()
|
|
330
|
-
return loadLog(false).then(function () {
|
|
331
|
-
// With nothing chosen, show the whole branch, or its newest commit.
|
|
332
|
-
if (!state.rev && state.head) select(state.base && log.commits.length ? state.base + '..' + state.head : log.commits.length ? log.commits[0].sha : null)
|
|
333
|
-
})
|
|
334
|
-
})
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function build() {
|
|
338
|
-
parts.title = el('span', 'repo')
|
|
339
|
-
var refresh = el('button', 'btn', 'Refresh')
|
|
340
|
-
refresh.type = 'button'
|
|
341
|
-
refresh.title = 'Re-read branches and commits'
|
|
342
|
-
refresh.addEventListener('click', function () { load().catch(fail) })
|
|
343
|
-
|
|
344
|
-
parts.head = picker(pickHead)
|
|
345
|
-
parts.base = picker(function (name) {
|
|
346
|
-
state.base = name
|
|
347
|
-
parts.base.setValue(name)
|
|
348
|
-
saveHash()
|
|
349
|
-
loadLog(false)
|
|
350
|
-
})
|
|
351
|
-
|
|
352
|
-
parts.rev = el('input')
|
|
353
|
-
parts.rev.type = 'text'
|
|
354
|
-
parts.rev.spellcheck = false
|
|
355
|
-
parts.rev.placeholder = 'HEAD~3, abc123, main..feature'
|
|
356
|
-
var go = el('button', 'btn', 'View')
|
|
357
|
-
go.type = 'submit'
|
|
358
|
-
var form = add(el('form', 'rev-row'), parts.rev, go)
|
|
359
|
-
form.addEventListener('submit', function (ev) {
|
|
360
|
-
ev.preventDefault()
|
|
361
|
-
var v = parts.rev.value.trim()
|
|
362
|
-
if (v) select(v)
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
parts.search = el('input')
|
|
366
|
-
parts.search.type = 'search'
|
|
367
|
-
parts.search.spellcheck = false
|
|
368
|
-
parts.search.placeholder = 'Search commit messages and authors…'
|
|
369
|
-
parts.search.setAttribute('aria-label', 'Search commits')
|
|
370
|
-
var searching = null
|
|
371
|
-
parts.search.addEventListener('input', function () {
|
|
372
|
-
// Wait for a pause in typing: each search asks git.
|
|
373
|
-
clearTimeout(searching)
|
|
374
|
-
searching = setTimeout(function () {
|
|
375
|
-
state.q = parts.search.value.trim()
|
|
376
|
-
loadLog(false)
|
|
377
|
-
}, 250)
|
|
378
|
-
})
|
|
379
|
-
|
|
380
|
-
parts.error = el('p', 'error')
|
|
381
|
-
parts.error.hidden = true
|
|
382
|
-
parts.list = el('div', 'list')
|
|
383
|
-
parts.viewer = el('section', 'viewer')
|
|
384
|
-
|
|
385
|
-
var top = add(el('div', 'side-top'),
|
|
386
|
-
add(el('div', 'title'), el('strong', null, 'git gloss ary'), parts.title, refresh),
|
|
387
|
-
parts.error,
|
|
388
|
-
add(el('div', 'field'), el('span', null, 'Branch'), parts.head.root),
|
|
389
|
-
add(el('div', 'field'), el('span', null, 'Compare with'), parts.base.root),
|
|
390
|
-
add(el('label', 'field'), document.createTextNode('Any revision or range'), form))
|
|
391
|
-
add(app, add(el('div', 'shell'), add(el('aside', 'side'), top, add(el('div', 'search-bar'), parts.search), parts.list), parts.viewer))
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function fail(err) {
|
|
395
|
-
parts.error.textContent = 'Could not read the repository: ' + err.message
|
|
396
|
-
parts.error.hidden = false
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
build()
|
|
400
|
-
load().catch(fail)
|
|
401
|
-
})()
|
package/dist/ary/index.html
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>git gloss ary</title>
|
|
7
|
-
<link rel="stylesheet" href="/ary.css">
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
<div id="app"><noscript>This page needs JavaScript.</noscript></div>
|
|
11
|
-
<script src="/ary.js"></script>
|
|
12
|
-
</body>
|
|
13
|
-
</html>
|
package/dist/ary/style.css
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
:root {
|
|
2
|
-
--bg: #f7f7f5;
|
|
3
|
-
--surface: #ffffff;
|
|
4
|
-
--surface-2: #f1f1ee;
|
|
5
|
-
--border: #e2e2dc;
|
|
6
|
-
--text: #1d1d1b;
|
|
7
|
-
--muted: #6b6b66;
|
|
8
|
-
--accent: #3b5bdb;
|
|
9
|
-
--ok: #1a7f37;
|
|
10
|
-
--warn-bg: #fff4e5;
|
|
11
|
-
--warn-border: #f0b35b;
|
|
12
|
-
--warn-text: #7a4300;
|
|
13
|
-
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
14
|
-
--sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
15
|
-
color-scheme: light;
|
|
16
|
-
}
|
|
17
|
-
@media (prefers-color-scheme: dark) {
|
|
18
|
-
:root {
|
|
19
|
-
--bg: #151514;
|
|
20
|
-
--surface: #1e1e1c;
|
|
21
|
-
--surface-2: #262624;
|
|
22
|
-
--border: #34342f;
|
|
23
|
-
--text: #e8e8e3;
|
|
24
|
-
--muted: #9a9a92;
|
|
25
|
-
--accent: #8ea2ff;
|
|
26
|
-
--ok: #57c47a;
|
|
27
|
-
--warn-bg: #33270f;
|
|
28
|
-
--warn-border: #a2701e;
|
|
29
|
-
--warn-text: #f3c77a;
|
|
30
|
-
color-scheme: dark;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
* { box-sizing: border-box; }
|
|
35
|
-
html, body, #app { height: 100%; }
|
|
36
|
-
body { margin: 0; background: var(--bg); color: var(--text); font: 14px/1.45 var(--sans); }
|
|
37
|
-
button, input, select { font: inherit; color: inherit; }
|
|
38
|
-
|
|
39
|
-
.shell { display: grid; grid-template-columns: 340px minmax(0, 1fr); height: 100%; }
|
|
40
|
-
@media (max-width: 800px) {
|
|
41
|
-
.shell { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 45%) minmax(0, 1fr); }
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/* Sidebar */
|
|
45
|
-
.side { display: flex; flex-direction: column; min-height: 0; border-right: 1px solid var(--border); background: var(--surface); }
|
|
46
|
-
.side-top { padding: 12px; border-bottom: 1px solid var(--border); display: grid; gap: 10px; }
|
|
47
|
-
.title { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
|
|
48
|
-
.title strong { font-size: 15px; }
|
|
49
|
-
.title .repo { color: var(--muted); font-family: var(--mono); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
50
|
-
label.field, div.field { display: grid; gap: 3px; color: var(--muted); font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
51
|
-
label.field > input, .rev-row input, .combo-btn, .combo-panel input {
|
|
52
|
-
width: 100%; padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px;
|
|
53
|
-
background: var(--bg); text-transform: none; letter-spacing: 0; font-size: 13px;
|
|
54
|
-
}
|
|
55
|
-
.combo { position: relative; }
|
|
56
|
-
.combo-btn { text-align: left; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-right: 24px; position: relative; }
|
|
57
|
-
.combo-btn::after { content: "▾"; position: absolute; right: 8px; color: var(--muted); }
|
|
58
|
-
.combo-panel {
|
|
59
|
-
position: absolute; z-index: 10; left: 0; right: 0; top: calc(100% + 4px);
|
|
60
|
-
display: grid; gap: 6px; padding: 6px; border: 1px solid var(--border); border-radius: 8px;
|
|
61
|
-
background: var(--surface); box-shadow: 0 8px 24px rgb(0 0 0 / 0.25);
|
|
62
|
-
}
|
|
63
|
-
.combo-panel[hidden] { display: none; }
|
|
64
|
-
.combo-list { max-height: min(50vh, 320px); overflow: auto; }
|
|
65
|
-
.combo-group {
|
|
66
|
-
margin-top: 8px; padding: 10px 8px 4px; border-top: 1px solid var(--border);
|
|
67
|
-
font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text);
|
|
68
|
-
}
|
|
69
|
-
.combo-group:first-child { margin-top: 0; padding-top: 4px; border-top: 0; }
|
|
70
|
-
.combo-group .count { margin-left: 6px; color: var(--muted); font-weight: 400; }
|
|
71
|
-
.combo-item { display: block; width: 100%; text-align: left; border: 0; background: none; border-radius: 5px; padding: 4px 8px 4px 20px; cursor: pointer; font-family: var(--mono); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
72
|
-
.combo-item:hover, .combo-item.active { background: var(--surface-2); }
|
|
73
|
-
.combo-item[aria-selected="true"] { color: var(--accent); font-weight: 600; }
|
|
74
|
-
.combo-more { padding: 6px 8px; color: var(--muted); font-size: 12.5px; }
|
|
75
|
-
.rev-row { display: flex; gap: 6px; }
|
|
76
|
-
.rev-row input { font-family: var(--mono); font-size: 12.5px; }
|
|
77
|
-
.btn {
|
|
78
|
-
border: 1px solid var(--border); background: var(--surface); border-radius: 6px;
|
|
79
|
-
padding: 4px 10px; cursor: pointer; white-space: nowrap;
|
|
80
|
-
}
|
|
81
|
-
.btn:hover { background: var(--surface-2); }
|
|
82
|
-
.error { margin: 0; padding: 8px 10px; border-radius: 6px; background: var(--warn-bg); border: 1px solid var(--warn-border); color: var(--warn-text); font-size: 12.5px; }
|
|
83
|
-
|
|
84
|
-
.list { flex: 1; min-height: 0; overflow: auto; padding: 6px; }
|
|
85
|
-
.list h2 { margin: 10px 8px 4px; font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); font-weight: 600; }
|
|
86
|
-
.row {
|
|
87
|
-
display: block; width: 100%; text-align: left; cursor: pointer;
|
|
88
|
-
border: 1px solid transparent; background: none; border-radius: 6px; padding: 6px 8px;
|
|
89
|
-
}
|
|
90
|
-
.row:hover { background: var(--surface-2); }
|
|
91
|
-
.row[aria-current="true"] { background: var(--surface-2); border-color: var(--accent); }
|
|
92
|
-
.row .subject {
|
|
93
|
-
/* the whole point of the list: enough of the subject to tell commits apart */
|
|
94
|
-
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
|
|
95
|
-
overflow: hidden; overflow-wrap: anywhere;
|
|
96
|
-
}
|
|
97
|
-
.row .meta { display: flex; flex-wrap: wrap; align-items: baseline; margin-top: 2px; color: var(--muted); font-size: 12px; }
|
|
98
|
-
.row .marks { margin-left: auto; padding-left: 10px; display: inline-flex; gap: 10px; white-space: nowrap; }
|
|
99
|
-
.sha { font-family: var(--mono); font-size: 12px; }
|
|
100
|
-
.mark.glossed { color: var(--ok); }
|
|
101
|
-
.mark.reviewed { color: var(--accent); }
|
|
102
|
-
.mark.warn { color: var(--warn-text); background: var(--warn-bg); border-radius: 4px; padding: 0 5px; }
|
|
103
|
-
.search-bar { padding: 8px 12px; border-bottom: 1px solid var(--border); }
|
|
104
|
-
.search-bar input { width: 100%; padding: 5px 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); font-size: 13px; }
|
|
105
|
-
.empty { color: var(--muted); padding: 8px; }
|
|
106
|
-
.more { margin: 6px 8px; }
|
|
107
|
-
|
|
108
|
-
/* Viewer */
|
|
109
|
-
.viewer { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
|
|
110
|
-
.viewer-bar { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-bottom: 1px solid var(--border); background: var(--surface); }
|
|
111
|
-
.viewer-bar .what { flex: 1; min-width: 0; font-family: var(--mono); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
112
|
-
.viewer-bar a { color: var(--accent); text-decoration: none; }
|
|
113
|
-
.viewer-bar a:hover { text-decoration: underline; }
|
|
114
|
-
iframe { flex: 1; width: 100%; min-height: 0; border: 0; background: var(--bg); }
|
|
115
|
-
.placeholder { flex: 1; display: grid; place-items: center; color: var(--muted); padding: 24px; text-align: center; }
|