directory.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /* Enhance the shared README without changing its GitHub presentation. */
  2. (function () {
  3. function enhanceDirectory() {
  4. var article = document.querySelector(".md-content__inner");
  5. if (!article || article.dataset.directoryReady) return;
  6. var contents = article.querySelector("#contents");
  7. if (!contents) return;
  8. article.dataset.directoryReady = "true";
  9. var toc = contents.nextElementSibling;
  10. var firstHeading = toc && toc.nextElementSibling;
  11. while (firstHeading && firstHeading.tagName !== "H2") {
  12. firstHeading = firstHeading.nextElementSibling;
  13. }
  14. if (!firstHeading) return;
  15. var controls = document.createElement("div");
  16. controls.className = "awesome-django-controls";
  17. controls.id = "contents";
  18. controls.innerHTML =
  19. '<div class="awesome-django-filter"><label for="directory-filter">Filter packages and resources</label>' +
  20. '<div class="awesome-django-filter__input"><input id="directory-filter" type="search" placeholder="Search names and descriptions…" aria-controls="directory-results">' +
  21. '<button type="button" id="directory-clear">Clear</button></div></div>' +
  22. '<div class="awesome-django-jump"><label for="directory-category">Jump to category</label>' +
  23. '<select id="directory-category"><option value="">Choose a category…</option></select></div>' +
  24. '<p class="awesome-django-result-count" role="status" aria-live="polite" aria-atomic="true"></p>';
  25. contents.replaceWith(controls);
  26. if (toc && toc.tagName === "UL") toc.remove();
  27. new ResizeObserver(function () {
  28. article.style.setProperty("--directory-controls-height", controls.getBoundingClientRect().height + "px");
  29. }).observe(controls);
  30. var contribution = document.createElement("p");
  31. contribution.className = "awesome-django-contribute";
  32. var link = document.createElement("a");
  33. link.href = "https://github.com/wsvincent/awesome-django/blob/main/.github/pull_request_template.md";
  34. link.textContent = "Suggest a project";
  35. contribution.append(link, " — read the submission criteria before opening a pull request.");
  36. article.querySelector("blockquote").after(contribution);
  37. article.querySelectorAll(":scope > p").forEach(function (p) {
  38. if (!p.textContent.trim() && p.querySelector("br")) p.classList.add("awesome-django-spacer");
  39. });
  40. var results = document.createElement("div");
  41. results.id = "directory-results";
  42. firstHeading.before(results);
  43. var group;
  44. var section;
  45. var node = firstHeading;
  46. while (node) {
  47. var next = node.nextSibling;
  48. if (node.nodeType === 1 && node.tagName === "H2") {
  49. group = document.createElement("section");
  50. group.className = "awesome-django-group";
  51. group.setAttribute("aria-labelledby", node.id);
  52. results.append(group);
  53. section = group;
  54. } else if (node.nodeType === 1 && node.tagName === "H3") {
  55. section = document.createElement("section");
  56. section.className = "awesome-django-category";
  57. section.setAttribute("aria-labelledby", node.id);
  58. group.append(section);
  59. }
  60. section.append(node);
  61. node = next;
  62. }
  63. var entries = Array.from(results.querySelectorAll("li"));
  64. var categories = Array.from(results.querySelectorAll("section"));
  65. var select = controls.querySelector("select");
  66. var counts = {};
  67. results.querySelectorAll("h2, h3").forEach(function (heading) {
  68. var label = heading.cloneNode(true);
  69. label.querySelectorAll(".headerlink").forEach(function (a) { a.remove(); });
  70. var count = heading.parentElement.querySelectorAll("li").length;
  71. counts[heading.id] = count;
  72. var option = document.createElement("option");
  73. option.value = heading.id;
  74. option.textContent = (heading.tagName === "H3" ? " " : "") + label.textContent.trim() + " (" + count + ")";
  75. select.append(option);
  76. });
  77. var input = controls.querySelector("input");
  78. var status = controls.querySelector("[role=status]");
  79. var empty = document.createElement("p");
  80. empty.textContent = "No matching entries. Try another term or clear the filter.";
  81. empty.hidden = true;
  82. results.after(empty);
  83. function filter() {
  84. var terms = input.value.trim().toLowerCase().split(/\s+/).filter(Boolean);
  85. var visible = 0;
  86. entries.forEach(function (entry) {
  87. var text = entry.textContent.toLowerCase();
  88. entry.hidden = !terms.every(function (term) { return text.includes(term); });
  89. if (!entry.hidden) visible++;
  90. });
  91. categories.forEach(function (category) {
  92. category.hidden = !Array.from(category.querySelectorAll("li")).some(function (entry) { return !entry.hidden; });
  93. });
  94. status.textContent = terms.length ? visible + " of " + entries.length + " entries match" : entries.length + " packages and resources";
  95. empty.hidden = visible !== 0;
  96. }
  97. input.addEventListener("input", filter);
  98. controls.querySelector("button").addEventListener("click", function () {
  99. input.value = "";
  100. filter();
  101. input.focus();
  102. });
  103. select.addEventListener("change", function () {
  104. if (!select.value) return;
  105. input.value = "";
  106. filter();
  107. var target = document.getElementById(select.value);
  108. window.location.hash = select.value;
  109. target.setAttribute("tabindex", "-1");
  110. target.focus({ preventScroll: true });
  111. target.scrollIntoView();
  112. });
  113. var disclosures = [];
  114. document.querySelectorAll('[data-md-component="toc"] a.md-nav__link').forEach(function (a) {
  115. var id = a.hash.slice(1);
  116. if (id === "contents") {
  117. a.closest("li").remove();
  118. return;
  119. }
  120. if (counts[id] === undefined) return;
  121. var badge = document.createElement("span");
  122. badge.className = "awesome-django-nav-count";
  123. badge.textContent = counts[id];
  124. badge.setAttribute("aria-label", counts[id] + " entries");
  125. a.append(badge);
  126. var nav = a.parentElement.querySelector(":scope > nav");
  127. if (!nav) return;
  128. var button = document.createElement("button");
  129. button.type = "button";
  130. button.className = "awesome-django-nav-toggle";
  131. button.textContent = "⌄";
  132. button.setAttribute("aria-label", "Toggle " + nav.getAttribute("aria-label") + " categories");
  133. nav.id = "directory-nav-" + id;
  134. button.setAttribute("aria-controls", nav.id);
  135. function setExpanded(expanded) {
  136. nav.hidden = !expanded;
  137. button.setAttribute("aria-expanded", String(expanded));
  138. }
  139. setExpanded(id === "third-party-packages");
  140. button.addEventListener("click", function () { setExpanded(nav.hidden); });
  141. a.after(button);
  142. a.parentElement.classList.add("awesome-django-nav-group");
  143. disclosures.push({ nav: nav, expand: function () { setExpanded(true); } });
  144. });
  145. function revealAnchor() {
  146. var id;
  147. try { id = decodeURIComponent(window.location.hash.slice(1)); } catch (_) { return; }
  148. var target = document.getElementById(id);
  149. if (target && results.contains(target) && input.value) {
  150. input.value = "";
  151. filter();
  152. }
  153. disclosures.forEach(function (item) {
  154. if (Array.from(item.nav.querySelectorAll("a")).some(function (a) { return a.hash === "#" + id; })) item.expand();
  155. });
  156. }
  157. window.addEventListener("hashchange", revealAnchor);
  158. filter();
  159. revealAnchor();
  160. var picture = article.querySelector("picture");
  161. if (picture) {
  162. var logo = picture.querySelector("img");
  163. var source = picture.querySelector("source");
  164. var light = logo.getAttribute("src");
  165. var dark = source.getAttribute("srcset");
  166. source.remove();
  167. logo.alt = "Django";
  168. function syncLogo() {
  169. var scheme = document.querySelector("[data-md-color-scheme]");
  170. logo.src = scheme && scheme.getAttribute("data-md-color-scheme") === "slate" ? dark : light;
  171. }
  172. syncLogo();
  173. new MutationObserver(syncLogo).observe(document.documentElement, {
  174. attributes: true, subtree: true, attributeFilter: ["data-md-color-scheme"],
  175. });
  176. }
  177. }
  178. if (typeof document$ !== "undefined") document$.subscribe(enhanceDirectory);
  179. else if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", enhanceDirectory);
  180. else enhanceDirectory();
  181. })();