diff --git a/helpers/frontend/views/frontend-form.njk b/helpers/frontend/views/frontend-form.njk index 0d6d19d79..2292fafa0 100644 --- a/helpers/frontend/views/frontend-form.njk +++ b/helpers/frontend/views/frontend-form.njk @@ -65,6 +65,18 @@ hint: "Textarea hint text" }) }} +{{ textarea({ + errorMessage: { text: "Richtext input error" } if errors, + name: "richtext", + label: "Richtext", + hint: "Richtext hint text", + field: { + attributes: { + editor: true + } + } +}) }} + {{ textarea({ errorMessage: { text: "Readonly textarea input error" } if errors, name: "textarea-readonly", diff --git a/indiekit.config.js b/indiekit.config.js index 76340aab5..652db334c 100644 --- a/indiekit.config.js +++ b/indiekit.config.js @@ -6,6 +6,7 @@ dotenv.config(); const config = { application: { _devMode: process.env.NODE_ENV === "development", + locale: "en-GB", mongodbUrl: process.env.MONGO_URL, ...(process.env.RAILWAY_ENVIRONMENT && { url: `https://${process.env.RAILWAY_STATIC_URL}`, diff --git a/package-lock.json b/package-lock.json index bd2cb4712..a8f9e0fb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5875,6 +5875,14 @@ "@types/node": "*" } }, + "node_modules/@types/codemirror": { + "version": "5.60.15", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.15.tgz", + "integrity": "sha512-dTOvwEQ+ouKJ/rE9LT1Ue2hmP6H1mZv5+CCnNWu2qtiOe2LQa9lCprEY20HxiDmV/Bxh+dXjywmy5aKvoGjULA==", + "dependencies": { + "@types/tern": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -5977,6 +5985,11 @@ "@types/mdurl": "*" } }, + "node_modules/@types/marked": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.3.2.tgz", + "integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==" + }, "node_modules/@types/mdurl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.5.tgz", @@ -6065,6 +6078,14 @@ "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", "dev": true }, + "node_modules/@types/tern": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz", + "integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/web-bluetooth": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", @@ -7751,6 +7772,19 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/codemirror": { + "version": "5.65.16", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.16.tgz", + "integrity": "sha512-br21LjYmSlVL0vFCPWPfhzUCT34FM/pAdK7rRIZwa0rrtrIdotvP4Oh4GUHsu2E3IrQMCfRkL/fN3ytMNxVQvg==" + }, + "node_modules/codemirror-spell-checker": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/codemirror-spell-checker/-/codemirror-spell-checker-1.1.2.tgz", + "integrity": "sha512-2Tl6n0v+GJRsC9K3MLCdLaMOmvWL0uukajNJseorZJsslaxZyZMgENocPU8R0DyoTAiKsyqiemSOZo7kjGV0LQ==", + "dependencies": { + "typo-js": "*" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -9317,6 +9351,18 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "node_modules/easymde": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/easymde/-/easymde-2.18.0.tgz", + "integrity": "sha512-IxVVUxNWIoXLeqtBU4BLc+eS/ScYhT1Dcb6yF5Wchoj1iXAV+TIIDWx+NCaZhY7RcSHqDPKllbYq7nwGKILnoA==", + "dependencies": { + "@types/codemirror": "^5.60.4", + "@types/marked": "^4.0.7", + "codemirror": "^5.63.1", + "codemirror-spell-checker": "1.1.2", + "marked": "^4.1.0" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -14266,6 +14312,17 @@ "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", "dev": true }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/masto": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/masto/-/masto-6.7.1.tgz", @@ -19944,6 +20001,11 @@ "node": ">=14.17" } }, + "node_modules/typo-js": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/typo-js/-/typo-js-1.2.4.tgz", + "integrity": "sha512-Oy/k+tFle5NAA3J/yrrYGfvEnPVrDZ8s8/WCwjUE75k331QyKIsFss7byQ/PzBmXLY6h1moRnZbnaxWBe3I3CA==" + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -21055,6 +21117,7 @@ "@rollup/plugin-commonjs": "^25.0.3", "@rollup/plugin-node-resolve": "^15.0.0", "color": "^4.0.1", + "easymde": "^2.18.0", "iso-639-1": "^3.0.0", "lightningcss": "^1.22.0", "lodash": "^4.17.21", diff --git a/packages/endpoint-posts/includes/post-types/content-field.njk b/packages/endpoint-posts/includes/post-types/content-field.njk index 7bc0fcb4b..8d67103fa 100644 --- a/packages/endpoint-posts/includes/post-types/content-field.njk +++ b/packages/endpoint-posts/includes/post-types/content-field.njk @@ -1,7 +1,17 @@ -{{ characterCount({ +{{ textarea({ name: "content", value: properties.content.text or fieldData("content").value, label: __("posts.form.content.label"), optional: not field.required, - errorMessage: fieldData("content").errorMessage + errorMessage: fieldData("content").errorMessage, + field: { + attributes: { + editor: true, + "editor-id": (properties.uid or ("new-" + postType)) + "-content", + "editor-height": "50vh" if field.required else "6rem", + "editor-locale": application.locale, + "editor-image-upload": "false" if postType == "note", + "editor-status": "false" if not field.required + } + } }) }} \ No newline at end of file diff --git a/packages/endpoint-posts/includes/post-types/summary-field.njk b/packages/endpoint-posts/includes/post-types/summary-field.njk index 08a8fa7b5..89221db5e 100644 --- a/packages/endpoint-posts/includes/post-types/summary-field.njk +++ b/packages/endpoint-posts/includes/post-types/summary-field.njk @@ -3,5 +3,15 @@ value: properties.summary, label: __("posts.form.summary.label"), optional: not field.required, - rows: 1 + rows: 1, + field: { + attributes: { + editor: true, + "editor-id": (properties.uid or ("new-" + postType)) + "-summary", + "editor-height": "60vh" if field.required else "2rem", + "editor-locale": application.locale, + "editor-status": "false", + "editor-toolbar": "false" + } + } }) }} \ No newline at end of file diff --git a/packages/frontend/assets/mona-sans.woff2 b/packages/frontend/assets/mona-sans.woff2 deleted file mode 100644 index 8208a5000..000000000 Binary files a/packages/frontend/assets/mona-sans.woff2 and /dev/null differ diff --git a/packages/frontend/components/textarea/index.js b/packages/frontend/components/textarea/index.js index 3d9c2c8ad..d5c493eb1 100644 --- a/packages/frontend/components/textarea/index.js +++ b/packages/frontend/components/textarea/index.js @@ -1,31 +1,168 @@ -import { debounce } from "../../lib/utils/debounce.js"; +import EasyMDE from "easymde"; + +const paths = { + bold: "M17 30c6.1 0 10-3 10-8 0-3.5-2.7-6.3-6.5-6.5V15c3-.4 5-3 5-6 0-4.5-3.5-7-9-7H5v28h12ZM12 7h2c2.5 0 4 1 4 3 0 1.5-1.5 3-4 3h-2V7Zm0 18v-7h2.3c3.1 0 4.7 1.1 4.7 3.4 0 2.5-1.4 3.6-4.8 3.6H12Z", + code: "m13.5 8.5-3-3L2 14C.5 15.5.5 16.5 2 18l8.5 8.5 3-3L6 16l7.5-7.5Zm5 0 3-3L30 14c1.5 1.5 1.5 2.5 0 4l-8.5 8.5-3-3L26 16l-7.5-7.5Z", + fullscreen: + "M4 20H0v10c0 1.1.9 2 2 2h10v-4H4v-8Zm-4-8h4V4h8V0H2a2 2 0 0 0-2 2v10Zm28 16h-8v4h10a2 2 0 0 0 2-2V20h-4v8ZM20 0v4h8v8h4V2a2 2 0 0 0-2-2H20Z", + heading: "M27 30h-6V18H11v12H5V2h6v11h10V2h6z", + italic: "m21.5 30 .8-4h-6l4-20h6l.7-4H10l-.7 4h6l-4 20h-6l-.8 4z", + link: "M14 8v4H8a4 4 0 1 0 0 8h6v4H8A8 8 0 1 1 8 8h6Zm10 0a8 8 0 1 1 0 16h-6v-4h6a4 4 0 1 0 0-8h-6V8h6Zm-2 6v4H10v-4h12Z", + "ordered-list": + "M10 2h22v4H10Zm0 12h22v4H10Zm0 12h22v4H10ZM0 0h4v8H2V2H0V0Zm5 12h1v2l-3 4h3v2H0v-2l3.3-4H0v-2h5ZM0 26v-2h6v8H0v-2h4v-1H2v-2h2v-1H0Z", + quote: + "M4 6h7a3 3 0 0 1 3 3v6.6a4 4 0 0 1-1 2.5L6.6 26h-3l3-8H4a3 3 0 0 1-3-3V9a3 3 0 0 1 3-3Zm17 0h7a3 3 0 0 1 3 3v6.6a4 4 0 0 1-1 2.5L23.6 26h-3l3-8H21a3 3 0 0 1-3-3V9a3 3 0 0 1 3-3Z", + "side-by-side": + "M16 6c7 0 13.5 4 16 10-2.5 6-9 10-16 10S2.5 22 0 16C2.5 10 9 6 16 6Zm0 3a7 7 0 1 0 0 14 7 7 0 0 0 0-14Zm0 3a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z", + table: + "M32 2v28a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2C0 .9.9 0 2 0h28a2 2 0 0 1 2 2Zm-4 2H4v6h24V4ZM14 14H4v5h10v-5Zm0 9H4v5h10v-5Zm14-9H18v5h10v-5Zm0 9H18v5h10v-5Z", + undo: "M2 24h-.1A2 2 0 0 1 0 22V9h4v7.5A15 15 0 0 1 32 24h-4a11 11 0 0 0-21.2-4H15v4H2Z", + "unordered-list": + "M9 2h22v4H9Zm0 12h22v4H9Zm0 12h22v4H9ZM3 7a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm0 12a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm0 12a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z", + "upload-image": + "M2 0h28a2 2 0 0 1 2 2v28a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2C0 .9.9 0 2 0Zm26 24-9-14-7 11-3-4.8L4 24h24Z", +}; + +/** + * Get SVG icon + * @param {string} name - Icon name + * @returns {string} SVG + */ +const getButtonSvg = (name) => { + return ``; +}; export const TextareaFieldComponent = class extends HTMLElement { constructor() { super(); - this.adjustHeight = this.adjustHeight.bind(this); + this.editor = this.getAttribute("editor"); + this.editorId = this.getAttribute("editor-id"); + this.editorHeight = this.getAttribute("editor-height"); + this.editorImageUpload = this.getAttribute("editor-image-upload"); + this.editorLocale = this.getAttribute("editor-locale"); + this.editorStatus = this.getAttribute("editor-status"); + this.editorToolbar = this.getAttribute("editor-toolbar"); + this.$label = this.querySelector("label"); this.$textarea = this.querySelector("textarea"); } connectedCallback() { - const delay = 100; + if (this.editor !== "") { + return; + } - this.$textarea.style.overflow = "hidden"; - this.onResize = - delay > 0 ? debounce(this.adjustHeight, delay) : this.adjustHeight; - this.adjustHeight(); + const status = + this?.editorStatus === "false" + ? false + : [ + ...(this.editorImageUpload === "false" ? [] : ["upload-image"]), + "words", + "characters", + "autosave", + ]; - this.$textarea.addEventListener("input", this.adjustHeight); - window.addEventListener("resize", this.onResize); - } + const toolbar = + this?.editorToolbar === "false" + ? false + : [ + "bold", + "italic", + "heading", + "quote", + "ordered-list", + "unordered-list", + "table", + "code", + "link", + ...(this.editorImageUpload === "false" ? [] : ["upload-image"]), + "|", + "undo", + "side-by-side", + "fullscreen", + ]; + + const editor = new EasyMDE({ + autoDownloadFontAwesome: false, + autosave: { + enabled: true, + uniqueId: this.editorId || this.$textarea.id, + timeFormat: { locale: this.editorLocale || "en" }, + }, + blockStyles: { + bold: "**", + italic: "_", + }, + element: this.$textarea, + imageUploadFunction: this.uploadFile, + maxHeight: this.editorHeight, + previewClass: ["editor-preview", "s-flow"], + status, + // @ts-ignore + toolbar, + unorderedListStyle: "-", + }); - disconnectedCallback() { - window.removeEventListener("resize", this.onResize); + // Restore label behaviour + /** @type {HTMLTextAreaElement} */ + const $codeMirrorTextarea = this.querySelector(".CodeMirror textarea"); + this.$label.addEventListener("click", () => { + $codeMirrorTextarea.focus(); + }); + + // Update character count + /** @type {HTMLElement} */ + const $characters = this.querySelector(".editor-statusbar .characters"); + editor.codemirror.on("update", () => { + $characters.innerHTML = String(editor.value().length); + }); + + const $editorToolbar = this.querySelector(".editor-toolbar"); + if ($editorToolbar) { + // Use custom SVG icons + const buttons = $editorToolbar.querySelectorAll("button"); + for (const button of buttons) { + button.innerHTML = getButtonSvg(button.classList[0]); + } + + // Get toolbar height to offset editor and preview in fullscreen mode + const resizeObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + this.style.setProperty( + "--toolbar-height", + `${entry.contentRect.height}px`, + ); + } + }); + + resizeObserver.observe($editorToolbar); + } } - adjustHeight() { - this.$textarea.style.height = "auto"; - this.$textarea.style.height = `${this.$textarea.scrollHeight + 4}px`; + /** + * Upload file + * @param {object} file - File + * @param {Function} onSuccess - Success callback + * @param {Function} onError - Error callback + * @returns {Promise} - File URL or error message + */ + async uploadFile(file, onSuccess, onError) { + const formData = new FormData(); + formData.append("file", file); + + try { + const endpointResponse = await fetch("http://localhost:3000/media", { + method: "POST", + body: formData, + }); + + return endpointResponse.ok + ? onSuccess(endpointResponse.headers.get("location")) + : onError(endpointResponse.statusText); + } catch (error) { + onError(error.message); + } } }; diff --git a/packages/frontend/components/textarea/styles.css b/packages/frontend/components/textarea/styles.css index fa45af5de..8c8116031 100644 --- a/packages/frontend/components/textarea/styles.css +++ b/packages/frontend/components/textarea/styles.css @@ -17,6 +17,7 @@ textarea-field { border-color: var(--color-on-background); border-width: var(--input-border-width-focus); inset-block-start: calc(var(--input-border-width-focus-offset) * -1); + margin-block-end: calc(var(--input-border-width-focus-offset) * -2); padding-inline-start: calc( var(--space-s) - var(--input-border-width-focus-offset) ); diff --git a/packages/frontend/layouts/default.njk b/packages/frontend/layouts/default.njk index b6b39de74..3118619b4 100644 --- a/packages/frontend/layouts/default.njk +++ b/packages/frontend/layouts/default.njk @@ -5,7 +5,6 @@ {% from "button/macro.njk" import button with context %} {% from "card/macro.njk" import card with context %} {% from "card-grid/macro.njk" import cardGrid with context %} -{% from "character-count/macro.njk" import characterCount with context %} {% from "checkboxes/macro.njk" import checkboxes with context %} {% from "details/macro.njk" import details with context %} {% from "error-summary/macro.njk" import errorSummary with context %} @@ -49,7 +48,6 @@ - diff --git a/packages/frontend/lib/utils/debounce.js b/packages/frontend/lib/utils/debounce.js deleted file mode 100644 index 226c56870..000000000 --- a/packages/frontend/lib/utils/debounce.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Delay a function - * @param {Function} callback - Function to delay - * @param {number} delay - Delay, in milliseconds - * @returns {Function} Debounced function - * @see {@link https://www.joshwcomeau.com/snippets/javascript/debounce/} - */ -export const debounce = (callback, delay) => { - let timeoutId; - - return (...arguments_) => { - window.clearTimeout(timeoutId); - - timeoutId = window.setTimeout(() => { - callback(...arguments_); - }, delay); - }; -}; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 47693101f..942b8dea9 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -41,6 +41,7 @@ "@rollup/plugin-commonjs": "^25.0.3", "@rollup/plugin-node-resolve": "^15.0.0", "color": "^4.0.1", + "easymde": "^2.18.0", "iso-639-1": "^3.0.0", "lightningcss": "^1.22.0", "lodash": "^4.17.21", diff --git a/packages/frontend/scripts/app.js b/packages/frontend/scripts/app.js index 62a1abe08..5d4fd551e 100644 --- a/packages/frontend/scripts/app.js +++ b/packages/frontend/scripts/app.js @@ -1,5 +1,4 @@ import { AddAnotherComponent } from "../components/add-another/index.js"; -import { CharacterCountComponent } from "../components/character-count/index.js"; import { CheckboxesFieldComponent } from "../components/checkboxes/index.js"; import { ErrorSummaryComponent } from "../components/error-summary/index.js"; import { EventDurationComponent } from "../components/event-duration/index.js"; @@ -12,7 +11,6 @@ import { TagInputFieldComponent } from "../components/tag-input/index.js"; import { TextareaFieldComponent } from "../components/textarea/index.js"; customElements.define("add-another", AddAnotherComponent); -customElements.define("character-count", CharacterCountComponent); customElements.define("checkboxes-field", CheckboxesFieldComponent); customElements.define("error-summary", ErrorSummaryComponent); customElements.define("file-input-field", FileInputFieldController); diff --git a/packages/frontend/styles/app.css b/packages/frontend/styles/app.css index aa75f57af..5d00f4fd9 100644 --- a/packages/frontend/styles/app.css +++ b/packages/frontend/styles/app.css @@ -1,6 +1,6 @@ /* Config */ @import url("./config/custom-properties.css"); -@import url("./config/font-face.css"); +/* @import url("./config/font-face.css"); */ /* Base */ @import url("./base/embedded.css"); @@ -65,4 +65,6 @@ @import url("./utilities/visually-hidden.css"); /* Vendor */ +@import url("./vendor/codemirror.css"); +@import url("./vendor/easy-markdown-editor.css"); @import url("./vendor/markdown-it-prism.css"); diff --git a/packages/frontend/styles/base/sections.css b/packages/frontend/styles/base/sections.css index 4df1c8cf8..7abe9687a 100644 --- a/packages/frontend/styles/base/sections.css +++ b/packages/frontend/styles/base/sections.css @@ -45,7 +45,7 @@ legend { text-rendering: optimizelegibility; } -[tabindex="-1"]:focus, +[tabindex="-1"]:focus:not(button), :focus-visible { border-radius: var(--border-radius-small); box-shadow: 0 0 0 var(--focus-width) var(--color-focus); diff --git a/packages/frontend/styles/config/custom-properties.css b/packages/frontend/styles/config/custom-properties.css index 326cdd0dd..949fdce6c 100644 --- a/packages/frontend/styles/config/custom-properties.css +++ b/packages/frontend/styles/config/custom-properties.css @@ -64,6 +64,15 @@ html { --color-selection: hsl(var(--tint-yellow) 50% / 30%); --color-shadow: hsl(var(--tint-neutral) 10% / 0.2); + /* Syntax highlighting */ + --color-token-comment: var(--color-on-offset); + --color-token-operator: var(--color-on-background); + --color-token-function: #900; + --color-token-keyword: #069; + --color-token-selector: #009; + --color-token-string: #c06; + --color-token-variable: #399; + /* Fluid type scale */ /* clamp(min=320, preferred, max=960) */ --font-size-xs: clamp(0.75rem, 0.6875rem + 0.3125vw, 0.875rem); /* 12 → 14 */ @@ -75,7 +84,7 @@ html { /* Typography */ --font-family-system: system-ui; - --font-family-sans: mona-sans, sans-serif; + --font-family-sans: system-ui, sans-serif; --font-family-monospace: ui-monospace, sfmono-regular, sf mono, menlo, consolas, liberation mono, monospace; @@ -152,6 +161,13 @@ html { --color-error: var(--color-red80); --color-error-variant: var(--color-red90); --color-on-error: var(--color-neutral10); + + /* Syntax highlighting */ + --color-token-function: #f99; + --color-token-keyword: #0cf; + --color-token-selector: #99f; + --color-token-string: #f6f; + --color-token-variable: #9ff; } } @@ -170,4 +186,11 @@ html { --color-error: var(--color-red80); --color-error-variant: var(--color-red90); --color-on-error: var(--color-neutral10); + + /* Syntax highlighting */ + --color-token-function: #f99; + --color-token-keyword: #0cf; + --color-token-selector: #99f; + --color-token-string: #f6f; + --color-token-variable: #9ff; } diff --git a/packages/frontend/styles/config/font-face.css b/packages/frontend/styles/config/font-face.css deleted file mode 100644 index 025295017..000000000 --- a/packages/frontend/styles/config/font-face.css +++ /dev/null @@ -1,9 +0,0 @@ -@font-face { - font-display: swap; - font-family: mona-sans; - font-stretch: 75% 125%; - font-weight: 200 900; - src: - url("mona-sans.woff2") format("woff2 supports variations"), - url("mona-sans.woff2") format("woff2-variations"); -} diff --git a/packages/frontend/styles/vendor/codemirror.css b/packages/frontend/styles/vendor/codemirror.css new file mode 100644 index 000000000..f0e6d7145 --- /dev/null +++ b/packages/frontend/styles/vendor/codemirror.css @@ -0,0 +1,107 @@ +@import url("../../../../node_modules/codemirror/lib/codemirror.css"); + +.CodeMirror { + --fieldset-flow-space: 0; + + background: var(--color-background); + block-size: auto; + border: var(--input-border-width) solid transparent; + border-radius: var(--border-radius-small); + color: var(--color-on-backround); + font-family: var(--font-family-monospace); + margin-block-start: 0; + padding: var(--space-s); + + :focus-visible { + box-shadow: none; + } + + .cm-attribute { + color: var(--color-token-keyword); + } + + .cm-comment { + color: var(--color-token-comment); + } + + .cm-string { + color: var(--color-token-string); + } + + .cm-tag { + color: var(--color-token-selector); + } + + .cm-header-1 { + font-size: var(--font-size-2xl); + } + + .cm-header-2 { + font-size: var(--font-size-xl); + } + + .cm-header-3 { + font-size: var(--font-size-l); + } + + .cm-header-4 { + font-size: var(--font-size-m); + } + + .cm-header-5 { + font-size: var(--font-size-s); + } + + .cm-header-6 { + font-size: var(--font-size-xs); + } + + .cm-link { + color: var(--anchor-color); + } + + .cm-quote { + color: var(--color-on-offset); + font-style: italic; + } + + .cm-url { + color: var(--anchor-color-hover); + } +} + +.CodeMirror-focused { + &:not(.CodeMirror-fullscreen) { + border-color: var(--color-on-background); + border-width: var(--input-border-width-focus); + margin-inline: calc(var(--input-border-width-focus) * -1); + outline: var(--input-border-width-focus) solid var(--color-focus); + padding-block: calc( + var(--space-s) - var(--input-border-width-focus-offset) + ); + padding-inline: calc( + var(--space-s) + var(--input-border-width-focus-offset) + ); + } + + .CodeMirror-selected { + background: var(--color-selection); + } +} + +.CodeMirror-fullscreen { + block-size: auto; + inset: 0; + inset-block-start: var(--toolbar-height); + position: fixed; + z-index: 8; +} + +.CodeMirror-placeholder { + color: var(--color-on-background); + opacity: 0.5; +} + +.CodeMirror-sided { + inline-size: 50%; +} diff --git a/packages/frontend/styles/vendor/easy-markdown-editor.css b/packages/frontend/styles/vendor/easy-markdown-editor.css new file mode 100644 index 000000000..c2f1c21b3 --- /dev/null +++ b/packages/frontend/styles/vendor/easy-markdown-editor.css @@ -0,0 +1,151 @@ +.EasyMDEContainer { + --toolbar-button-size: 44px; + --toolbar-padding: var(--space-2xs); + + background-color: var(--color-background); + border: var(--input-border-width) solid var(--color-outline-variant); + border-radius: var(--border-radius-small); +} + +.editor-statusbar, +.editor-toolbar { + --fieldset-flow-space: 0; + + background-color: var(--color-offset); + border: var(--border-width-thickest) solid var(--color-background); + border-radius: var(--border-radius-small); + color: var(--color-on-offset); + display: flex; + flex-wrap: wrap; + -webkit-user-select: none; + user-select: none; +} + +.editor-statusbar { + font: var(--font-caption); + gap: var(--space-xs) var(--space-l); + justify-content: space-between; + margin-block-start: 0; + padding-block: var(--space-s); + padding-inline: var(--space-m); + + & > * { + font-variant-numeric: tabular-nums; + min-inline-size: max-content; + } + + & .autosave { + font-variant-numeric: normal; + text-align: end; + } + + & .characters { + margin-inline-end: auto; + } + + & .characters::after { + content: " characters"; + } + + & .words::after { + content: " words"; + } + + & .upload-image { + min-inline-size: 100%; + } +} + +.editor-toolbar { + display: grid; + gap: var(--toolbar-padding); + grid-template-columns: repeat( + auto-fit, + minmax(var(--toolbar-button-size), 1fr) + ); + padding: var(--toolbar-padding); + place-items: center; + position: relative; + + &.fullscreen { + border: 0; + border-radius: 0; + inset-block-start: 0; + inset-inline: 0; + position: fixed; + z-index: 10; + } + + & button { + background: none; + block-size: var(--toolbar-button-size); + border-radius: var(--border-radius-small); + display: flex; + inline-size: 100%; + min-inline-size: var(--toolbar-button-size); + place-content: center; + + & svg { + place-self: center; + } + + &:hover, + &.active { + background: var(--color-offset-variant); + } + + &:active { + background: var(--color-offset-variant-darker); + } + } + + & .separator { + border-left: var(--border-width-thin) solid var(--color-outline); + color: transparent; + inline-size: 0; + + @media (width < 48rem) { + display: none; + } + } + + &.disabled-for-preview button:not(.no-disable) { + opacity: 0.5; + pointer-events: none; + } +} + +.editor-preview { + --fieldset-flow-space: 1em; + + background-color: var(--color-offset); + font: var(--font-body); + margin-block-start: 0; + padding: var(--space-m); + + &:not(.editor-preview-active):not(.editor-preview-active-side) { + display: none; + } +} + +.editor-preview-full { + inset-block: 0; + inset-inline: 2px; + overflow: auto; + position: absolute; +} + +.editor-preview-side { + border: var(--border-width-thickest) solid var(--color-background); + inline-size: 50%; + inset-block: 0; + inset-block-start: calc( + var(--toolbar-height) + calc(var(--toolbar-padding) * 2) + ); + inset-inline-end: 0; + overflow: auto; + padding: var(--space-l); + position: fixed; + word-wrap: break-word; + z-index: 9; +} diff --git a/packages/frontend/styles/vendor/markdown-it-prism.css b/packages/frontend/styles/vendor/markdown-it-prism.css index fdbc36559..37421db7e 100644 --- a/packages/frontend/styles/vendor/markdown-it-prism.css +++ b/packages/frontend/styles/vendor/markdown-it-prism.css @@ -1,31 +1,3 @@ -:root { - --color-token-comment: var(--color-on-offset); - --color-token-operator: var(--color-on-background); - --color-token-function: #900; - --color-token-keyword: #069; - --color-token-selector: #009; - --color-token-string: #c06; - --color-token-variable: #399; -} - -@media (prefers-color-scheme: dark) { - :root:not([data-color-scheme]) { - --color-token-function: #f99; - --color-token-keyword: #0cf; - --color-token-selector: #99f; - --color-token-string: #f6f; - --color-token-variable: #9ff; - } -} - -[data-color-scheme="dark"] { - --color-token-function: #f99; - --color-token-keyword: #0cf; - --color-token-selector: #99f; - --color-token-string: #f6f; - --color-token-variable: #9ff; -} - .token.namespace { opacity: 0.7; }