{"id":13576400,"url":"https://github.com/71/logseq-snippets","last_synced_at":"2025-06-14T04:06:38.491Z","repository":{"id":39115611,"uuid":"314390027","full_name":"71/logseq-snippets","owner":"71","description":"Snippets I'm using with logseq.com.","archived":false,"fork":false,"pushed_at":"2021-06-23T19:32:01.000Z","size":47,"stargazers_count":73,"open_issues_count":0,"forks_count":4,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-06-10T23:56:26.954Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/71.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-11-19T22:59:01.000Z","updated_at":"2025-04-09T13:25:44.000Z","dependencies_parsed_at":"2022-08-01T07:50:04.555Z","dependency_job_id":null,"html_url":"https://github.com/71/logseq-snippets","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/71/logseq-snippets","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/71%2Flogseq-snippets","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/71%2Flogseq-snippets/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/71%2Flogseq-snippets/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/71%2Flogseq-snippets/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/71","download_url":"https://codeload.github.com/71/logseq-snippets/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/71%2Flogseq-snippets/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259756871,"owners_count":22906678,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T15:01:09.942Z","updated_at":"2025-06-14T04:06:38.478Z","avatar_url":"https://github.com/71.png","language":"JavaScript","readme":"## Loading scripts from this repository\nA few scripts are exported from this repository. To load them within a page, you can use\n\n```js\n// For update-rss.js\nimport(\"https://cdn.jsdelivr.net/gh/71/logseq-snippets/update-rss.js\")\n```\n\nSince you shouldn't trust random scripts you load from the internet, you should specify\nthe hash of the last commit you looked at, e.g.\n\n```js\n// For update-rss.js\nimport(\"https://cdn.jsdelivr.net/gh/71/logseq-snippets@de5c4a035657dd96a91252d189fb2d0aed6261b3/update-rss.js\")\n```\n\nNote that you can't directly link to GitHub (via `?raw=true`), since the MIME type wouldn't be correct.\n\n## Fake \"Recent\" block in `Contents.md`\nThis will render a block-like list with all the recently modified pages.\n\nIt's a little hacky because Logseq queries cannot return pages, so we must\nmake them look like blocks manually.\n\n```clojure\n@@html: \u003cdiv style=\"display:none\"\u003e@@\n#+BEGIN_QUERY\n{:title \"Recent\"\n :query [:find (pull ?p [*])\n         :in $ ?start\n         :where\n         [?p :page/last-modified-at ?d]\n         [?p :page/name ?n]\n         [(\u003e= ?d ?start)]\n         [(not= ?n \"contents\")]]\n :inputs [:4d-before]\n :view (fn [result]\n  (for [p (take 4 (sort-by :page/last-modified-at \u003e result))]\n    [:div.ls-block.flex.flex-col.pt-1\n      [:div.flex-1.flex-row\n        [:div.mr-2.flex.flex-row.items-center\n          {:style {:height \"24px\" :margin-top \"0\" :float \"left\"}}\n          [:a.block-control.opacity-50.hover:opacity-100\n            {:style {:min-width \"0\" :height \"16px\" :margin-right \"2px\"}}\n            [:span]]\n          [:a\n            {:href (str \"/page/\" (:page/name p))}\n            [:span.bullet-container.cursor\n              [:span.bullet]]]]\n        [:div.flex.relative\n          [:div.flex-1.flex-col.relative.block-content\n            [:div\n              [:span.page-reference\n                [:a.page-ref\n                  {:href (str \"/page/\" (:page/name p))}\n                  (-\u003e p :page/properties :title)]]]]]]]))}\n#+END_QUERY\n\u003cstyle\u003e\n.custom-query { margin-top: 0; }\n.custom-query .opacity-70 { opacity: 1; }\n\u003c/style\u003e\n```\n\n## The `\u003cscript-block\u003e` element\n\nThis is a WebComponent that executes its content as JavaScript.\n\nFirst, import it:\n\n```html\n\u003cstyle onload=\"Function(this.innerHTML.slice(2, this.innerHTML.length - 2))()\"\u003e/*\nimport(\"https://cdn.jsdelivr.net/gh/71/logseq-snippets@main/script-block.js\")\n*/\u003c/style\u003e\n```\n\nUsage is:\n```html\n\u003cscript-block state='{}'\u003e\u003c!-- return document.createElement(\"div\"); --\u003e\u003c/script-block\u003e\n```\n\nWithin the JavaScript function, the function has access to:\n- `this`, the current state; can be mutated, and takes the value of the `state` attribute (after parsing from JSON).\n- `save`, a function that takes an object and:\n  1. Serializes the object to JSON.\n  2. Edits the source of the block so that `\u003cscript-block state='...'\u003e` becomes `\u003cscript-block state='${json}'\u003e`.\n  3. Saves the changes to the block.\n- `html` and `svg` from [htl](https://observablehq.com/@observablehq/htl).\n\nThis element therefore essentially allows you to define custom components based on JavaScript whose state\ncan be mutated and persisted. Since their state is saved to the underlying file, it will also be saved in\nthe Git repository.\n\nFor an example, see the [counter](#counter).\n\n### Counter\n\nIncrements by one everytime it is clicked.\n\n```html\n@@html: \u003cscript-block state='{\"count\":0}'\u003e\u003c!-- return html`\u003cbutton onclick=${() =\u003e save({ count: this.count + 1 })}\u003e${this.count ?? 0}`; --\u003e\u003c/script-block\u003e@@\n```\n\n## The `\u003cdefine-script-block\u003e` element\n\nThis is a WebComponent used to define reusable `\u003cscript-block\u003e` elements. For instance, let's implement\nthe [counter](#counter) again!\n\n### Reusable counter\n\nPut this anywhere:\n\n```html\n\u003cdefine-script-block name=\"x-counter\"\u003e\u003c!--\n  return html`\n    \u003cbutton onclick=${() =\u003e save({ count: +this.count + 1 })}\u003e\n      ${this.count}\n  `;\n--\u003e\u003c/define-script-block\u003e\n```\n\nAnd use it like this:\n\n```html\n@@html: \u003cx-counter count=\"0\"\u003e\u003c/x-counter\u003e@@\n```\n\nAlternatively to `\u003cdefine-script-block\u003e`:\n\n```html\n\u003cstyle onload=\"Function(this.innerHTML.slice(2, this.innerHTML.length - 2))()\"\u003e/*\nimport(\"https://cdn.jsdelivr.net/gh/71/logseq-snippets@main/script-block.js\")\n  .then(({ defineElement }) =\u003e defineElement(\"x-counter\", ({ save, html, count }) =\u003e html`\u003ca onclick=${() =\u003e save({ count: +count + 1 })}\u003e${count}`))\n*/\u003c/style\u003e\n```\n\n## [`update-rss.js`](./update-rss.js)\n\nThis script can be loaded in Logseq to automatically update a page named \"RSS\".\n\nIt must contain two top-level items. One must start with \"Feeds\", and contains \"feed descriptions.\"\nThe other must start with \"Items\", and will contain the feed items.\n\nFor instance:\n\n```md\n---\ntitle: RSS\n---\n\n- Feeds ( \u003ca onclick=\"import('https://cdn.jsdelivr.net/gh/71/logseq-snippets/update-rss.js#interval=0')\"\u003eRefresh\u003c/a\u003e )\n\t- [The Pudding](https://pudding.cool/feed/index.xml) \n\t  SCHEDULED: \u003c2021-06-24 Thu 11:0 .+1d\u003e\n\t- [XKCD](https://xkcd.com/atom.xml) \n\t  SCHEDULED: \u003c2021-06-25 Fri 12:0 .+2d\u003e\n\t- [The Rust Blog](https://blog.rust-lang.org/feed.xml) \n\t  SCHEDULED: \u003c2021-06-24 Thu 18:0 .+1d\u003e\n\t  \u003c!-- REGEXP: /^Announcing // --\u003e\n- Items\n\t- \u003c2021-06-21 00:00\u003e [[XKCD]]: [Houseguests](https://xkcd.com/2479/)\n```\n\nIf you use [ViolentMonkey](https://github.com/violentmonkey/violentmonkey), you can load the script on start-up and\ntell it to refresh its data every e.g. 60,000ms:\n\n```js\n// ==UserScript==\n// @match       https://logseq.com/\n// @grant       none\n// ==/UserScript==\n\nimport(\"https://cdn.jsdelivr.net/gh/71/logseq-snippets/update-rss.js#interval=60000\")\n```\n\nLoading it with the `force` parameter will reload all feeds, even if their `SCHEDULED` time hasn't been reached yet, e.g.\n\n```js\nimport(\"https://cdn.jsdelivr.net/gh/71/logseq-snippets/update-rss.js#force\")\n```\n\n### CORS\nTo bypass CORS, I use the following [ViolentMonkey](https://github.com/violentmonkey/violentmonkey) script:\n```js\n// ==UserScript==\n// @match       https://logseq.com/*\n// @grant       GM_xmlhttpRequest\n// @inject-into page\n// ==/UserScript==\n\nunsafeWindow.fetchNoCors = (url) =\u003e new Promise((resolve, reject) =\u003e GM_xmlhttpRequest({\n  url,\n  method: 'GET',\n\n  onabort: () =\u003e reject(),\n  onerror: () =\u003e reject(),\n\n  onloadend: (res) =\u003e resolve({ async text() { return res.responseText; } }),\n}));\n```\n\n## Execution scripts\n\nI use the following script to execute JS scripts automatically.\n\n```js\nconst watchedElements = [];\nconst observer = new MutationObserver((mutationsList) =\u003e {\n  for (const mutation of mutationsList) {\n    if (mutation.target.classList.contains(\"extensions__code\") \u0026\u0026\n        mutation.target.firstChild.textContent === \"js,run\") {\n      const code = mutation.target.children[1].value,\n            dispose = Function(code)();\n\n      if (typeof dispose === \"function\") {\n        watchedElements.push([mutation.target, dispose]);\n      }\n    } else if (mutation.removedNodes.length === 1) {\n      const removedNode = mutation.removedNodes[0];\n\n      for (const [watchedElement, dispose] of watchedElements) {\n        if (removedNode.contains(watchedElement)) {\n          dispose();\n        }\n      }\n    }\n  }\n});\n\nobserver.observe(\n  document.getElementById(\"main-content-container\"),\n  { subtree: true, childList: true },\n);\n```\n\nAnd then:\n\n````markdown\n```js,run\nconsole.log(\"Script loaded.\");\n\nreturn () =\u003e console.log(\"Script unloaded.\");\n```\n````\n","funding_links":[],"categories":["JavaScript","💡 Workflows and Innovations"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F71%2Flogseq-snippets","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F71%2Flogseq-snippets","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F71%2Flogseq-snippets/lists"}