Initial commit

Created from https://vercel.com/new
pull/1/head
Shwetha Jayaraj 2023-08-15 19:32:53 +00:00
commit 7988eb4033
55 changed files with 19610 additions and 0 deletions

500
.eleventy.js 100644
View File

@ -0,0 +1,500 @@
const slugify = require("@sindresorhus/slugify");
const markdownIt = require("markdown-it");
const fs = require("fs");
const matter = require("gray-matter");
const faviconsPlugin = require("eleventy-plugin-gen-favicons");
const tocPlugin = require("eleventy-plugin-nesting-toc");
const { parse } = require("node-html-parser");
const htmlMinifier = require("html-minifier");
const pluginRss = require("@11ty/eleventy-plugin-rss");
const { headerToId, namedHeadingsFilter } = require("./src/helpers/utils");
const {
userMarkdownSetup,
userEleventySetup,
} = require("./src/helpers/userSetup");
const Image = require("@11ty/eleventy-img");
function transformImage(src, cls, alt, sizes, widths = ["500", "700", "auto"]) {
let options = {
widths: widths,
formats: ["webp", "jpeg"],
outputDir: "./dist/img/optimized",
urlPath: "/img/optimized",
};
// generate images, while this is async we dont wait
Image(src, options);
let metadata = Image.statsSync(src, options);
return metadata;
}
const tagRegex = /(^|\s|\>)(#[^\s!@#$%^&*()=+\.,\[{\]};:'"?><]+)(?!([^<]*>))/g;
module.exports = function (eleventyConfig) {
eleventyConfig.setLiquidOptions({
dynamicPartials: true,
});
let markdownLib = markdownIt({
breaks: true,
html: true,
linkify: true,
})
.use(require("markdown-it-anchor"), {
slugify: headerToId,
})
.use(require("markdown-it-mark"))
.use(require("markdown-it-footnote"))
.use(function (md) {
md.renderer.rules.hashtag_open = function (tokens, idx) {
return '<a class="tag" onclick="toggleTagSearch(this)">';
};
})
.use(require("markdown-it-mathjax3"), {
tex: {
inlineMath: [["$", "$"]],
},
options: {
skipHtmlTags: { "[-]": ["pre"] },
},
})
.use(require("markdown-it-attrs"))
.use(require("markdown-it-task-checkbox"), {
disabled: true,
divWrap: false,
divClass: "checkbox",
idPrefix: "cbx_",
ulClass: "task-list",
liClass: "task-list-item",
})
.use(require("markdown-it-plantuml"), {
openMarker: "```plantuml",
closeMarker: "```",
})
.use(namedHeadingsFilter)
.use(function (md) {
//https://github.com/DCsunset/markdown-it-mermaid-plugin
const origFenceRule =
md.renderer.rules.fence ||
function (tokens, idx, options, env, self) {
return self.renderToken(tokens, idx, options, env, self);
};
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
const token = tokens[idx];
if (token.info === "mermaid") {
const code = token.content.trim();
return `<pre class="mermaid">${code}</pre>`;
}
if (token.info === "transclusion") {
const code = token.content.trim();
return `<div class="transclusion">${md.render(code)}</div>`;
}
if (token.info.startsWith("ad-")) {
const code = token.content.trim();
const parts = code.split("\n")
let titleLine;
let collapse;
let collapsible = false
let collapsed = true
let icon;
let color;
let nbLinesToSkip = 0
for (let i = 0; i<4; i++) {
if (parts[i] && parts[i].trim()) {
let line = parts[i] && parts[i].trim().toLowerCase()
if (line.startsWith("title:")) {
titleLine = line.substring(6);
nbLinesToSkip ++;
} else if (line.startsWith("icon:")) {
icon = line.substring(5);
nbLinesToSkip ++;
} else if (line.startsWith("collapse:")) {
collapsible = true
collapse = line.substring(9);
if (collapse && collapse.trim().toLowerCase() == 'open') {
collapsed = false
}
nbLinesToSkip ++;
} else if (line.startsWith("color:")) {
color = line.substring(6);
nbLinesToSkip ++;
}
}
}
const foldDiv = collapsible ? `<div class="callout-fold">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-chevron-down">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>` : "";
const titleDiv = titleLine
? `<div class="callout-title"><div class="callout-title-inner">${titleLine}</div>${foldDiv}</div>`
: "";
let collapseClasses = titleLine && collapsible ? 'is-collapsible' : ''
if (collapsible && collapsed) {
collapseClasses += " is-collapsed"
}
let res = `<div data-callout-metadata class="callout ${collapseClasses}" data-callout="${
token.info.substring(3)
}">${titleDiv}\n<div class="callout-content">${md.render(
parts.slice(nbLinesToSkip).join("\n")
)}</div></div>`;
return res
}
// Other languages
return origFenceRule(tokens, idx, options, env, slf);
};
const defaultImageRule =
md.renderer.rules.image ||
function (tokens, idx, options, env, self) {
return self.renderToken(tokens, idx, options, env, self);
};
md.renderer.rules.image = (tokens, idx, options, env, self) => {
const imageName = tokens[idx].content;
const [fileName, width] = imageName.split("|");
if (width) {
const widthIndex = tokens[idx].attrIndex("width");
const widthAttr = `${width}px`;
if (widthIndex < 0) {
tokens[idx].attrPush(["width", widthAttr]);
} else {
tokens[idx].attrs[widthIndex][1] = widthAttr;
}
}
return defaultImageRule(tokens, idx, options, env, self);
};
const defaultLinkRule =
md.renderer.rules.link_open ||
function (tokens, idx, options, env, self) {
return self.renderToken(tokens, idx, options, env, self);
};
md.renderer.rules.link_open = function (tokens, idx, options, env, self) {
const aIndex = tokens[idx].attrIndex("target");
const classIndex = tokens[idx].attrIndex("class");
if (aIndex < 0) {
tokens[idx].attrPush(["target", "_blank"]);
} else {
tokens[idx].attrs[aIndex][1] = "_blank";
}
if (classIndex < 0) {
tokens[idx].attrPush(["class", "external-link"]);
} else {
tokens[idx].attrs[classIndex][1] = "external-link";
}
return defaultLinkRule(tokens, idx, options, env, self);
};
})
.use(userMarkdownSetup);
eleventyConfig.setLibrary("md", markdownLib);
eleventyConfig.addFilter("isoDate", function (date) {
return date && date.toISOString();
});
eleventyConfig.addFilter("link", function (str) {
return (
str &&
str.replace(/\[\[(.*?\|.*?)\]\]/g, function (match, p1) {
//Check if it is an embedded excalidraw drawing or mathjax javascript
if (p1.indexOf("],[") > -1 || p1.indexOf('"$"') > -1) {
return match;
}
const [fileLink, linkTitle] = p1.split("|");
let fileName = fileLink.replaceAll("&amp;", "&");
let header = "";
let headerLinkPath = "";
if (fileLink.includes("#")) {
[fileName, header] = fileLink.split("#");
headerLinkPath = `#${headerToId(header)}`;
}
let permalink = `/notes/${slugify(fileName)}`;
let noteIcon = process.env.NOTE_ICON_DEFAULT;
const title = linkTitle ? linkTitle : fileName;
let deadLink = false;
try {
const startPath = "./src/site/notes/";
const fullPath = fileName.endsWith(".md")
? `${startPath}${fileName}`
: `${startPath}${fileName}.md`;
const file = fs.readFileSync(fullPath, "utf8");
const frontMatter = matter(file);
if (frontMatter.data.permalink) {
permalink = frontMatter.data.permalink;
}
if (
frontMatter.data.tags &&
frontMatter.data.tags.indexOf("gardenEntry") != -1
) {
permalink = "/";
}
if (frontMatter.data.noteIcon) {
noteIcon = frontMatter.data.noteIcon;
}
} catch {
deadLink = true;
}
if(deadLink){
return `<a class="internal-link is-unresolved" href="/404">${title}</a>`;
}
return `<a class="internal-link" data-note-icon="${noteIcon}" href="${permalink}${headerLinkPath}">${title}</a>`;
})
);
});
eleventyConfig.addFilter("taggify", function (str) {
return (
str &&
str.replace(tagRegex, function (match, precede, tag) {
return `${precede}<a class="tag" onclick="toggleTagSearch(this)" data-content="${tag}">${tag}</a>`;
})
);
});
eleventyConfig.addFilter("searchableTags", function (str) {
let tags;
let match = str && str.match(tagRegex);
if (match) {
tags = match
.map((m) => {
return `"${m.split("#")[1]}"`;
})
.join(", ");
}
if (tags) {
return `${tags},`;
} else {
return "";
}
});
eleventyConfig.addFilter("hideDataview", function (str) {
return (
str &&
str.replace(/\(\S+\:\:(.*)\)/g, function (_, value) {
return value.trim();
})
);
});
eleventyConfig.addTransform("callout-block", function (str) {
const parsed = parse(str);
const transformCalloutBlocks = (
blockquotes = parsed.querySelectorAll("blockquote")
) => {
for (const blockquote of blockquotes) {
transformCalloutBlocks(blockquote.querySelectorAll("blockquote"));
let content = blockquote.innerHTML;
let titleDiv = "";
let calloutType = "";
let isCollapsable;
let isCollapsed;
const calloutMeta = /\[!([\w-]*)\](\+|\-){0,1}(\s?.*)/;
if (!content.match(calloutMeta)) {
continue;
}
content = content.replace(
calloutMeta,
function (metaInfoMatch, callout, collapse, title) {
isCollapsable = Boolean(collapse);
isCollapsed = collapse === "-";
const titleText = title.replace(/(<\/{0,1}\w+>)/, "")
? title
: `${callout.charAt(0).toUpperCase()}${callout
.substring(1)
.toLowerCase()}`;
const fold = isCollapsable
? `<div class="callout-fold"><i icon-name="chevron-down"></i></div>`
: ``;
calloutType = callout;
titleDiv = `<div class="callout-title"><div class="callout-title-inner">${titleText}</div>${fold}</div>`;
return "";
}
);
blockquote.tagName = "div";
blockquote.classList.add("callout");
blockquote.classList.add(isCollapsable ? "is-collapsible" : "");
blockquote.classList.add(isCollapsed ? "is-collapsed" : "");
blockquote.setAttribute("data-callout", calloutType.toLowerCase());
blockquote.innerHTML = `${titleDiv}\n<div class="callout-content">${content}</div>`;
}
};
transformCalloutBlocks();
return str && parsed.innerHTML;
});
function fillPictureSourceSets(src, cls, alt, meta, width, imageTag) {
imageTag.tagName = "picture";
let html = `<source
media="(max-width:480px)"
srcset="${meta.webp[0].url}"
type="image/webp"
/>
<source
media="(max-width:480px)"
srcset="${meta.jpeg[0].url}"
/>
`
if (meta.webp && meta.webp[1] && meta.webp[1].url) {
html += `<source
media="(max-width:1920px)"
srcset="${meta.webp[1].url}"
type="image/webp"
/>`
}
if (meta.jpeg && meta.jpeg[1] && meta.jpeg[1].url) {
html += `<source
media="(max-width:1920px)"
srcset="${meta.jpeg[1].url}"
/>`
}
html += `<img
class="${cls.toString()}"
src="${src}"
alt="${alt}"
width="${width}"
/>`;
imageTag.innerHTML = html;
}
eleventyConfig.addTransform("picture", function (str) {
const parsed = parse(str);
for (const imageTag of parsed.querySelectorAll(".cm-s-obsidian img")) {
const src = imageTag.getAttribute("src");
if (src && src.startsWith("/") && !src.endsWith(".svg")) {
const cls = imageTag.classList.value;
const alt = imageTag.getAttribute("alt");
const width = imageTag.getAttribute("width") || '';
try {
const meta = transformImage(
"./src/site" + decodeURI(imageTag.getAttribute("src")),
cls.toString(),
alt,
["(max-width: 480px)", "(max-width: 1024px)"]
);
if (meta) {
fillPictureSourceSets(src, cls, alt, meta, width, imageTag);
}
} catch {
// Make it fault tolarent.
}
}
}
return str && parsed.innerHTML;
});
eleventyConfig.addTransform("table", function (str) {
const parsed = parse(str);
for (const t of parsed.querySelectorAll(".cm-s-obsidian > table")) {
let inner = t.innerHTML;
t.tagName = "div";
t.classList.add("table-wrapper");
t.innerHTML = `<table>${inner}</table>`;
}
for (const t of parsed.querySelectorAll(
".cm-s-obsidian > .block-language-dataview > table"
)) {
t.classList.add("dataview");
t.classList.add("table-view-table");
t.querySelector("thead")?.classList.add("table-view-thead");
t.querySelector("tbody")?.classList.add("table-view-tbody");
t.querySelectorAll("thead > tr")?.forEach((tr) => {
tr.classList.add("table-view-tr-header");
});
t.querySelectorAll("thead > tr > th")?.forEach((th) => {
th.classList.add("table-view-th");
});
}
return str && parsed.innerHTML;
});
eleventyConfig.addTransform("htmlMinifier", (content, outputPath) => {
if (
process.env.NODE_ENV === "production" &&
outputPath &&
outputPath.endsWith(".html")
) {
return htmlMinifier.minify(content, {
useShortDoctype: true,
removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true,
keepClosingSlash: true,
});
}
return content;
});
eleventyConfig.addPassthroughCopy("src/site/img");
eleventyConfig.addPassthroughCopy("src/site/scripts");
eleventyConfig.addPassthroughCopy("src/site/styles/_theme.*.css");
eleventyConfig.addPlugin(faviconsPlugin, { outputDir: "dist" });
eleventyConfig.addPlugin(tocPlugin, {
ul: true,
tags: ["h1", "h2", "h3", "h4", "h5", "h6"],
});
eleventyConfig.addFilter("dateToZulu", function (date) {
if (!date) return "";
return new Date(date).toISOString("dd-MM-yyyyTHH:mm:ssZ");
});
eleventyConfig.addFilter("jsonify", function (variable) {
return JSON.stringify(variable) || '""';
});
eleventyConfig.addFilter("validJson", function (variable) {
if (Array.isArray(variable)) {
return variable.map((x) => x.replaceAll("\\", "\\\\")).join(",");
} else if (typeof variable === "string") {
return variable.replaceAll("\\", "\\\\");
}
return variable;
});
eleventyConfig.addPlugin(pluginRss, {
posthtmlRenderOptions: {
closingSingleTag: "slash",
singleTags: ["link"],
},
});
userEleventySetup(eleventyConfig);
return {
dir: {
input: "src/site",
output: "dist",
data: `_data`,
},
templateFormats: ["njk", "md", "11ty.js"],
htmlTemplateEngine: "njk",
markdownTemplateEngine: "njk",
passthroughFileCopy: true,
};
};

