tenseleyflow/shithub / ea2e141

Browse files

Align README chrome with GitHub

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
ea2e1417abc58b12aaa388a9619a4ddcb1964a9e
Parents
e32e338
Tree
71245e4

4 changed files

StatusFile+-
M internal/web/render/octicons.go 2 0
M internal/web/static/css/shithub.css 169 6
M internal/web/templates/_layout.html 154 0
M internal/web/templates/repo/tree.html 13 0
internal/web/render/octicons.gomodified
@@ -161,6 +161,8 @@ func BuiltinOcticons() OcticonResolver {
161161
 			`><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14zm5.78-2.92a.749.749 0 0 1-1.06 0L3.72 7.78a.749.749 0 1 1 1.06-1.06L7.25 9.19V1.75a.75.75 0 0 1 1.5 0v7.44l2.47-2.47a.749.749 0 1 1 1.06 1.06z"/></svg>`),
162162
 		"copy": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls +
163163
 			`><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/></svg>`),
164
+		"list-unordered": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls +
165
+			`><path d="M5.75 2.5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5ZM2 14a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-6a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM2 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"/></svg>`),
164166
 		// Profile sub-nav: Stars tab.
165167
 		"star": trustedSVG(`<svg xmlns="http://www.w3.org/2000/svg" ` + cls +
166168
 			`><path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"/></svg>`),
internal/web/static/css/shithub.cssmodified
@@ -4567,16 +4567,17 @@ button.shithub-repo-action {
45674567
   border: 1px solid var(--border-default);
45684568
   border-radius: 6px;
45694569
   background: var(--canvas-default);
4570
-  overflow: hidden;
4570
+  overflow: visible;
45714571
 }
45724572
 .shithub-readme-head {
4573
+  position: relative;
45734574
   display: flex;
45744575
   align-items: center;
4576
+  gap: 1rem;
45754577
   min-height: 48px;
45764578
   padding: 0 1rem;
45774579
   border-bottom: 1px solid var(--border-default);
4578
-  overflow-x: auto;
4579
-  overflow-y: hidden;
4580
+  overflow: visible;
45804581
   scrollbar-width: none;
45814582
 }
