datalog-theme 0.9.1 → 0.10.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.
@@ -3,26 +3,28 @@
3
3
  "assets/js/academic.js": "c4a510fe7457b70273361e0dec773299604d17c2fbd43eab62a78dd9ff2175da",
4
4
  "assets/js/analytics-dashboard.js": "98458c96b2f93c4842a3c4c4197cbcc6e1ebc84f74b322e1935408d3d0d8a3ae",
5
5
  "assets/js/comments.js": "5710a30b20bdffa02b256ec9f9e6b07f0871fa5903eb5b60880aba6e915956f5",
6
- "assets/js/comments/thread.js": "33e15eaba2eb8d18cf407eb1c1bfacc7b211d29317536e71c2c2e6ad115cea31",
6
+ "assets/js/comments/thread.js": "50684540ae6f03555ed375df46116f9f91c0c756477a2426972274ec581f18bc",
7
7
  "assets/js/contact.js": "7b93f9f5d8f3c0852303ea22c970845072935de50fd6f765087825e60b845c69",
8
8
  "assets/js/contact/form.js": "502c755ee72b7f6d050cd3965c25c4979586c1e5c444dd944a77767925c8c5ae",
9
- "assets/js/core/copy-buttons.js": "339e890158e3c9e5b06f85c56f8658b352a15df8c955e1208dbfa8a75443a55e",
9
+ "assets/js/core/copy-buttons.js": "313e22057b56b8a3721c2d049a4fcf2b02839105ce70a9c82b570539732686b3",
10
10
  "assets/js/core/dark-mode.js": "1d4a699f6b810c829138823e346d96fa95bb89a3247937f5ff001a476d1856f3",
11
11
  "assets/js/core/github-cards.js": "7eb0c4f489fd838f4c62ed0e5ba59ad4c51e01c8a7ccf04c2ef26c1b47fa284e",
12
+ "assets/js/core/install-tabs.js": "855499d313f2f67b0df2d96e67643df495710497bb7f5ce47282b5cf27bf4e68",
12
13
  "assets/js/core/language-filter.js": "adbe34d62cbbf77bbaab8339101f5c75d8934eed735c7279c95ab007bd56f461",
13
14
  "assets/js/core/navigation.js": "5d8457d996c040343fdb2ba44c204cdb04dfa2f509c127f702095d720c4d3469",
14
15
  "assets/js/core/scroll-progress.js": "9610488213fa83f9270d0f2c9026b7f3e689503f41efa0c01317a51760d44b5f",
15
16
  "assets/js/core/search-hotkeys.js": "19881663e3db178e92ab58a382c5369aab5a3045b58410d906eb117924125186",
16
17
  "assets/js/core/skip-links.js": "138265a3aa853bf07a798efd03d56b14a5733f6e6c1810ccf87e4cfb86d52c30",
18
+ "assets/js/core/toc.js": "d66665aaa7f5a6359d3d1bee259a6d0781f816782087f670de2c7fc04562789e",
17
19
  "assets/js/corrections.js": "16010732e7d653490d7b08d8dea88616008f9ae15efbc0f42848c52a7c8a6209",
18
20
  "assets/js/corrections/form.js": "525c281ca7c964b0415c5571360b87c7827299d870c673d87f2b1b398b240e8b",
19
21
  "assets/js/dynamic-services/client.js": "32e9cf9ad040553b2b96c3d92733c9b7626846f6dfb0aa6072ceab9f498b90b0",
20
22
  "assets/js/dynamic-services/form-state.js": "96bfe09e7ea789d57d50f9f36cce8f53823d650dab6e5b4cc10f17c3fbec504e",
21
23
  "assets/js/loader.js": "4544818b74f4c34a634c58008ca3348ad0bede869ccf81af27ba4dad1cbb9283",
22
- "assets/js/main.js": "3f613ee824f48b83f9065f087bc8e1a75f0368e37a23dacf3196c2b9ffcd38ad",
23
- "assets/js/math.js": "267693a35a1b9cbf3a9c5475fdaadf3eac5b715bd60af0761b4c0adf0eaf521a",
24
+ "assets/js/main.js": "ea45ab96106172ab105c96f32042af4d9587d7ae197ac854af7e95accfebdc7c",
25
+ "assets/js/math.js": "1b457f5dbce7ed5476f973839c21d05a1700a3ecd5933aeca238e9ec66839adb",
24
26
  "assets/js/moderation.js": "2e9bcb00f0dcf4dd436458d99764989c9728794716dd33c59310d65206323c46",