1
.eleventyignore 100644
View File

@ -0,0 +1 @@
netlify/functions

4
.env 100644
View File

@ -0,0 +1,4 @@
#THEME=https://raw.githubusercontent.com/colineckert/obsidian-things/main/obsidian.css
#THEME=https://github.com/kepano/obsidian-minimal/blob/master/obsidian.css
#BASE_THEME=light
dgHomeLink=true

15
.github/dependabot.yml vendored 100644
View File

@ -0,0 +1,15 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
ignore:
- dependency-name: "@sindresorhus/slugify"
# For slugify, ignore all updates.

13
.gitignore vendored 100644
View File

@ -0,0 +1,13 @@
node_modules
dist
netlify/functions/search/data.json
netlify/functions/search/index.json
src/site/styles/theme.*.css
src/site/styles/_theme.*.css
# Local Netlify folder
.netlify
.idea/
.vercel
.cache
_site/
**/.DS_Store

9
README.md 100644
View File

@ -0,0 +1,9 @@
# Digital Obsidian Garden
This is the template to be used together with the [Digital Garden Obsidian Plugin](https://github.com/oleeskild/Obsidian-Digital-Garden).
See the README in the plugin repo for information on how to set it up.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/oleeskild/digitalgarden)
---
## Docs
Docs are available at [dg-docs.ole.dev](https://dg-docs.ole.dev/)

14
netlify.toml 100644
View File

@ -0,0 +1,14 @@
[build]
publish = "dist"
command = "npm install && npm run build"
[[redirects]]
from = "/api/*"
to = "/.netlify/functions/:splat"
status = 200
[[redirects]]
from = "/*"
to = "/404"
status = 404

5360
package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

46
package.json 100644
View File

@ -0,0 +1,46 @@
{
"name": "web",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "npm-run-all get-theme build:sass --parallel watch:*",
"watch:sass": "sass --watch src/site/styles:dist/styles",
"watch:eleventy": "cross-env ELEVENTY_ENV=dev eleventy --serve",
"build:eleventy": "cross-env ELEVENTY_ENV=prod NODE_OPTIONS=--max-old-space-size=4096 eleventy",
"build:sass": "sass src/site/styles:dist/styles --style compressed",
"get-theme": "node src/site/get-theme.js",
"build": "npm-run-all get-theme build:*"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@11ty/eleventy": "^2.0.1",
"@11ty/eleventy-plugin-rss": "^1.2.0",
"cross-env": "^7.0.3",
"html-minifier": "^4.0.0",
"node-html-parser": "^6.1.4",
"sass": "^1.49.9"
},
"dependencies": {
"@11ty/eleventy-img": "^3.0.0",
"@sindresorhus/slugify": "^1.1.0",
"axios": "^1.2.2",
"dotenv": "^16.0.3",
"eleventy-plugin-gen-favicons": "^1.1.2",
"eleventy-plugin-nesting-toc": "^1.3.0",
"fs-file-tree": "^1.1.1",
"glob": "^10.2.1",
"gray-matter": "^4.0.3",
"markdown-it": "^13.0.1",
"markdown-it-anchor": "^8.6.7",
"markdown-it-attrs": "^4.1.6",
"markdown-it-footnote": "^3.0.3",
"markdown-it-mark": "^3.0.1",
"markdown-it-mathjax3": "^4.3.1",
"markdown-it-plantuml": "^1.4.1",
"markdown-it-task-checkbox": "^1.0.6",
"npm-run-all": "^4.1.5"
}
}

72
plugin-info.json 100644
View File

@ -0,0 +1,72 @@
{
"filesToDelete": [
"src/site/styles/style.css",
"src/site/index.njk",
"src/site/index.11tydata.js",
"src/site/_data/filetree.js",
"api/search.js",
"netlify/functions/search/search.js",
"src/site/versionednote.njk",
"src/site/_includes/layouts/versionednote.njk",
"src/site/lunr-index.js",
"src/site/_data/versionednotes.js",
"src/site/lunr.njk"
],
"filesToAdd": [
"src/site/styles/custom-style.scss",
".env",
"src/site/favicon.svg",
"src/site/img/default-note-icon.svg",
"src/site/img/tree-1.svg",
"src/site/img/tree-2.svg",
"src/site/img/tree-3.svg",
"src/helpers/userUtils.js",
"src/helpers/userSetup.js",
"vercel.json",
"netlify.toml"
],
"filesToModify": [
".eleventy.js",
".eleventyignore",
"README.md",
"package-lock.json",
"package.json",
"src/site/404.njk",
"src/site/sitemap.njk",
"src/site/feed.njk",
"src/site/styles/style.scss",
"src/site/styles/digital-garden-base.scss",
"src/site/styles/obsidian-base.scss",
"src/site/notes/notes.json",
"src/site/notes/notes.11tydata.js",
"src/site/_includes/layouts/note.njk",
"src/site/_includes/layouts/index.njk",
"src/site/_includes/components/notegrowthhistory.njk",
"src/site/_includes/components/pageheader.njk",
"src/site/_includes/components/linkPreview.njk",
"src/site/_includes/components/references.njk",
"src/site/_includes/components/sidebar.njk",
"src/site/_includes/components/graphScript.njk",
"src/site/_includes/components/filetree.njk",
"src/site/_includes/components/filetreeNavbar.njk",
"src/site/_includes/components/navbar.njk",
"src/site/_includes/components/searchButton.njk",
"src/site/_includes/components/searchContainer.njk",
"src/site/_includes/components/searchScript.njk",
"src/site/_includes/components/calloutScript.njk",
"src/site/_includes/components/lucideIcons.njk",
"src/site/_includes/components/timestamps.njk",
"src/site/_data/meta.js",
"src/site/_data/dynamics.js",
"src/site/img/outgoing.svg",
"src/helpers/constants.js",
"src/helpers/utils.js",
"src/helpers/filetreeUtils.js",
"src/helpers/linkUtils.js",
"src/site/get-theme.js",
"src/site/_data/eleventyComputed.js",
"src/site/graph.njk",
"src/site/search-index.njk"
]
}

View File

@ -0,0 +1,12 @@
exports.ALL_NOTE_SETTINGS= [
"dgHomeLink",
"dgPassFrontmatter",
"dgShowBacklinks",
"dgShowLocalGraph",
"dgShowInlineTitle",
"dgShowFileTree",
"dgEnableSearch",
"dgShowToc",
"dgLinkPreview",
"dgShowTags"
];

View File

@ -0,0 +1,114 @@
const sortTree = (unsorted) => {
//Sort by folder before file, then by name
const orderedTree = Object.keys(unsorted)
.sort((a, b) => {
let a_pinned = unsorted[a].pinned || false;
let b_pinned = unsorted[b].pinned || false;
if (a_pinned != b_pinned) {
if (a_pinned) {
return -1;
} else {
return 1;
}
}
const a_is_note = a.indexOf(".md") > -1;
const b_is_note = b.indexOf(".md") > -1;
if (a_is_note && !b_is_note) {
return 1;
}
if (!a_is_note && b_is_note) {
return -1;
}
if (a.toLowerCase() > b.toLowerCase()) {
return 1;
}
return -1;
})
.reduce((obj, key) => {
obj[key] = unsorted[key];
return obj;
}, {});
for (const key of Object.keys(orderedTree)) {
if (orderedTree[key].isFolder) {
orderedTree[key] = sortTree(orderedTree[key]);
}
}
return orderedTree;
};
function getPermalinkMeta(note, key) {
let permalink = "/";
let parts = note.filePathStem.split("/");
let name = parts[parts.length - 1];
let noteIcon = process.env.NOTE_ICON_DEFAULT;
let hide = false;
let pinned = false;
let folders = null;
try {
if (note.data.permalink) {
permalink = note.data.permalink;
}
if (note.data.tags && note.data.tags.indexOf("gardenEntry") != -1) {
permalink = "/";
}
if (note.data.title) {
name = note.data.title;
}
if (note.data.noteIcon) {
noteIcon = note.data.noteIcon;
}
// Reason for adding the hide flag instead of removing completely from file tree is to
// allow users to use the filetree data elsewhere without the fear of losing any data.
if (note.data.hide) {
hide = note.data.hide;
}
if (note.data.pinned) {
pinned = note.data.pinned;
}
if (note.data["dg-path"]) {
folders = note.data["dg-path"].split("/");
} else {
folders = note.filePathStem
.split("notes/")[1]
.split("/");
}
folders[folders.length - 1]+= ".md";
} catch {
//ignore
}
return [{ permalink, name, noteIcon, hide, pinned }, folders];
}
function assignNested(obj, keyPath, value) {
lastKeyIndex = keyPath.length - 1;
for (var i = 0; i < lastKeyIndex; ++i) {
key = keyPath[i];
if (!(key in obj)) {
obj[key] = { isFolder: true };
}
obj = obj[key];
}
obj[keyPath[lastKeyIndex]] = value;
}
function getFileTree(data) {
const tree = {};
(data.collections.note || []).forEach((note) => {
const [meta, folders] = getPermalinkMeta(note);
assignNested(tree, folders, { isNote: true, ...meta });
});
const fileTree = sortTree(tree);
return fileTree;
}
exports.getFileTree = getFileTree;

View File

@ -0,0 +1,100 @@
const wikiLinkRegex = /\[\[(.*?\|.*?)\]\]/g;
const internalLinkRegex = /href="\/(.*?)"/g;
function caselessCompare(a, b) {
return a.toLowerCase() === b.toLowerCase();
}
function extractLinks(content) {
return [
...(content.match(wikiLinkRegex) || []).map(
(link) =>
link
.slice(2, -2)
.split("|")[0]
.replace(/.(md|markdown)\s?$/i, "")
.replace("\\", "")
.trim()
.split("#")[0]
),
...(content.match(internalLinkRegex) || []).map(
(link) =>
link
.slice(6, -1)
.split("|")[0]
.replace(/.(md|markdown)\s?$/i, "")
.replace("\\", "")
.trim()
.split("#")[0]
),
];
}
function getGraph(data) {
let nodes = {};
let links = [];
let stemURLs = {};
let homeAlias = "/";
(data.collections.note || []).forEach((v, idx) => {
let fpath = v.filePathStem.replace("/notes/", "");
let parts = fpath.split("/");
let group = "none";
if (parts.length >= 3) {
group = parts[parts.length - 2];
}
nodes[v.url] = {
id: idx,
title: v.data.title || v.fileSlug,
url: v.url,
group,
home:
v.data["dg-home"] ||
(v.data.tags && v.data.tags.indexOf("gardenEntry") > -1) ||
false,
outBound: extractLinks(v.template.frontMatter.content),
neighbors: new Set(),
backLinks: new Set(),
noteIcon: v.data.noteIcon || process.env.NOTE_ICON_DEFAULT,
hide: v.data.hideInGraph || false,
};
stemURLs[fpath] = v.url;
if (
v.data["dg-home"] ||
(v.data.tags && v.data.tags.indexOf("gardenEntry") > -1)
) {
homeAlias = v.url;
}
});
Object.values(nodes).forEach((node) => {
let outBound = new Set();
node.outBound.forEach((olink) => {
let link = (stemURLs[olink] || olink).split("#")[0];
outBound.add(link);
});
node.outBound = Array.from(outBound);
node.outBound.forEach((link) => {
let n = nodes[link];
if (n) {
n.neighbors.add(node.url);
n.backLinks.add(node.url);
node.neighbors.add(n.url);
links.push({ source: node.id, target: n.id });
}
});
});
Object.keys(nodes).map((k) => {
nodes[k].neighbors = Array.from(nodes[k].neighbors);
nodes[k].backLinks = Array.from(nodes[k].backLinks);
nodes[k].size = nodes[k].neighbors.length;
});
return {
homeAlias,
nodes,
links,
};
}
exports.wikiLinkRegex = wikiLinkRegex;
exports.internalLinkRegex = internalLinkRegex;
exports.extractLinks = extractLinks;
exports.getGraph = getGraph;