45824583
 .shithub-readme-head::-webkit-scrollbar {
@@ -4585,8 +4586,14 @@ button.shithub-repo-action {
45854586
 .shithub-readme-tabs {
45864587
   display: flex;
45874588
   align-items: stretch;
4589
+  flex: 1 1 auto;
45884590
   gap: 1.25rem;
45894591
   min-width: 0;
4592
+  overflow-x: auto;
4593
+  scrollbar-width: none;
4594
+}
4595
+.shithub-readme-tabs::-webkit-scrollbar {
4596
+  display: none;
45904597
 }
45914598
 .shithub-readme-tab {
45924599
   display: inline-flex;
@@ -4609,6 +4616,99 @@ button.shithub-repo-action {
46094616
   border-bottom-color: var(--accent-emphasis, #fd8c73);
46104617
   color: var(--fg-default);
46114618
 }
4619
+.shithub-readme-outline {
4620
+  position: relative;
4621
+  flex: 0 0 auto;
4622
+  margin: 0 0 0 auto;
4623
+}
4624
+.shithub-readme-outline > summary {
4625
+  display: inline-flex;
4626
+  align-items: center;
4627
+  justify-content: center;
4628
+  width: 32px;
4629
+  height: 32px;
4630
+  border: 0;
4631
+  border-radius: 6px;
4632
+  color: var(--fg-muted);
4633
+  background: transparent;
4634
+  cursor: pointer;
4635
+  list-style: none;
4636
+}
4637
+.shithub-readme-outline > summary::-webkit-details-marker {
4638
+  display: none;
4639
+}
4640
+.shithub-readme-outline > summary:hover,
4641
+.shithub-readme-outline[open] > summary {
4642
+  color: var(--fg-default);
4643
+  background: var(--canvas-subtle);
4644
+}
4645
+.shithub-readme-outline-panel {
4646
+  position: absolute;
4647
+  top: calc(100% + 8px);
4648
+  right: 0;
4649
+  z-index: 40;
4650
+  width: min(360px, calc(100vw - 2rem));
4651
+  max-height: min(520px, calc(100vh - 8rem));
4652
+  padding: 0.75rem;
4653
+  overflow: auto;
4654
+  border: 1px solid var(--border-default);
4655
+  border-radius: 12px;
4656
+  background: var(--canvas-default);
4657
+  box-shadow: 0 16px 32px rgba(31, 35, 40, 0.16);
4658
+}
4659
+[data-theme="dark"] .shithub-readme-outline-panel,
4660
+[data-theme="high-contrast"] .shithub-readme-outline-panel {
4661
+  box-shadow: 0 16px 32px rgba(1, 4, 9, 0.6);
4662
+}
4663
+.shithub-readme-outline-filter {
4664
+  display: block;
4665
+  margin: 0 0 0.75rem;
4666
+}
4667
+.shithub-readme-outline-filter input {
4668
+  width: 100%;
4669
+  min-height: 40px;
4670
+  padding: 0.5rem 0.75rem;
4671
+  border: 1px solid var(--border-default);
4672
+  border-radius: 6px;
4673
+  color: var(--fg-default);
4674
+  background: var(--canvas-default);
4675
+  font: inherit;
4676
+}
4677
+.shithub-readme-outline-filter input:focus {
4678
+  border-color: var(--accent-emphasis);
4679
+  outline: 2px solid var(--accent-emphasis);
4680
+  outline-offset: -1px;
4681
+}
4682
+.shithub-readme-outline-list {
4683
+  display: flex;
4684
+  flex-direction: column;
4685
+  gap: 0.15rem;
4686
+}
4687
+.shithub-readme-outline-item {
4688
+  display: block;
4689
+  padding: 0.35rem 0.5rem;
4690
+  border-radius: 6px;
4691
+  color: var(--fg-default);
4692
+  font-size: 0.875rem;
4693
+  font-weight: 600;
4694
+  line-height: 1.35;
4695
+  text-decoration: none;
4696
+}
4697
+.shithub-readme-outline-item:hover {
4698
+  background: var(--canvas-subtle);
4699
+  text-decoration: none;
4700
+}
4701
+.shithub-readme-outline-item.is-depth-2 { padding-left: 1.25rem; }
4702
+.shithub-readme-outline-item.is-depth-3 { padding-left: 2rem; }
4703
+.shithub-readme-outline-item.is-depth-4,
4704
+.shithub-readme-outline-item.is-depth-5,
4705
+.shithub-readme-outline-item.is-depth-6 { padding-left: 2.75rem; }
4706
+.shithub-readme-outline-empty {
4707
+  margin: 0;
4708
+  padding: 0.35rem 0.5rem;
4709
+  color: var(--fg-muted);
4710
+  font-size: 0.875rem;
4711
+}
46124712
 .shithub-readme-body { padding: 2rem; }
46134713
 .markdown-body {
46144714
   color: var(--fg-default);
@@ -4662,9 +4762,72 @@ button.shithub-repo-action {
46624762
 .markdown-body [align="center"] { text-align: center; }
46634763
 .markdown-body [align="right"] { text-align: right; }
46644764
 .markdown-body [align="left"] { text-align: left; }
4665
-.shithub-readme code { font-family: monospace; padding: 0.1em 0.3em; background: var(--canvas-default); border-radius: 3px; }
4666
-.shithub-readme pre code { padding: 0; background: none; }
4667
-.shithub-readme-plain { white-space: pre-wrap; }
4765
+.markdown-body :not(pre) > code {
4766
+  padding: 0.2em 0.4em;
4767
+  margin: 0;
4768
+  border-radius: 6px;
4769
+  background: var(--canvas-subtle);
4770
+  font-size: 85%;
4771
+}
4772
+.markdown-body pre {
4773
+  padding: 1rem;
4774
+  overflow: auto;
4775
+  border-radius: 6px;
4776
+  background: var(--canvas-subtle);
4777
+  font-size: 85%;
4778
+  line-height: 1.45;
4779
+}
4780
+.markdown-body pre code {
4781
+  display: inline;
4782
+  max-width: none;
4783
+  padding: 0;
4784
+  margin: 0;
4785
+  overflow: visible;
4786
+  border: 0;
4787
+  background: transparent;
4788
+  color: inherit;
4789
+  font-size: 100%;
4790
+  line-height: inherit;
4791
+  white-space: pre;
4792
+  word-break: normal;
4793
+  overflow-wrap: normal;
4794
+}
4795
+.markdown-body .shithub-markdown-codeblock {
4796
+  position: relative;
4797
+  margin: 0 0 1rem;
4798
+}
4799
+.markdown-body > .shithub-markdown-codeblock:last-child {
4800
+  margin-bottom: 0;
4801
+}
4802
+.markdown-body .shithub-markdown-codeblock pre {
4803
+  margin: 0;
4804
+  padding-right: 3rem;
4805
+}
4806
+.shithub-markdown-code-copy {
4807
+  position: absolute;
4808
+  top: 0.5rem;
4809
+  right: 0.5rem;
4810
+  display: inline-flex;
4811
+  align-items: center;
4812
+  justify-content: center;
4813
+  width: 28px;
4814
+  height: 28px;
4815
+  padding: 0;
4816
+  border: 1px solid var(--border-default);
4817
+  border-radius: 6px;
4818
+  color: var(--fg-muted);
4819
+  background: var(--canvas-subtle);
4820
+  cursor: pointer;
4821
+}
4822
+.shithub-markdown-code-copy:hover,
4823
+.shithub-markdown-code-copy:focus {
4824
+  color: var(--fg-default);
4825
+  background: var(--button-default-hover-bg);
4826
+}
4827
+.shithub-markdown-code-copy.is-copied {
4828
+  color: var(--success-fg);
4829
+}
4830
+.shithub-readme-plain { white-space: pre; }
46684831
 
46694832
 @media (max-width: 900px) {
46704833
   .shithub-repo-header-inner { align-items: flex-start; flex-direction: column; }
internal/web/templates/_layout.htmlmodified
@@ -273,6 +273,160 @@
273273
     });
274274
     refreshPins();
275275
   })();
276
+
277
+  (function () {
278
+    var copyIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg>';
279
+    var checkIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg>';
280
+
281
+    function fallbackCopy(text) {
282
+      var textarea = document.createElement("textarea");
283
+      textarea.value = text;
284
+      textarea.setAttribute("readonly", "");
285
+      textarea.style.position = "fixed";
286
+      textarea.style.top = "-1000px";
287
+      textarea.style.left = "-1000px";
288
+      document.body.appendChild(textarea);
289
+      textarea.select();
290
+      var ok = false;
291
+      try { ok = document.execCommand("copy"); } catch (e) { ok = false; }
292
+      document.body.removeChild(textarea);
293
+      return ok;
294
+    }
295
+
296
+    function copyText(text) {
297
+      if (navigator.clipboard && navigator.clipboard.writeText) {
298
+        return navigator.clipboard.writeText(text).then(function () {
299
+          return true;
300
+        }).catch(function () {
301
+          return fallbackCopy(text);
302
+        });
303
+      }
304
+      return Promise.resolve(fallbackCopy(text));
305
+    }
306
+
307
+    document.querySelectorAll(".markdown-body pre").forEach(function (pre) {
308
+      if (pre.closest(".shithub-markdown-codeblock")) return;
309
+      var parent = pre.parentNode;
310
+      if (!parent) return;
311
+
312
+      var wrapper = document.createElement("div");
313
+      wrapper.className = "shithub-markdown-codeblock";
314
+      parent.insertBefore(wrapper, pre);
315
+      wrapper.appendChild(pre);
316
+
317
+      var button = document.createElement("button");
318
+      button.type = "button";
319
+      button.className = "shithub-markdown-code-copy";
320
+      button.setAttribute("aria-label", "Copy code");
321
+      button.setAttribute("title", "Copy");
322
+      button.innerHTML = copyIcon;
323
+      wrapper.appendChild(button);
324
+
325
+      button.addEventListener("click", function () {
326
+        var target = pre.querySelector("code") || pre;
327
+        copyText(target.textContent || "").then(function (ok) {
328
+          button.classList.toggle("is-copied", ok);
329
+          button.innerHTML = ok ? checkIcon : copyIcon;
330
+          button.setAttribute("title", ok ? "Copied" : "Copy failed");
331
+          window.setTimeout(function () {
332
+            button.classList.remove("is-copied");
333
+            button.innerHTML = copyIcon;
334
+            button.setAttribute("title", "Copy");
335
+          }, 1400);
336
+        });
337
+      });
338
+    });
339
+  })();
340
+
341
+  (function () {
342
+    function slugify(text) {
343
+      return text.toLowerCase()
344
+        .trim()
345
+        .replace(/[^a-z0-9 _-]+/g, "")
346
+        .replace(/\s+/g, "-")
347
+        .replace(/-+/g, "-")
348
+        .replace(/^-|-$/g, "") || "heading";
349
+    }
350
+
351
+    document.querySelectorAll("[data-readme-outline]").forEach(function (root) {
352
+      var readme = root.closest(".shithub-readme");
353
+      var body = readme && readme.querySelector(".shithub-readme-body");
354
+      var list = root.querySelector("[data-readme-outline-list]");
355
+      var filter = root.querySelector("[data-readme-outline-filter]");
356
+      var empty = root.querySelector("[data-readme-outline-empty]");
357
+      var summary = root.querySelector("summary");
358
+      if (!body || !list) return;
359
+
360
+      var used = {};
361
+      body.querySelectorAll("[id]").forEach(function (element) {
362
+        if (element.id) used[element.id] = true;
363
+      });
364
+
365
+      var headings = Array.prototype.slice.call(body.querySelectorAll("h1, h2, h3, h4, h5, h6")).filter(function (heading) {
366
+        return (heading.textContent || "").trim() !== "";
367
+      });
368
+      if (headings.length === 0) return;
369
+
370
+      headings.forEach(function (heading) {
371
+        var text = (heading.textContent || "").trim().replace(/\s+/g, " ");
372
+        if (!heading.id) {
373
+          var base = slugify(text);
374
+          var id = base;
375
+          var index = 1;
376
+          while (used[id]) {
377
+            id = base + "-" + index;
378
+            index += 1;
379
+          }
380
+          heading.id = id;
381
+          used[id] = true;
382
+        }
383
+
384
+        var level = parseInt(heading.tagName.slice(1), 10) || 1;
385
+        var link = document.createElement("a");
386
+        link.href = "#" + heading.id;
387
+        link.className = "shithub-readme-outline-item is-depth-" + Math.min(level, 6);
388
+        link.textContent = text;
389
+        link.setAttribute("data-readme-outline-text", text.toLowerCase());
390
+        list.appendChild(link);
391
+      });
392
+
393
+      var links = Array.prototype.slice.call(list.querySelectorAll("a"));
394
+      function applyFilter() {
395
+        var query = filter ? filter.value.trim().toLowerCase() : "";
396
+        var shown = 0;
397
+        links.forEach(function (link) {
398
+          var matches = query === "" || (link.getAttribute("data-readme-outline-text") || "").indexOf(query) !== -1;
399
+          link.hidden = !matches;
400
+          if (matches) shown += 1;
401
+        });
402
+        if (empty) empty.hidden = shown !== 0;
403
+      }
404
+
405
+      root.hidden = false;
406
+      if (filter) filter.addEventListener("input", applyFilter);
407
+      root.addEventListener("toggle", function () {
408
+        if (root.open && filter) {
409
+          window.setTimeout(function () {
410
+            filter.focus();
411
+            filter.select();
412
+          }, 0);
413
+        }
414
+      });
415
+      list.addEventListener("click", function (event) {
416
+        if (event.target.closest("a")) root.open = false;
417
+      });
418
+      root.addEventListener("keydown", function (event) {
419
+        if (event.key === "Escape" && root.open) {
420
+          root.open = false;
421
+          if (summary) summary.focus();
422
+        }
423
+      });
424
+      document.addEventListener("click", function (event) {
425
+        if (root.open && !root.contains(event.target)) root.open = false;
426
+      });
427
+      applyFilter();
428
+    });
429
+  })();
276430
 </script>
277431
 </body>
278432
 </html>
internal/web/templates/repo/tree.htmlmodified
@@ -141,6 +141,19 @@
141141
           {{ else }}
142142
           <span class="shithub-readme-tab is-active">{{ octicon "book" }} <span>README</span></span>
143143
           {{ end }}
144
+          <details class="shithub-readme-outline" data-readme-outline hidden>
145
+            <summary aria-label="Outline" title="Outline">
146
+              {{ octicon "list-unordered" }}
147
+            </summary>
148
+            <div class="shithub-readme-outline-panel" role="dialog" aria-label="README outline">
149
+              <label class="shithub-readme-outline-filter">
150
+                <span class="sr-only">Filter headings</span>
151
+                <input type="search" placeholder="Filter headings" aria-label="Filter headings" autocomplete="off" data-readme-outline-filter>
152
+              </label>
153
+              <nav class="shithub-readme-outline-list" aria-label="README headings" data-readme-outline-list></nav>
154
+              <p class="shithub-readme-outline-empty" data-readme-outline-empty hidden>No matching headings.</p>
155
+            </div>
156
+          </details>
144157
         </div>
145158
         <div class="shithub-readme-body">{{ .README }}</div>
146159
       </section>