From 938425f3039ebd4685caafc0b794cfb4ad67ee46 Mon Sep 17 00:00:00 2001 From: Achref Boukhili Date: Sat, 6 May 2023 11:25:44 +0200 Subject: [PATCH] fix: placeholder defined in code instead of css --- package-lock.json | 32 ++++++ packages/core/package.json | 1 + .../extensions/Blocks/nodes/Block.module.css | 105 +++++++----------- .../Placeholder/PlaceholderExtension.ts | 52 ++++----- .../Placeholder/localisation/index.ts | 10 ++ .../Placeholder/localisation/translation.ts | 52 +++++++++ 6 files changed, 159 insertions(+), 93 deletions(-) create mode 100644 packages/core/src/extensions/Placeholder/localisation/index.ts create mode 100644 packages/core/src/extensions/Placeholder/localisation/translation.ts diff --git a/package-lock.json b/package-lock.json index 839630d779..dca1cb197b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10148,6 +10148,28 @@ "ms": "^2.0.0" } }, + "node_modules/i18next": { + "version": "22.4.15", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.15.tgz", + "integrity": "sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "dependencies": { + "@babel/runtime": "^7.20.6" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -17918,6 +17940,7 @@ "@tiptap/extension-underline": "2.0.0-beta.217", "@tiptap/pm": "2.0.0-beta.217", "hast-util-from-dom": "^4.2.0", + "i18next": "^22.4.15", "lodash": "^4.17.21", "rehype-parse": "^8.0.4", "rehype-remark": "^9.1.2", @@ -19587,6 +19610,7 @@ "eslint": "^8.10.0", "eslint-config-react-app": "^7.0.0", "hast-util-from-dom": "^4.2.0", + "i18next": "^22.4.15", "jsdom": "^21.1.0", "lodash": "^4.17.21", "prettier": "^2.7.1", @@ -25647,6 +25671,14 @@ "ms": "^2.0.0" } }, + "i18next": { + "version": "22.4.15", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.15.tgz", + "integrity": "sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==", + "requires": { + "@babel/runtime": "^7.20.6" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", diff --git a/packages/core/package.json b/packages/core/package.json index 7a11a58e6f..464455d790 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -69,6 +69,7 @@ "@tiptap/extension-underline": "2.0.0-beta.217", "@tiptap/pm": "2.0.0-beta.217", "hast-util-from-dom": "^4.2.0", + "i18next": "^22.4.15", "lodash": "^4.17.21", "rehype-parse": "^8.0.4", "rehype-remark": "^9.1.2", diff --git a/packages/core/src/extensions/Blocks/nodes/Block.module.css b/packages/core/src/extensions/Blocks/nodes/Block.module.css index d803811852..33d13f10af 100644 --- a/packages/core/src/extensions/Blocks/nodes/Block.module.css +++ b/packages/core/src/extensions/Blocks/nodes/Block.module.css @@ -27,11 +27,11 @@ NESTED BLOCKS margin-left: 1.5em; } -.blockGroup .blockGroup > .blockOuter { +.blockGroup .blockGroup>.blockOuter { position: relative; } -.blockGroup .blockGroup > .blockOuter:not([data-prev-depth-changed])::before { +.blockGroup .blockGroup>.blockOuter:not([data-prev-depth-changed])::before { content: " "; display: inline; position: absolute; @@ -40,17 +40,15 @@ NESTED BLOCKS transition: all 0.2s 0.1s; } -[data-theme="light"] .blockGroup .blockGroup -> .blockOuter:not([data-prev-depth-changed])::before { +[data-theme="light"] .blockGroup .blockGroup>.blockOuter:not([data-prev-depth-changed])::before { border-left: 1px solid #CCCCCC; } -[data-theme="dark"] .blockGroup .blockGroup -> .blockOuter:not([data-prev-depth-changed])::before { +[data-theme="dark"] .blockGroup .blockGroup>.blockOuter:not([data-prev-depth-changed])::before { border-left: 1px solid #999999; } -.blockGroup .blockGroup > .blockOuter[data-prev-depth-change="-2"]::before { +.blockGroup .blockGroup>.blockOuter[data-prev-depth-change="-2"]::before { height: 0; } @@ -59,15 +57,19 @@ NESTED BLOCKS [data-prev-depth-change="1"] { --x: 1; } + [data-prev-depth-change="2"] { --x: 2; } + [data-prev-depth-change="3"] { --x: 3; } + [data-prev-depth-change="4"] { --x: 4; } + [data-prev-depth-change="5"] { --x: 5; } @@ -75,15 +77,19 @@ NESTED BLOCKS [data-prev-depth-change="-1"] { --x: -1; } + [data-prev-depth-change="-2"] { --x: -2; } + [data-prev-depth-change="-3"] { --x: -3; } + [data-prev-depth-change="-4"] { --x: -4; } + [data-prev-depth-change="-5"] { --x: -5; } @@ -100,9 +106,11 @@ NESTED BLOCKS [data-level="1"] { --level: 3em; } + [data-level="2"] { --level: 2em; } + [data-level="3"] { --level: 1.3em; } @@ -110,21 +118,21 @@ NESTED BLOCKS [data-prev-level="1"] { --prev-level: 3em; } + [data-prev-level="2"] { --prev-level: 2em; } + [data-prev-level="3"] { --prev-level: 1.3em; } -.blockOuter[data-prev-type="heading"] > .block > .blockContent { +.blockOuter[data-prev-type="heading"]>.block>.blockContent { font-size: var(--prev-level); font-weight: bold; } -.blockOuter:not([data-prev-type]) - > .block - > .blockContent[data-content-type="heading"] { +.blockOuter:not([data-prev-type])>.block>.blockContent[data-content-type="heading"] { font-size: var(--level); font-weight: bold; } @@ -145,84 +153,58 @@ NESTED BLOCKS --prev-index: attr(data-prev-index); } -.blockOuter[data-prev-type="numberedListItem"]:not([data-prev-index="none"]) - > .block - > .blockContent::before { +.blockOuter[data-prev-type="numberedListItem"]:not([data-prev-index="none"])>.block>.blockContent::before { margin-right: 1.2em; content: var(--prev-index) "."; } -.blockOuter:not([data-prev-type]) - > .block - > .blockContent[data-content-type="numberedListItem"]::before { +.blockOuter:not([data-prev-type])>.block>.blockContent[data-content-type="numberedListItem"]::before { margin-right: 1.2em; content: var(--index) "."; } /* Unordered */ /* No list nesting */ -.blockOuter[data-prev-type="bulletListItem"] > .block > .blockContent::before { +.blockOuter[data-prev-type="bulletListItem"]>.block>.blockContent::before { margin-right: 1.2em; content: "•"; } -.blockOuter:not([data-prev-type]) - > .block - > .blockContent[data-content-type="bulletListItem"]::before { +.blockOuter:not([data-prev-type])>.block>.blockContent[data-content-type="bulletListItem"]::before { margin-right: 1.2em; content: "•"; } /* 1 level of list nesting */ -[data-content-type="bulletListItem"] - ~ .blockGroup - > .blockOuter[data-prev-type="bulletListItem"] - > .block - > .blockContent::before { +[data-content-type="bulletListItem"]~.blockGroup>.blockOuter[data-prev-type="bulletListItem"]>.block>.blockContent::before { margin-right: 1.2em; content: "◦"; } -[data-content-type="bulletListItem"] - ~ .blockGroup - > .blockOuter:not([data-prev-type]) - > .block - > .blockContent[data-content-type="bulletListItem"]::before { +[data-content-type="bulletListItem"]~.blockGroup>.blockOuter:not([data-prev-type])>.block>.blockContent[data-content-type="bulletListItem"]::before { margin-right: 1.2em; content: "◦"; } /* 2 levels of list nesting */ -[data-content-type="bulletListItem"] - ~ .blockGroup - [data-content-type="bulletListItem"] - ~ .blockGroup - > .blockOuter[data-prev-type="bulletListItem"] - > .block - > .blockContent::before { +[data-content-type="bulletListItem"]~.blockGroup [data-content-type="bulletListItem"]~.blockGroup>.blockOuter[data-prev-type="bulletListItem"]>.block>.blockContent::before { margin-right: 1.2em; content: "▪"; } -[data-content-type="bulletListItem"] - ~ .blockGroup - [data-content-type="bulletListItem"] - ~ .blockGroup - > .blockOuter:not([data-prev-type]) - > .block - > .blockContent[data-content-type="bulletListItem"]::before { +[data-content-type="bulletListItem"]~.blockGroup [data-content-type="bulletListItem"]~.blockGroup>.blockOuter:not([data-prev-type])>.block>.blockContent[data-content-type="bulletListItem"]::before { margin-right: 1.2em; content: "▪"; } /* PLACEHOLDERS*/ -.blockContent > :first-child { +.blockContent> :first-child { display: inline; } -.blockContent.isEmpty > :first-child:before, -.blockContent.isFilter > :first-child:before { +.blockContent.isEmpty> :first-child:before, +.blockContent.isFilter> :first-child:before { /*float: left; */ content: ""; pointer-events: none; @@ -232,34 +214,31 @@ NESTED BLOCKS font-style: italic; } -[data-theme="light"] .blockContent.isEmpty > :first-child:before, -.blockContent.isFilter > :first-child:before { +[data-theme="light"] .blockContent.isEmpty> :first-child:before, +.blockContent.isFilter> :first-child:before { color: #CCCCCC; } -[data-theme="dark"] .blockContent.isEmpty > :first-child:before, -.blockContent.isFilter > :first-child:before { +[data-theme="dark"] .blockContent.isEmpty> :first-child:before, +.blockContent.isFilter> :first-child:before { color: #999999; } -/* TODO: would be nicer if defined from code */ - -.blockContent.isEmpty.hasAnchor > :first-child:before { - content: "Enter text or type '/' for commands"; +.blockContent.isEmpty.hasAnchor> :first-child:before { + content: var(--placeholder); } -.blockContent.isFilter.hasAnchor > :first-child:before { +.blockContent.isFilter.hasAnchor> :first-child:before { content: "Type to filter"; } -.blockContent[data-content-type="heading"].isEmpty > :first-child::before { - content: "Heading"; +.blockContent[data-content-type="heading"].isEmpty> :first-child::before { + content: var(--placeholder); } -.blockContent[data-content-type="bulletListItem"].isEmpty > :first-child:before, -.blockContent[data-content-type="numberedListItem"].isEmpty - > :first-child:before { - content: "List"; +.blockContent[data-content-type="bulletListItem"].isEmpty> :first-child:before, +.blockContent[data-content-type="numberedListItem"].isEmpty> :first-child:before { + content: var(--placeholder); } /* TEXT COLORS */ diff --git a/packages/core/src/extensions/Placeholder/PlaceholderExtension.ts b/packages/core/src/extensions/Placeholder/PlaceholderExtension.ts index ba6de34a1d..b35f975449 100644 --- a/packages/core/src/extensions/Placeholder/PlaceholderExtension.ts +++ b/packages/core/src/extensions/Placeholder/PlaceholderExtension.ts @@ -3,8 +3,9 @@ import { Node as ProsemirrorNode } from "prosemirror-model"; import { Plugin, PluginKey } from "prosemirror-state"; import { Decoration, DecorationSet } from "prosemirror-view"; import { SlashMenuPluginKey } from "../SlashMenu/SlashMenuExtension"; +import i18next from "./localisation"; -const PLUGIN_KEY = new PluginKey(`blocknote-placeholder`); +const PLUGIN_KEY = new PluginKey("blocknote-placeholder"); /** * This is a modified version of the tiptap @@ -33,14 +34,14 @@ export interface PlaceholderOptions { export const Placeholder = Extension.create({ name: "placeholder", - addOptions() { return { emptyEditorClass: "is-editor-empty", emptyNodeClass: "is-empty", isFilterClass: "is-filter", hasAnchorClass: "has-anchor", - placeholder: "Write something …", + placeholder: + i18next.t("placeholder") || "Enter text or type '/' for commands", showOnlyWhenEditable: true, showOnlyCurrent: true, includeChildren: false, @@ -84,37 +85,28 @@ export const Placeholder = Extension.create({ if (menuState?.triggerCharacter === "" && menuState?.active) { classes.push(this.options.isFilterClass); } - // using widget, didn't work (caret position bug) - // const decoration = Decoration.widget( - // pos + 1, - // () => { - // const el = document.createElement("span"); - // el.innerText = "hello"; - // return el; - // }, - // { side: 0 } - - // Code that sets variables / classes - // const ph = - // typeof this.options.placeholder === "function" - // ? this.options.placeholder({ - // editor: this.editor, - // node, - // pos, - // hasAnchor, - // }) - // : this.options.placeholder; - // const decoration = Decoration.node(pos, pos + node.nodeSize, { - // class: classes.join(" "), - // style: `--placeholder:'${ph.replaceAll("'", "\\'")}';`, - // "data-placeholder": ph, - // }); - - // Latest version, only set isEmpty and hasAnchor, rest is done via CSS + const pph = + typeof this.options.placeholder === "function" + ? this.options.placeholder({ + editor: this.editor, + node, + pos, + hasAnchor, + }) + : this.options.placeholder; + const typePh: Record = { + paragraph: pph, + heading: i18next.t("heading"), + numberedListItem: i18next.t("list"), + bulletListItem: i18next.t("list"), + }; + const ph = typePh[node.type.name] || ""; const decoration = Decoration.node(pos, pos + node.nodeSize, { class: classes.join(" "), + style: `--placeholder:'${ph.replaceAll("'", "\\'")}';`, }); + decorations.push(decoration); } diff --git a/packages/core/src/extensions/Placeholder/localisation/index.ts b/packages/core/src/extensions/Placeholder/localisation/index.ts new file mode 100644 index 0000000000..450e9cb871 --- /dev/null +++ b/packages/core/src/extensions/Placeholder/localisation/index.ts @@ -0,0 +1,10 @@ +import i18next from "i18next"; +import { translation } from "./translation"; + +i18next.init({ + lng: navigator.language || "en", + debug: import.meta.env.DEV, + resources: translation, +}); + +export default i18next; diff --git a/packages/core/src/extensions/Placeholder/localisation/translation.ts b/packages/core/src/extensions/Placeholder/localisation/translation.ts new file mode 100644 index 0000000000..20347b0ac4 --- /dev/null +++ b/packages/core/src/extensions/Placeholder/localisation/translation.ts @@ -0,0 +1,52 @@ +export const translation = { + // languagesCode: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + en: { + translation: { + placeholder: "Enter text or type '/' for commands", + heading: "heading", + list: "List", + }, + }, + de: { + translation: { + placeholder: "Geben Sie Text ein oder geben Sie '/' für Befehle ein", + heading: "Überschrift", + list: "Aufführen", + }, + }, + it: { + translation: { + placeholder: "Inserisci il testo o digita '/' per i comandi", + heading: "intestazione", + list: "Elenco", + }, + }, + es: { + translation: { + placeholder: "Ingrese texto o escriba '/' para comandos", + heading: "título", + list: "Lista", + }, + }, + fr: { + translation: { + placeholder: "Entrez du texte ou tapez '/' pour les commandes", + heading: "titre", + list: "Liste", + }, + }, + fi: { + translation: { + placeholder: "Kirjoita tekstiä tai kirjoita '/' komentoja varten", + heading: "otsikko", + list: "Lista", + }, + }, + sv: { + translation: { + placeholder: "Skriv in text eller skriv '/' för kommandon", + heading: "rubrik", + list: "Lista", + }, + }, +};