25
- "assets/js/moderation/inbox.js": "80d3cdf358faf1f0440a4b99f2609cc8a958700c0a4768f00ae10dd6a3c5b624",
27
+ "assets/js/moderation/inbox.js": "ddbe3f4c251ff4bf10bd799fbd2e43777d3d728643fd5b962dfdf258c7365993",
26
28
  "assets/js/notebook.js": "361f716a76b398aeb564ef05ba026018b1bc083de28d75c9b8df5776a6ccb21a",
27
29
  "assets/js/reactions.js": "8386cffaacba9a1f976274f8145b95e9c1c2409da977dfc94ca195477971848b",
28
30
  "assets/js/reactions/widget.js": "8407a55582e017fc9652cf2968db084c06e63e116150abf11c4eac9b7ef90596",
@@ -42,7 +44,7 @@
42
44
  "assets/js/search/utils.js": "a805c21d30ce0d02007609d510c43e99cbb53681ed37b77dd0755af4ccb39e08",
43
45
  "assets/js/subscriptions.js": "053b8387521deb5d4f940ef9effc816bc7e754e351460bda098cccd112d0b0c3",
44
46
  "assets/js/subscriptions/form.js": "8eee3b658940140bdada2648daae37f11261e38a5a896e863771b12586030898",
45
- "assets/js/subscriptions/manage.js": "0600cb83cf580e2430c87b9128fba62d42aa1741a12d8ce49ddefb940881f4f3",
47
+ "assets/js/subscriptions/manage.js": "4948505dacad370caaccfac389e8721a02f3755d50bdbf54dffafb4e44e1932d",
46
48
  "assets/js/visualizations.js": "a0414ea4ec2fd0829d049642420d753529c331966958c298901bff46d8cc26d4",
47
49
  "assets/js/webmentions.js": "f36402477bf0e6bd835e3ccf3a0fb67eb3af590ef6da179613eab10409e79803",
48
50
  "assets/js/webmentions/list.js": "64dfbad25539d4ae2571bcd61c24a869520c1ec3c1a191b22d4984f1e88c21cf",
