From d20b0f43a52944e5562b5824bdf6cc49dcf1c52a Mon Sep 17 00:00:00 2001 From: Alessandro Afloarei Date: Thu, 5 Sep 2024 17:29:02 +0200 Subject: [PATCH 1/3] Implementation --- README.md | 10 +- package.json | 4 +- src/index.css | 9 ++ src/index.ts | 222 +++++++++++++++++++++++++++++++++++++++---- src/types/icons.d.ts | 4 - src/types/types.ts | 29 ++++++ tsconfig.json | 4 +- yarn.lock | 8 +- 8 files changed, 256 insertions(+), 34 deletions(-) delete mode 100644 src/types/icons.d.ts create mode 100644 src/types/types.ts diff --git a/README.md b/README.md index 61293ef..6efa055 100644 --- a/README.md +++ b/README.md @@ -50,19 +50,21 @@ The Paragraph Tool supports these configuration parameters: | ----- | -------- | ------------------ | | placeholder | `string` | The placeholder. Will be shown only in the first paragraph when the whole editor is empty. | | preserveBlank | `boolean` | (default: `false`) Whether or not to keep blank paragraphs when saving editor data | +| defaultAlignment | `left|center|right` | (default: `left`) Where should be aligned the by default the paragraph text | ## Output data -| Field | Type | Description | -| ------ | -------- | ---------------- | -| text | `string` | paragraph's text | - +| Field | Type | Description | +| ------ | -------- | ---------------- | +| text | `string` | paragraph's text | +| alignment | `left|center|right` | paragraph's alignment | ```json { "type" : "paragraph", "data" : { "text" : "Check out our projects on a GitHub page.", + "alignment": "left" } } ``` diff --git a/package.json b/package.json index 1305350..9dc13b6 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@editorjs/paragraph", + "name": "@blade47/editorjs-paragraph", "version": "2.11.6", "keywords": [ "codex editor", @@ -39,6 +39,6 @@ "vite-plugin-dts": "^3.9.1" }, "dependencies": { - "@codexteam/icons": "^0.0.4" + "@codexteam/icons": "^0.3.0" } } diff --git a/src/index.css b/src/index.css index 0cd106e..7eb7535 100644 --- a/src/index.css +++ b/src/index.css @@ -2,6 +2,15 @@ line-height: 1.6em; outline: none; } +.ce-paragraph--right { + text-align: right; +} +.ce-paragraph--center { + text-align: center; +} +.ce-paragraph--left { + text-align: left; +} /** * Normally paragraph placeholder is shown only for the focused block (thanks to "data-placeholder-active"). diff --git a/src/index.ts b/src/index.ts index 8179b63..6678cef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,8 @@ import './index.css'; import { IconText } from '@codexteam/icons'; import makeFragment from './utils/makeFragment'; +import { IconAlignLeft, IconAlignCenter, IconAlignRight } from '@codexteam/icons'; + import type { API, ConversionConfig, @@ -15,6 +17,8 @@ import type { ToolConfig, ToolboxConfig, } from '@editorjs/editorjs'; +import { ActionConfig } from './types/types'; +import { TunesMenuConfig } from '@editorjs/editorjs/types/tools'; /** * Base Paragraph Block for the Editor.js. @@ -52,6 +56,11 @@ export interface ParagraphData { * Paragraph's content */ text: string; + + /** + * Paragraph alignment + */ + alignment: ParagraphAlignmentsEnum; } /** @@ -81,6 +90,12 @@ interface ParagraphParams { readOnly: boolean; } +interface ParagraphAlignments { + left: string; + center: string; + right: string; +} + /** * @typedef {object} ParagraphCSS * @description CSS classes names @@ -96,6 +111,14 @@ interface ParagraphCSS { * Paragraph CSS Class */ wrapper: string; + /** + * Paragraph CSS Alignment + */ + alignment: ParagraphAlignments; +} + +enum ParagraphAlignmentsEnum { + LEFT = 'left', CENTER = 'center', RIGHT = 'right' } export default class Paragraph { @@ -105,15 +128,44 @@ export default class Paragraph { * @returns {string} * @class */ - static get DEFAULT_PLACEHOLDER() { + static get DEFAULT_PLACEHOLDER(): string { return ''; } + /** + * Allowed paragraph alignments + * + * @public + * @returns {{left: string, center: string, right: string}} + */ + static get ALIGNMENTS(): ParagraphAlignments { + return { + left: ParagraphAlignmentsEnum.LEFT.toString(), + center: ParagraphAlignmentsEnum.CENTER.toString(), + right: ParagraphAlignmentsEnum.RIGHT.toString(), + }; + } + + /** + * Default paragraph alignment + * + * @public + * @returns {string} + */ + static get DEFAULT_ALIGNMENT(): ParagraphAlignmentsEnum { + return ParagraphAlignmentsEnum.LEFT; + } + /** * The Editor.js API */ api: API; + /** + * Paragraph's configuration + */ + config: ParagraphConfig; + /** * Is Paragraph Tool read-only */ @@ -155,11 +207,22 @@ export default class Paragraph { */ constructor({ data, config, api, readOnly }: ParagraphParams) { this.api = api; + + this.config = { + placeholder: config.placeholder, + preserveBlank: config.preserveBlank + }; + this.readOnly = readOnly; this._CSS = { block: this.api.styles.block, wrapper: 'ce-paragraph', + alignment: { + left: 'ce-paragraph--left', + center: 'ce-paragraph--center', + right: 'ce-paragraph--right', + } }; if (!this.readOnly) { @@ -174,9 +237,14 @@ export default class Paragraph { this._placeholder = config.placeholder ? config.placeholder : Paragraph.DEFAULT_PLACEHOLDER; - this._data = data ?? {}; + this._data = { + text: '', + alignment: Paragraph.DEFAULT_ALIGNMENT + } this._element = null; this._preserveBlank = config.preserveBlank ?? false; + + this.data = data; } /** @@ -185,7 +253,7 @@ export default class Paragraph { * * @param {KeyboardEvent} e - key up event */ - onKeyUp(e: KeyboardEvent): void { + public onKeyUp(e: KeyboardEvent): void { if (e.code !== 'Backspace' && e.code !== 'Delete') { return; } @@ -207,10 +275,10 @@ export default class Paragraph { * @returns {HTMLDivElement} * @private */ - drawView(): HTMLDivElement { + public drawView(): HTMLDivElement { const div = document.createElement('DIV'); - div.classList.add(this._CSS.wrapper, this._CSS.block); + div.classList.add(this._CSS.wrapper, this._CSS.block, this._CSS.alignment[this._data.alignment]); div.contentEditable = 'false'; div.dataset.placeholderActive = this.api.i18n.t(this._placeholder); @@ -223,9 +291,6 @@ export default class Paragraph { div.addEventListener('keyup', this.onKeyUp); } - /** - * bypass property 'align' required in html div element - */ return div as HTMLDivElement; } @@ -234,7 +299,7 @@ export default class Paragraph { * * @returns {HTMLDivElement} */ - render(): HTMLDivElement { + public render(): HTMLDivElement { this._element = this.drawView(); return this._element; @@ -247,12 +312,13 @@ export default class Paragraph { * @param {ParagraphData} data * @public */ - merge(data: ParagraphData): void { + public merge(data: ParagraphData): void { if (!this._element) { return; } this._data.text += data.text; + this._data.alignment = data.alignment; /** * We use appendChild instead of innerHTML to keep the links of the existing nodes @@ -261,7 +327,6 @@ export default class Paragraph { const fragment = makeFragment(data.text); this._element.appendChild(fragment); - this._element.normalize(); } @@ -273,7 +338,7 @@ export default class Paragraph { * @returns {boolean} false if saved data is not correct, otherwise true * @public */ - validate(savedData: ParagraphData): boolean { + public validate(savedData: ParagraphData): boolean { if (savedData.text.trim() === '' && !this._preserveBlank) { return false; } @@ -288,10 +353,42 @@ export default class Paragraph { * @returns {ParagraphData} - saved data * @public */ - save(toolsContent: HTMLDivElement): ParagraphData { - return { - text: toolsContent.innerHTML, - }; + public save(): ParagraphData { + this._data.text = this._element?.innerHTML ?? ""; + + return this.data; + } + + /** + * Apply visual representation of activated tune + * @param tune + * @param status + */ + public applyTune(tune: ParagraphAlignmentsEnum, status: boolean): void { + this._element?.classList.toggle(`${this._CSS.alignment[tune]}`, status); + } + + /** + * @returns TunesMenuConfig + */ + public renderSettings(): TunesMenuConfig { + return Paragraph.alignmentTunes.map(tune => ({ + icon: tune.icon, + label: this.api.i18n.t(tune.title), + name: tune.name, + toggle: tune.toggle, + closeOnActivate: true, + isActive: this.data.alignment.toString() === tune.name, + onActivate: () => { + /** If it'a user defined tune, execute it's callback stored in action property */ + if (typeof tune.action === 'function') { + tune.action(tune.name); + + return; + } + this.tuneToggled(tune.name); + }, + })); } /** @@ -299,12 +396,13 @@ export default class Paragraph { * * @param {HTMLPasteEvent} event - event with pasted data */ - onPaste(event: HTMLPasteEvent): void { + public onPaste(event: HTMLPasteEvent): void { const data = { text: event.detail.data.innerHTML, + alignment: this.stringToAlignmentEnum(event.detail.data.style.textAlign) || Paragraph.DEFAULT_ALIGNMENT }; - this._data = data; + this._data = data /** * We use requestAnimationFrame for performance purposes @@ -317,6 +415,68 @@ export default class Paragraph { }); } + /** + * Stores all Tool's data + */ + private set data(data: ParagraphData) { + this._data.text = data.text || ''; + this._data.alignment = data.alignment || Paragraph.DEFAULT_ALIGNMENT + } + + /** + * Get current Tools`s data + */ + private get data(): ParagraphData { + return this._data; + } + + /** + * Convert string to @ParagraphAlignmentsEnum + * @param value + * @returns + */ + private stringToAlignmentEnum(value: string): ParagraphAlignmentsEnum | undefined { + for (const key in ParagraphAlignmentsEnum) { + if (ParagraphAlignmentsEnum[key as keyof typeof ParagraphAlignmentsEnum] === value) { + return value as ParagraphAlignmentsEnum; + } + } + return undefined; + } + + /** + * Tune has been toggled + * @param tuneName + * @returns @void + */ + private tuneToggled(tuneName: string): void { + const tuneAlignment = this.stringToAlignmentEnum(tuneName) + if(!tuneAlignment) return + + this.resetTunes(); + this.setTune(tuneAlignment); + this.applyTune(tuneAlignment, true) + } + + /** + * Set one tune + * @param tune + * @param force - tune state + */ + private setTune(tuneAlignment: ParagraphAlignmentsEnum): void { + this._data.alignment = tuneAlignment + } + + /** + * Remove all tunes + */ + private resetTunes(): void { + Paragraph.alignmentTunes.forEach((tune) => { + const tuneAlignment = this.stringToAlignmentEnum(tune.name); + tuneAlignment && this.applyTune(tuneAlignment, false) + }); + } + /** * Enable Conversion Toolbar. Paragraph can be converted to/from other tools * @returns {ConversionConfig} @@ -372,4 +532,30 @@ export default class Paragraph { title: 'Text', }; } + + /** + * Available paragraph tools + */ + public static get alignmentTunes(): Array { + return [ + { + name: 'left', + icon: IconAlignLeft, + title: 'Align left', + toggle: true, + }, + { + name: 'center', + icon: IconAlignCenter, + title: 'Align center', + toggle: true, + }, + { + name: 'right', + icon: IconAlignRight, + title: 'Align right', + toggle: true, + }, + ]; + } } diff --git a/src/types/icons.d.ts b/src/types/icons.d.ts deleted file mode 100644 index a8ef75f..0000000 --- a/src/types/icons.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -// temporary fix for the missing types -declare module "@codexteam/icons" { - export const IconText: string; -} diff --git a/src/types/types.ts b/src/types/types.ts new file mode 100644 index 0000000..e89c430 --- /dev/null +++ b/src/types/types.ts @@ -0,0 +1,29 @@ +/** + * User configuration of Paragraph block tunes. Allows to add custom tunes through the config + */ +export interface ActionConfig { + /** + * The name of the tune. + */ + name: string; + + /** + * The icon for the tune. Should be an SVG string. + */ + icon: string; + + /** + * The title of the tune. This will be displayed in the UI. + */ + title: string; + + /** + * An optional flag indicating whether the tune is a toggle (true) or not (false). + */ + toggle?: boolean; + + /** + * An optional action function to be executed when the tune is activated. + */ + action?: Function; +}; diff --git a/tsconfig.json b/tsconfig.json index bb45b6a..8d07ec4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { - "target": "es2016", + "target": "es2017", "module": "commonjs", - "lib": ["dom", "es2015"], + "lib": ["dom", "es2017"], "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, diff --git a/yarn.lock b/yarn.lock index e062e98..9732237 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.24.6.tgz" integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q== -"@codexteam/icons@^0.0.4": - version "0.0.4" - resolved "https://registry.npmjs.org/@codexteam/icons/-/icons-0.0.4.tgz" - integrity sha512-V8N/TY2TGyas4wLrPIFq7bcow68b3gu8DfDt1+rrHPtXxcexadKauRJL6eQgfG7Z0LCrN4boLRawR4S9gjIh/Q== +"@codexteam/icons@^0.3.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.3.2.tgz#b7aed0ba7b344e07953101f5476cded570d4f150" + integrity sha512-P1ep2fHoy0tv4wx85eic+uee5plDnZQ1Qa6gDfv7eHPkCXorMtVqJhzMb75o1izogh6G7380PqmFDXV3bW3Pig== "@editorjs/editorjs@^2.29.1": version "2.29.1" From 5fa48c67e2f1c730ad1af128a00afc3017d54a1b Mon Sep 17 00:00:00 2001 From: Alessandro Afloarei Date: Thu, 5 Sep 2024 17:40:42 +0200 Subject: [PATCH 2/3] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9dc13b6..7e2ea7d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@blade47/editorjs-paragraph", + "name": "@editorjs/paragraph", "version": "2.11.6", "keywords": [ "codex editor", From c7181814eb12aacc32a281630c2f9a02039b6572 Mon Sep 17 00:00:00 2001 From: Alessandro Afloarei Date: Thu, 5 Sep 2024 18:01:51 +0200 Subject: [PATCH 3/3] Add Justify --- src/index.css | 3 +++ src/index.ts | 13 +++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/index.css b/src/index.css index 7eb7535..d9e016c 100644 --- a/src/index.css +++ b/src/index.css @@ -11,6 +11,9 @@ .ce-paragraph--left { text-align: left; } +.ce-paragraph--justify { + text-align: justify; +} /** * Normally paragraph placeholder is shown only for the focused block (thanks to "data-placeholder-active"). diff --git a/src/index.ts b/src/index.ts index 6678cef..f052fa7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import './index.css'; import { IconText } from '@codexteam/icons'; import makeFragment from './utils/makeFragment'; -import { IconAlignLeft, IconAlignCenter, IconAlignRight } from '@codexteam/icons'; +import { IconAlignLeft, IconAlignCenter, IconAlignRight, IconAlignJustify } from '@codexteam/icons'; import type { API, @@ -94,6 +94,7 @@ interface ParagraphAlignments { left: string; center: string; right: string; + justify: string; } /** @@ -118,7 +119,7 @@ interface ParagraphCSS { } enum ParagraphAlignmentsEnum { - LEFT = 'left', CENTER = 'center', RIGHT = 'right' + LEFT = 'left', CENTER = 'center', RIGHT = 'right', JUSTIFY = 'justify' } export default class Paragraph { @@ -143,6 +144,7 @@ export default class Paragraph { left: ParagraphAlignmentsEnum.LEFT.toString(), center: ParagraphAlignmentsEnum.CENTER.toString(), right: ParagraphAlignmentsEnum.RIGHT.toString(), + justify: ParagraphAlignmentsEnum.JUSTIFY.toString(), }; } @@ -222,6 +224,7 @@ export default class Paragraph { left: 'ce-paragraph--left', center: 'ce-paragraph--center', right: 'ce-paragraph--right', + justify: 'ce-paragraph--justify', } }; @@ -556,6 +559,12 @@ export default class Paragraph { title: 'Align right', toggle: true, }, + { + name: 'justify', + icon: IconAlignJustify, + title: 'Justify', + toggle: true, + }, ]; } }