View File

@ -0,0 +1,10 @@
function userMarkdownSetup(md) {
// The md parameter stands for the markdown-it instance used throughout the site generator.
// Feel free to add any plugin you want here instead of /.eleventy.js
}
function userEleventySetup(eleventyConfig) {
// The eleventyConfig parameter stands for the the config instantiated in /.eleventy.js.
// Feel free to add any plugin you want here instead of /.eleventy.js
}
exports.userMarkdownSetup = userMarkdownSetup;
exports.userEleventySetup = userEleventySetup;

View File

@ -0,0 +1,7 @@
// Put your computations here.
function userComputed(data) {
return {};
}
exports.userComputed = userComputed;

View File

@ -0,0 +1,51 @@
const slugify = require("@sindresorhus/slugify");
function headerToId(heading) {
var slugifiedHeader = slugify(heading);
if(!slugifiedHeader){
return heading;
}
return slugifiedHeader;
}
function namedHeadings(md, state) {
var ids = {}
state.tokens.forEach(function(token, i) {
if (token.type === 'heading_open') {
var text = md.renderer.render(state.tokens[i + 1].children, md.options)
var id = headerToId(text);
var uniqId = uncollide(ids, id)
ids[uniqId] = true
setAttr(token, 'id', uniqId)
}
})
}
function uncollide(ids, id) {
if (!ids[id]) return id
var i = 1
while (ids[id + '-' + i]) { i++ }
return id + '-' + i
}
function setAttr(token, attr, value, options) {
var idx = token.attrIndex(attr)
if (idx === -1) {
token.attrPush([attr, value])
} else if (options && options.append) {
token.attrs[idx][1] =
token.attrs[idx][1] + ' ' + value
} else {
token.attrs[idx][1] = value
}
}
//https://github.com/rstacruz/markdown-it-named-headings/blob/master/index.js
exports.namedHeadingsFilter = function (md, options) {
md.core.ruler.push('named_headings', namedHeadings.bind(null, md));
}
exports.headerToId = headerToId;

28
src/site/404.njk 100644
View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Nothing here</title>
<link href="/styles/digital-garden-base.css" rel="stylesheet">
{%-if meta.themeStyle%}
<link href="/styles/obsidian-base.css" rel="stylesheet">
<link href="{{meta.themeStyle}}" rel="stylesheet">
{% else %}
<link href="/styles/style.css" rel="stylesheet">
{%endif%}
<link href="/styles/custom-style.css" rel="stylesheet">
</head>
<body class="theme-{{meta.baseTheme}} markdown-preview-view">
<div class="content centered">
{%-if not meta.themeStyle%}
<div class="font-bg"> &#x1F60E; </div>
{%endif%}
<h1>There is nothing here</h1>
<p>If you got here from a link, this note is probably not made public</p>
<a href="/">Go back home</a>
</div>
</body>
</html>

View File

@ -0,0 +1,60 @@
const fsFileTree = require("fs-file-tree");
const BASE_PATH = "src/site/_includes/components/user";
const STYLE_PATH = "src/site/styles/user";
const NAMESPACES = ["index", "notes", "common"];
const SLOTS = ["head", "header", "beforeContent", "afterContent", "footer"];
const FILE_TREE_NAMESPACE = "filetree";
const FILE_TREE_SLOTS = ["beforeTitle", "afterTitle"];
const SIDEBAR_NAMESPACE = "sidebar";
const SIDEBAR_SLOTS = ["top", "bottom"];
const STYLES_NAMESPACE = "styles";
const generateComponentPaths = async (namespace, slots) => {
const data = {};
for (let index = 0; index < slots.length; index++) {
const slot = slots[index];
try {
const tree = await fsFileTree(`${BASE_PATH}/${namespace}/${slot}`);
let comps = Object.keys(tree)
.filter((p) => p.indexOf(".njk") != -1)
.map((p) => `components/user/${namespace}/${slot}/${p}`);
comps.sort();
data[slot] = comps;
} catch {
data[slot] = [];
}
}
return data;
};
const generateStylesPaths = async () => {
try {
const tree = await fsFileTree(`${STYLE_PATH}`);
let comps = Object.keys(tree).map((p) =>
`/styles/user/${p}`.replace(".scss", ".css")
);
comps.sort();
return comps;
} catch {
return [];
}
};
module.exports = async () => {
const data = {};
for (let index = 0; index < NAMESPACES.length; index++) {
const ns = NAMESPACES[index];
data[ns] = await generateComponentPaths(ns, SLOTS);
}
data[FILE_TREE_NAMESPACE] = await generateComponentPaths(
FILE_TREE_NAMESPACE,
FILE_TREE_SLOTS
);
data[SIDEBAR_NAMESPACE] = await generateComponentPaths(
SIDEBAR_NAMESPACE,
SIDEBAR_SLOTS
);
data[STYLES_NAMESPACE] = await generateStylesPaths();
return data;
};

View File

@ -0,0 +1,9 @@
const { getGraph } = require("../../helpers/linkUtils");
const { getFileTree } = require("../../helpers/filetreeUtils");
const { userComputed } = require("../../helpers/userUtils");
module.exports = {
graph: (data) => getGraph(data),
filetree: (data) => getFileTree(data),
userComputed: (data) => userComputed(data)
};

View File

@ -0,0 +1,75 @@
require("dotenv").config();
const axios = require("axios");
const fs = require("fs");
const crypto = require("crypto");
const { globSync } = require("glob");
module.exports = async (data) => {
let baseUrl = process.env.SITE_BASE_URL || "";
if (baseUrl && !baseUrl.startsWith("http")) {
baseUrl = "https://" + baseUrl;
}
let themeStyle = globSync("src/site/styles/_theme.*.css")[0] || "";
if (themeStyle) {
themeStyle = themeStyle.split("site")[1];
}
let bodyClasses = [];
let noteIconsSettings = {
filetree: false,
links: false,
title: false,
default: process.env.NOTE_ICON_DEFAULT,
};
const styleSettingsCss = process.env.STYLE_SETTINGS_CSS || "";
if (process.env.NOTE_ICON_TITLE && process.env.NOTE_ICON_TITLE == "true") {
bodyClasses.push("title-note-icon");
noteIconsSettings.title = true;
}
if (
process.env.NOTE_ICON_FILETREE &&
process.env.NOTE_ICON_FILETREE == "true"
) {
bodyClasses.push("filetree-note-icon");
noteIconsSettings.filetree = true;
}
if (
process.env.NOTE_ICON_INTERNAL_LINKS &&
process.env.NOTE_ICON_INTERNAL_LINKS == "true"
) {
bodyClasses.push("links-note-icon");
noteIconsSettings.links = true;
}
if (
process.env.NOTE_ICON_BACK_LINKS &&
process.env.NOTE_ICON_BACK_LINKS == "true"
) {
bodyClasses.push("backlinks-note-icon");
noteIconsSettings.backlinks = true;
}
if(styleSettingsCss){
bodyClasses.push("css-settings-manager");
}
let timestampSettings = {
timestampFormat: process.env.TIMESTAMP_FORMAT || "MMM dd, yyyy h:mm a",
showCreated: process.env.SHOW_CREATED_TIMESTAMP == "true",
showUpdated: process.env.SHOW_UPDATED_TIMESTAMP == "true",
};
const meta = {
env: process.env.ELEVENTY_ENV,
theme: process.env.THEME,
themeStyle,
bodyClasses: bodyClasses.join(" "),
noteIconsSettings,
timestampSettings,
baseTheme: process.env.BASE_THEME || "dark",
siteName: process.env.SITE_NAME_HEADER || "Digital Garden",
siteBaseUrl: baseUrl,
styleSettingsCss,
buildDate: new Date(),
};
return meta;
};

View File

@ -0,0 +1,37 @@
<script src="https://cdn.jsdelivr.net/npm/lucide@0.115.0/dist/umd/lucide.min.js"></script>
<script>
// Create callout icons
window.addEventListener("load", () => {
document.querySelectorAll(".callout").forEach((elem) => {
const icon = getComputedStyle(elem).getPropertyValue('--callout-icon');
const iconName = icon && icon.trim().replace(/^lucide-/, "");
if (iconName) {
const calloutTitle = elem.querySelector(".callout-title");
if (calloutTitle) {
const calloutIconContainer = document.createElement("div");
const calloutIcon = document.createElement("i");
calloutIconContainer.appendChild(calloutIcon);
calloutIcon.setAttribute("icon-name", iconName);
calloutIconContainer.setAttribute("class", "callout-icon");
calloutTitle.insertBefore(calloutIconContainer, calloutTitle.firstChild);
}
}
});
lucide.createIcons();
// Collapse callouts
Array.from(document.querySelectorAll(".callout.is-collapsible")).forEach((elem) => {
elem.querySelector('.callout-title').addEventListener("click", (event) => {
if (elem.classList.contains("is-collapsed")) {
elem.classList.remove("is-collapsed");
} else {
elem.classList.add("is-collapsed");
}
});
});
});
</script>

View File

@ -0,0 +1,56 @@
{% macro menuItem(fileOrFolderName, fileOrFolder, step, currentPath) %}
{%if fileOrFolder.isNote or fileOrFolder.isFolder%}
<div x-show="isOpen" style="display:none" class="{{'filelist' if step>0}}">
{%if fileOrFolder.isNote and not fileOrFolder.hide %}
<div @click.stop class="notelink {{ 'active-note' if fileOrFolder.permalink === permalink}}">
{%- if not meta.noteIconsSettings.filetree -%}<i icon-name="sticky-note" aria-hidden="true"></i>{%- endif -%}
<a data-note-icon="{{fileOrFolder.noteIcon}}" style="text-decoration: none;" class="filename" href="{{fileOrFolder.permalink}}">{{fileOrFolder.name}} </a>
</div>
{% elif fileOrFolder.isFolder%}
<div class="folder inner-folder" x-data="{isOpen: $persist(false).as('{{currentPath}}')}" @click.stop="isOpen=!isOpen">
<div class="foldername-wrapper align-icon">
<i x-show="isOpen" style="display: none;" icon-name="chevron-down"></i>
<i x-show="!isOpen" icon-name="chevron-right"></i>
<span class="foldername">{{fileOrFolderName}}</span>
</div>
{% for fileOrFolderName, child in fileOrFolder %}
{{menuItem(fileOrFolderName, child, step+1, (currentPath+"/"+fileOrFolderName))}}
{% endfor %}
</div>
{% endif %}
</div>
{%endif%}
{% endmacro %}
<div x-init="isDesktop = (window.innerWidth>=1400) ? true: false;"
x-on:resize.window="isDesktop = (window.innerWidth>=1400) ? true : false;"
x-data="{isDesktop: true, showFilesMobile: false}">
<div x-show.important="!isDesktop" style="display: none;">
{%include "components/filetreeNavbar.njk"%}
</div>
<div x-show="showFilesMobile && !isDesktop" @click="showFilesMobile = false" style="display:none;" class="fullpage-overlay"></div>
<nav class="filetree-sidebar" x-show.important="isDesktop || showFilesMobile" style="display: none;">
{% for imp in dynamics.filetree.beforeTitle %}
{% include imp %}
{% endfor %}
<a href="/" style="text-decoration: none;">
<h1 style="text-align:center;">{{meta.siteName}}</h1>
</a>
{% for imp in dynamics.filetree.afterTitle %}
{% include imp %}
{% endfor %}
{% if settings.dgEnableSearch === true%}
<div style="display: flex; justify-content: center;">
{% include "components/searchButton.njk" %}
</div>
{%endif%}
<div class="folder" x-data="{isOpen: true}">
{%- for fileOrFolderName, fileOrFolder in filetree -%}
{{menuItem(fileOrFolderName, fileOrFolder, 0, fileOrFolderName)}}
{%- endfor -%}
</div>
</nav>
</div>

View File

@ -0,0 +1,18 @@
<nav class="navbar">
<div class="navbar-inner">
<span style="font-size: 1.5rem; margin-right: 10px;" @click="showFilesMobile=!showFilesMobile"><i icon-name="menu"></i></span>
{% for imp in dynamics.filetree.beforeTitle %}
{% include imp %}
{% endfor %}
<a href="/" style="text-decoration: none;">
<h1 style="margin: 15px !important;">{{meta.siteName}}</h1>
</a>
{% for imp in dynamics.filetree.afterTitle %}
{% include imp %}
{% endfor %}
</div>
{% if settings.dgEnableSearch === true%}
{% include "components/searchButton.njk" %}
{%endif%}
</nav>

View File