@@ -1 +1 @@
1
- import{a as O,b as R,c as q,d as T,e as I}from"./chunks/chunk-WIRUK3OZ.js";import{a as x,c as C}from"./chunks/chunk-VZ5WKQVA.js";import"./chunks/chunk-PATLC23F.js";var h="subscriptions",M=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;function N(e,a){let s=e.querySelector(a);if(!s)return{};try{return JSON.parse(s.textContent)||{}}catch(t){return{}}}function $(e,a={}){let s={email:e.email||""};return a.hasTopics&&(s.topics=[].concat(e.topics||[]).filter(Boolean)),a.sourceUrl&&(s.source_url=a.sourceUrl),a.locale&&(s.locale=a.locale),s}function j(e,a,s={}){let t={};return M.test(e.email)||(t.email=s.email_invalid||"That does not look like an email address."),Array.isArray(e.topics)&&e.topics.length===0&&(t.topics=s.topics_required||"Pick at least one."),a.website&&(t.website="spam"),Object.keys(t).length>0?t:null}function P(e,a={}){let s=a.client||C(),t=e.querySelector("form"),c=N(e,"[data-subscribe-labels]"),g=c.errors||{},S=e.ownerDocument,o=S.querySelector('link[rel="canonical"]'),k={sourceUrl:e.dataset.subscribeSource||o&&o.href||"",locale:e.dataset.subscribeLocale||S.documentElement.lang||"",hasTopics:t.querySelector('[name="topics"]')!==null},v=e.dataset.subscribeDoubleOptIn!=="false",E=O(t),w=d=>{t.dataset.result=d,q(t,"success",c[d]||"")};if(!s.enabled)return q(t,"disabled",g.disabled||""),{form:t,available:!1,submit:async()=>null};let f=R(s,h,t,g);t.addEventListener("focusin",()=>f().catch(()=>{}),{once:!0});let b={form:t,available:!0,async submit(){let d=T(t),p=$(d,k),l=j(p,d,c);if(delete t.dataset.result,l)return l.website?(w("pending"),null):(q(t,"invalid",g.invalid||"",l),null);q(t,"pending",c.sending||"");try{await f()}catch(i){return null}try{let i=await s.post(s.pathFor(h),p,{idempotencyKey:E.key(p)}),u=i&&i.data&&typeof i.data.status=="string"?i.data.status:"";return E.clear(),t.reset(),w(u==="confirmed"||u==="subscribed"||u===""&&!v?"confirmed":"pending"),i}catch(i){return i&&i.kind==="conflict"?w("duplicate"):I(t,i,g),null}}};return t.addEventListener("submit",d=>{d.preventDefault(),b.submit()}),b}function U(e=document){return Array.from(e.querySelectorAll("[data-subscribe]")).map(a=>P(a))}var J=["confirm","unsubscribe","manage"],K=/^[A-Za-z0-9._~-]{8,512}$/;function z(e,a){let s=e.querySelector(a);if(!s)return{};try{return JSON.parse(s.textContent)||{}}catch(t){return{}}}function B(e){let a=new URLSearchParams(e||""),s=J.find(c=>a.has(c));if(!s)return null;let t=(a.get(s)||"").trim();return K.test(t)?{action:s,token:t}:null}function V(e,a={}){let s=e.ownerDocument,t=s.defaultView,c=a.client||C(),g=a.location||t&&t.location||{search:"",pathname:"/"},S=a.history===void 0?t&&t.history:a.history,o=z(e,"[data-manage-labels]"),k=o.errors||{},v=e.querySelector("[data-manage-status]"),E=e.querySelector("[data-manage-idle]"),w=e.querySelector("[data-manage-unsubscribe-panel]"),f=e.querySelector("[data-manage-preferences]"),b=B(g.search),d=b?`${c.pathFor(h)}/${encodeURIComponent(b.token)}`:"",p=null,l=s.createElement("button");l.type="button",l.className="post-tool",l.dataset.manageRetry="",l.textContent=o.retry||"Try again",l.hidden=!0,l.addEventListener("click",()=>p==null?void 0:p()),e.appendChild(l);let i=(r,n="",A=!1)=>{e.dataset.state=r,l.hidden=r!=="error"||!p,v&&(v.textContent=n,v.hidden=n==="",v.setAttribute("role",A?"alert":"status"))},u=r=>{[E,w,f].forEach(n=>{n&&(n.hidden=n!==r)})},F=(r,n,A)=>{let L=n&&r&&(r.kind==="not_found"||r.status===410);p=r!=null&&r.retryable?A:null,u(null),i("error",L?o.invalid_link||"":x(r,k),!0)},y=r=>{e.setAttribute("aria-busy",r?"true":"false"),e.querySelectorAll("button").forEach(n=>{n.disabled=r})},m={root:e,request:b,async confirm(){let r=!1;y(!0),u(null),i("pending",o.confirming||"");try{await c.feature(h),r=!0;let n=await c.post(`${c.pathFor(h)}/confirm`,{token:b.token});return i("confirmed",o.confirmed||""),n}catch(n){return F(n,r,()=>m.confirm()),null}finally{y(!1)}},async unsubscribe(){let r=!1;y(!0),i("pending",o.unsubscribing||"");try{await c.feature(h),r=!0;let n=await c.delete(d);return u(null),i("unsubscribed",o.unsubscribed||""),n}catch(n){return F(n,r,()=>m.unsubscribe()),null}finally{y(!1)}},async load(){let r=!1;y(!0),u(null),i("loading",o.loading||"");try{await c.feature(h),r=!0;let n=await c.get(d),A=n&&n.data&&Array.isArray(n.data.topics)?n.data.topics.map(String):[];return f&&f.querySelectorAll('[name="topics"]').forEach(L=>{L.checked=A.includes(L.value)}),u(f),i("loaded"),A}catch(n){return F(n,r,()=>m.load()),null}finally{y(!1)}},async save(){let r=Array.from(f.querySelectorAll('[name="topics"]:checked')).map(n=>n.value);y(!0),i("pending",o.saving||"");try{let n=await c.patch(d,{topics:r});return i("saved",o.saved||""),n}catch(n){return i("error",x(n,k),!0),null}finally{y(!1)}}};return e.querySelectorAll("[data-manage-unsubscribe]").forEach(r=>{r.addEventListener("click",()=>m.unsubscribe())}),f&&f.addEventListener("submit",r=>{r.preventDefault(),m.save()}),c.enabled?b?(S&&typeof S.replaceState=="function"&&S.replaceState(null,"",g.pathname),b.action==="confirm"?m.confirm():b.action==="manage"?m.load():(u(w),i("idle")),m):(u(E),i("idle"),m):(u(null),i("disabled",k.disabled||""),m)}function D(e=document){return Array.from(e.querySelectorAll("[data-subscription-manage]")).map(a=>V(a))}function _(){U(document),D(document)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",_,{once:!0}):_();
1
+ import{a as O,b as R,c as q,d as T,e as I}from"./chunks/chunk-WIRUK3OZ.js";import{a as x,c as C}from"./chunks/chunk-VZ5WKQVA.js";import"./chunks/chunk-PATLC23F.js";var h="subscriptions",M=/^[^\s@]+@[^\s@]+\.[^\s@]+$/;function N(e,a){let s=e.querySelector(a);if(!s)return{};try{return JSON.parse(s.textContent)||{}}catch(n){return{}}}function $(e,a={}){let s={email:e.email||""};return a.hasTopics&&(s.topics=[].concat(e.topics||[]).filter(Boolean)),a.sourceUrl&&(s.source_url=a.sourceUrl),a.locale&&(s.locale=a.locale),s}function j(e,a,s={}){let n={};return M.test(e.email)||(n.email=s.email_invalid||"That does not look like an email address."),Array.isArray(e.topics)&&e.topics.length===0&&(n.topics=s.topics_required||"Pick at least one."),a.website&&(n.website="spam"),Object.keys(n).length>0?n:null}function P(e,a={}){let s=a.client||C(),n=e.querySelector("form"),c=N(e,"[data-subscribe-labels]"),g=c.errors||{},S=e.ownerDocument,o=S.querySelector('link[rel="canonical"]'),k={sourceUrl:e.dataset.subscribeSource||o&&o.href||"",locale:e.dataset.subscribeLocale||S.documentElement.lang||"",hasTopics:n.querySelector('[name="topics"]')!==null},v=e.dataset.subscribeDoubleOptIn!=="false",E=O(n),w=d=>{n.dataset.result=d,q(n,"success",c[d]||"")};if(!s.enabled)return q(n,"disabled",g.disabled||""),{form:n,available:!1,submit:async()=>null};let m=R(s,h,n,g);n.addEventListener("focusin",()=>m().catch(()=>{}),{once:!0});let p={form:n,available:!0,async submit(){let d=T(n),f=$(d,k),l=j(f,d,c);if(delete n.dataset.result,l)return l.website?(w("pending"),null):(q(n,"invalid",g.invalid||"",l),null);q(n,"pending",c.sending||"");try{await m()}catch(i){return null}try{let i=await s.post(s.pathFor(h),f,{idempotencyKey:E.key(f)}),u=i&&i.data&&typeof i.data.status=="string"?i.data.status:"";return E.clear(),n.reset(),w(u==="confirmed"||u==="subscribed"||u===""&&!v?"confirmed":"pending"),i}catch(i){return i&&i.kind==="conflict"?w("duplicate"):I(n,i,g),null}}};return n.addEventListener("submit",d=>{d.preventDefault(),p.submit()}),p}function U(e=document){return Array.from(e.querySelectorAll("[data-subscribe]")).map(a=>P(a))}var J=["confirm","unsubscribe","manage"],K=/^[A-Za-z0-9._~-]{8,512}$/;function z(e,a){let s=e.querySelector(a);if(!s)return{};try{return JSON.parse(s.textContent)||{}}catch(n){return{}}}function B(e){let a=new URLSearchParams(e||""),s=J.find(c=>a.has(c));if(!s)return null;let n=(a.get(s)||"").trim();return K.test(n)?{action:s,token:n}:null}function V(e,a={}){let s=e.ownerDocument,n=s.defaultView,c=a.client||C(),g=a.location||n&&n.location||{search:"",pathname:"/"},S=a.history===void 0?n&&n.history:a.history,o=z(e,"[data-manage-labels]"),k=o.errors||{},v=e.querySelector("[data-manage-status]"),E=e.querySelector("[data-manage-idle]"),w=e.querySelector("[data-manage-unsubscribe-panel]"),m=e.querySelector("[data-manage-preferences]"),p=B(g.search),d=p?`${c.pathFor(h)}/${encodeURIComponent(p.token)}`:"",f=null,l=s.createElement("button");l.type="button",l.className="post-tool",l.dataset.manageRetry="",l.textContent=o.retry||"Try again",l.hidden=!0,l.addEventListener("click",()=>f==null?void 0:f()),e.appendChild(l);let i=(r,t="",A=!1)=>{r!=="error"&&(f=null),e.dataset.state=r,l.hidden=r!=="error"||!f,v&&(v.textContent=t,v.hidden=t==="",v.setAttribute("role",A?"alert":"status"))},u=r=>{[E,w,m].forEach(t=>{t&&(t.hidden=t!==r)})},F=(r,t,A)=>{let L=t&&r&&(r.kind==="not_found"||r.status===410);f=r!=null&&r.retryable?A:null,u(null),i("error",L?o.invalid_link||"":x(r,k),!0)},y=r=>{e.setAttribute("aria-busy",r?"true":"false"),e.querySelectorAll("button").forEach(t=>{t.disabled=r})},b={root:e,request:p,async confirm(){let r=!1;y(!0),u(null),i("pending",o.confirming||"");try{await c.feature(h),r=!0;let t=await c.post(`${c.pathFor(h)}/confirm`,{token:p.token});return i("confirmed",o.confirmed||""),t}catch(t){return F(t,r,()=>b.confirm()),null}finally{y(!1)}},async unsubscribe(){let r=!1;y(!0),i("pending",o.unsubscribing||"");try{await c.feature(h),r=!0;let t=await c.delete(d);return u(null),i("unsubscribed",o.unsubscribed||""),t}catch(t){return F(t,r,()=>b.unsubscribe()),null}finally{y(!1)}},async load(){let r=!1;y(!0),u(null),i("loading",o.loading||"");try{await c.feature(h),r=!0;let t=await c.get(d),A=t&&t.data&&Array.isArray(t.data.topics)?t.data.topics.map(String):[];return m&&m.querySelectorAll('[name="topics"]').forEach(L=>{L.checked=A.includes(L.value)}),u(m),i("loaded"),A}catch(t){return F(t,r,()=>b.load()),null}finally{y(!1)}},async save(){let r=Array.from(m.querySelectorAll('[name="topics"]:checked')).map(t=>t.value);y(!0),i("pending",o.saving||"");try{let t=await c.patch(d,{topics:r});return i("saved",o.saved||""),t}catch(t){return f=t!=null&&t.retryable?()=>b.save():null,i("error",x(t,k),!0),null}finally{y(!1)}}};return e.querySelectorAll("[data-manage-unsubscribe]").forEach(r=>{r.addEventListener("click",()=>b.unsubscribe())}),m&&m.addEventListener("submit",r=>{r.preventDefault(),b.save()}),c.enabled?p?(S&&typeof S.replaceState=="function"&&S.replaceState(null,"",g.pathname),p.action==="confirm"?b.confirm():p.action==="manage"?b.load():(u(w),i("idle")),b):(u(E),i("idle"),b):(u(null),i("disabled",k.disabled||""),b)}function D(e=document){return Array.from(e.querySelectorAll("[data-subscription-manage]")).map(a=>V(a))}function _(){U(document),D(document)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",_,{once:!0}):_();
data/lib/datalog/cli.rb CHANGED
@@ -9,6 +9,7 @@ require "tmpdir"
9
9
  require "yaml"
10
10
  require "thor"
11
11
 
12
+ require_relative "slug"
12
13
  require_relative "theme/version"
13
14
 
14
15
  module Datalog
@@ -116,19 +117,17 @@ module Datalog
116
117
  gemfile = gemfile_path(root)
117
118
 
118
119
  if File.exist?(gemfile)
119
- say_status :bundle, "bundle update datalog-theme", :blue
120
- unless system({ "BUNDLE_GEMFILE" => gemfile }, "bundle update datalog-theme", chdir: root)
121
- say_error "Bundler could not update datalog-theme."
122
- exit 1
123
- end
120
+ run_or_exit(:bundle, "bundle update datalog-theme", root, "Bundler could not update datalog-theme.",
121
+ { "BUNDLE_GEMFILE" => gemfile })
124
122
  else
125
123
  say_status :skip, "No Gemfile detected—skipping Bundler update", :yellow
126
124
  end
127
125
 
128
126
  package_json = File.join(root, "package.json")
129
127
  if File.exist?(package_json) && command_available?("npm")
130
- say_status :npm, "npm install", :blue
131
- system("npm install", chdir: root)
128
+ # npm's exit status used to be dropped, so a failed install still ended
129
+ # with "Theme dependencies are up to date!".
130
+ run_or_exit(:npm, "npm install", root, "npm could not install the site's packages.")
132
131
  elsif File.exist?(package_json)
133
132
  say_status :warn, "Node.js tooling not available—skipping npm install", :yellow
134
133
  end
@@ -157,7 +156,7 @@ module Datalog
157
156
  end
158
157
 
159
158
  def slugify(text)
160
- text.downcase.strip.gsub(/[^a-z0-9]+/, "-").gsub(/^-|-$/, "")
159
+ Datalog::Slug.of(text)
161
160
  end
162
161
 
163
162
  def ask_with_default(prompt, default)
@@ -434,6 +433,16 @@ module Datalog
434
433
 
435
434
  private
436
435
 
436
+ # Announces one dependency command, runs it, and stops the update where it
437
+ # fails rather than carrying on to report success.
438
+ def run_or_exit(label, command, root, message, env = {})
439
+ say_status label, command, :blue
440
+ return if system(env, command, chdir: root)
441
+
442
+ say_error message
443
+ exit 1
444
+ end
445
+
437
446
  def site_root
438
447
  File.expand_path(options[:root])
439
448
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datalog
4
+ # The slug `datalog new` gives a scaffolded post, notebook or project. It
5
+ # names the file, and through the filename the page's URL, so it follows
6
+ # Jekyll's own rule rather than a second one of its own.
7
+ module Slug
8
+ module_function
9
+
10
+ # "Análise de Séries" becomes "analise-de-series". The ASCII-only
11
+ # character class this replaced deleted every accented letter instead of
12
+ # carrying it across, leaving "an-lise-de-s-ries".
13
+ def of(text)
14
+ require "jekyll"
15
+
16
+ normalized = utf8(text)
17
+ latin = Jekyll::Utils.slugify(normalized, mode: "latin").to_s
18
+ # A title written in a script the transliteration table does not cover —
19
+ # Greek, Cyrillic, Chinese — comes back empty. Keeping its own
20
+ # characters beats naming the file after its date alone.
21
+ latin.empty? ? Jekyll::Utils.slugify(normalized).to_s : latin
22
+ end
23
+
24
+ # What the console hands over is tagged with the console's encoding, which
25
+ # on Windows is rarely UTF-8, and transliteration raises on a string whose
26
+ # tag and bytes disagree. The bytes are usually UTF-8 already, so they are
27
+ # re-tagged when that reads, and converted when it does not.
28
+ def utf8(text)
29
+ string = text.to_s
30
+ return string if string.encoding == Encoding::UTF_8 && string.valid_encoding?
31
+
32
+ retagged = string.dup.force_encoding(Encoding::UTF_8)
33
+ return retagged if retagged.valid_encoding?
34
+
35
+ string.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "")
36
+ end
37
+ end
38
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Datalog
4
4
  module Theme
5
- VERSION = "0.9.1" unless const_defined?(:VERSION)
5
+ VERSION = "0.10.0" unless const_defined?(:VERSION)
6
6
  # The Ruby the gem installs on, for the gemspec and `datalog check`. The
7
7
  # release workflow rewrites every quoted version assigned to a name ending
8
8
  # in VERSION in this file, so this name must not end that way.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datalog-theme
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Diogo Ribeiro
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-20 00:00:00.000000000 Z
11
+ date: 2026-09-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -329,7 +329,6 @@ files:
329
329
  - _includes/components/responsive-image.html
330
330
  - _includes/components/revision-history.html
331
331
  - _includes/components/revision-notice.html
332
- - _includes/components/search-facets.html
333
332
  - _includes/components/series-nav.html
334
333
  - _includes/components/social-share.html
335
334
  - _includes/components/subscribe-form.html
@@ -387,6 +386,7 @@ files:
387
386
  - _plugins/front_matter_compat.rb
388
387
  - _plugins/i18n.rb
389
388
  - _plugins/image_optimizer.rb
389
+ - _plugins/js_manifest_filter.rb
390
390
  - _plugins/licenses.rb
391
391
  - _plugins/math_preprocessor.rb
392
392
  - _plugins/notebook_converter.rb
@@ -481,6 +481,7 @@ files:
481
481
  - lib/datalog/plugins/comments.rb
482
482
  - lib/datalog/plugins/search.rb
483
483
  - lib/datalog/plugins/slides.rb
484
+ - lib/datalog/slug.rb
484
485
  - lib/datalog/theme/installed_files.rb
485
486
  - lib/datalog/theme/package.rb
486
487
  - lib/datalog/theme/repository_checkout.rb
@@ -1,307 +0,0 @@
1
- {%- comment -%}
2
- Advanced Search with Facets/Filters
3
- Provides filtering by tags, categories, dates, and authors
4
-
5
- Parameters:
6
- - show_tags: Show tag filter (default: true)
7
- - show_categories: Show category filter (default: true)
8
- - show_dates: Show date filter (default: true)
9
- - show_authors: Show author filter (default: true)
10
- - show_difficulty: Show difficulty filter (default: true)
11
- {%- endcomment -%}
12
-
13
- {%- assign show_tags = include.show_tags | default: true -%}
14
- {%- assign show_categories = include.show_categories | default: true -%}
15
- {%- assign show_dates = include.show_dates | default: true -%}
16
- {%- assign show_authors = include.show_authors | default: true -%}
17
- {%- assign show_difficulty = include.show_difficulty | default: true -%}
18
-
19
- <div class="search-facets" data-search-facets>
20
- <div class="search-facets__header">
21
- <h2 class="search-facets__title">Filter & Search</h2>
22
- <button type="button"
23
- class="search-facets__reset"
24
- data-reset-filters
25
- aria-label="Reset all filters">
26
- Reset
27
- </button>
28
- </div>
29
-
30
- {%- comment -%} Search input {%- endcomment -%}
31
- <div class="search-facets__search">
32
- <label for="search-input" class="visually-hidden">Search posts</label>
33
- <input type="search"
34
- id="search-input"
35
- class="search-facets__input"
36
- placeholder="Search posts..."
37
- data-search-input
38
- aria-label="Search posts">
39
- <svg class="search-facets__search-icon" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
40
- <circle cx="11" cy="11" r="8"></circle>
41
- <path d="m21 21-4.35-4.35"></path>
42
- </svg>
43
- </div>
44
-
45
- {%- comment -%} Filter panels {%- endcomment -%}
46
- <div class="search-facets__panels">
47
-
48
- {%- if show_tags -%}
49
- {%- comment -%} Collect all tags {%- endcomment -%}
50
- {%- assign all_tags = "" | split: "" -%}
51
- {%- for post in site.posts -%}
52
- {%- if post.tags -%}
53
- {%- assign all_tags = all_tags | concat: post.tags -%}
54
- {%- endif -%}
55
- {%- endfor -%}
56
- {%- assign unique_tags = all_tags | uniq | sort -%}
57
-
58
- {%- if unique_tags.size > 0 -%}
59
- <details class="search-facets__panel" open>
60
- <summary class="search-facets__panel-title">
61
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
62
- <path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path>
63
- <line x1="7" y1="7" x2="7.01" y2="7"></line>
64
- </svg>
65
- Tags
66
- </summary>
67
- <div class="search-facets__options">
68
- {%- for tag in unique_tags limit: 20 -%}
69
- <label class="search-facets__checkbox">
70
- <input type="checkbox"
71
- value="{{ tag | slugify }}"
72
- data-filter="tag"
73
- aria-label="Filter by {{ tag }}">
74
- <span>{{ tag }}</span>
75
- </label>
76
- {%- endfor -%}
77
- </div>
78
- </details>
79
- {%- endif -%}
80
- {%- endif -%}
81
-
82
- {%- if show_categories -%}
83
- {%- comment -%} Collect all categories {%- endcomment -%}
84
- {%- assign all_categories = "" | split: "" -%}
85
- {%- for post in site.posts -%}
86
- {%- if post.categories -%}
87
- {%- assign all_categories = all_categories | concat: post.categories -%}
88
- {%- endif -%}
89
- {%- endfor -%}
90
- {%- assign unique_categories = all_categories | uniq | sort -%}
91
-
92
- {%- if unique_categories.size > 0 -%}
93
- <details class="search-facets__panel">
94
- <summary class="search-facets__panel-title">
95
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
96
- <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
97
- </svg>
98
- Categories
99
- </summary>
100
- <div class="search-facets__options">
101
- {%- for category in unique_categories -%}
102
- <label class="search-facets__checkbox">
103
- <input type="checkbox"
104
- value="{{ category | slugify }}"
105
- data-filter="category"
106
- aria-label="Filter by {{ category }}">
107
- <span>{{ category }}</span>
108
- </label>
109
- {%- endfor -%}
110
- </div>
111
- </details>
112
- {%- endif -%}
113
- {%- endif -%}
114
-
115
- {%- if show_difficulty -%}
116
- <details class="search-facets__panel">
117
- <summary class="search-facets__panel-title">
118
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
119
- <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
120
- </svg>
121
- Difficulty
122
- </summary>
123
- <div class="search-facets__options">
124
- <label class="search-facets__checkbox">
125
- <input type="checkbox" value="beginner" data-filter="difficulty" aria-label="Filter by beginner">
126
- <span>Beginner</span>
127
- </label>
128
- <label class="search-facets__checkbox">
129
- <input type="checkbox" value="intermediate" data-filter="difficulty" aria-label="Filter by intermediate">
130
- <span>Intermediate</span>
131
- </label>
132
- <label class="search-facets__checkbox">
133
- <input type="checkbox" value="advanced" data-filter="difficulty" aria-label="Filter by advanced">
134
- <span>Advanced</span>
135
- </label>
136
- <label class="search-facets__checkbox">
137
- <input type="checkbox" value="expert" data-filter="difficulty" aria-label="Filter by expert">
138
- <span>Expert</span>
139
- </label>
140
- </div>
141
- </details>
142
- {%- endif -%}
143
-
144
- {%- if show_dates -%}
145
- <details class="search-facets__panel">
146
- <summary class="search-facets__panel-title">
147
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
148
- <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
149
- <line x1="16" y1="2" x2="16" y2="6"></line>
150
- <line x1="8" y1="2" x2="8" y2="6"></line>
151
- <line x1="3" y1="10" x2="21" y2="10"></line>
152
- </svg>
153
- Date Range
154
- </summary>
155
- <div class="search-facets__options">
156
- <label class="search-facets__checkbox">
157
- <input type="radio" name="date-range" value="week" data-filter="date" aria-label="Past week">
158
- <span>Past week</span>
159
- </label>
160
- <label class="search-facets__checkbox">
161
- <input type="radio" name="date-range" value="month" data-filter="date" aria-label="Past month">
162
- <span>Past month</span>
163
- </label>
164
- <label class="search-facets__checkbox">
165
- <input type="radio" name="date-range" value="year" data-filter="date" aria-label="Past year">
166
- <span>Past year</span>
167
- </label>
168
- <label class="search-facets__checkbox">
169
- <input type="radio" name="date-range" value="all" data-filter="date" checked aria-label="All time">
170
- <span>All time</span>
171
- </label>
172
- </div>
173
- </details>
174
- {%- endif -%}
175
- </div>
176
-
177
- {%- comment -%} Results count {%- endcomment -%}
178
- <div class="search-facets__results" data-results-count aria-live="polite">
179
- <span class="search-facets__count">{{ site.posts.size }}</span> posts found
180
- </div>
181
- </div>
182
-
183
- <script>
184
- (function() {
185
- 'use strict';
186
-
187
- document.addEventListener('DOMContentLoaded', function() {
188
- const facetsContainer = document.querySelector('[data-search-facets]');
189
- if (!facetsContainer) return;
190
-
191
- const searchInput = facetsContainer.querySelector('[data-search-input]');
192
- const resetButton = facetsContainer.querySelector('[data-reset-filters]');
193
- const resultsCount = facetsContainer.querySelector('[data-results-count] .search-facets__count]');
194
- const checkboxes = facetsContainer.querySelectorAll('[data-filter]');
195
-
196
- // Get all posts data
197
- const posts = Array.from(document.querySelectorAll('[data-post]')).map(post => ({
198
- element: post,
199
- title: post.dataset.title?.toLowerCase() || '',
200
- tags: post.dataset.tags?.toLowerCase().split(',') || [],
201
- categories: post.dataset.categories?.toLowerCase().split(',') || [],
202
- difficulty: post.dataset.difficulty?.toLowerCase() || '',
203
- date: new Date(post.dataset.date || 0),
204
- excerpt: post.dataset.excerpt?.toLowerCase() || ''
205
- }));
206
-
207
- function applyFilters() {
208
- const searchTerm = searchInput?.value.toLowerCase() || '';
209
- const activeFilters = {
210
- tags: [],
211
- categories: [],
212
- difficulty: [],
213
- date: 'all'
214
- };
215
-
216
- checkboxes.forEach(cb => {
217
- if (cb.checked) {
218
- const filterType = cb.dataset.filter;
219
- if (filterType === 'date') {
220
- activeFilters.date = cb.value;
221
- } else {
222
- activeFilters[filterType].push(cb.value);
223
- }
224
- }
225
- });
226
-
227
- let visibleCount = 0;
228
-
229
- posts.forEach(post => {
230
- let isVisible = true;
231
-
232
- // Search term filter
233
- if (searchTerm && !post.title.includes(searchTerm) && !post.excerpt.includes(searchTerm)) {
234
- isVisible = false;
235
- }
236
-
237
- // Tag filter
238
- if (activeFilters.tags.length > 0) {
239
- const hasTag = activeFilters.tags.some(tag => post.tags.includes(tag));
240
- if (!hasTag) isVisible = false;
241
- }
242
-
243
- // Category filter
244
- if (activeFilters.categories.length > 0) {
245
- const hasCategory = activeFilters.categories.some(cat => post.categories.includes(cat));
246
- if (!hasCategory) isVisible = false;
247
- }
248
-
249
- // Difficulty filter
250
- if (activeFilters.difficulty.length > 0 && !activeFilters.difficulty.includes(post.difficulty)) {
251
- isVisible = false;
252
- }
253
-
254
- // Date filter
255
- if (activeFilters.date !== 'all') {
256
- const now = new Date();
257
- const cutoff = new Date();
258
- switch (activeFilters.date) {
259
- case 'week':
260
- cutoff.setDate(now.getDate() - 7);
261
- break;
262
- case 'month':
263
- cutoff.setMonth(now.getMonth() - 1);
264
- break;
265
- case 'year':
266
- cutoff.setFullYear(now.getFullYear() - 1);
267
- break;
268
- }
269
- if (post.date < cutoff) isVisible = false;
270
- }
271
-
272
- // Show/hide post
273
- post.element.style.display = isVisible ? '' : 'none';
274
- if (isVisible) visibleCount++;
275
- });
276
-
277
- // Update count
278
- if (resultsCount) {
279
- resultsCount.textContent = visibleCount;
280
- }
281
- }
282
-
283
- // Event listeners
284
- if (searchInput) {
285
- searchInput.addEventListener('input', applyFilters);
286
- }
287
-
288
- checkboxes.forEach(cb => {
289
- cb.addEventListener('change', applyFilters);
290
- });
291
-
292
- if (resetButton) {
293
- resetButton.addEventListener('click', function() {
294
- if (searchInput) searchInput.value = '';
295
- checkboxes.forEach(cb => {
296
- if (cb.type === 'radio' && cb.value === 'all') {
297
- cb.checked = true;
298
- } else {
299
- cb.checked = false;
300
- }
301
- });
302
- applyFilters();
303
- });
304
- }
305
- });
306
- })();
307
- </script>