Add ability to view or edit documents, switch from milkdown to codemirror
This commit is contained in:
parent
65ad20d197
commit
92835d93e2
18 changed files with 28530 additions and 31851 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -1240,6 +1240,15 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getopts"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -2257,6 +2266,7 @@ dependencies = [
|
||||||
"free-icons",
|
"free-icons",
|
||||||
"minijinja",
|
"minijinja",
|
||||||
"minijinja-autoreload",
|
"minijinja-autoreload",
|
||||||
|
"pulldown-cmark",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"redb",
|
"redb",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2346,6 +2356,25 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pulldown-cmark"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8746739f11d39ce5ad5c2520a9b75285310dbfe78c541ccf832d38615765aec0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.5.0",
|
||||||
|
"getopts",
|
||||||
|
"memchr",
|
||||||
|
"pulldown-cmark-escape",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pulldown-cmark-escape"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quanta"
|
name = "quanta"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
|
@ -3686,6 +3715,12 @@ version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode_categories"
|
name = "unicode_categories"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|
|
@ -23,6 +23,7 @@ env_logger = "0.11.3"
|
||||||
free-icons = "0.7.0"
|
free-icons = "0.7.0"
|
||||||
minijinja = { version = "1.0.14", features = ["loader", "json", "builtins"] }
|
minijinja = { version = "1.0.14", features = ["loader", "json", "builtins"] }
|
||||||
minijinja-autoreload = "1.0.14"
|
minijinja-autoreload = "1.0.14"
|
||||||
|
pulldown-cmark = "0.11.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
redb = "2.1.0"
|
redb = "2.1.0"
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
|
44
frontend/editor.ts
Normal file
44
frontend/editor.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import {basicSetup} from "codemirror"
|
||||||
|
import {EditorView, keymap} from "@codemirror/view"
|
||||||
|
import {indentWithTab} from "@codemirror/commands"
|
||||||
|
import {markdown} from "@codemirror/lang-markdown"
|
||||||
|
|
||||||
|
export function makeEditor(divSelector, value) {
|
||||||
|
let div = document.querySelector(divSelector);
|
||||||
|
div.classList.remove("hidden");
|
||||||
|
div.classList.remove("h-0");
|
||||||
|
|
||||||
|
let documentTheme = EditorView.theme({
|
||||||
|
"&": {
|
||||||
|
"background-color": "white",
|
||||||
|
},
|
||||||
|
".cm-editor": {
|
||||||
|
"height": "100%",
|
||||||
|
},
|
||||||
|
".cm-scroller": {overflow: "auto"}
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
// add a hidden textarea inside the div for form submission
|
||||||
|
let textarea = document.createElement("textarea");
|
||||||
|
textarea.setAttribute("name", "content");
|
||||||
|
textarea.style.display = "none";
|
||||||
|
div.appendChild(textarea);
|
||||||
|
|
||||||
|
let extensions = [
|
||||||
|
basicSetup,
|
||||||
|
keymap.of([indentWithTab]),
|
||||||
|
markdown(),
|
||||||
|
documentTheme,
|
||||||
|
EditorView.lineWrapping,
|
||||||
|
];
|
||||||
|
|
||||||
|
let view = new EditorView({parent: div, doc: value, extensions})
|
||||||
|
|
||||||
|
textarea.form.addEventListener("submit", () => {
|
||||||
|
textarea.value = view.state.doc.toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
// remove the "hidden" and "h-0" classes from the div
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
|
@ -2,59 +2,16 @@
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
.prose li {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
/* Borrowed from https://github.com/Milkdown/milkdown/blob/main/e2e/src/list-item-block/style.css
|
|
||||||
which is licensed under MIT. */
|
|
||||||
|
|
||||||
.prose :where(li):not(:where([class~="not-prose"] *)) {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose :where(blockquote):not(:where([class~="not-prose"] *)) {
|
|
||||||
font-style: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"] *)) {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose ol,
|
|
||||||
.prose ul {
|
.prose ul {
|
||||||
list-style: none !important;
|
margin-top: 0;
|
||||||
padding: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose li p {
|
.prose h1 {
|
||||||
@apply !m-0 !leading-6;
|
margin-bottom: 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose li p + p {
|
|
||||||
@apply !mt-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose li.ProseMirror-selectednode {
|
|
||||||
outline: 2px solid #8cf;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose li::after {
|
|
||||||
all: unset !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
milkdown-list-item-block .list-item {
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
milkdown-list-item-block .label-wrapper {
|
|
||||||
height: 24px;
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
color: darkcyan;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* End borrowed block. */
|
|
||||||
|
|
|
@ -1,52 +1,2 @@
|
||||||
import { defaultValueCtx, Editor, rootCtx } from '@milkdown/core';
|
import { makeEditor } from './editor';
|
||||||
import { html } from 'atomico';
|
window.makeEditor = makeEditor;
|
||||||
import { listItemBlockComponent, listItemBlockConfig, ListItemBlockConfig, listItemBlockView } from '@milkdown/components/list-item-block'
|
|
||||||
import { commonmark } from '@milkdown/preset-commonmark';
|
|
||||||
import { gfm } from '@milkdown/preset-gfm';
|
|
||||||
import { nord } from '@milkdown/theme-nord'
|
|
||||||
import { listener, listenerCtx } from '@milkdown/plugin-listener';
|
|
||||||
import '@milkdown/theme-nord/style.css'
|
|
||||||
|
|
||||||
function configureListItem(ctx: Ctx) {
|
|
||||||
ctx.set(listItemBlockConfig.key, {
|
|
||||||
renderLabel: (label: string, listType, checked?: boolean) => {
|
|
||||||
if (checked == null) {
|
|
||||||
if (listType === 'bullet') {
|
|
||||||
return html`<span class='label'>•</span>`
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`<span class='label'>${label}</span>`
|
|
||||||
} else {
|
|
||||||
return html`<input class='label' type="checkbox" checked=${checked} />`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function createEditor(rootId, fieldId, content) {
|
|
||||||
Editor
|
|
||||||
.make()
|
|
||||||
.config(ctx => {
|
|
||||||
ctx.set(rootCtx, rootId)
|
|
||||||
ctx.set(defaultValueCtx, content)
|
|
||||||
|
|
||||||
const listener = ctx.get(listenerCtx);
|
|
||||||
listener.markdownUpdated((ctx, markdown, prevMarkdown) => {
|
|
||||||
if (markdown !== prevMarkdown) {
|
|
||||||
console.log(markdown);
|
|
||||||
console.log(fieldId);
|
|
||||||
document.getElementById(fieldId).value = markdown;
|
|
||||||
console.log("updated");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.config(configureListItem)
|
|
||||||
.use(commonmark)
|
|
||||||
.use(gfm)
|
|
||||||
.use(nord)
|
|
||||||
.use(listener)
|
|
||||||
.use(listItemBlockComponent)
|
|
||||||
.create();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.createEditor = createEditor;
|
|
||||||
|
|
180
package-lock.json
generated
180
package-lock.json
generated
|
@ -5,6 +5,7 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/lang-markdown": "^6.2.5",
|
||||||
"@milkdown/components": "^7.3.6",
|
"@milkdown/components": "^7.3.6",
|
||||||
"@milkdown/core": "^7.3.6",
|
"@milkdown/core": "^7.3.6",
|
||||||
"@milkdown/plugin-listener": "^7.3.6",
|
"@milkdown/plugin-listener": "^7.3.6",
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
"@milkdown/theme-nord": "^7.3.6",
|
"@milkdown/theme-nord": "^7.3.6",
|
||||||
"@prosemirror-adapter/lit": "^0.2.6",
|
"@prosemirror-adapter/lit": "^0.2.6",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
"tailwindcss": "^3.4.3"
|
"tailwindcss": "^3.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -387,11 +389,94 @@
|
||||||
"atomico": "^1.75.1"
|
"atomico": "^1.75.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "6.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.16.2.tgz",
|
||||||
|
"integrity": "sha512-MjfDrHy0gHKlPWsvSsikhO1+BOh+eBHNgfH1OXs1+DAf30IonQldgMM3kxLDTG9ktE7kDLaA1j/l7KMPA4KNfw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/commands": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-rK+sj4fCAN/QfcY9BEzYMgp4wwL/q5aj/VfNSoH1RWPF9XS/dUwBkvlL3hpWgEjOqlpdN1uLC9UkjJ4tmyjJYg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.4.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-css": {
|
||||||
|
"version": "6.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.2.1.tgz",
|
||||||
|
"integrity": "sha512-/UNWDNV5Viwi/1lpr/dIXJNWiwDxpw13I4pTUAsNxZdg6E0mI2kTQb0P2iHczg1Tu+H4EBgJR+hYhKiHKko7qg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.0.2",
|
||||||
|
"@lezer/css": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-html": {
|
||||||
|
"version": "6.4.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz",
|
||||||
|
"integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/lang-css": "^6.0.0",
|
||||||
|
"@codemirror/lang-javascript": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.4.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/css": "^1.1.0",
|
||||||
|
"@lezer/html": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-javascript": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.6.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/javascript": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lang-markdown": {
|
||||||
|
"version": "6.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.2.5.tgz",
|
||||||
|
"integrity": "sha512-Hgke565YcO4fd9pe2uLYxnMufHO5rQwRr+AAhFq8ABuhkrjyX8R5p5s+hZUTdV60O0dMRjxKhBLxz8pu/MkUVA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.7.1",
|
||||||
|
"@codemirror/lang-html": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.3.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@lezer/common": "^1.2.1",
|
||||||
|
"@lezer/markdown": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@codemirror/language": {
|
"node_modules/@codemirror/language": {
|
||||||
"version": "6.10.1",
|
"version": "6.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz",
|
||||||
"integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
|
"integrity": "sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
"@codemirror/view": "^6.23.0",
|
"@codemirror/view": "^6.23.0",
|
||||||
|
@ -401,17 +486,35 @@
|
||||||
"style-mod": "^4.0.0"
|
"style-mod": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/lint": {
|
||||||
|
"version": "6.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.0.tgz",
|
||||||
|
"integrity": "sha512-lsFofvaw0lnPRJlQylNsC4IRt/1lI4OD/yYslrSGVndOJfStc58v+8p9dgGiD90ktOfL7OhBWns1ZETYgz0EJA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/search": {
|
||||||
|
"version": "6.5.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.6.tgz",
|
||||||
|
"integrity": "sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@codemirror/state": {
|
"node_modules/@codemirror/state": {
|
||||||
"version": "6.4.1",
|
"version": "6.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz",
|
||||||
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==",
|
"integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A=="
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/view": {
|
"node_modules/@codemirror/view": {
|
||||||
"version": "6.26.3",
|
"version": "6.26.3",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.26.3.tgz",
|
||||||
"integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==",
|
"integrity": "sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.4.0",
|
"@codemirror/state": "^6.4.0",
|
||||||
"style-mod": "^4.1.0",
|
"style-mod": "^4.1.0",
|
||||||
|
@ -876,27 +979,63 @@
|
||||||
"node_modules/@lezer/common": {
|
"node_modules/@lezer/common": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz",
|
||||||
"integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==",
|
"integrity": "sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ=="
|
||||||
"peer": true
|
},
|
||||||
|
"node_modules/@lezer/css": {
|
||||||
|
"version": "1.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.8.tgz",
|
||||||
|
"integrity": "sha512-7JhxupKuMBaWQKjQoLtzhGj83DdnZY9MckEOG5+/iLKNK2ZJqKc6hf6uc0HjwCX7Qlok44jBNqZhHKDhEhZYLA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lezer/highlight": {
|
"node_modules/@lezer/highlight": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.0.tgz",
|
||||||
"integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==",
|
"integrity": "sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lezer/common": "^1.0.0"
|
"@lezer/common": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@lezer/html": {
|
||||||
|
"version": "1.3.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz",
|
||||||
|
"integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/javascript": {
|
||||||
|
"version": "1.4.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.16.tgz",
|
||||||
|
"integrity": "sha512-84UXR3N7s11MPQHWgMnjb9571fr19MmXnr5zTv2XX0gHXXUvW3uPJ8GCjKrfTXmSdfktjRK0ayKklw+A13rk4g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.2.0",
|
||||||
|
"@lezer/highlight": "^1.1.3",
|
||||||
|
"@lezer/lr": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@lezer/lr": {
|
"node_modules/@lezer/lr": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz",
|
||||||
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
|
"integrity": "sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lezer/common": "^1.0.0"
|
"@lezer/common": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@lezer/markdown": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-ErbEQ15eowmJUyT095e9NJc3BI9yZ894fjSDtHftD0InkfUBGgnKSU6dvan9jqsZuNHg2+ag/1oyDRxNsENupQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0",
|
||||||
|
"@lezer/highlight": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@lit-labs/context": {
|
"node_modules/@lit-labs/context": {
|
||||||
"version": "0.3.3",
|
"version": "0.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/@lit-labs/context/-/context-0.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/@lit-labs/context/-/context-0.3.3.tgz",
|
||||||
|
@ -1535,6 +1674,20 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codemirror": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/commands": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
@ -1562,6 +1715,11 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crelt": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
@ -3618,8 +3776,7 @@
|
||||||
"node_modules/style-mod": {
|
"node_modules/style-mod": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||||
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
|
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/sucrase": {
|
"node_modules/sucrase": {
|
||||||
"version": "3.35.0",
|
"version": "3.35.0",
|
||||||
|
@ -3859,8 +4016,7 @@
|
||||||
"node_modules/w3c-keyname": {
|
"node_modules/w3c-keyname": {
|
||||||
"version": "2.2.8",
|
"version": "2.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"css": "tailwindcss -i ./frontend/main.css -o ./static/style.css"
|
"css": "tailwindcss -i ./frontend/main.css -o ./static/style.css"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@codemirror/lang-markdown": "^6.2.5",
|
||||||
"@milkdown/components": "^7.3.6",
|
"@milkdown/components": "^7.3.6",
|
||||||
"@milkdown/core": "^7.3.6",
|
"@milkdown/core": "^7.3.6",
|
||||||
"@milkdown/plugin-listener": "^7.3.6",
|
"@milkdown/plugin-listener": "^7.3.6",
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
"@milkdown/theme-nord": "^7.3.6",
|
"@milkdown/theme-nord": "^7.3.6",
|
||||||
"@prosemirror-adapter/lit": "^0.2.6",
|
"@prosemirror-adapter/lit": "^0.2.6",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
"tailwindcss": "^3.4.3"
|
"tailwindcss": "^3.4.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,6 +104,45 @@ pub async fn create_document_submit(
|
||||||
Ok(Redirect::to("/documents").into_response())
|
Ok(Redirect::to("/documents").into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn view_document_page(
|
||||||
|
State(provider): State<Provider>,
|
||||||
|
auth_session: AuthSession<Provider>,
|
||||||
|
Path((id,)): Path<(Uuid,)>,
|
||||||
|
) -> Result<Response, (StatusCode, String)> {
|
||||||
|
let user = match auth_session.user {
|
||||||
|
Some(user) => user,
|
||||||
|
None => return Ok(Redirect::to("/login").into_response()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut db = provider.db_pool.get().map_err(internal_error)?;
|
||||||
|
|
||||||
|
let document_allowed =
|
||||||
|
permissions::q::check_user_document(&mut db, &user.id, &id.to_string(), Permission::Write)
|
||||||
|
.map_err(internal_error)?;
|
||||||
|
|
||||||
|
if !document_allowed {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "permission denied".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let document = match documents::q::by_id(&mut db, &id.to_string()).map_err(internal_error)? {
|
||||||
|
Some(doc) => doc,
|
||||||
|
None => return Err((StatusCode::NOT_FOUND, "document not found".to_owned())),
|
||||||
|
};
|
||||||
|
let projects =
|
||||||
|
permissions::q::accessible_projects(&mut db, &user.id).map_err(internal_error)?;
|
||||||
|
|
||||||
|
let rendered_document = document.render_html();
|
||||||
|
|
||||||
|
let values = context! {
|
||||||
|
user => user,
|
||||||
|
document => document,
|
||||||
|
projects => projects,
|
||||||
|
rendered_document => rendered_document,
|
||||||
|
};
|
||||||
|
|
||||||
|
provider.render_resp("documents/view_document.html", values)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn edit_document_page(
|
pub async fn edit_document_page(
|
||||||
State(provider): State<Provider>,
|
State(provider): State<Provider>,
|
||||||
auth_session: AuthSession<Provider>,
|
auth_session: AuthSession<Provider>,
|
||||||
|
@ -176,5 +215,6 @@ pub async fn edit_document_submit(
|
||||||
)
|
)
|
||||||
.map_err(internal_error)?;
|
.map_err(internal_error)?;
|
||||||
|
|
||||||
Ok(Redirect::to("/documents").into_response())
|
let view_url = format!("/documents/view/{}", document_id);
|
||||||
|
Ok(Redirect::to(&view_url).into_response())
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ pub async fn login_submit(
|
||||||
Form(creds): Form<Credentials>,
|
Form(creds): Form<Credentials>,
|
||||||
) -> Result<Response, (StatusCode, String)> {
|
) -> Result<Response, (StatusCode, String)> {
|
||||||
if let Some(user) = auth_session.authenticate(creds).await.map_err(internal_error)? {
|
if let Some(user) = auth_session.authenticate(creds).await.map_err(internal_error)? {
|
||||||
let _ = auth_session.login(&user).await.map_err(internal_error)?;
|
auth_session.login(&user).await.map_err(internal_error)?;
|
||||||
Ok(Redirect::to("/").into_response())
|
Ok(Redirect::to("/").into_response())
|
||||||
} else {
|
} else {
|
||||||
render_login_page(&provider, "", "", Some(LOGIN_ERROR_MSG))
|
render_login_page(&provider, "", "", Some(LOGIN_ERROR_MSG))
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use pulldown_cmark as markdown;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -16,6 +17,21 @@ pub struct Document {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Document {
|
||||||
|
pub fn render_html(&self) -> String {
|
||||||
|
let mut options = markdown::Options::empty();
|
||||||
|
options.insert(markdown::Options::ENABLE_TASKLISTS);
|
||||||
|
options.insert(markdown::Options::ENABLE_STRIKETHROUGH);
|
||||||
|
|
||||||
|
let parser = markdown::Parser::new_ext(&self.content, options);
|
||||||
|
|
||||||
|
let mut html_output = String::new();
|
||||||
|
markdown::html::push_html(&mut html_output, parser);
|
||||||
|
|
||||||
|
html_output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Insertable)]
|
#[derive(Insertable)]
|
||||||
#[diesel(table_name = crate::schema::documents)]
|
#[diesel(table_name = crate::schema::documents)]
|
||||||
pub struct NewDocument {
|
pub struct NewDocument {
|
||||||
|
|
|
@ -17,7 +17,7 @@ use crate::config::CommandLineOptions;
|
||||||
use crate::db;
|
use crate::db;
|
||||||
use crate::handler::documents::{
|
use crate::handler::documents::{
|
||||||
create_document_page, create_document_submit, documents_page, edit_document_page,
|
create_document_page, create_document_submit, documents_page, edit_document_page,
|
||||||
edit_document_submit,
|
edit_document_submit, view_document_page,
|
||||||
};
|
};
|
||||||
use crate::handler::home::home_page;
|
use crate::handler::home::home_page;
|
||||||
use crate::handler::login::logout;
|
use crate::handler::login::logout;
|
||||||
|
@ -66,6 +66,7 @@ pub async fn run() -> Result<()> {
|
||||||
.route("/documents", get(documents_page))
|
.route("/documents", get(documents_page))
|
||||||
.route("/documents/new", get(create_document_page))
|
.route("/documents/new", get(create_document_page))
|
||||||
.route("/documents/new", post(create_document_submit))
|
.route("/documents/new", post(create_document_submit))
|
||||||
|
.route("/documents/view/:id", get(view_document_page))
|
||||||
.route("/documents/edit/:id", get(edit_document_page))
|
.route("/documents/edit/:id", get(edit_document_page))
|
||||||
.route("/documents/edit/:id", post(edit_document_submit))
|
.route("/documents/edit/:id", post(edit_document_submit))
|
||||||
.layer(trace_layer)
|
.layer(trace_layer)
|
||||||
|
|
196
static/main.css
196
static/main.css
|
@ -1,196 +0,0 @@
|
||||||
/* node_modules/@milkdown/theme-nord/lib/style.css */
|
|
||||||
.ProseMirror {
|
|
||||||
position: relative;
|
|
||||||
word-wrap: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
white-space: break-spaces;
|
|
||||||
font-variant-ligatures: none;
|
|
||||||
font-feature-settings: "liga" 0;
|
|
||||||
}
|
|
||||||
.ProseMirror pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
.ProseMirror li {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.ProseMirror-hideselection *::selection {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.ProseMirror-hideselection *::-moz-selection {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
.ProseMirror-hideselection {
|
|
||||||
caret-color: transparent;
|
|
||||||
}
|
|
||||||
.ProseMirror [draggable][contenteditable=false] {
|
|
||||||
-webkit-user-select: text;
|
|
||||||
-moz-user-select: text;
|
|
||||||
user-select: text;
|
|
||||||
}
|
|
||||||
.ProseMirror-selectednode {
|
|
||||||
outline: 2px solid #8cf;
|
|
||||||
}
|
|
||||||
li.ProseMirror-selectednode {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
li.ProseMirror-selectednode:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
left: -32px;
|
|
||||||
right: -2px;
|
|
||||||
top: -2px;
|
|
||||||
bottom: -2px;
|
|
||||||
border: 2px solid #8cf;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
img.ProseMirror-separator {
|
|
||||||
display: inline !important;
|
|
||||||
border: none !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
.ProseMirror .tableWrapper {
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
.ProseMirror table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
table-layout: fixed;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.ProseMirror td,
|
|
||||||
.ProseMirror th {
|
|
||||||
vertical-align: top;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.ProseMirror .column-resize-handle {
|
|
||||||
position: absolute;
|
|
||||||
right: -2px;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 4px;
|
|
||||||
z-index: 20;
|
|
||||||
background-color: #adf;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.ProseMirror.resize-cursor {
|
|
||||||
cursor: ew-resize;
|
|
||||||
cursor: col-resize;
|
|
||||||
}
|
|
||||||
.ProseMirror .selectedCell:after {
|
|
||||||
z-index: 2;
|
|
||||||
position: absolute;
|
|
||||||
content: "";
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: #c8c8ff66;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord blockquote {
|
|
||||||
border-left-width: 4px;
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgb(94 129 172 / var(--tw-border-opacity));
|
|
||||||
padding-left: 1rem;
|
|
||||||
font-family:
|
|
||||||
ui-serif,
|
|
||||||
Georgia,
|
|
||||||
Cambria,
|
|
||||||
Times New Roman,
|
|
||||||
Times,
|
|
||||||
serif;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord code {
|
|
||||||
font-family:
|
|
||||||
ui-monospace,
|
|
||||||
SFMono-Regular,
|
|
||||||
Menlo,
|
|
||||||
Monaco,
|
|
||||||
Consolas,
|
|
||||||
Liberation Mono,
|
|
||||||
Courier New,
|
|
||||||
monospace;
|
|
||||||
font-weight: 400;
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(94 129 172 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord pre code {
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord img {
|
|
||||||
margin-top: 0 !important;
|
|
||||||
margin-bottom: 0 !important;
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose :where(blockquote):not(:where([class~=not-prose] *)) {
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose :where(ol > li):not(:where([class~=not-prose] *))::marker,
|
|
||||||
.milkdown-theme-nord.prose :where(ul > li):not(:where([class~=not-prose] *))::marker {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(94 129 172 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose] *)):before,
|
|
||||||
.milkdown-theme-nord.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose] *)):after {
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose :where(code):not(:where([class~=not-prose] *)):before,
|
|
||||||
.milkdown-theme-nord.prose :where(code):not(:where([class~=not-prose] *)):after {
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose .tableWrapper {
|
|
||||||
position: relative;
|
|
||||||
margin-bottom: .5rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose table {
|
|
||||||
margin: 1rem !important;
|
|
||||||
overflow: visible !important;
|
|
||||||
font-size: .875rem;
|
|
||||||
line-height: 1.25rem;
|
|
||||||
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);
|
|
||||||
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
|
|
||||||
box-shadow:
|
|
||||||
var(--tw-ring-offset-shadow, 0 0 #0000),
|
|
||||||
var(--tw-ring-shadow, 0 0 #0000),
|
|
||||||
var(--tw-shadow);
|
|
||||||
}
|
|
||||||
@media (min-width: 640px) {
|
|
||||||
.milkdown-theme-nord.prose table {
|
|
||||||
border-radius: .5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose td,
|
|
||||||
.milkdown-theme-nord.prose th {
|
|
||||||
padding: .75rem 1.5rem !important;
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose tr {
|
|
||||||
border-bottom-width: 1px;
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgb(229 231 235 / var(--tw-border-opacity));
|
|
||||||
}
|
|
||||||
:is(.dark .milkdown-theme-nord.prose tr) {
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgb(75 85 99 / var(--tw-border-opacity));
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose :where(td, th) p {
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose :where(td, th):nth-child(odd) {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
:is(.dark .milkdown-theme-nord.prose :where(td, th):nth-child(odd)) {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(17 24 39 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose.ProseMirror .selectedCell:after {
|
|
||||||
background-color: #88c0d04d;
|
|
||||||
}
|
|
||||||
.milkdown-theme-nord.prose br[data-is-inline=true],
|
|
||||||
.milkdown-theme-nord.prose br[data-is-inline=true]:after {
|
|
||||||
content: " ";
|
|
||||||
}
|
|
59480
static/main.js
59480
static/main.js
File diff suppressed because one or more lines are too long
190
static/style.css
190
static/style.css
|
@ -985,22 +985,6 @@ select {
|
||||||
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
|
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox {
|
|
||||||
flex-shrink: 0;
|
|
||||||
--chkbg: var(--fallback-bc,oklch(var(--bc)/1));
|
|
||||||
--chkfg: var(--fallback-b1,oklch(var(--b1)/1));
|
|
||||||
height: 1.5rem;
|
|
||||||
width: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-moz-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
border-radius: var(--rounded-btn, 0.5rem);
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
|
|
||||||
--tw-border-opacity: 0.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
--tw-border-opacity: 1;
|
--tw-border-opacity: 1;
|
||||||
|
@ -1341,54 +1325,6 @@ select {
|
||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checkbox:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox:focus-visible {
|
|
||||||
outline-style: solid;
|
|
||||||
outline-width: 2px;
|
|
||||||
outline-offset: 2px;
|
|
||||||
outline-color: var(--fallback-bc,oklch(var(--bc)/1));
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox:disabled {
|
|
||||||
border-width: 0px;
|
|
||||||
cursor: not-allowed;
|
|
||||||
border-color: transparent;
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
|
|
||||||
opacity: 0.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox:checked,
|
|
||||||
.checkbox[aria-checked="true"] {
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
animation: checkmark var(--animation-input, 0.2s) ease-out;
|
|
||||||
background-color: var(--chkbg);
|
|
||||||
background-image: linear-gradient(-45deg, transparent 65%, var(--chkbg) 65.99%),
|
|
||||||
linear-gradient(45deg, transparent 75%, var(--chkbg) 75.99%),
|
|
||||||
linear-gradient(-45deg, var(--chkbg) 40%, transparent 40.99%),
|
|
||||||
linear-gradient(
|
|
||||||
45deg,
|
|
||||||
var(--chkbg) 30%,
|
|
||||||
var(--chkfg) 30.99%,
|
|
||||||
var(--chkfg) 40%,
|
|
||||||
transparent 40.99%
|
|
||||||
),
|
|
||||||
linear-gradient(-45deg, var(--chkfg) 50%, var(--chkbg) 50.99%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox:indeterminate {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
animation: checkmark var(--animation-input, 0.2s) ease-out;
|
|
||||||
background-image: linear-gradient(90deg, transparent 80%, var(--chkbg) 80%),
|
|
||||||
linear-gradient(-90deg, transparent 80%, var(--chkbg) 80%),
|
|
||||||
linear-gradient(0deg, var(--chkbg) 43%, var(--chkfg) 43%, var(--chkfg) 57%, var(--chkbg) 57%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes checkmark {
|
@keyframes checkmark {
|
||||||
0% {
|
0% {
|
||||||
background-position-y: 5px;
|
background-position-y: 5px;
|
||||||
|
@ -1707,6 +1643,27 @@ select {
|
||||||
border-bottom-color: var(--fallback-bc,oklch(var(--bc)/0.2));
|
border-bottom-color: var(--fallback-bc,oklch(var(--bc)/0.2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-sm {
|
||||||
|
height: 2rem;
|
||||||
|
min-height: 2rem;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-square:where(.btn-sm) {
|
||||||
|
height: 2rem;
|
||||||
|
width: 2rem;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-circle:where(.btn-sm) {
|
||||||
|
height: 2rem;
|
||||||
|
width: 2rem;
|
||||||
|
border-radius: 9999px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.card-compact .card-body {
|
.card-compact .card-body {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
@ -2264,6 +2221,11 @@ select {
|
||||||
margin-right: -1.5rem;
|
margin-right: -1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.my-auto {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-10 {
|
.mt-10 {
|
||||||
margin-top: 2.5rem;
|
margin-top: 2.5rem;
|
||||||
}
|
}
|
||||||
|
@ -2292,6 +2254,10 @@ select {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-0 {
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.h-16 {
|
.h-16 {
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
}
|
}
|
||||||
|
@ -2308,11 +2274,6 @@ select {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-max {
|
|
||||||
height: -moz-max-content;
|
|
||||||
height: max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.min-h-full {
|
.min-h-full {
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
}
|
}
|
||||||
|
@ -2345,11 +2306,6 @@ select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-max {
|
|
||||||
width: -moz-max-content;
|
|
||||||
width: max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-1 {
|
.flex-1 {
|
||||||
flex: 1 1 0%;
|
flex: 1 1 0%;
|
||||||
}
|
}
|
||||||
|
@ -2362,6 +2318,10 @@ select {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-row {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-col {
|
.flex-col {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
@ -2386,6 +2346,11 @@ select {
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gap-x-1 {
|
||||||
|
-moz-column-gap: 0.25rem;
|
||||||
|
column-gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.gap-x-3 {
|
.gap-x-3 {
|
||||||
-moz-column-gap: 0.75rem;
|
-moz-column-gap: 0.75rem;
|
||||||
column-gap: 0.75rem;
|
column-gap: 0.75rem;
|
||||||
|
@ -2467,16 +2432,6 @@ select {
|
||||||
border-color: rgb(229 231 235 / var(--tw-border-opacity));
|
border-color: rgb(229 231 235 / var(--tw-border-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-black {
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgb(0 0 0 / var(--tw-border-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-gray-400 {
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgb(156 163 175 / var(--tw-border-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-accent {
|
.bg-accent {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity)));
|
background-color: var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity)));
|
||||||
|
@ -2511,6 +2466,16 @@ select {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.px-1 {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
padding-right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.px-4 {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.px-6 {
|
.px-6 {
|
||||||
padding-left: 1.5rem;
|
padding-left: 1.5rem;
|
||||||
padding-right: 1.5rem;
|
padding-right: 1.5rem;
|
||||||
|
@ -2559,6 +2524,10 @@ select {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.align-middle {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
.text-2xl {
|
.text-2xl {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
line-height: 2rem;
|
line-height: 2rem;
|
||||||
|
@ -2679,61 +2648,20 @@ select {
|
||||||
--tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity));
|
--tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Borrowed from https://github.com/Milkdown/milkdown/blob/main/e2e/src/list-item-block/style.css
|
.prose li {
|
||||||
which is licensed under MIT. */
|
margin-top: 0;
|
||||||
|
|
||||||
.prose :where(li):not(:where([class~="not-prose"] *)) {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose :where(blockquote):not(:where([class~="not-prose"] *)) {
|
|
||||||
font-style: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"] *)) {
|
|
||||||
margin-top: 0.5em;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose ol,
|
|
||||||
.prose ul {
|
.prose ul {
|
||||||
list-style: none !important;
|
margin-top: 0;
|
||||||
padding: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose li p {
|
.prose h1 {
|
||||||
margin: 0px !important;
|
margin-bottom: 0.25em;
|
||||||
line-height: 1.5rem !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose li p + p {
|
|
||||||
margin-top: 0.5rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose li.ProseMirror-selectednode {
|
|
||||||
outline: 2px solid #8cf;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prose li::after {
|
|
||||||
all: unset !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
milkdown-list-item-block .list-item {
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
milkdown-list-item-block .label-wrapper {
|
|
||||||
height: 24px;
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
color: darkcyan;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* End borrowed block. */
|
|
||||||
|
|
||||||
.placeholder\:text-gray-400::-moz-placeholder {
|
.placeholder\:text-gray-400::-moz-placeholder {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(156 163 175 / var(--tw-text-opacity));
|
color: rgb(156 163 175 / var(--tw-text-opacity));
|
||||||
|
|
|
@ -8,27 +8,21 @@
|
||||||
{% include "components/sidebar.html" %}
|
{% include "components/sidebar.html" %}
|
||||||
|
|
||||||
<main class="pl-72 bg-gray-50 h-full">
|
<main class="pl-72 bg-gray-50 h-full">
|
||||||
<form action="/documents/edit/{{ document.id }}" method="POST">
|
<form action="/documents/edit/{{ document.id }}" method="POST" class="h-full flex flex-col">
|
||||||
<div class="navbar bg-accent text-accent-content">
|
<div class="py-1 px-1 gap-x-1 flex flex-row justify-center align-middle bg-accent text-accent-content">
|
||||||
<div class="navbar-start gap-2">
|
<input type="text" id="title" name="title" class="grow font-bold" value="{{ document.title }}" />
|
||||||
<a class="btn" href="/documents">
|
<a class="btn btn-sm my-auto" href="/documents">
|
||||||
{{ "arrow-left"|heroicon("w-6 h-6")|safe }} Back
|
{{ "arrow-left"|heroicon("w-6 h-6")|safe }} Exit
|
||||||
</a>
|
</a>
|
||||||
<button class="btn" onClick="saveDocument()">Save</button>
|
<button class="btn btn-sm my-auto">Save</button>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-8 py-8 flex flex-col gap-y-4">
|
<div class="flex flex-col h-full">
|
||||||
<label class="input input-bordered flex items-center gap-2">
|
<div id="editor-{{ document.id }}" name="editor" class="w-full h-full">
|
||||||
Title
|
<noscript>
|
||||||
<input type="text" id="title" name="title" class="grow" value="{{ document.title }}" />
|
<textarea id="content-{{ document.id }}" name="content" class="w-full h-full">{{ document.content }}</textarea>
|
||||||
</label>
|
</noscript>
|
||||||
|
</div>
|
||||||
<div id="editor-{{document.id}}" class="prose bg-white"></div>
|
|
||||||
|
|
||||||
<textarea id="content-{{ document.id }}" name="content" class="hidden">
|
|
||||||
</textarea>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
@ -36,13 +30,8 @@
|
||||||
|
|
||||||
<script type="text/javascript" src="/static/main.js"></script>
|
<script type="text/javascript" src="/static/main.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
console.log("hi");
|
window.makeEditor("#editor-{{document.id}}", {{ document.content | tojson }});
|
||||||
window.createEditor("#editor-{{document.id}}", "content-{{document.id}}", {{document.content | tojson }});
|
|
||||||
console.log({{document.content | tojson}});
|
|
||||||
console.log("hi2");
|
|
||||||
function saveDocument() {
|
|
||||||
console.log("saving");
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -28,8 +28,8 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h2 class="card-title">{{ document.title }}</h2>
|
<h2 class="card-title">{{ document.title }}</h2>
|
||||||
<div class="card-actions justify-end">
|
<div class="card-actions justify-end">
|
||||||
<a href="/documents/edit/{{ document.id }}" class="btn btn-primary">Open</a>
|
<a href="/documents/view/{{ document.id }}" class="btn btn-primary">View</a>
|
||||||
|
<a href="/documents/edit/{{ document.id }}" class="btn btn-primary">Edit</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
33
templates/documents/view_document.html
Normal file
33
templates/documents/view_document.html
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" class="h-full bg-white">
|
||||||
|
{% include "head.html" %}
|
||||||
|
<body class="h-full">
|
||||||
|
|
||||||
|
<div class="h-full">
|
||||||
|
{% set current_page = "documents" %}
|
||||||
|
{% include "components/sidebar.html" %}
|
||||||
|
|
||||||
|
<main class="pl-72 bg-gray-50 h-full">
|
||||||
|
<form action="" method="POST" class="h-full flex flex-col">
|
||||||
|
<div class="py-1 px-1 gap-x-1 flex flex-row justify-center align-middle bg-accent text-accent-content">
|
||||||
|
<h1 class="grow text-2xl font-bold">{{ document.title }}</h1>
|
||||||
|
<a class="btn btn-sm my-auto" href="/documents">
|
||||||
|
{{ "arrow-left"|heroicon("w-6 h-6")|safe }} Exit
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-sm my-auto" href="/documents/edit/{{ document.id }}">Edit</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="py-8 px-4 h-full prose prose-md">
|
||||||
|
{{ rendered_document|safe }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/static/main.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.makeEditor("#editor-{{document.id}}", {{ document.content | tojson }});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -4,6 +4,5 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Pique</title>
|
<title>Pique</title>
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
<link rel="stylesheet" href="/static/main.css">
|
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⛰️</text></svg>"/>
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⛰️</text></svg>"/>
|
||||||
</head>
|
</head>
|
||||||
|
|
Loading…
Reference in a new issue