@ -0,0 +1,214 @@
<script>
async function fetchGraphData() {
const graphData = await fetch('/graph.json').then(res => res.json());
const fullGraphData = filterFullGraphData(graphData);
return {graphData, fullGraphData}
}
function getNextLevelNeighbours(existing, remaining) {
const keys = Object.values(existing).map((n) => n.neighbors).flat();
const n_remaining = Object.keys(remaining).reduce((acc, key) => {
if (keys.indexOf(key) != -1) {
if (!remaining[key].hide) {
existing[key] = remaining[key];
}
} else {
acc[key] = remaining[key];
}
return acc;
}, {});
return existing, n_remaining;
}
function filterLocalGraphData(graphData, depth) {
if (graphData == null) {
return null;
}
let remaining = JSON.parse(JSON.stringify(graphData.nodes));
let links = JSON.parse(JSON.stringify(graphData.links));
let currentLink = decodeURI(window.location.pathname);
let currentNode = remaining[currentLink] || Object.values(remaining).find((v) => v.home);
delete remaining[currentNode.url];
if (!currentNode.home) {
let home = Object.values(remaining).find((v) => v.home);
delete remaining[home.url];
}
currentNode.current = true;
let existing = {};
existing[currentNode.url] = currentNode;
for (let i = 0; i < depth; i++) {
existing, remaining = getNextLevelNeighbours(existing, remaining);
}
nodes = Object.values(existing);
if (!currentNode.home) {
nodes = nodes.filter(n => !n.home);
}
let ids = nodes.map((n) => n.id);
return {
nodes,
links: links.filter(function (con) {
return ids.indexOf(con.target) > -1 && ids.indexOf(con.source) > -1;
}),
}
}
function getCssVar(variable) {return getComputedStyle(document.body).getPropertyValue(variable)}
function htmlDecode(input) {
var doc = new DOMParser().parseFromString(input, "text/html");
return doc.documentElement.textContent;
}
function renderGraph(graphData, id, delay, fullScreen) {
if (graphData == null) {
return;
}
const el = document.getElementById(id);
width = el.offsetWidth;
height = el.offsetHeight;
const highlightNodes = new Set();
let hoverNode = null;
const color = getCssVar("--graph-main");
const mutedColor = getCssVar("--graph-muted");
let Graph = ForceGraph()
(el)
.graphData(graphData)
.nodeId('id')
.nodeLabel('title')
.linkSource('source')
.linkTarget('target')
.d3AlphaDecay(0.10)
.width(width)
.height(height)
.linkDirectionalArrowLength(2)
.linkDirectionalArrowRelPos(0.5)
.autoPauseRedraw(false)
.linkColor((link) => {
if (hoverNode == null) {
return color;
}
if (link.source.id == hoverNode.id || link.target.id == hoverNode.id) {
return color;
} else {
return mutedColor;
}
})
.nodeCanvasObject((node, ctx) => {
const numberOfNeighbours = (node.neighbors && node.neighbors.length) || 2;
const nodeR = Math.min(7, Math.max(numberOfNeighbours / 2, 2));
ctx.beginPath();
ctx.arc(node.x, node.y, nodeR, 0, 2 * Math.PI, false);
if (hoverNode == null) {
ctx.fillStyle = color;
} else {
if (node == hoverNode || highlightNodes.has(node.url)) {
ctx.fillStyle = color;
} else {
ctx.fillStyle = mutedColor;
}
}
ctx.fill();
if (node.current) {
ctx.beginPath();
ctx.arc(node.x, node.y, nodeR + 1, 0, 2 * Math.PI, false);
ctx.lineWidth = 0.5;
ctx.strokeStyle = color;
ctx.stroke();
}
const label = htmlDecode(node.title)
const fontSize = 3.5;
ctx.font = `${fontSize}px Sans-Serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'top';
ctx.fillText(label, node.x, node.y + nodeR + 2);
})
.onNodeClick(node => {
window.location = node.url;
})
.onNodeHover(node => {
highlightNodes.clear();
if (node) {
highlightNodes.add(node);
node.neighbors.forEach(neighbor => highlightNodes.add(neighbor));
}
hoverNode = node || null;
});
if (fullScreen || (delay != null && graphData.nodes.length > 4)) {
setTimeout(() => {
Graph.zoomToFit(5, 75);
}, delay || 200);
}
return Graph;
}
function renderLocalGraph(graphData, depth, fullScreen) {
if (window.graph){
window.graph._destructor();
}
const data = filterLocalGraphData(graphData, depth);
return renderGraph(data, 'link-graph', null, fullScreen);
}
function filterFullGraphData(graphData) {
if (graphData == null) {
return null;
}
graphData = JSON.parse(JSON.stringify(graphData));
const hiddens = Object.values(graphData.nodes).filter((n) => n.hide).map((n) => n.id);
const data = {
links: JSON.parse(JSON.stringify(graphData.links)).filter((l) => hiddens.indexOf(l.source) == -1 && hiddens.indexOf(l.target) == -1),
nodes: [...Object.values(graphData.nodes).filter((n) => !n.hide)]
}
return data
}
function openFullGraph(fullGraphData) {
lucide.createIcons({
attrs: {
class: ["svg-icon"]
}
});
return renderGraph(fullGraphData, "full-graph-container", 200, false);;
}
function closefullGraph(fullGraph) {
if (fullGraph) {
fullGraph._destructor();
}
return null;
}
</script>
<div x-init="{graphData, fullGraphData} = await fetchGraphData();" x-data="{ graphData: null, depth: 1, graph: null, fullGraph: null, showFullGraph: false, fullScreen: false, fullGraphData: null}" id="graph-component" x-bind:class="fullScreen ? 'graph graph-fs' : 'graph'" v-scope>
<div class="graph-title-container">
<div class="graph-title">Connected Pages</div>
<div id="graph-controls">
<div class="depth-control">
<label for="graph-depth">Depth</label>
<div class="slider">
<input x-model.number="depth" name="graph-depth" list="depthmarkers" type="range" step="1" min="1" max="3" id="graph-depth"/>
<datalist id="depthmarkers">
<option value="1" label="1"></option>
<option value="2" label="2"></option>
<option value="3" label="3"></option>
</datalist>
</div>
<span id="depth-display" x-text="depth"></span>
</div>
<div class="ctrl-right">
<span id="global-graph-btn" x-on:click="showFullGraph = true; setTimeout(() => {fullGraph = openFullGraph(fullGraphData)}, 100)"><i icon-name="globe" aria-hidden="true"></i></span>
<span id="graph-fs-btn" x-on:click="fullScreen = !fullScreen"><i icon-name="expand" aria-hidden="true"></i></span>
</div>
</div>
</div>
<div x-effect="window.graph = renderLocalGraph(graphData, depth, fullScreen)" id="link-graph" ></div>
<div x-show="showFullGraph" id="full-graph" class="show" style="display: none;">
<span id="full-graph-close" x-on:click="fullGraph = closefullGraph(fullGraph); showFullGraph = false;"><i icon-name="x" aria-hidden="true"></i></span><div id="full-graph-container"></div>
</div>
</div>

View File

@ -0,0 +1,156 @@
<!-- Credit for the link preview implementation goes to https://github.com/maximevaillancourt/digital-garden-jekyll-template/blob/master/_includes/link-previews.html -->
<style>
#tooltip-wrapper {
background: var(--background-primary);
padding: 1em;
border-radius: 4px;
overflow: hidden;
position: fixed;
width: 80%;
max-width: 400px;
height: auto;
max-height: 300px;
font-size: 0.8em;
box-shadow: 0 5px 10px rgba(0,0,0,0.1);
opacity: 0;
transition: opacity 100ms;
unicode-bidi: plaintext;
overflow-y: scroll;
z-index: 10;
}
#tooltip-wrapper:after {
content: "";
position: absolute;
z-index: 1;
bottom: 0;
left: 0;
pointer-events: none;
width: 100%;
unicode-bidi: plaintext;
height: 75px;
}
</style>
<div style="opacity: 0; display: none;" id='tooltip-wrapper'>
<div id='tooltip-content'>
</div>
</div>
<iframe style="display: none; height: 0; width: 0;" id='link-preview-iframe' src="">
</iframe>
<script>
var opacityTimeout;
var contentTimeout;
var transitionDurationMs = 100;
var iframe = document.getElementById('link-preview-iframe')
var tooltipWrapper = document.getElementById('tooltip-wrapper')
var tooltipContent = document.getElementById('tooltip-content')
var linkHistories = {};
function hideTooltip() {
opacityTimeout = setTimeout(function () {
tooltipWrapper.style.opacity = 0;
contentTimeout = setTimeout(function () {
tooltipContent.innerHTML = '';
tooltipWrapper.style.display = 'none';
}, transitionDurationMs + 1);
}, transitionDurationMs)
}
function showTooltip(event) {
var elem = event.target;
var elem_props = elem.getClientRects()[elem.getClientRects().length - 1];
var top = window.pageYOffset || document.documentElement.scrollTop;
var url = event.target.getAttribute("href");
if (url.indexOf("http") === -1 || url.indexOf(window.location.host) !== -1) {
let contentURL = url.split('#')[0]
if (!linkHistories[contentURL]) {
iframe.src = contentURL
iframe.onload = function () {
tooltipContentHtml = ''
tooltipContentHtml += '<div style="font-weight: bold; unicode-bidi: plaintext;">' + iframe.contentWindow.document.querySelector('h1').innerHTML + '</div>'
tooltipContentHtml += iframe.contentWindow.document.querySelector('.content').innerHTML
tooltipContent.innerHTML = tooltipContentHtml
linkHistories[contentURL] = tooltipContentHtml
tooltipWrapper.style.display = 'block';
tooltipWrapper.scrollTop = 0;
setTimeout(function () {
tooltipWrapper.style.opacity = 1;
if (url.indexOf("#") != -1) {
let id = url.split('#')[1];
const focus = tooltipWrapper.querySelector(`[id='${id}']`);
focus.classList.add('referred');
console.log(focus);
focus.scrollIntoView({behavior: 'smooth'}, true)
} else {
tooltipWrapper.scroll(0, 0);
}
}, 1)
}
} else {
tooltipContent.innerHTML = linkHistories[contentURL]
tooltipWrapper.style.display = 'block';
setTimeout(function () {
tooltipWrapper.style.opacity = 1;
if (url.indexOf("#") != -1) {
let id = url.split('#')[1];
const focus = tooltipWrapper.querySelector(`[id='${id}']`);
focus.classList.add('referred');
focus.scrollIntoView({behavior: 'smooth'}, true)
} else {
tooltipWrapper.scroll(0, 0);
}
}, 1)
}
function getInnerWidth(elem) {
var style = window.getComputedStyle(elem);
return elem.offsetWidth - parseFloat(style.paddingLeft) - parseFloat(style.paddingRight) - parseFloat(style.borderLeft) - parseFloat(style.borderRight) - parseFloat(style.marginLeft) - parseFloat(style.marginRight);
}
tooltipWrapper.style.left = elem_props.left - (tooltipWrapper.offsetWidth / 2) + (elem_props.width / 2) + "px";
if ((window.innerHeight - elem_props.top) < (tooltipWrapper.offsetHeight)) {
tooltipWrapper.style.top = elem_props.top + top - tooltipWrapper.offsetHeight - 10 + "px";
} else if ((window.innerHeight - elem_props.top) > (tooltipWrapper.offsetHeight)) {
tooltipWrapper.style.top = elem_props.top + top + 35 + "px";
}
if ((elem_props.left + (elem_props.width / 2)) < (tooltipWrapper.offsetWidth / 2)) {
tooltipWrapper.style.left = "10px";
} else if ((document.body.clientWidth - elem_props.left - (elem_props.width / 2)) < (tooltipWrapper.offsetWidth / 2)) {
tooltipWrapper.style.left = document.body.clientWidth - tooltipWrapper.offsetWidth - 20 + "px";
}
}
}
function setupListeners(linkElement) {
linkElement.addEventListener('mouseleave', function (_event) {
hideTooltip();
});
tooltipWrapper.addEventListener('mouseleave', function (_event) {
hideTooltip();
});
linkElement.addEventListener('mouseenter', function (event) {
clearTimeout(opacityTimeout);
clearTimeout(contentTimeout);
showTooltip(event);
});
tooltipWrapper.addEventListener('mouseenter', function (event) {
clearTimeout(opacityTimeout);
clearTimeout(contentTimeout);
});
}
window.addEventListener("load", function(event)
{
document.querySelectorAll('.internal-link').forEach(setupListeners);
document.querySelectorAll('.backlink-card a').forEach(setupListeners);
});
</script>

View File

@ -0,0 +1,7 @@
<script>
lucide.createIcons({
attrs: {
class: ["svg-icon"]
}
});
</script>

View File

@ -0,0 +1,18 @@
{%if settings.dgHomeLink === true%}
<nav class="navbar">
<div class="navbar-inner">
<a href="/" style="text-decoration: none;">
<h1 style="margin: 15px !important;">{{meta.siteName}}</h1>
</a>
</div>
{% if settings.dgEnableSearch === true%}
{% include "components/searchButton.njk" %}
{%endif%}
</nav>
{%else%}
<div class="empty-navbar" >
{% if settings.dgEnableSearch === true%}
{% include "components/searchButton.njk" %}
{%endif%}
</div>
{%endif%}

View File

@ -0,0 +1,16 @@
{%- if collections.versionednote.length > 0 -%}
<div style="position:absolute; top:100px; right:0; padding:20px 15px 40px 15px; background-color:#2a2a2a; border-radius: 25px;">
<!-- animate big when moving over -->
<h2>Notegrowth</h2>
<ul style="list-style-type: none; padding:0;">
{%- for note in collections.versionednote -%}
{%- if note.data.item.title === page.fileSlug -%}
<li style="padding:10px">
<a href="{{ note.url }}">
{{note.data.item.date}}</a>
</li>
{%- endif -%}
{%- endfor -%}
</ul>
</div>
{%-endif-%}

View File

@ -0,0 +1,49 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://fastly.jsdelivr.net/npm/mermaid@9.4.0/dist/mermaid.min.js"></script>
<script>
mermaid.initialize({
startOnLoad: true,
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/prism.min.js" integrity="sha512-hpZ5pDCF2bRCweL5WoA0/N1elet1KYL5mx3LP555Eg/0ZguaHawxNvEjF6O3rufAChs16HVNhEc6blF/rZoowQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/plugins/autoloader/prism-autoloader.min.js" integrity="sha512-sv0slik/5O0JIPdLBCR2A3XDg/1U3WuDEheZfI/DI5n8Yqc3h5kjrnr46FGBNiUAJF7rE4LHKwQ/SoSLRKAxEA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
{%include "components/calloutScript.njk"%}
<script src="https://fastly.jsdelivr.net/npm/force-graph@1.43.0/dist/force-graph.min.js"></script>
<script defer src="https://fastly.jsdelivr.net/npm/@alpinejs/persist@3.11.1/dist/cdn.min.js"></script>
<script src="https://fastly.jsdelivr.net/npm/alpinejs@3.11.1/dist/cdn.min.js" defer></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/themes/prism-okaidia.min.css" integrity="sha512-mIs9kKbaw6JZFfSuo+MovjU+Ntggfoj8RwAmJbVXQ5mkAX5LlgETQEweFPI18humSPHymTb5iikEOKWF7I8ncQ==" crossorigin="anonymous" referrerpolicy="no-referrer"/>
<script src="https://fastly.jsdelivr.net/npm/whatwg-fetch@3.6.2/dist/fetch.umd.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<link href="/styles/digital-garden-base.css" rel="stylesheet">
{%-if meta.themeStyle%}
<link href="/styles/obsidian-base.css" rel="stylesheet">
<link href="{{meta.themeStyle}}" rel="stylesheet">
{% else %}
<link href="/styles/style.css" rel="stylesheet">
{%endif%}
<link href="/styles/custom-style.css" rel="stylesheet">
{%- for style in dynamics.styles -%}
<link href="{{style}}" rel="stylesheet">
{%- endfor -%}
{% favicons './src/site/favicon.svg', appleIconBgColor='#123' %}
{% if metatags %}
{% for name, content in metatags %}
<meta name="{{ name }}" content="{{ content }}">
{% endfor %}
{% endif %}
{% if meta.styleSettingsCss %}
<style>
{{ meta.styleSettingsCss | safe }}
</style>
{% endif %}
<style>
</style>

View File

@ -0,0 +1,26 @@
<script>
if (window.location.hash) {
document.getElementById(window.location.hash.slice(1)).classList.add('referred');
}
window.addEventListener('hashchange', (evt) => {
const oldParts = evt.oldURL.split("#");
if (oldParts[1]) {
document.getElementById(oldParts[1]).classList.remove('referred');
}
const newParts = evt.newURL.split("#");
if (newParts[1]) {
document.getElementById(newParts[1]).classList.add('referred');
}
}, false);
const url_parts = window.location.href.split("#")
const url = url_parts[0];
const referrence = url_parts[1];
document.querySelectorAll(".cm-s-obsidian > *[id]").forEach(function (el) {
el.ondblclick = function(evt) {
const ref_url = url + '#' + evt.target.id
navigator.clipboard.writeText(ref_url);
}
});
</script>

View File

@ -0,0 +1,11 @@
<div class="search-button align-icon" onclick="toggleSearch()">
<span class="search-icon">
<i icon-name="search" ></i>
</span>
<span class="search-text">
<span>Search</span>
<span style="font-size: 0.6rem; padding: 2px 2px 0 6px; text-align:center; transform: translateY(4px);" class="search-keys">
CTRL + K
</span>
</span>
</div>

View File

@ -0,0 +1,23 @@
<div class="search-container" id="globalsearch" onclick="toggleSearch()">
<div class="search-box">
<input type="search" id="term" placeholder="Start typing...">
<div id="search-results"></div>
<footer class="search-box-footer">
<div class="navigation-hint">
<span>Enter to select</span>
</div>
<div class="navigation-hint align-icon">
<i icon-name="arrow-up" aria-hidden="true"></i>
<i icon-name="arrow-down" aria-hidden="true"></i>
<span>to navigate</span>
</div>
<div class="navigation-hint">
<span>ESC to close</span>
</div>
</footer>
</div>
</div>
{%include "components/searchScript.njk"%}

View File

@ -0,0 +1,380 @@
<script src="https://cdn.jsdelivr.net/npm/flexsearch@0.7.21/dist/flexsearch.bundle.js"></script>
<script>
document.addEventListener('DOMContentLoaded', init, false);
document.addEventListener('DOMContentLoaded', setCorrectShortcut, false);
window.toggleSearch = function () {
if (document.getElementById('globalsearch').classList.contains('active')) {
document
.getElementById('globalsearch')
.classList
.remove('active');
} else {
document
.getElementById('globalsearch')
.classList
.add('active');
document
.getElementById('term')
.focus();
}
}
window.toggleTagSearch = function (evt) {
console.log(evt.textContent);
const term = evt.textContent;
if (term) {
window
.document
.getElementById('term')
.value = term.trim();
window.toggleSearch();
window.search();
}
}
const loadingSvg = `
<svg width="100" height="100" viewBox="0 0 45 45" xmlns="http://www.w3.org/2000/svg" stroke="#fff">
<g fill="none" fill-rule="evenodd" transform="translate(1 1)" stroke-width="2">
<circle cx="22" cy="22" r="6" stroke-opacity="0">
<animate attributeName="r"
begin="1.5s" dur="3s"
values="6;22"
calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="1.5s" dur="3s"
values="1;0" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="stroke-width"
begin="1.5s" dur="3s"
values="2;0" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="22" cy="22" r="6" stroke-opacity="0">
<animate attributeName="r"
begin="3s" dur="3s"
values="6;22"
calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="stroke-opacity"
begin="3s" dur="3s"
values="1;0" calcMode="linear"
repeatCount="indefinite" />
<animate attributeName="stroke-width"
begin="3s" dur="3s"
values="2;0" calcMode="linear"
repeatCount="indefinite" />
</circle>
<circle cx="22" cy="22" r="8">
<animate attributeName="r"
begin="0s" dur="1.5s"
values="6;1;2;3;4;5;6"
calcMode="linear"
repeatCount="indefinite" />
</circle>
</g>
</svg>`;
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this,
args = arguments;
var later = function () {
timeout = null;
if (!immediate)
func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow)
func.apply(context, args);
};
};
function setCorrectShortcut() {
if (navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
document
.querySelectorAll(".search-keys")
.forEach(x => x.innerHTML = "⌘ + K");
}
}
function createIndex(posts) {
const encoder = (str) => str
.toLowerCase()
.split(/([^a-z]|[^\x00-\x7F])/)
const contentIndex = new FlexSearch.Document({
cache: true,
charset: "latin:extra",
optimize: true,
index: [
{
field: "content",
tokenize: "reverse",
encode: encoder
}, {
field: "title",
tokenize: "forward",
encode: encoder
}, {
field: "tags",
tokenize: "forward",
encode: encoder
}
]
})
posts.forEach((p, idx) => {
contentIndex.add({
id: idx, title: p.title, content: p.content, tags: p.tags //Change to removeHTML
})
});
return contentIndex;
}
async function init() {
//init offline search index
const searchIndexDate = '{{meta.buildDate|isoDate}}';
let shouldFetch = true;
if(localStorage.getItem("searchIndex")) {
let {date, docs}= JSON.parse(localStorage.getItem('searchIndex'));
if(date === searchIndexDate){
shouldFetch = false;
let index = createIndex(docs);
window.docs = docs
window.index = index;
}
}
if(shouldFetch){
let docs = await(await fetch('/searchIndex.json?v={{meta.buildDate|isoDate}}')).json();
let index = createIndex(docs);
localStorage.setItem("searchIndex", JSON.stringify({date: '{{meta.buildDate|isoDate}}', docs}));
window.docs = docs
window.index = index;
}
//open searchmodal when ctrl + k is pressed, cmd + k on mac
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
toggleSearch();
}
if (e.key === 'Escape') {
document
.getElementById('globalsearch')
.classList
.remove('active');
}
//navigate search results with arrow keys
if (document.getElementById('globalsearch').classList.contains('active')) {
if (e.key === 'ArrowDown') {
e.preventDefault();
let active = document.querySelector('.searchresult.active');
if (active) {
active
.classList
.remove('active');
if (active.nextElementSibling) {
active
.nextElementSibling
.classList
.add('active');
} else {
document
.querySelector('.searchresult')
.classList
.add('active');
}
} else {
document
.querySelector('.searchresult')
.classList
.add('active');
}
let currentActive = document.querySelector('.searchresult.active');
if (currentActive) {
currentActive.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'start'});
}
}
if (e.key === 'ArrowUp') {
e.preventDefault();
let active = document.querySelector('.searchresult.active');
if (active) {
active
.classList
.remove('active');
if (active.previousElementSibling) {
active
.previousElementSibling
.classList
.add('active');
} else {
document
.querySelectorAll('.searchresult')
.forEach((el) => {
if (!el.nextElementSibling) {
el
.classList
.add('active');
}
});
}
} else {
document
.querySelectorAll('.searchresult')
.forEach((el) => {
if (el.nextElementSibling) {
el
.classList
.add('active');
}
});
}
let currentActive = document.querySelector('.searchresult.active');
if (currentActive) {
currentActive.scrollIntoView({behavior: 'smooth', block: 'nearest', inline: 'start'});
}
}
if (e.key === 'Enter') {
e.preventDefault();
let active = document.querySelector('.searchresult.active');
if (active) {
window.location.href = active
.querySelector("a")
.href;
}
}
}
});
const debouncedSearch = debounce(search, 200, false);
field = document.querySelector('#term');
field.addEventListener('keydown', (e) => {
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') {
debouncedSearch();
}
});
resultsDiv = document.querySelector('#search-results');
const params = new URL(location.href).searchParams;
if (params.get('q')) {
field.setAttribute('value', params.get('q'));
toggleSearch();
search();
}
}
window.lastSearch = '';
async function search() {
let search = field
.value
.trim();
if (!search)
return;
if (search == lastSearch)
return;
console.log(`search for ${search}`);
window.lastSearch = search;
resultsDiv.innerHTML = loadingSvg;
//let searchRequest = await fetch(`/api/search?term=${encodeURIComponent(search)}`);
//let results = await searchRequest.json();
let results = offlineSearch(search);
let resultsHTML = '';
if (!results.length) {
let resultParagraph = document.createElement("p");
resultParagraph.innerText = `No results for "${search}"`;
resultsDiv.innerHTML = '';
resultsDiv.appendChild(resultParagraph);
return;
}
resultsHTML += '<div style="max-width:100%;">';
// we need to add title, url from ref
results.forEach(r => {
if(r.tags && r.tags.length > 0){
resultsHTML += `<div class="searchresult">
<a class="search-link" href="${r.url}">${r.title}</a>
<div onclick="window.location='${r.url}'">
<div class="header-meta">
<div class="header-tags">
${r.tags.map(tag=>'<a class="tag" href="JavaScript:Void(0);">#'+tag+'</a>').join("")}
</div>
</div>
${r.content}
</div>
</div>`;
} else {
resultsHTML += `<div class="searchresult">
<a class="search-link" href="${r.url}">${r.title}</a>
<div onclick="window.location='${r.url}'">
${r.content}
</div>
</div>`;
}
});
resultsHTML += '</div>';
resultsDiv.innerHTML = resultsHTML;
}
function truncate(str, size) {
//first, remove HTML
str = str.replaceAll(/<[^>]*>/g, '');
if (str.length < size)
return str;
return str.substring(0, size - 3) + '...';
}
function offlineSearch(searchQuery) {
let data = window.docs;
let isTagSearch = searchQuery[0] === "#" && searchQuery.length > 1;
let searchResults = isTagSearch
? index.search(searchQuery.substring(1), [
{
field: "tags"
}
])
: index.search(searchQuery, [
{
field: "title",
limit: 5
}, {
field: "content",
weight: 10
}
]);
const getByField = (field) => {
const results = searchResults.filter((x) => x.field === field)
if (results.length === 0) {
return []
} else {
return [...results[0].result]
}
}
const allIds = new Set([
...getByField("title"),
...getByField("content"),
...getByField("tags")
])
const dataIds = [...allIds];
const finalResults = dataIds.map((id) => {
let result = data[id];
result.content = truncate(result.content, 400);
result.tags = result
.tags
.filter((x) => x != "gardenEntry" && x != "note"); //Note is automatically added by 11ty. GardenEntry is used internally to mark the home page
return result;
})
return finalResults;
}
</script>

View File

@ -0,0 +1,69 @@
<aside>
<div class="sidebar">
<div class="sidebar-container">
{% for imp in dynamics.sidebar.top %}
{% include imp %}
{% endfor %}
{%if settings.dgShowLocalGraph === true%}
{%include "components/graphScript.njk"%}
{%endif%}
{%if settings.dgShowToc === true%}
{%set tocHtml= (content and (content|toc)) %}
{%if tocHtml %}
<div class="toc">
<div class="toc-title-container">
<div class="toc-title">
On this page
</div>
</div>
<div class="toc-container">
{{ tocHtml | safe }}
</div>
</div>
{%endif%}
{%endif%}
{%if settings.dgShowBacklinks === true %}
{%if settings.dgShowBacklinks === true %}
<div class="backlinks">
<div class="backlink-title" style="margin: 4px 0 !important;">Pages mentioning this page</div>
<div class="backlink-list">
{%- if page.url == "/" -%}
{%- if graph.nodes[graph.homeAlias].backLinks.length === 0 -%}
<div class="backlink-card">
<span class="no-backlinks-message">No other pages mentions this page</span>
</div>
{%- endif -%}
{%- for backlink in graph.nodes[graph.homeAlias].backLinks -%}
{%- if graph.nodes[backlink].url != graph.homeAlias -%}
<div class="backlink-card">
{%- if not meta.noteIconsSettings.backlinks -%}<i icon-name="link"></i> {%- endif -%}<a href="{{graph.nodes[backlink].url}}" data-note-icon="{{graph.nodes[backlink].noteIcon}}" class="backlink">{{graph.nodes[backlink].title}}</a>
</div>
{%- endif -%}
{%- endfor -%}
{%- else -%}
{%- if graph.nodes[page.url].backLinks.length === 0 -%}
<div class="backlink-card">
<span class="no-backlinks-message">No other pages mentions this page</span>
</div>
{%- endif -%}
{%- for backlink in graph.nodes[page.url].backLinks -%}
{%- if graph.nodes[backlink].url != page.url -%}
<div class="backlink-card">
{%- if not meta.noteIconsSettings.backlinks -%}<i icon-name="link"></i> {%- endif -%}<a href="{{graph.nodes[backlink].url}}" data-note-icon="{{graph.nodes[backlink].noteIcon}}" class="backlink">{{graph.nodes[backlink].title}}</a>
</div>
{%- endif -%}
{%- endfor -%}
{%- endif -%}
</div>
</div>
{%endif%}
{%endif%}
{% for imp in dynamics.sidebar.bottom %}
{% include imp %}
{% endfor %}
</div>
</div>
</aside>

View File

@ -0,0 +1,7 @@
<script src=" https://fastly.jsdelivr.net/npm/luxon@3.2.1/build/global/luxon.min.js "></script>
<script defer>
TIMESTAMP_FORMAT = "{{meta.timestampSettings.timestampFormat}}";
document.querySelectorAll('.human-date').forEach(function (el) {
el.innerHTML = luxon.DateTime.fromISO(el.getAttribute('data-date') || el.innerText).toFormat(TIMESTAMP_FORMAT);
});
</script>

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% if title %}{{ title }}{% else %}{{ page.fileSlug }}{% endif %}</title>
{%include "components/pageheader.njk"%}
{% for imp in dynamics.common.head %}
{% include imp %}
{% endfor %}
{% for imp in dynamics.index.head %}
{% include imp %}
{% endfor %}
</head>
<body class="theme-{{meta.baseTheme}} markdown-preview-view markdown-rendered markdown-preview-section {{meta.bodyClasses}}">
{%include "components/notegrowthhistory.njk"%}
{% if settings.dgShowFileTree !== true %}
{%include "components/navbar.njk"%}
{%else%}
{%include "components/filetree.njk"%}
{% endif %}
{% if settings.dgEnableSearch === true %}
{%include "components/searchContainer.njk"%}
{% endif %}
<main class="content cm-s-obsidian {{contentClasses}}">
<header>
{% if settings.dgShowInlineTitle === true %}
<h1>{{ noteTitle }}</h1>
{% endif %}
<div class="header-meta">
{% if settings.dgShowTags === true and tags %}
<div class="header-tags">
{% for tag in tags %}
{% if tag != 'gardenEntry' and tag !='note' %}
<a class="tag" onclick="toggleTagSearch(this)">
#{{tag}}
</a>
{% endif %}
{% endfor %}
</div>
{% endif %}
</div>
{% for imp in dynamics.common.header %}
{% include imp %}
{% endfor %}
{% for imp in dynamics.index.header %}
{% include imp %}
{% endfor %}
</header>
{% for imp in dynamics.common.beforeContent %}
{% include imp %}
{% endfor %}
{% for imp in dynamics.index.beforeContent %}
{% include imp %}
{% endfor %}
{{ content | hideDataview | link | taggify | safe}}
{% for imp in dynamics.common.afterContent %}
{% include imp %}
{% endfor %}
{% for imp in dynamics.index.afterContent %}
{% include imp %}
{% endfor %}
</main>
{% if settings.dgShowBacklinks === true or settings.dgShowLocalGraph === true or settings.dgShowToc === true%}
{%include "components/sidebar.njk" %}
{%endif%}
{% if settings.dgLinkPreview === true %}
{%include "components/linkPreview.njk"%}
{% endif %}
{% for imp in dynamics.common.footer %}
{% include imp %}
{% endfor %}
{% for imp in dynamics.index.footer %}
{% include imp %}
{% endfor %}
{%include "components/lucideIcons.njk"%}
</body>
</html>

View File

@ -0,0 +1,96 @@
---
permalink: "notes/{{ page.fileSlug | slugify }}/"
---
<!DOCTYPE html>
<html lang="en">
<head>
<title>{% if title %}{{ title }}{% else %}{{ page.fileSlug }}{% endif %}</title>
{%include "components/pageheader.njk"%}
{% for imp in dynamics.common.head %}
{% include imp %}
{% endfor %}
{% for imp in dynamics.notes.head %}
{% include imp %}
{% endfor %}
</head>
<body class="theme-{{meta.baseTheme}} markdown-preview-view markdown-rendered markdown-preview-section {{meta.bodyClasses}}">
{%include "components/notegrowthhistory.njk"%}
{% if settings.dgShowFileTree !== true %}
{%include "components/navbar.njk"%}
{%else%}
{%include "components/filetree.njk"%}
{% endif %}
{% if settings.dgEnableSearch === true %}
{%include "components/searchContainer.njk"%}
{% endif %}
<main class="content cm-s-obsidian {{contentClasses}}">
<header>
{% if settings.dgShowInlineTitle === true %}
<h1 data-note-icon="{% if noteIcon %}{{noteIcon}}{% else %}{{meta.noteIconsSettings.default}}{% endif %}">{% if title %}{{ title }}{% else %}{{ page.fileSlug }}{% endif %}</h1>
{% endif %}
<div class="header-meta">
{% if settings.dgShowTags === true and tags %}
<div class="header-tags">
{% for tag in tags %}
{% if tag != 'gardenEntry' and tag !='note' %}
<a class="tag" onclick="toggleTagSearch(this)">
#{{tag}}
</a>
{% endif %}
{% endfor %}
</div>
{% endif %}
{%- if meta.timestampSettings.showCreated or meta.timestampSettings.showUpdated -%}
<div class="timestamps">
{%- if meta.timestampSettings.showCreated and created -%}
<div><i icon-name="calendar-plus"></i> <span class="human-date" data-date="{{created}}"></span></div>
{%- endif -%}
{%- if meta.timestampSettings.showUpdated and updated -%}
<div><i icon-name="calendar-clock"></i> <span class="human-date" data-date="{{updated}}"></span></div>
{%- endif -%}
</div>
{%- endif -%}
</div>
{% for imp in dynamics.common.header %}
{% include imp %}
{% endfor %}
{% for imp in dynamics.notes.header %}
{% include imp %}
{% endfor %}
</header>
{% for imp in dynamics.common.beforeContent %}
{% include imp %}
{% endfor %}
{% for imp in dynamics.notes.beforeContent %}
{% include imp %}
{% endfor %}
{{ content | hideDataview | link | taggify | safe}}
{% for imp in dynamics.common.afterContent %}
{% include imp %}
{% endfor %}
{% for imp in dynamics.notes.afterContent %}
{% include imp %}
{% endfor %}
</main>
{% if settings.dgShowBacklinks === true or settings.dgShowLocalGraph === true or settings.dgShowToc === true%}
{%include "components/sidebar.njk"%}
{% endif %}
{% if settings.dgLinkPreview === true %}
{%include "components/linkPreview.njk"%}
{% endif %}
{% include "components/references.njk" %}
{% include "components/timestamps.njk" %}
{% for imp in dynamics.common.footer %}
{% include imp %}
{% endfor %}
{% for imp in dynamics.notes.footer %}
{% include imp %}
{% endfor %}
{%include "components/lucideIcons.njk"%}
</body>
</html>

View File

@ -0,0 +1,24 @@
<svg id="emoji" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g id="color">
<rect x="37.0753" y="36.0532" width="26.8836" height="18.1139" fill="#FFFFFF"/>
<polygon fill="#EA5A47" stroke="#EA5A47" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="50.2353,23.2 38.1,36 63.5,36"/>
<path fill="#EA5A47" d="M47.3,26l-6.4,6.0595v-8.5733c0-0.9313,0.7549-1.4162,1.6862-1.4162h3.0276 c0.9313,0,1.6862,0.4849,1.6862,1.4162C47.3,23.4862,47.3,26,47.3,26z"/>
<rect x="43" y="42" width="6" height="12" fill="#A57939"/>
<rect x="54" y="42" width="6" height="5" fill="#92D3F5"/>
<path fill="#B1CC33" d="M41.0059,28.3393c0-3.3123-3.4447-6.0025-7.6937-6.0025c-0.3549,0.0029-0.7095,0.025-1.0622,0.0662 c-1.7995-3.4553-5.7065-5.8554-10.2499-5.8554c-6.2478,0-11.3122,4.5286-11.3122,10.1144c0.0012,0.3692,0.0247,0.738,0.0704,1.1043 C7.9656,28.6665,6,30.8102,6,33.3136c0,3.3123,3.4446,6.0025,7.6936,6.0025c1.6151,0.0139,3.2056-0.4022,4.6137-1.207 c1.8177,1.4766,4.7264,2.4385,8.0154,2.4385c5.5105,0,9.9776-2.6875,9.9776-6.0025c-0.0009-0.2191-0.0214-0.4377-0.0612-0.6531 C39.0357,32.9904,41.0059,30.8453,41.0059,28.3393z"/>
</g>
<g id="hair"/>
<g id="skin"/>
<g id="skin-shadow"/>
<g id="line">
<polyline fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="46,27 50.2353,23 64,35.0976 64,54 38,54 38,41.5482"/>
<rect x="43" y="42" width="6" height="12" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
<rect x="54" y="42" width="6" height="5" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
<polyline fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2" points="43.5,23 46.5,23 46.5,26"/>
<path fill="none" stroke="#000000" stroke-miterlimit="10" stroke-width="2" d="M31.245,39.3396"/>
<polyline fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" points="22.5,54 22.5,46.8269 17.5,42"/>
<line x1="23.0408" x2="25.885" y1="46.7265" y2="43.7657" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
<path fill="none" stroke="#000000" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M41.0059,28.3393 c0-3.3123-3.4447-6.0025-7.6937-6.0025c-0.3549,0.0029-0.7095,0.025-1.0622,0.0662c-1.7995-3.4553-5.7065-5.8554-10.2499-5.8554 c-6.2478,0-11.3122,4.5286-11.3122,10.1144c0.0012,0.3692,0.0247,0.738,0.0704,1.1043C7.9656,28.6665,6,30.8102,6,33.3136 c0,3.3123,3.4446,6.0025,7.6936,6.0025c1.6151,0.0139,3.2056-0.4022,4.6137-1.207c1.8177,1.4766,4.7264,2.4385,8.0154,2.4385 c5.5105,0,9.9776-2.6875,9.9776-6.0025c-0.0009-0.2191-0.0214-0.4377-0.0612-0.6531 C39.0357,32.9904,41.0059,30.8453,41.0059,28.3393z"/>
<line x1="63.5" x2="42.5" y1="36" y2="36" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

30
src/site/feed.njk 100644
View File

@ -0,0 +1,30 @@
---json
{
"permalink": "/feed.xml",
"eleventyExcludeFromCollections": true
}
---
{%if meta.siteBaseUrl %}<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="{{ meta.siteBaseUrl }}">
<title>{{ meta.siteName}}</title>
<link href="{{ meta.siteBaseUrl }}{{ permalink }}" rel="self" ////>
<link href="{{ meta.siteBaseUrl }}" ////>
<updated>{{ collections.notes | getNewestCollectionItemDate | dateToRfc3339 }}</updated>
<id>{{ meta.siteBaseUrl }}</id>
{%- for note in collections.note | reverse %}
<entry>
<title>
{% if note.title %}{{ note.title }}
{% else %}{{ note.fileSlug }}
{% endif %}
</title>
<updated>{%if note.data.updated %}{{ note.data.updated | dateToZulu }}{%else%}{{ note.date | dateToRfc3339 }}{%endif%}</updated>
<id>{{ meta.siteBaseUrl }}{{note.url | url }}</id>
<content type="html">
{{ note.templateContent | hideDataview | link | taggify | htmlToAbsoluteUrls(meta.siteBaseUrl) }}
</content>
<link href="{{ meta.siteBaseUrl }}{{note.url | url }}" ////>
</entry>
{%- endfor %}
</feed>
{% endif %}

View File

@ -0,0 +1,47 @@
require("dotenv").config();
const axios = require("axios");
const fs = require("fs");
const crypto = require("crypto");
const {globSync} = require("glob");
const themeCommentRegex = /\/\*[\s\S]*?\*\//g;
async function getTheme() {
let themeUrl = process.env.THEME;
if (themeUrl) {
//https://forum.obsidian.md/t/1-0-theme-migration-guide/42537
//Not all themes with no legacy mark have a theme.css file, so we need to check for it
try {
await axios.get(themeUrl);
} catch {
if (themeUrl.indexOf("theme.css") > -1) {
themeUrl = themeUrl.replace("theme.css", "obsidian.css");
} else if (themeUrl.indexOf("obsidian.css") > -1) {
themeUrl = themeUrl.replace("obsidian.css", "theme.css");
}
}
const res = await axios.get(themeUrl);
try {
const existing = globSync("src/site/styles/_theme.*.css");
existing.forEach((file) => {
fs.rmSync(file);
});
} catch {}
let skippedFirstComment = false;
const data = res.data.replace(themeCommentRegex, (match) => {
if (skippedFirstComment) {
return "";
} else {
skippedFirstComment = true;
return match;
}
});
const hashSum = crypto.createHash("sha256");
hashSum.update(data);
const hex = hashSum.digest("hex");
fs.writeFileSync(`src/site/styles/_theme.${hex.substring(0, 8)}.css`, data);
}
}
getTheme();

View File

@ -0,0 +1,5 @@
---
permalink: /graph.json
eleventyExcludeFromCollections: true
---
{{ graph | jsonify | safe }}

View File

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
<polyline points="14 2 14 8 20 8"></polyline>
<line x1="16" y1="13" x2="8" y2="13"></line>
<line x1="16" y1="17" x2="8" y2="17"></line>
<line x1="10" y1="9" x2="8" y2="9"></line>
</svg>

After

Width:  |  Height:  |  Size: 479 B

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns='http://www.w3.org/2000/svg' class='i-external' viewBox='0 0 32 32' width='14' height='14' fill='none' stroke='#888888' stroke-linecap='round' stroke-linejoin='round' stroke-width='9.38%'><path d='M14 9 L3 9 3 29 23 29 23 18 M18 4 L28 4 28 14 M28 4 L14 18'/></svg>

After

Width:  |  Height:  |  Size: 315 B

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="150" height="205" version="1.1" viewBox="0 0 39.688 54.24" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-69.7 -93.956)" fill="none" stroke="#008000">
<path d="m69.7 146.87h39.688" stroke-width="2.6458"/>
<g transform="translate(-.36252)">
<path d="m89.544 146.87v-6.794" stroke-width="2.6458"/>
<path d="m88.77 141.34 6.6272-8.1886" stroke-width="2.3347"/>
<path d="m89.919 141.46-5.5766-5.8386" stroke-width="2.3102"/>
</g>
<circle cx="100.95" cy="126.47" r="6.9136" stroke-width="2.6458"/>
<circle cx="79.351" cy="130.4" r="5.0854" stroke-width="2.6458"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 657 B

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="150" height="205" version="1.1" viewBox="0 0 39.688 54.24" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(0 -.8262)" fill="none" stroke="#20b2aa">
<circle cx="33.971" cy="33.263" r="4.79" stroke-width="1.8521"/>
<circle cx="5.716" cy="33.263" r="4.79" stroke-width="1.8521"/>
<g stroke-width="2.6458">
<path d="m6.8958 53.743h25.896"/>
<path d="m19.844 53.743v-25.896"/>
<circle cx="19.844" cy="18.683" r="7.0212"/>
<path d="m6.8958 40.795 12.948 3.237 12.948-3.237"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 630 B

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg width="150" height="205" version="1.1" viewBox="0 0 39.688 54.24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="#2e8b57">
<g>
<circle cx="34.846" cy="31.29" r="3.9152" stroke-width="1.8521"/>
<circle cx="4.8413" cy="31.29" r="3.9152" stroke-width="1.8521"/>
<path d="m9.2604 52.775h21.167" stroke-width="2.6458"/>
</g>
<path d="m19.844 53.834v-37.849" stroke-width="2.5838"/>
<circle cx="19.844" cy="7.1851" r="5.739" stroke-width="2.6458"/>
<path d="m6.6146 37.959 5.2917 5.2917h7.9375" stroke-width="2.6458"/>
<path d="m33.073 37.959-5.2917 5.2917h-7.9375" stroke-width="2.6458"/>
<circle cx="31.804" cy="17.056" r="3.9152" stroke-width="1.3229"/>
<circle cx="7.884" cy="17.056" r="3.9152" stroke-width="1.3229"/>
<path d="m9.2604 23.406 10.583 2.6458 10.583-2.6458" stroke-width="2.6458"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 949 B

View File

@ -0,0 +1,33 @@
require("dotenv").config();
const settings = require("../../helpers/constants");
const allSettings = settings.ALL_NOTE_SETTINGS;
module.exports = {
eleventyComputed: {
layout: (data) => {
if (data.tags.indexOf("gardenEntry") != -1) {
return "layouts/index.njk";
}
return "layouts/note.njk";
},
permalink: (data) => {
if (data.tags.indexOf("gardenEntry") != -1) {
return "/";
}
return data.permalink || undefined;
},
settings: (data) => {
const noteSettings = {};
allSettings.forEach((setting) => {
let noteSetting = data[setting];
let globalSetting = process.env[setting];
let settingValue =
noteSetting || (globalSetting === "true" && noteSetting !== false);
noteSettings[setting] = settingValue;
});
return noteSettings;
},
},
};

View File

@ -0,0 +1,4 @@
{
"tags": "note",
"templateEngineOverride": "njk,md"
}

View File

@ -0,0 +1,13 @@
---
permalink: /searchIndex.json
eleventyExcludeFromCollections: true
---
[{% for post in collections.note %}
{
"title": {% if post.data.title %}{{post.data.title | jsonify | safe }}{% else %}{{post.fileSlug | jsonify | safe }}{% endif %},
"date":"{{ post.date }}",
"url":"{{ post.url }}",
"content": {{ post.templateContent | striptags(true) | link | jsonify | safe }},
"tags": [{{post.templateContent | link | searchableTags | safe }} {% if post.data.tags %}{% for tag in post.data.tags %}"{{tag|validJson}}"{% if not loop.last %},{% endif %}{% endfor %}{% endif %}]
}{% if not loop.last %},{% endif %}
{% endfor %}]

View File

@ -0,0 +1,13 @@
---
permalink: /sitemap.xml
eleventyExcludeFromCollections: true
---
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for page in collections.all %}
<url>
<loc>{{ meta.siteBaseUrl }}{{ page.url | url }}</loc>
<lastmod>{{ page.date.toISOString() }}</lastmod>
</url>
{% endfor %}
</urlset>

View File

@ -0,0 +1,13 @@
body {
/***
ADD YOUR CUSTOM STYLIING HERE. (INSIDE THE body {...} section.)
IT WILL TAKE PRECEDENCE OVER THE STYLING IN THE STYLE.CSS FILE.
***/
// background-color: white;
// .content {
// font-size: 14px;
// }
// h1 {
// color: black;
// }
}

View File

@ -0,0 +1,753 @@
/***
* DO NOT ADD/MODIFY STYLING HERE. IT WILL BE OVERWRITTEN IF YOU UPDATE THE TEMPLATE VERSION.
* MODIFY THE custom-style.scss FILE INSTEAD.
***/
body {
overflow-x: hidden;
--note-icon-1: url(/img/tree-1.svg);
--note-icon-2: url(/img/tree-2.svg);
--note-icon-3: url(/img/tree-3.svg);
--note-icon-fallback: url(/img/default-note-icon.svg);
--graph-main: var(--text-accent);
--graph-muted: var(--text-muted);
}
.content {
max-width: 700px;
margin: auto;
font-size: 18px;
line-height: 1.5;
margin-top: 90px;
position: relative;
@media (max-width: 800px) {
margin-top: 75px;
}
}
.external-link {
background-position: center right;
background-repeat: no-repeat;
background-image: linear-gradient(transparent, transparent), url("/img/outgoing.svg");
background-size: 13px;
padding-right: 16px;
background-position-y: 4px;
cursor: pointer;
}
.markdown-preview-view pre.mermaid {
background: white;
border-radius: 25px;
padding: 10px;
}
div.transclusion {
position: relative;
padding: 8px;
.markdown-embed-link {
z-index: 99;
display: block;
width: auto !important;
}
}
ul.task-list {
list-style: none;
padding-left: 15px;
}
.sidebar {
position: fixed;
top: 75px;
right: 0;
height: 100%;
min-width: 25px;
display: flex;
z-index: 3;
max-width: 350px;
.graph {
width: 320px;
min-height: 320px;
#link-graph {
width: 320px;
height: 320px;
}
#graph-fs-btn {
margin-right: 10px;
}
}
.graph-fs {
position: fixed;
top: 50%;
left: 50%;
height: calc(100vmin - 100px);
width: calc(100vmin - 100px);
min-height: 350px;
min-width: 350px;
transform: translate(-50%, -50%);
z-index: 9999;
display: block;
background-color: var(--background-secondary);
border: 1px solid var(--text-accent);
border-radius: 5px;
padding-top: 5px;
#link-graph {
width: 100%;
height: 100%;
}
#graph-controls {
margin-top: 10px;
}
.graph-title {
display: none;
}
}
}
.expand-line {
display: flex;
flex-direction: column;
justify-content: center;
}
.sidebar-container {
padding-right: 20px;
width: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
height: 87%;
}
.toc {
padding-right: 5px;
background-color: var(--background-primary);
padding: 10px;
border-radius: 10px;
ol {
list-style: none;
padding-left: 10px;
border-left: 2px solid var(--background-secondary);
}
& > ol {
padding-left: 0;
border-left: none;
}
}
.toc-container {
font-size: 1rem;
max-height: 220px;
overflow-y: auto;
margin-bottom: 10px;
border-left: 1px solid var(--text-accent);
ul {
list-style-type: none;
padding-inline-start: 15px !important;
margin-top: 0;
margin-bottom: 0;
}
ul:not(:first-child) {
margin-bottom: 3px;
}
li {
padding-top: 4px;
&::before {
content: "# " !important;
color: var(--text-accent);
font-size: 0.8rem;
}
a {
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}
.toc-title-container {
display: flex;
justify-content: flex-start;
.toc-title {
font-size: 1.2rem !important;
color: var(--h6-color);
width: fit-content;
padding: 3px 7px 3px 0;
border-radius: 10px 10px 0 0;
}
}
.backlinks {
flex: 1;
margin-top: 10px;
background-color: var(--background-primary);
border-radius: 10px;
padding: 10px;
.backlink-title {
margin: 4px 0;
font-size: 18px !important;
color: var(--h6-color);
}
}
.backlink-list {
border-left: 1px solid var(--text-accent);
max-height: 200px;
overflow-y: auto;
overflow-x: hidden;
}
.backlink-card {
padding-bottom: 8px;
border-radius: 4px;
width: 100%;
font-size: 1rem;
margin-left: 10px;
color: var(--text-accent);
i {
font-size: 0.8rem;
}
.backlink {
margin-left: 5px;
}
}
.no-backlinks-message {
font-size: 0.8rem;
color: var(--text-normal);
}
.graph {
.graph-title-container {
display: flex;
justify-content: flex-end;
width: 100%;
}
.graph-title {
width: fit-content;
background-color: var(--background-secondary);
margin: 10px 0 0 0;
padding: 3px 7px;
font-size: 1.2rem !important;
border-radius: 10px 10px 0 0;
color: var(--h6-color);
}
}
#link-graph {
background-color: var(--background-secondary);
margin-bottom: 20px;
border-radius: 10px 0 10px 10px;
width: fit-content;
}
@media (max-width: 1400px) {
#link-graph {
border-radius: 0 10px 10px 10px;
}
.sidebar {
position: relative;
transform: none;
border-radius: 4px;
margin-top: 50px;
max-width: 700px;
margin: auto;
border-radius: 0;
border-top: 2px solid var(--background-secondary);
justify-content: space-between;
}
.sidebar-container {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.graph {
flex: 1;
.graph-title-container {
justify-content: flex-start;
}
}
.toc {
display: none;
}
}
@media (max-width: 800px) {
.sidebar-container {
display: flex;
flex-direction: column-reverse;
}
}
.filetree-sidebar {
margin: 0;
z-index: 10;
padding: 10px;
top: 0px;
left: 0;
position: fixed;
height: 100%;
background-color: var(--background-secondary);
color: var(--text-muted);
overflow-y: auto;
width: 250px;
h1 {
font-size: 32px !important;
}
@media (max-width: 800px) {
h1 {
display: none;
}
.search-button {
width: 100%;
margin: 2px;
}
}
}
.empty-navbar {
display: flex;
justify-content: flex-end;
margin-bottom: -60px;
@media (max-width: 800px) {
justify-content: center;
}
.search-button {
margin: 10px 65px 10px 65px;
}
}
.navbar {
background-color: var(--background-secondary);
position: absolute;
width: 100%;
top: 0;
left: 0;
right: 0;
z-index: 3;
padding-left: var(--file-margins);
display: flex;
justify-content: space-between;
align-items: center;
@media (max-width: 1400px) {
position: fixed;
}
@media (max-width: 800px) {
h1 {
font-size: 18px !important;
}
}
.navbar-inner {
display: flex;
align-items: center;
cursor: pointer;
}
.search-button {
@media (max-width: 800px) {
min-width: 36px;
margin: 10px 25px 10px 25px;
}
}
.search-text {
display: flex;
justify-content: center;
@media (max-width: 800px) {
display: none;
}
}
}
.notelink {
padding: 5px 0 5px 25px;
display: flex;
align-items: center;
a {
&:hover {
text-decoration: underline !important;
}
}
svg {
flex-shrink: 0;
}
}
.foldername-wrapper {
cursor: pointer;
margin: 4px 0 4px 2px;
}
.inner-folder {
padding: 3px 0 3px 10px;
}
.filename {
margin-left: 5px;
}
.notelink.active-note {
a {
color: var(--text-accent);
}
color: var(--text-accent);
background-color: var(--background-primary);
transform: translateX(10px);
}
.fullpage-overlay {
background-color: rgba(0, 0, 0, 0.5);
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 5;
}
.search-container {
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
z-index: 15;
height: 100%;
justify-content: center;
display: none;
}
.search-container.active {
display: flex;
}
.search-box {
transform: translateY(100px);
background-color: var(--background-primary);
max-width: 80%;
width: 900px;
border-radius: 15px;
padding: 10px;
height: fit-content;
}
.search-box input {
width: 100%;
padding: 10px;
border: none;
border-radius: 5px;
font-size: 2rem;
background-color: var(--background-primary);
color: var(--text-primary);
}
.search-box input:focus {
outline: none;
}
#search-results {
margin-top: 20px;
overflow-y: auto;
padding: 20px;
display: flex;
justify-content: center;
max-height: 50vh;
}
#search-results .searchresult {
margin-bottom: 15px;
list-style: none;
background-color: var(--background-secondary);
padding: 10px;
border-radius: 10px;
font-size: 1.2rem;
cursor: pointer;
&.active {
border: 2px solid var(--text-accent);
}
}
.search-box-footer {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
.navigation-hint {
font-size: 1rem;
color: var(--text-secondary);
margin-right: 20px;
}
.search-link {
display: block;
margin-bottom: 4px;
font-size: 1.4rem;
}
.search-button {
background-color: var(--background-primary);
border-radius: 20px;
height: 2em;
display: flex;
align-items: center;
min-width: 150px;
margin: 10px 40px;
border: 1px solid var(--text-normal);
cursor: pointer;
> i {
margin-left: 10px;
}
&:hover {
border: 1px solid var(--text-accent);
}
.search-icon {
display: flex;
}
}
.search-keys {
@media (max-width: 800px) {
display: none;
}
}
.callout-title-inner,
.callout-title-inner p,
.callout-icon,
.callout-fold,
.callout-content {
margin: 0;
padding: 0;
}
.callout-fold {
cursor: pointer;
}
.callout-title {
margin-top: 0;
align-items: center;
}
.callout {
font-family: var(--font-default);
word-wrap: break-word;
display: block;
font-size: 1rem;
}
.callout.is-collapsed {
.callout-content {
display: none;
}
.callout-fold .lucide {
transform: rotate(-90deg);
}
}
.callout-fold .lucide {
transition: transform 100ms ease-in-out;
}
.referred {
border: 1px dashed;
border-color: var(--text-accent);
padding: 10px;
margin-left: -10px;
margin-right: -10px;
}
// Graph Controls
.graph-title-container {
position: relative;
}
#full-graph {
position: fixed;
top: 50%;
left: 50%;
height: calc(100vmin - 100px);
width: calc(100vmin - 100px);
min-height: 350px;
min-width: 350px;
transform: translate(-50%, -50%);
z-index: 9999;
display: none;
background-color: var(--background-secondary);
border: 1px solid var(--text-accent);
border-radius: 5px;
#full-graph-container {
width: 100%;
height: 100%;
}
#full-graph-close {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
z-index: 9;
}
}
#graph-full-btn {
margin-right: 10px;
}
#full-graph.show {
display: block;
}
#graph-controls {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 5px;
position: absolute;
top: 115%;
cursor: pointer;
right: 0px;
left: 10px;
color: var(--text-accent);
z-index: 9;
i {
cursor: pointer;
padding-right: 10px;
}
.depth-control {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 7px;
.slider {
datalist {
display: flex;
flex-direction: row;
justify-content: space-between;
font-size: 0.6rem;
// padding: 2px 0px;
// width: 200px;
}
option {
padding: 0;
}
}
#depth-display {
background-color: var(--text-accent);
color: white;
width: 1.3rem;
height: 1.3rem;
font-size: 0.8rem;
display: flex;
justify-content: center;
align-items: center;
margin-top: 0.3rem;
border-radius: 50%;
}
}
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
margin-top: -10px;
}
body.title-note-icon .cm-s-obsidian > header > h1[data-note-icon]::before,
body.filetree-note-icon .filename[data-note-icon]::before,
body.links-note-icon .internal-link[data-note-icon]::before,
body.backlinks-note-icon .backlink[data-note-icon]::before {
content: " ";
display: inline-block;
width: 0.9em;
height: 1em;
background-size: contain;
background-repeat: no-repeat;
background-position: bottom;
background-image: var(--note-icon-fallback);
}
body.title-note-icon .cm-s-obsidian > header > h1[data-note-icon="1"]::before,
body.filetree-note-icon .filename[data-note-icon="1"]::before,
body.links-note-icon .internal-link[data-note-icon="1"]::before,
body.backlinks-note-icon .backlink[data-note-icon="1"]::before {
background-image: var(--note-icon-1);
}
body.title-note-icon .cm-s-obsidian > header > h1[data-note-icon="2"]::before,
body.filetree-note-icon .filename[data-note-icon="2"]::before,
body.links-note-icon .internal-link[data-note-icon="2"]::before,
body.backlinks-note-icon .backlink[data-note-icon="2"]::before {
background-image: var(--note-icon-2);
}
body.title-note-icon .cm-s-obsidian > header > h1[data-note-icon="3"]::before,
body.filetree-note-icon .filename[data-note-icon="3"]::before,
body.links-note-icon .internal-link[data-note-icon="3"]::before,
body.backlinks-note-icon .backlink[data-note-icon="3"]::before {
background-image: var(--note-icon-3);
}
.timestamps {
display: flex;
flex-direction: row;
font-size: 0.8em;
color: var(--text-muted);
gap: 10px;
margin-top: 20px;
div {
display: flex;
flex-direction: row;
gap: 3px;
align-items: center;
}
}
.align-icon {
display: inline-flex;
align-items: center;
justify-content: space-evenly;
}
.cm-s-obsidian {
.table-wrapper {
overflow-x: auto;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,219 @@
/***
* DO NOT ADD/MODIFY STYLING HERE. IT WILL BE OVERWRITTEN IF YOU UPDATE THE TEMPLATE VERSION.
* MODIFY THE custom-style.scss FILE INSTEAD.
***/
:root {
--background-primary: rgb(32, 31, 31);
--background-secondary: rgb(57, 56, 56);
--text-normal: #dcddde;
--text-accent: rgb(97, 186, 245);
--file-margins: 32px;
--callout-border-width: 0px;
--callout-border-opacity: 0.25;
--callout-padding: 12px 12px 12px 24px;
--callout-radius: 4px;
--callout-blend-mode: lighten;
--callout-title-padding: 0;
--callout-title-size: inherit;
--callout-content-padding: 0;
}
h1 {
color: #ffef60;
}
h2 {
color: #f06449;
}
h3 {
color: #d4fcc3;
}
h4 {
color: #72dcff;
}
button {
border: none;
color: white;
padding: 5px 15px;
}
.centered {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.theme-dark {
background: var(--background-primary);
color: var(--text-normal);
font-family: var(--font-default);
}
.theme-light {
background: white;
color: black;
font-family: var(--font-default);
}
a.is-unresolved {
color: rgb(97 186 245 / 65%);
}
a {
text-decoration: underline;
color: var(--text-accent);
}
.font-bg {
font-size: 92px;
}
blockquote {
background: #ffffff17;
border-left: 10px solid #c1dbe3;
margin: 1.5em 10px;
padding: 0.5em 10px;
quotes: "\201C""\201D""\2018""\2019";
}
blockquote:before {
color: #ccc;
content: open-quote;
font-size: 4em;
line-height: 0.1em;
margin-right: 0.1em;
vertical-align: -0.4em;
}
blockquote p {
display: inline;
}
p > code {
//Inline code
color: #c7254e;
background-color: #1a1a1a;
}
.callout {
--callout-color: 68, 138, 255;
--callout-icon: lucide-pencil;
}
.callout[data-callout="abstract"],
.callout[data-callout="summary"],
.callout[data-callout="tldr"] {
--callout-color: 0, 176, 255;
--callout-icon: lucide-clipboard-list;
}
.callout[data-callout="info"],
.callout[data-callout="todo"] {
--callout-color: 0, 184, 212;
}
.callout[data-callout="info"] {
--callout-icon: lucide-info;
}
.callout[data-callout="todo"] {
--callout-icon: lucide-check-circle-2;
}
.callout[data-callout="tip"],
.callout[data-callout="hint"],
.callout[data-callout="important"] {
--callout-color: 0, 191, 165;
--callout-icon: lucide-flame;
}
.callout[data-callout="success"],
.callout[data-callout="check"],
.callout[data-callout="done"] {
--callout-color: 0, 200, 83;
--callout-icon: lucide-check;
}
.callout[data-callout="question"],
.callout[data-callout="help"],
.callout[data-callout="faq"] {
--callout-color: 100, 221, 23;
--callout-icon: help-circle;
}
.callout[data-callout="warning"],
.callout[data-callout="caution"],
.callout[data-callout="attention"] {
--callout-color: 255, 145, 0;
--callout-icon: lucide-alert-triangle;
}
.callout[data-callout="failure"],
.callout[data-callout="fail"],
.callout[data-callout="missing"] {
--callout-color: 255, 82, 82;
--callout-icon: lucide-x;
}
.callout[data-callout="danger"],
.callout[data-callout="error"] {
--callout-color: 255, 23, 68;
--callout-icon: lucide-zap;
}
.callout[data-callout="bug"] {
--callout-color: 245, 0, 87;
--callout-icon: lucide-bug;
}
.callout[data-callout="example"] {
--callout-color: 124, 77, 255;
--callout-icon: lucide-list;
}
.callout[data-callout="quote"],
.callout[data-callout="cite"] {
--callout-color: 158, 158, 158;
--callout-icon: lucide-quote;
}
.callout {
overflow: hidden;
border-style: solid;
border-color: rgba(var(--callout-color), var(--callout-border-opacity));
border-width: var(--callout-border-width);
border-radius: var(--callout-radius);
margin: 1em 0;
mix-blend-mode: var(--callout-blend-mode);
background-color: rgba(var(--callout-color), 0.1);
padding: var(--callout-padding);
}
.callout.is-collapsible .callout-title {
cursor: default;
}
.callout-title {
padding: var(--callout-title-padding);
display: flex;
gap: 4px;
font-size: var(--callout-title-size);
color: rgb(var(--callout-color));
line-height: 1.3;
}
.callout-content {
overflow-x: auto;
padding: var(--callout-content-padding);
}
.callout-icon {
flex: 0 0 auto;
display: flex;
align-self: center;
}
.callout-icon .svg-icon {
color: rgb(var(--callout-color));
}
.callout-title-inner {
font-weight: 600;
}
.callout-fold {
display: flex;
align-items: center;
padding-right: 8px;
}
.callout-fold .svg-icon {
transition: transform 100ms ease-in-out;
}
.callout.is-collapsed .callout-fold .svg-icon {
transform: rotate(-90deg);
}

10
vercel.json 100644
View File

@ -0,0 +1,10 @@
{
"outputDirectory": "dist",
"installCommand": "npm install",
"buildCommand": "npm run build",
"devCommand": "npm run start",
"routes": [
{ "handle": "filesystem" },
{ "src": "/(.*)", "status": 404, "dest": "/404" }
]
}