From 16d85ddb5294d4c3b8d83e04c41da866eb46cc39 Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 1 Aug 2024 19:48:13 +0300 Subject: [PATCH 01/43] implemented dev model --- index.html | 403 +++++++++++++++++++++++++++++++++++++++++++++++++ vite.config.js | 4 + 2 files changed, 407 insertions(+) create mode 100644 index.html diff --git a/index.html b/index.html new file mode 100644 index 00000000..700f9f03 --- /dev/null +++ b/index.html @@ -0,0 +1,403 @@ + + + + + Editor.js 🤩🧦🤨 example + + + + + + +
+ +
+
+ +
+ editor.save() +
+ +
+ Readonly: + + Off + +
+ toggle +
+
+
+
+

+
+      
+    
+
+ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vite.config.js b/vite.config.js index 64b79224..4a6bc518 100644 --- a/vite.config.js +++ b/vite.config.js @@ -20,5 +20,9 @@ export default { VERSION: JSON.stringify(VERSION), }, + server: { + port: 3303, + open: true, + }, plugins: [cssInjectedByJsPlugin(), dts()], }; From 88eb6c6cd74a958878a026e31d00726387a4ce2e Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 1 Aug 2024 21:19:12 +0300 Subject: [PATCH 02/43] added ol and ul list renderer classes --- src/ListRenderer.ts | 233 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 src/ListRenderer.ts diff --git a/src/ListRenderer.ts b/src/ListRenderer.ts new file mode 100644 index 00000000..0a2830b6 --- /dev/null +++ b/src/ListRenderer.ts @@ -0,0 +1,233 @@ +import NestedListConfig from "./types/config"; +import * as Dom from './utils/dom'; + +/** + * CSS classes for the Nested List Tool + */ +interface NestedListCssClasses { + wrapper: string; + wrapperOrdered: string; + wrapperUnordered: string; + item: string; + itemBody: string; + itemContent: string; + itemChildren: string; + settingsWrapper: string; +} + +/** + * List renderer class + * Used for storing css classes and + */ +abstract class ListRenderer { + /** + * Styles + * + * @returns {NestedListCssClasses} - CSS classes names by keys + * @private + */ + protected get CSS(): NestedListCssClasses { + return { + wrapper: 'cdx-nested-list', + wrapperOrdered: 'cdx-nested-list--ordered', + wrapperUnordered: 'cdx-nested-list--unordered', + item: 'cdx-nested-list__item', + itemBody: 'cdx-nested-list__item-body', + itemContent: 'cdx-nested-list__item-content', + itemChildren: 'cdx-nested-list__item-children', + settingsWrapper: 'cdx-nested-list__settings', + }; + } +} + +/** + * Class that is responsible for unordered list rendering + */ +export class UnorderedListRenderer extends ListRenderer { + /** + * Tool's configuration + */ + protected config?: NestedListConfig; + + constructor(config: NestedListConfig) { + super(); + this.config = config; + } + + /** + * Renders ol wrapper for list + * @param classes - + * @returns - created html ol element + */ + renderWrapper(classes: string[] = []): HTMLOListElement { + classes.push(this.CSS.wrapperOrdered); + + const olElement = Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; + + return olElement; + } + + /** + * Redners list item element + * @param content - content of the list item + * @returns - created html list item element + */ + renderItem(content: string): HTMLLIElement { + const itemWrapper = Dom.make('li', this.CSS.item); + const itemBody = Dom.make('div', this.CSS.itemBody); + const itemContent = Dom.make('div', this.CSS.itemContent, { + innerHTML: content, + }); + + itemBody.appendChild(itemContent); + itemWrapper.appendChild(itemBody); + + return itemWrapper as HTMLLIElement; + } + + /** + * Return the item content + * + * @param {Element} item - item wrapper (
  • ) + * @returns {string} + */ + getItemContent(item: Element): string { + const contentNode = item.querySelector(`.${this.CSS.itemContent}`); + if (!contentNode) { + return ''; + } + + if (Dom.isEmpty(contentNode)) { + return ''; + } + + return contentNode.innerHTML; + } +} + +/** + * Class that is responsible for ordered list rendering + */ +export class OrderedListRenderer extends ListRenderer { + /** + * Tool's configuration + */ + protected config?: NestedListConfig; + + constructor(config: NestedListConfig) { + super(); + this.config = config; + } + + /** + * Renders ol wrapper for list + * @param classes - + * @returns - created html ol element + */ + renderWrapper(classes: string[] = []): HTMLOListElement { + classes.push(this.CSS.wrapperOrdered); + + return Dom.make('ol', [this.CSS.wrapper, ...classes]) as HTMLOListElement; + } + + /** + * Redners list item element + * @param content - content of the list item + * @returns - created html list item element + */ + renderItem(content: string): HTMLLIElement { + const itemWrapper = Dom.make('li', this.CSS.item); + const itemBody = Dom.make('div', this.CSS.itemBody); + const itemContent = Dom.make('div', this.CSS.itemContent, { + innerHTML: content, + }); + + itemBody.appendChild(itemContent); + itemWrapper.appendChild(itemBody); + + return itemWrapper as HTMLLIElement; + } + + /** + * Return the item content + * + * @param {Element} item - item wrapper (
  • ) + * @returns {string} + */ + getItemContent(item: Element): string { + const contentNode = item.querySelector(`.${this.CSS.itemContent}`); + if (!contentNode) { + return ''; + } + + if (Dom.isEmpty(contentNode)) { + return ''; + } + + return contentNode.innerHTML; + } +} + +/** + * Class that is responsible for checklist rendering + */ +export class CheckListRenderer extends ListRenderer { + /** + * Tool's configuration + */ + protected config?: NestedListConfig; + + constructor(config: NestedListConfig) { + super(); + this.config = config; + } + + /** + * Renders ol wrapper for list + * @param classes - + * @returns - created html ol element + */ + renderWrapper(classes: string[] = []): HTMLOListElement { + classes.push(this.CSS.wrapperOrdered); + + return Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; + } + + /** + * Redners list item element + * @param content - content of the list item + * @returns - created html list item element + */ + renderItem(content: string): HTMLLIElement { + const itemWrapper = Dom.make('li', this.CSS.item); + const itemBody = Dom.make('div', this.CSS.itemBody); + const itemContent = Dom.make('div', this.CSS.itemContent, { + innerHTML: content, + }); + + itemBody.appendChild(itemContent); + itemWrapper.appendChild(itemBody); + + return itemWrapper as HTMLLIElement; + } + + /** + * Return the item content + * + * @param {Element} item - item wrapper (
  • ) + * @returns {string} + */ + getItemContent(item: Element): string { + const contentNode = item.querySelector(`.${this.CSS.itemContent}`); + if (!contentNode) { + return ''; + } + + if (Dom.isEmpty(contentNode)) { + return ''; + } + + return contentNode.innerHTML; + } +} + From d057bcad30ca191af6559a0dfe2354525c8c411b Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 1 Aug 2024 21:19:28 +0300 Subject: [PATCH 03/43] patched types --- src/index.ts | 12 +----------- src/types/config.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 src/types/config.ts diff --git a/src/index.ts b/src/index.ts index 2a15f7ad..05449d3c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,7 @@ import { isHtmlElement } from './utils/type-guards'; import * as Dom from './utils/dom'; import Caret from './utils/caret'; import { IconListBulleted, IconListNumbered } from '@codexteam/icons'; +import NestedListConfig from './types/config'; /** * Build styles @@ -49,17 +50,6 @@ interface ListItem { items: ListItem[]; } -/** - * Tool's configuration - */ -interface NestedListConfig { - /** - * default list style: ordered or unordered - * default is unordered - */ - defaultStyle?: ListDataStyle; -} - /** * Constructor Params for Nested List Tool, use to pass initial data and settings */ diff --git a/src/types/config.ts b/src/types/config.ts new file mode 100644 index 00000000..c02b5e96 --- /dev/null +++ b/src/types/config.ts @@ -0,0 +1,18 @@ + +/** + * list style to make list as ordered or unordered + */ +type ListDataStyle = 'ordered' | 'unordered'; + +/** + * Tool's configuration + */ +export default interface NestedListConfig { + /** + * default list style: ordered or unordered + * default is unordered + */ + defaultStyle?: ListDataStyle; + + starts: number; +} From da80ff6eba4c338b51295e34c64c44dd10bdd4d2 Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 1 Aug 2024 21:29:35 +0300 Subject: [PATCH 04/43] added styles for checklist --- src/ListRenderer.ts | 8 ++++ styles/index.pcss | 95 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/src/ListRenderer.ts b/src/ListRenderer.ts index 0a2830b6..eca4d7b4 100644 --- a/src/ListRenderer.ts +++ b/src/ListRenderer.ts @@ -13,6 +13,10 @@ interface NestedListCssClasses { itemContent: string; itemChildren: string; settingsWrapper: string; + itemChecked: string; + noHover: string; + checkbox: string; + checkboxContainer: string; } /** @@ -36,6 +40,10 @@ abstract class ListRenderer { itemContent: 'cdx-nested-list__item-content', itemChildren: 'cdx-nested-list__item-children', settingsWrapper: 'cdx-nested-list__settings', + itemChecked: 'cdx-checklist__item--checked', + noHover: 'cdx-checklist__item-checkbox--no-hover', + checkbox: 'cdx-checklist__item-checkbox-check', + checkboxContainer: 'cdx-checklist__item-checkbox' }; } } diff --git a/styles/index.pcss b/styles/index.pcss index d6edb682..5eb9bbfc 100644 --- a/styles/index.pcss +++ b/styles/index.pcss @@ -28,6 +28,97 @@ white-space: pre-wrap; } + &-checkbox { + width: var(--width-checkbox); + height: var(--height-checkbox); + display: flex; + align-items: center; + margin-right: 8px; + margin-top: calc(var(--line-height)/2 - var(--height-checkbox)/2); + cursor: pointer; + + svg { + opacity: 0; + height: 20px; + width: 20px; + position: absolute; + left: -1px; + top: -1px; + max-height: 20px; + } + + @media (hover: hover) { + &:not(&--no-hover):hover { + ^&-check { + svg { + opacity: 1; + } + } + } + } + + &-check { + cursor: pointer; + display: inline-block; + flex-shrink: 0; + position: relative; + width: 20px; + height: 20px; + box-sizing: border-box; + margin-left: 0; + border-radius: var(--radius-border); + border: 1px solid var(--color-border); + background: var(--checkbox-background); + + &::before { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + border-radius: 100%; + background-color: var(--color-bg-checked); + visibility: hidden; + pointer-events: none; + transform: scale(1); + transition: transform 400ms ease-out, opacity 400ms; + } + } + } + + &--checked { + ^&-checkbox { + @media (hover: hover) { + &:not(&--no-hover):hover { + .cdx-checklist__item-checkbox-check { + background: var(--color-bg-checked-hover); + border-color: var(--color-bg-checked-hover); + } + } + } + + &-check { + background: var(--color-bg-checked); + border-color: var(--color-bg-checked); + + svg { + opacity: 1; + + path { + stroke: var(--color-tick); + } + } + + &::before { + opacity: 0; + visibility: visible; + transform: scale(2.5); + } + } + } + } + &-children {} &::before { @@ -45,6 +136,10 @@ content: "•"; } + &--checklist > &__item::before { + content: none; + } + &__settings { display: flex; From 2afda749723f8e5e7e9a2defd19c6f0d09cabee0 Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 1 Aug 2024 21:30:11 +0300 Subject: [PATCH 05/43] pathced style names --- src/ListRenderer.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ListRenderer.ts b/src/ListRenderer.ts index eca4d7b4..da351b26 100644 --- a/src/ListRenderer.ts +++ b/src/ListRenderer.ts @@ -40,10 +40,10 @@ abstract class ListRenderer { itemContent: 'cdx-nested-list__item-content', itemChildren: 'cdx-nested-list__item-children', settingsWrapper: 'cdx-nested-list__settings', - itemChecked: 'cdx-checklist__item--checked', - noHover: 'cdx-checklist__item-checkbox--no-hover', - checkbox: 'cdx-checklist__item-checkbox-check', - checkboxContainer: 'cdx-checklist__item-checkbox' + itemChecked: 'cdx-nested-list__item--checked', + noHover: 'cdx-nested-list__item-checkbox--no-hover', + checkbox: 'cdx-nested-list__item-checkbox-check', + checkboxContainer: 'cdx-nested-list__item-checkbox' }; } } From e7a585e50b766ec55f0d2bd4b56a1d741580b1b8 Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 1 Aug 2024 22:11:05 +0300 Subject: [PATCH 06/43] added checklist renderer --- src/ListRenderer.ts | 241 ---------------------- src/ListRenderer/checklistRenderer.ts | 113 ++++++++++ src/ListRenderer/orderedListRenderer.ts | 65 ++++++ src/ListRenderer/unorderedListRenderer.ts | 67 ++++++ src/types/icons.d.ts | 2 + src/types/itemMeta.ts | 6 + styles/index.pcss | 7 +- 7 files changed, 256 insertions(+), 245 deletions(-) delete mode 100644 src/ListRenderer.ts create mode 100644 src/ListRenderer/checklistRenderer.ts create mode 100644 src/ListRenderer/orderedListRenderer.ts create mode 100644 src/ListRenderer/unorderedListRenderer.ts create mode 100644 src/types/itemMeta.ts diff --git a/src/ListRenderer.ts b/src/ListRenderer.ts deleted file mode 100644 index da351b26..00000000 --- a/src/ListRenderer.ts +++ /dev/null @@ -1,241 +0,0 @@ -import NestedListConfig from "./types/config"; -import * as Dom from './utils/dom'; - -/** - * CSS classes for the Nested List Tool - */ -interface NestedListCssClasses { - wrapper: string; - wrapperOrdered: string; - wrapperUnordered: string; - item: string; - itemBody: string; - itemContent: string; - itemChildren: string; - settingsWrapper: string; - itemChecked: string; - noHover: string; - checkbox: string; - checkboxContainer: string; -} - -/** - * List renderer class - * Used for storing css classes and - */ -abstract class ListRenderer { - /** - * Styles - * - * @returns {NestedListCssClasses} - CSS classes names by keys - * @private - */ - protected get CSS(): NestedListCssClasses { - return { - wrapper: 'cdx-nested-list', - wrapperOrdered: 'cdx-nested-list--ordered', - wrapperUnordered: 'cdx-nested-list--unordered', - item: 'cdx-nested-list__item', - itemBody: 'cdx-nested-list__item-body', - itemContent: 'cdx-nested-list__item-content', - itemChildren: 'cdx-nested-list__item-children', - settingsWrapper: 'cdx-nested-list__settings', - itemChecked: 'cdx-nested-list__item--checked', - noHover: 'cdx-nested-list__item-checkbox--no-hover', - checkbox: 'cdx-nested-list__item-checkbox-check', - checkboxContainer: 'cdx-nested-list__item-checkbox' - }; - } -} - -/** - * Class that is responsible for unordered list rendering - */ -export class UnorderedListRenderer extends ListRenderer { - /** - * Tool's configuration - */ - protected config?: NestedListConfig; - - constructor(config: NestedListConfig) { - super(); - this.config = config; - } - - /** - * Renders ol wrapper for list - * @param classes - - * @returns - created html ol element - */ - renderWrapper(classes: string[] = []): HTMLOListElement { - classes.push(this.CSS.wrapperOrdered); - - const olElement = Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; - - return olElement; - } - - /** - * Redners list item element - * @param content - content of the list item - * @returns - created html list item element - */ - renderItem(content: string): HTMLLIElement { - const itemWrapper = Dom.make('li', this.CSS.item); - const itemBody = Dom.make('div', this.CSS.itemBody); - const itemContent = Dom.make('div', this.CSS.itemContent, { - innerHTML: content, - }); - - itemBody.appendChild(itemContent); - itemWrapper.appendChild(itemBody); - - return itemWrapper as HTMLLIElement; - } - - /** - * Return the item content - * - * @param {Element} item - item wrapper (
  • ) - * @returns {string} - */ - getItemContent(item: Element): string { - const contentNode = item.querySelector(`.${this.CSS.itemContent}`); - if (!contentNode) { - return ''; - } - - if (Dom.isEmpty(contentNode)) { - return ''; - } - - return contentNode.innerHTML; - } -} - -/** - * Class that is responsible for ordered list rendering - */ -export class OrderedListRenderer extends ListRenderer { - /** - * Tool's configuration - */ - protected config?: NestedListConfig; - - constructor(config: NestedListConfig) { - super(); - this.config = config; - } - - /** - * Renders ol wrapper for list - * @param classes - - * @returns - created html ol element - */ - renderWrapper(classes: string[] = []): HTMLOListElement { - classes.push(this.CSS.wrapperOrdered); - - return Dom.make('ol', [this.CSS.wrapper, ...classes]) as HTMLOListElement; - } - - /** - * Redners list item element - * @param content - content of the list item - * @returns - created html list item element - */ - renderItem(content: string): HTMLLIElement { - const itemWrapper = Dom.make('li', this.CSS.item); - const itemBody = Dom.make('div', this.CSS.itemBody); - const itemContent = Dom.make('div', this.CSS.itemContent, { - innerHTML: content, - }); - - itemBody.appendChild(itemContent); - itemWrapper.appendChild(itemBody); - - return itemWrapper as HTMLLIElement; - } - - /** - * Return the item content - * - * @param {Element} item - item wrapper (
  • ) - * @returns {string} - */ - getItemContent(item: Element): string { - const contentNode = item.querySelector(`.${this.CSS.itemContent}`); - if (!contentNode) { - return ''; - } - - if (Dom.isEmpty(contentNode)) { - return ''; - } - - return contentNode.innerHTML; - } -} - -/** - * Class that is responsible for checklist rendering - */ -export class CheckListRenderer extends ListRenderer { - /** - * Tool's configuration - */ - protected config?: NestedListConfig; - - constructor(config: NestedListConfig) { - super(); - this.config = config; - } - - /** - * Renders ol wrapper for list - * @param classes - - * @returns - created html ol element - */ - renderWrapper(classes: string[] = []): HTMLOListElement { - classes.push(this.CSS.wrapperOrdered); - - return Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; - } - - /** - * Redners list item element - * @param content - content of the list item - * @returns - created html list item element - */ - renderItem(content: string): HTMLLIElement { - const itemWrapper = Dom.make('li', this.CSS.item); - const itemBody = Dom.make('div', this.CSS.itemBody); - const itemContent = Dom.make('div', this.CSS.itemContent, { - innerHTML: content, - }); - - itemBody.appendChild(itemContent); - itemWrapper.appendChild(itemBody); - - return itemWrapper as HTMLLIElement; - } - - /** - * Return the item content - * - * @param {Element} item - item wrapper (
  • ) - * @returns {string} - */ - getItemContent(item: Element): string { - const contentNode = item.querySelector(`.${this.CSS.itemContent}`); - if (!contentNode) { - return ''; - } - - if (Dom.isEmpty(contentNode)) { - return ''; - } - - return contentNode.innerHTML; - } -} - diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts new file mode 100644 index 00000000..c400f41b --- /dev/null +++ b/src/ListRenderer/checklistRenderer.ts @@ -0,0 +1,113 @@ +import { IconCheck } from '@codexteam/icons' +import ItemMeta from "../types/itemMeta"; +import NestedListConfig from "../types/config"; +import * as Dom from '../utils/dom'; + +/** + * Class that is responsible for checklist rendering + */ +export class CheckListRenderer extends ListRenderer { + /** + * Tool's configuration + */ + protected config?: NestedListConfig; + + constructor(config: NestedListConfig) { + super(); + this.config = config; + } + + /** + * Renders ol wrapper for list + * @param classes - + * @returns - created html ol element + */ + renderWrapper(classes: string[] = []): HTMLOListElement { + classes.push(this.CSS.wrapperOrdered); + + return Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; + } + + /** + * Redners list item element + * @param content - content of the list item + * @returns - created html list item element + */ + renderItem(content: string): HTMLLIElement { + const itemWrapper = Dom.make('li', this.CSS.item); + const itemBody = Dom.make('div', this.CSS.itemBody); + const itemContent = Dom.make('div', this.CSS.itemContent, { + innerHTML: content, + }); + + const checkbox = Dom.make('span', this.CSS.checkbox); + const checkboxContainer = Dom.make('div', this.CSS.checkboxContainer); + + checkbox.innerHTML = IconCheck; + checkboxContainer.appendChild(checkbox); + + checkboxContainer.addEventListener('click', (event) => { + this.toggleCheckbox(event); + }); + + itemBody.appendChild(itemContent); + itemWrapper.appendChild(checkboxContainer); + itemWrapper.appendChild(itemBody); + return itemWrapper as HTMLLIElement; + } + + /** + * Return the item content + * + * @param {Element} item - item wrapper (
  • ) + * @returns {string} + */ + getItemContent(item: Element): string { + const contentNode = item.querySelector(`.${this.CSS.itemContent}`); + if (!contentNode) { + return ''; + } + + if (Dom.isEmpty(contentNode)) { + return ''; + } + + return contentNode.innerHTML; + } + + getItemMeta(item: HTMLElement): ItemMeta { + return { + checked: item.classList.contains(this.CSS.itemChecked) + } + } + + /** + * Toggle checklist item state + * + * @private + * @param {MouseEvent} event - click + * @returns {void} + */ + private toggleCheckbox(event: any): void { + const checkListItem = event.target.closest(`.${this.CSS.item}`); + const checkbox = checkListItem.querySelector(`.${this.CSS.checkboxContainer}`); + + if (checkbox.contains(event.target)) { + checkListItem.classList.toggle(this.CSS.itemChecked); + checkbox.classList.add(this.CSS.noHover); + checkbox.addEventListener('mouseleave', () => this.removeSpecialHoverBehavior(checkbox), { once: true }); + } + } + + /** + * Removes class responsible for special hover behavior on an item + * + * @private + * @param {Element} el - item wrapper + * @returns {Element} + */ + private removeSpecialHoverBehavior(el: HTMLElement) { + el.classList.remove(this.CSS.noHover); + } +} + diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts new file mode 100644 index 00000000..29e2a7c5 --- /dev/null +++ b/src/ListRenderer/orderedListRenderer.ts @@ -0,0 +1,65 @@ +import NestedListConfig from "../types/config"; +import * as Dom from '../utils/dom'; + +/** + * Class that is responsible for ordered list rendering + */ +export class OrderedListRenderer extends ListRenderer { + /** + * Tool's configuration + */ + protected config?: NestedListConfig; + + constructor(config: NestedListConfig) { + super(); + this.config = config; + } + + /** + * Renders ol wrapper for list + * @param classes - + * @returns - created html ol element + */ + renderWrapper(classes: string[] = []): HTMLOListElement { + classes.push(this.CSS.wrapperOrdered); + + return Dom.make('ol', [this.CSS.wrapper, ...classes]) as HTMLOListElement; + } + + /** + * Redners list item element + * @param content - content of the list item + * @returns - created html list item element + */ + renderItem(content: string): HTMLLIElement { + const itemWrapper = Dom.make('li', this.CSS.item); + const itemBody = Dom.make('div', this.CSS.itemBody); + const itemContent = Dom.make('div', this.CSS.itemContent, { + innerHTML: content, + }); + + itemBody.appendChild(itemContent); + itemWrapper.appendChild(itemBody); + + return itemWrapper as HTMLLIElement; + } + + /** + * Return the item content + * + * @param {Element} item - item wrapper (
  • ) + * @returns {string} + */ + getItemContent(item: Element): string { + const contentNode = item.querySelector(`.${this.CSS.itemContent}`); + if (!contentNode) { + return ''; + } + + if (Dom.isEmpty(contentNode)) { + return ''; + } + + return contentNode.innerHTML; + } +} diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts new file mode 100644 index 00000000..6d8ece1e --- /dev/null +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -0,0 +1,67 @@ +import NestedListConfig from "../types/config"; +import * as Dom from '../utils/dom'; + +/** + * Class that is responsible for unordered list rendering + */ +export class UnorderedListRenderer extends ListRenderer { + /** + * Tool's configuration + */ + protected config?: NestedListConfig; + + constructor(config: NestedListConfig) { + super(); + this.config = config; + } + + /** + * Renders ol wrapper for list + * @param classes - + * @returns - created html ol element + */ + renderWrapper(classes: string[] = []): HTMLOListElement { + classes.push(this.CSS.wrapperOrdered); + + const olElement = Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; + + return olElement; + } + + /** + * Redners list item element + * @param content - content of the list item + * @returns - created html list item element + */ + renderItem(content: string): HTMLLIElement { + const itemWrapper = Dom.make('li', this.CSS.item); + const itemBody = Dom.make('div', this.CSS.itemBody); + const itemContent = Dom.make('div', this.CSS.itemContent, { + innerHTML: content, + }); + + itemBody.appendChild(itemContent); + itemWrapper.appendChild(itemBody); + + return itemWrapper as HTMLLIElement; + } + + /** + * Return the item content + * + * @param {Element} item - item wrapper (
  • ) + * @returns {string} + */ + getItemContent(item: Element): string { + const contentNode = item.querySelector(`.${this.CSS.itemContent}`); + if (!contentNode) { + return ''; + } + + if (Dom.isEmpty(contentNode)) { + return ''; + } + + return contentNode.innerHTML; + } +} diff --git a/src/types/icons.d.ts b/src/types/icons.d.ts index 79bafd56..708e058b 100644 --- a/src/types/icons.d.ts +++ b/src/types/icons.d.ts @@ -2,4 +2,6 @@ declare module '@codexteam/icons' { export const IconListBulleted: string; export const IconListNumbered: string; + // export const IconCheckList: string + export const IconCheck: string; } diff --git a/src/types/itemMeta.ts b/src/types/itemMeta.ts new file mode 100644 index 00000000..7675b5eb --- /dev/null +++ b/src/types/itemMeta.ts @@ -0,0 +1,6 @@ +export default interface ItemMeta { + /** + * State of the item + */ + checked?: boolean; +} diff --git a/styles/index.pcss b/styles/index.pcss index 5eb9bbfc..86b5cda2 100644 --- a/styles/index.pcss +++ b/styles/index.pcss @@ -88,6 +88,9 @@ } &--checked { + line-height: 1.6em; + display: flex; + margin: 2px 0; ^&-checkbox { @media (hover: hover) { &:not(&--no-hover):hover { @@ -136,10 +139,6 @@ content: "•"; } - &--checklist > &__item::before { - content: none; - } - &__settings { display: flex; From 7cbd0d77dee144fe3208710cf26f61ced703f904 Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 1 Aug 2024 22:11:24 +0300 Subject: [PATCH 07/43] separated list renderer classes --- src/ListRenderer/listRenderer.ts | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/ListRenderer/listRenderer.ts diff --git a/src/ListRenderer/listRenderer.ts b/src/ListRenderer/listRenderer.ts new file mode 100644 index 00000000..c7e87c14 --- /dev/null +++ b/src/ListRenderer/listRenderer.ts @@ -0,0 +1,46 @@ +/** + * CSS classes for the Nested List Tool + */ +interface NestedListCssClasses { + wrapper: string; + wrapperOrdered: string; + wrapperUnordered: string; + item: string; + itemBody: string; + itemContent: string; + itemChildren: string; + settingsWrapper: string; + itemChecked: string; + noHover: string; + checkbox: string; + checkboxContainer: string; +} + +/** + * List renderer class + * Used for storing css classes and + */ +abstract class ListRenderer { + /** + * Styles + * + * @returns {NestedListCssClasses} - CSS classes names by keys + * @private + */ + protected get CSS(): NestedListCssClasses { + return { + wrapper: 'cdx-nested-list', + wrapperOrdered: 'cdx-nested-list--ordered', + wrapperUnordered: 'cdx-nested-list--unordered', + item: 'cdx-nested-list__item', + itemBody: 'cdx-nested-list__item-body', + itemContent: 'cdx-nested-list__item-content', + itemChildren: 'cdx-nested-list__item-children', + settingsWrapper: 'cdx-nested-list__settings', + itemChecked: 'cdx-nested-list__item--checked', + noHover: 'cdx-nested-list__item-checkbox--no-hover', + checkbox: 'cdx-nested-list__item-checkbox-check', + checkboxContainer: 'cdx-nested-list__item-checkbox' + }; + } +} From 745218592165811b83757390e1126fa4688eb439 Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 1 Aug 2024 22:29:53 +0300 Subject: [PATCH 08/43] types separated --- src/ListRenderer/index.ts | 5 +++++ src/Tabulator/index.ts | 15 +++++++++++++ src/index.ts | 35 +---------------------------- src/types/config.ts | 18 --------------- src/types/listParams.ts | 47 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 52 deletions(-) create mode 100644 src/ListRenderer/index.ts create mode 100644 src/Tabulator/index.ts delete mode 100644 src/types/config.ts create mode 100644 src/types/listParams.ts diff --git a/src/ListRenderer/index.ts b/src/ListRenderer/index.ts new file mode 100644 index 00000000..18bb4f7a --- /dev/null +++ b/src/ListRenderer/index.ts @@ -0,0 +1,5 @@ +import { CheckListRenderer } from "./checklistRenderer"; +import { OrderedListRenderer } from "./orderedListRenderer"; +import { UnorderedListRenderer } from "./unorderedListRenderer"; + +export default { CheckListRenderer, OrderedListRenderer, UnorderedListRenderer }; diff --git a/src/Tabulator/index.ts b/src/Tabulator/index.ts new file mode 100644 index 00000000..881e8a06 --- /dev/null +++ b/src/Tabulator/index.ts @@ -0,0 +1,15 @@ +import NestedListConfig from "../types/config" + +/** + * Class that is responsible for list tabulation + */ +export default class Tabulator { + /** + * Tool's configuration + */ + config: NestedListConfig + + constructor(config: NestedListConfig) { + this.config = config; + } +} diff --git a/src/index.ts b/src/index.ts index 05449d3c..f134d52a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,46 +10,13 @@ import { isHtmlElement } from './utils/type-guards'; import * as Dom from './utils/dom'; import Caret from './utils/caret'; import { IconListBulleted, IconListNumbered } from '@codexteam/icons'; -import NestedListConfig from './types/config'; +import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/listParams'; /** * Build styles */ import './../styles/index.pcss'; -/** - * list style to make list as ordered or unordered - */ -type ListDataStyle = 'ordered' | 'unordered'; - -/** - * Output data - */ -interface ListData { - /** - * list type 'ordered' or 'unordered' - */ - style: ListDataStyle; - /** - * list of first-level elements - */ - items: ListItem[]; -} - -/** - * List item within the output data - */ -interface ListItem { - /** - * list item text content - */ - content: string; - /** - * sublist items - */ - items: ListItem[]; -} - /** * Constructor Params for Nested List Tool, use to pass initial data and settings */ diff --git a/src/types/config.ts b/src/types/config.ts deleted file mode 100644 index c02b5e96..00000000 --- a/src/types/config.ts +++ /dev/null @@ -1,18 +0,0 @@ - -/** - * list style to make list as ordered or unordered - */ -type ListDataStyle = 'ordered' | 'unordered'; - -/** - * Tool's configuration - */ -export default interface NestedListConfig { - /** - * default list style: ordered or unordered - * default is unordered - */ - defaultStyle?: ListDataStyle; - - starts: number; -} diff --git a/src/types/listParams.ts b/src/types/listParams.ts new file mode 100644 index 00000000..196791b7 --- /dev/null +++ b/src/types/listParams.ts @@ -0,0 +1,47 @@ +/** + * list style to make list as ordered or unordered + */ +export type ListDataStyle = 'ordered' | 'unordered'; + +/** + * Output data + */ +export interface ListData { + /** + * list type 'ordered' or 'unordered' + */ + style: ListDataStyle; + /** + * list of first-level elements + */ + items: ListItem[]; +} + +/** + * List item within the output data + */ +export interface ListItem { + /** + * list item text content + */ + content: string; + /** + * sublist items + */ + items: ListItem[]; +} + +/** + * Tool's configuration + */ +export interface NestedListConfig { + /** + * default list style: ordered or unordered + * default is unordered + */ + defaultStyle?: ListDataStyle; + + starts?: number; + + maxLevel?: number; +} From d949cbc46b18a8a6c6425e4d3010c2a4f3837e9f Mon Sep 17 00:00:00 2001 From: e11sy Date: Thu, 1 Aug 2024 22:32:18 +0300 Subject: [PATCH 09/43] added tabulator class --- src/ListRenderer/checklistRenderer.ts | 2 +- src/ListRenderer/orderedListRenderer.ts | 2 +- src/ListRenderer/unorderedListRenderer.ts | 2 +- src/Tabulator/index.ts | 7 +++++-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index c400f41b..304df300 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -1,6 +1,6 @@ import { IconCheck } from '@codexteam/icons' import ItemMeta from "../types/itemMeta"; -import NestedListConfig from "../types/config"; +import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; /** diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts index 29e2a7c5..073d3821 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/orderedListRenderer.ts @@ -1,4 +1,4 @@ -import NestedListConfig from "../types/config"; +import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; /** diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts index 6d8ece1e..cfe1d121 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -1,4 +1,4 @@ -import NestedListConfig from "../types/config"; +import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; /** diff --git a/src/Tabulator/index.ts b/src/Tabulator/index.ts index 881e8a06..f6abb044 100644 --- a/src/Tabulator/index.ts +++ b/src/Tabulator/index.ts @@ -1,4 +1,4 @@ -import NestedListConfig from "../types/config" +import { NestedListConfig, ListData } from "../types/listParams" /** * Class that is responsible for list tabulation @@ -9,7 +9,10 @@ export default class Tabulator { */ config: NestedListConfig - constructor(config: NestedListConfig) { + data: ListData; + + constructor(data: ListData, config: NestedListConfig) { this.config = config; + this.data = data; } } From df88c86af7dd96492d48a54adc181d0b9e580827 Mon Sep 17 00:00:00 2001 From: e11sy Date: Fri, 2 Aug 2024 18:43:19 +0300 Subject: [PATCH 10/43] added new index --- package.json | 2 +- src/ListRenderer/checklistRenderer.ts | 8 +- src/ListRenderer/index.ts | 3 +- src/ListRenderer/listRenderer.ts | 4 +- src/ListRenderer/orderedListRenderer.ts | 7 +- src/ListRenderer/unorderedListRenderer.ts | 6 +- src/Tabulator/index.ts | 79 +- src/ex.ts | 1070 +++++++++++++++++++++ src/index.ts | 937 +----------------- src/types/listParams.ts | 2 +- styles/index.pcss | 14 + yarn.lock | 8 +- 12 files changed, 1216 insertions(+), 924 deletions(-) create mode 100644 src/ex.ts diff --git a/package.json b/package.json index cb9559b7..25e7e233 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,6 @@ "vite-plugin-dts": "^3.9.1" }, "dependencies": { - "@codexteam/icons": "^0.0.2" + "@codexteam/icons": "^0.3.2" } } diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index 304df300..566d0d11 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -2,6 +2,7 @@ import { IconCheck } from '@codexteam/icons' import ItemMeta from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; +import { ListRenderer } from './listRenderer'; /** * Class that is responsible for checklist rendering @@ -12,7 +13,7 @@ export class CheckListRenderer extends ListRenderer { */ protected config?: NestedListConfig; - constructor(config: NestedListConfig) { + constructor(config?: NestedListConfig) { super(); this.config = config; } @@ -23,7 +24,7 @@ export class CheckListRenderer extends ListRenderer { * @returns - created html ol element */ renderWrapper(classes: string[] = []): HTMLOListElement { - classes.push(this.CSS.wrapperOrdered); + classes.push(this.CSS.wrapperChecklist); return Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; } @@ -34,7 +35,7 @@ export class CheckListRenderer extends ListRenderer { * @returns - created html list item element */ renderItem(content: string): HTMLLIElement { - const itemWrapper = Dom.make('li', this.CSS.item); + const itemWrapper = Dom.make('li', [this.CSS.item, this.CSS.item]); const itemBody = Dom.make('div', this.CSS.itemBody); const itemContent = Dom.make('div', this.CSS.itemContent, { innerHTML: content, @@ -53,6 +54,7 @@ export class CheckListRenderer extends ListRenderer { itemBody.appendChild(itemContent); itemWrapper.appendChild(checkboxContainer); itemWrapper.appendChild(itemBody); + return itemWrapper as HTMLLIElement; } diff --git a/src/ListRenderer/index.ts b/src/ListRenderer/index.ts index 18bb4f7a..6f3d423b 100644 --- a/src/ListRenderer/index.ts +++ b/src/ListRenderer/index.ts @@ -1,5 +1,6 @@ import { CheckListRenderer } from "./checklistRenderer"; import { OrderedListRenderer } from "./orderedListRenderer"; import { UnorderedListRenderer } from "./unorderedListRenderer"; +import { ListRenderer } from './listRenderer'; -export default { CheckListRenderer, OrderedListRenderer, UnorderedListRenderer }; +export { CheckListRenderer, OrderedListRenderer, UnorderedListRenderer, ListRenderer }; diff --git a/src/ListRenderer/listRenderer.ts b/src/ListRenderer/listRenderer.ts index c7e87c14..9050404b 100644 --- a/src/ListRenderer/listRenderer.ts +++ b/src/ListRenderer/listRenderer.ts @@ -5,6 +5,7 @@ interface NestedListCssClasses { wrapper: string; wrapperOrdered: string; wrapperUnordered: string; + wrapperChecklist: string; item: string; itemBody: string; itemContent: string; @@ -20,7 +21,7 @@ interface NestedListCssClasses { * List renderer class * Used for storing css classes and */ -abstract class ListRenderer { +export abstract class ListRenderer { /** * Styles * @@ -32,6 +33,7 @@ abstract class ListRenderer { wrapper: 'cdx-nested-list', wrapperOrdered: 'cdx-nested-list--ordered', wrapperUnordered: 'cdx-nested-list--unordered', + wrapperChecklist: 'cdx-nested-list--checklist', item: 'cdx-nested-list__item', itemBody: 'cdx-nested-list__item-body', itemContent: 'cdx-nested-list__item-content', diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts index 073d3821..1f4210e3 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/orderedListRenderer.ts @@ -1,5 +1,6 @@ import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; +import { ListRenderer } from './listRenderer'; /** * Class that is responsible for ordered list rendering @@ -10,7 +11,7 @@ export class OrderedListRenderer extends ListRenderer { */ protected config?: NestedListConfig; - constructor(config: NestedListConfig) { + constructor(config?: NestedListConfig) { super(); this.config = config; } @@ -38,9 +39,13 @@ export class OrderedListRenderer extends ListRenderer { innerHTML: content, }); + console.log(itemContent, itemContent instanceof Node); itemBody.appendChild(itemContent); + console.log(itemBody, itemBody instanceof Node); itemWrapper.appendChild(itemBody); + console.log('created item wrapper', itemWrapper, typeof itemWrapper) + return itemWrapper as HTMLLIElement; } diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts index cfe1d121..2c66d7ed 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -1,5 +1,6 @@ import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; +import { ListRenderer } from './listRenderer'; /** * Class that is responsible for unordered list rendering @@ -10,7 +11,7 @@ export class UnorderedListRenderer extends ListRenderer { */ protected config?: NestedListConfig; - constructor(config: NestedListConfig) { + constructor(config?: NestedListConfig) { super(); this.config = config; } @@ -40,7 +41,10 @@ export class UnorderedListRenderer extends ListRenderer { innerHTML: content, }); + + console.log(itemContent, itemContent instanceof Node); itemBody.appendChild(itemContent); + console.log(itemBody, itemBody instanceof Node); itemWrapper.appendChild(itemBody); return itemWrapper as HTMLLIElement; diff --git a/src/Tabulator/index.ts b/src/Tabulator/index.ts index f6abb044..eaaf1f52 100644 --- a/src/Tabulator/index.ts +++ b/src/Tabulator/index.ts @@ -1,4 +1,12 @@ +import { CheckListRenderer } from "../ListRenderer/checklistRenderer"; +import { OrderedListRenderer } from "../ListRenderer/orderedListRenderer"; +import { UnorderedListRenderer } from "../ListRenderer/unorderedListRenderer"; import { NestedListConfig, ListData } from "../types/listParams" +import { ListItem } from "../types/listParams"; + +type NestedListStyle = 'ordered' | 'unordered' | 'checklist'; + +type ListRendererTypes = OrderedListRenderer | UnorderedListRenderer | CheckListRenderer; /** * Class that is responsible for list tabulation @@ -7,12 +15,79 @@ export default class Tabulator { /** * Tool's configuration */ - config: NestedListConfig + config?: NestedListConfig; + + /** + * Style of the nested list + */ + style: NestedListStyle; + /** + * Full content of the list + */ data: ListData; - constructor(data: ListData, config: NestedListConfig) { + constructor(data: ListData, style: NestedListStyle, config?: NestedListConfig) { this.config = config; this.data = data; + this.style = style; + } + + render() { + let list; + + console.log('style', this.style) + + switch (this.style) { + case 'ordered': + list = new OrderedListRenderer(this.config); + case 'unordered': + list = new UnorderedListRenderer(this.config); + case 'checklist': + list = new CheckListRenderer(this.config); + } + + const listWrapper = list.renderWrapper(); + + // fill with data + if (this.data.items.length) { + this.appendItems(list, this.data.items, listWrapper); + } else { + this.appendItems( + list, + [ + { + content: '', + items: [], + }, + ], + listWrapper, + ); + } + + return listWrapper; + } + + /** + * Renders children list + * + * @param list - initialized ListRenderer instance + * @param {ListItem[]} items - items data to append + * @param {Element} parentItem - where to append + * @returns {void} + */ + appendItems(list: ListRendererTypes, items: ListItem[], parentItem: Element): void { + items.forEach((item) => { + const itemEl = list.renderItem(item.content); + + console.log('rendered item', itemEl, itemEl instanceof HTMLLIElement) + + if (itemEl instanceof Node) { + console.log(parentItem instanceof Node, itemEl instanceof Node); + parentItem.appendChild(itemEl); + } else { + console.log(itemEl) + } + }); } } diff --git a/src/ex.ts b/src/ex.ts new file mode 100644 index 00000000..f134d52a --- /dev/null +++ b/src/ex.ts @@ -0,0 +1,1070 @@ +import type { API, PasteConfig, ToolboxConfig } from '@editorjs/editorjs'; +import type { PasteEvent } from './types'; +import type { + BlockToolConstructorOptions, + TunesMenuConfig, +} from '@editorjs/editorjs/types/tools'; + +import { isHtmlElement } from './utils/type-guards'; + +import * as Dom from './utils/dom'; +import Caret from './utils/caret'; +import { IconListBulleted, IconListNumbered } from '@codexteam/icons'; +import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/listParams'; + +/** + * Build styles + */ +import './../styles/index.pcss'; + +/** + * Constructor Params for Nested List Tool, use to pass initial data and settings + */ +export type NestedListParams = BlockToolConstructorOptions< + ListData, + NestedListConfig +>; + +/** + * CSS classes for the Nested List Tool + */ +interface NestedListCssClasses { + baseBlock: string; + wrapper: string; + wrapperOrdered: string; + wrapperUnordered: string; + item: string; + itemBody: string; + itemContent: string; + itemChildren: string; + settingsWrapper: string; + settingsButton: string; + settingsButtonActive: string; +} + +/** + * NestedList Tool for EditorJS + */ +export default class NestedList { + /** + * Notify core that read-only mode is supported + * + * @returns {boolean} + */ + static get isReadOnlySupported(): boolean { + return true; + } + + /** + * Allow to use native Enter behaviour + * + * @returns {boolean} + * @public + */ + static get enableLineBreaks(): boolean { + return true; + } + + /** + * Get Tool toolbox settings + * icon - Tool icon's SVG + * title - title to show in toolbox + * + * @returns {ToolboxConfig} + */ + static get toolbox(): ToolboxConfig { + return { + icon: IconListNumbered, + title: 'List', + }; + } + + /** + * The Editor.js API + */ + private api: API; + + /** + * Is NestedList Tool read-only + */ + private readOnly: boolean; + + /** + * Tool's configuration + */ + private config?: NestedListConfig; + + /** + * Default list style + */ + private defaultListStyle?: NestedListConfig['defaultStyle']; + + /** + * Corresponds to UiNodes type from Editor.js but with wrapper being nullable + */ + private nodes: { wrapper: HTMLElement | null }; + + /** + * Tool's data + */ + private data: ListData; + + /** + * Caret helper + */ + private caret: Caret; + + /** + * Render plugin`s main Element and fill it with saved data + * + * @param {object} params - tool constructor options + * @param {ListData} params.data - previously saved data + * @param {object} params.config - user config for Tool + * @param {object} params.api - Editor.js API + * @param {boolean} params.readOnly - read-only mode flag + */ + constructor({ data, config, api, readOnly }: NestedListParams) { + /** + * HTML nodes used in tool + */ + this.nodes = { + wrapper: null, + }; + + this.api = api; + this.readOnly = readOnly; + this.config = config; + + /** + * Set the default list style from the config. + */ + this.defaultListStyle = + this.config?.defaultStyle === 'ordered' ? 'ordered' : 'unordered'; + + const initialData = { + style: this.defaultListStyle, + items: [], + }; + this.data = data && Object.keys(data).length ? data : initialData; + + /** + * Instantiate caret helper + */ + this.caret = new Caret(); + } + + /** + * Returns list tag with items + * + * @returns {Element} + * @public + */ + render(): Element { + this.nodes.wrapper = this.makeListWrapper(this.data.style, [ + this.CSS.baseBlock, + ]); + + // fill with data + if (this.data.items.length) { + this.appendItems(this.data.items, this.nodes.wrapper); + } else { + this.appendItems( + [ + { + content: '', + items: [], + }, + ], + this.nodes.wrapper + ); + } + + if (!this.readOnly) { + // detect keydown on the last item to escape List + this.nodes.wrapper.addEventListener( + 'keydown', + (event) => { + switch (event.key) { + case 'Enter': + this.enterPressed(event); + break; + case 'Backspace': + this.backspace(event); + break; + case 'Tab': + if (event.shiftKey) { + this.shiftTab(event); + } else { + this.addTab(event); + } + break; + } + }, + false + ); + } + + return this.nodes.wrapper; + } + + /** + * Creates Block Tune allowing to change the list style + * + * @public + * @returns {Array} + */ + renderSettings(): TunesMenuConfig { + const tunes = [ + { + name: 'unordered' as const, + label: this.api.i18n.t('Unordered'), + icon: IconListBulleted, + }, + { + name: 'ordered' as const, + label: this.api.i18n.t('Ordered'), + icon: IconListNumbered, + }, + ]; + + return tunes.map((tune) => ({ + name: tune.name, + icon: tune.icon, + label: tune.label, + isActive: this.data.style === tune.name, + closeOnActivate: true, + onActivate: () => { + this.listStyle = tune.name; + }, + })); + } + + /** + * On paste sanitzation config. Allow only tags that are allowed in the Tool. + * + * @returns {PasteConfig} - paste config. + */ + static get pasteConfig(): PasteConfig { + return { + tags: ['OL', 'UL', 'LI'], + }; + } + + /** + * On paste callback that is fired from Editor. + * + * @param {PasteEvent} event - event with pasted data + */ + onPaste(event: PasteEvent): void { + const list = event.detail.data; + + this.data = this.pasteHandler(list); + + // render new list + const oldView = this.nodes.wrapper; + + if (oldView && oldView.parentNode) { + oldView.parentNode.replaceChild(this.render(), oldView); + } + } + + /** + * Handle UL, OL and LI tags paste and returns List data + * + * @param {HTMLUListElement|HTMLOListElement|HTMLLIElement} element + * @returns {ListData} + */ + pasteHandler(element: PasteEvent['detail']['data']): ListData { + const { tagName: tag } = element; + let style: ListDataStyle = 'unordered'; + let tagToSearch: string; + + // set list style and tag to search. + switch (tag) { + case 'OL': + style = 'ordered'; + tagToSearch = 'ol'; + break; + case 'UL': + case 'LI': + style = 'unordered'; + tagToSearch = 'ul'; + } + + const data: ListData = { + style, + items: [], + }; + + // get pasted items from the html. + const getPastedItems = (parent: Element): ListItem[] => { + // get first level li elements. + const children = Array.from(parent.querySelectorAll(`:scope > li`)); + + return children.map((child) => { + // get subitems if they exist. + const subItemsWrapper = child.querySelector(`:scope > ${tagToSearch}`); + // get subitems. + const subItems = subItemsWrapper ? getPastedItems(subItemsWrapper) : []; + // get text content of the li element. + const content = child?.firstChild?.textContent || ''; + + return { + content, + items: subItems, + }; + }); + }; + + // get pasted items. + data.items = getPastedItems(element); + + return data; + } + + /** + * Renders children list + * + * @param {ListItem[]} items - items data to append + * @param {Element} parentItem - where to append + * @returns {void} + */ + appendItems(items: ListItem[], parentItem: Element): void { + items.forEach((item) => { + const itemEl = this.createItem(item.content, item.items); + + parentItem.appendChild(itemEl); + }); + } + + /** + * Renders the single item + * + * @param {string} content - item content to render + * @param {ListItem[]} [items] - children + * @returns {Element} + */ + createItem(content: string, items: ListItem[] = []): Element { + const itemWrapper = Dom.make('li', this.CSS.item); + const itemBody = Dom.make('div', this.CSS.itemBody); + const itemContent = Dom.make('div', this.CSS.itemContent, { + innerHTML: content, + contentEditable: (!this.readOnly).toString(), + }); + + itemBody.appendChild(itemContent); + itemWrapper.appendChild(itemBody); + + /** + * Append children if we have some + */ + if (items && items.length > 0) { + this.addChildrenList(itemWrapper, items); + } + + return itemWrapper; + } + + /** + * Extracts tool's data from the DOM + * + * @returns {ListData} + */ + save(): ListData { + /** + * The method for recursive collecting of the child items + * + * @param {Element} parent - where to find items + * @returns {ListItem[]} + */ + const getItems = (parent: Element): ListItem[] => { + const children = Array.from( + parent.querySelectorAll(`:scope > .${this.CSS.item}`) + ); + + return children.map((el) => { + const subItemsWrapper = el.querySelector(`.${this.CSS.itemChildren}`); + const content = this.getItemContent(el); + const subItems = subItemsWrapper ? getItems(subItemsWrapper) : []; + + return { + content, + items: subItems, + }; + }); + }; + + return { + style: this.data.style, + items: this.nodes.wrapper ? getItems(this.nodes.wrapper) : [], + }; + } + + /** + * Append children list to passed item + * + * @param {Element} parentItem - item that should contain passed sub-items + * @param {ListItem[]} items - sub items to append + */ + addChildrenList(parentItem: Element, items: ListItem[]): void { + const itemBody = parentItem.querySelector(`.${this.CSS.itemBody}`); + const sublistWrapper = this.makeListWrapper(undefined, [ + this.CSS.itemChildren, + ]); + + this.appendItems(items, sublistWrapper); + + if (!itemBody) { + return; + } + + itemBody.appendChild(sublistWrapper); + } + + /** + * Creates main
      or
        tag depended on style + * + * @param {string} [style] - 'ordered' or 'unordered' + * @param {string[]} [classes] - additional classes to append + * @returns {HTMLOListElement|HTMLUListElement} + */ + makeListWrapper( + style: string = this.listStyle, + classes: string[] = [] + ): HTMLOListElement | HTMLUListElement { + const tag = style === 'ordered' ? 'ol' : 'ul'; + const styleClass = + style === 'ordered' ? this.CSS.wrapperOrdered : this.CSS.wrapperUnordered; + + classes.push(styleClass); + + // since tag is either 'ol' or 'ul' we can safely cast it to HTMLOListElement | HTMLUListElement + return Dom.make(tag, [this.CSS.wrapper, ...classes]) as + | HTMLOListElement + | HTMLUListElement; + } + + /** + * Styles + * + * @returns {NestedListCssClasses} - CSS classes names by keys + * @private + */ + get CSS(): NestedListCssClasses { + return { + baseBlock: this.api.styles.block, + wrapper: 'cdx-nested-list', + wrapperOrdered: 'cdx-nested-list--ordered', + wrapperUnordered: 'cdx-nested-list--unordered', + item: 'cdx-nested-list__item', + itemBody: 'cdx-nested-list__item-body', + itemContent: 'cdx-nested-list__item-content', + itemChildren: 'cdx-nested-list__item-children', + settingsWrapper: 'cdx-nested-list__settings', + settingsButton: this.api.styles.settingsButton, + settingsButtonActive: this.api.styles.settingsButtonActive, + }; + } + + /** + * Get list style name + * + * @returns {string} + */ + get listStyle(): string { + return this.data.style || this.defaultListStyle; + } + + /** + * Set list style + * + * @param {ListDataStyle} style - new style to set + */ + set listStyle(style: ListDataStyle) { + if (!this.nodes) { + return; + } + if (!this.nodes.wrapper) { + return; + } + /** + * Get lists elements + * + * @type {Element[]} + */ + const lists: Element[] = Array.from( + this.nodes.wrapper.querySelectorAll(`.${this.CSS.wrapper}`) + ); + + /** + * Add main wrapper to the list + */ + lists.push(this.nodes.wrapper); + + /** + * For each list we need to update classes + */ + lists.forEach((list) => { + list.classList.toggle(this.CSS.wrapperUnordered, style === 'unordered'); + list.classList.toggle(this.CSS.wrapperOrdered, style === 'ordered'); + }); + + /** + * Update the style in data + * + * @type {ListDataStyle} + */ + this.data.style = style; + } + + /** + * Returns current List item by the caret position + * + * @returns {Element} + */ + get currentItem(): Element | null { + const selection = window.getSelection(); + + if (!selection) { + return null; + } + let currentNode = selection.anchorNode; + + if (!currentNode) { + return null; + } + + if (!isHtmlElement(currentNode)) { + currentNode = currentNode.parentNode; + } + if (!currentNode) { + return null; + } + if (!isHtmlElement(currentNode)) { + return null; + } + + return currentNode.closest(`.${this.CSS.item}`); + } + + /** + * Handles Enter keypress + * + * @param {KeyboardEvent} event - keydown + * @returns {void} + */ + enterPressed(event: KeyboardEvent): void { + const currentItem = this.currentItem; + + /** + * Prevent editor.js behaviour + */ + event.stopPropagation(); + + /** + * Prevent browser behaviour + */ + event.preventDefault(); + + /** + * Prevent duplicated event in Chinese, Japanese and Korean languages + */ + if (event.isComposing) { + return; + } + + /** + * On Enter in the last empty item, get out of list + */ + const isEmpty = currentItem + ? this.getItemContent(currentItem).trim().length === 0 + : true; + const isFirstLevelItem = currentItem?.parentNode === this.nodes.wrapper; + const isLastItem = currentItem?.nextElementSibling === null; + + if (isFirstLevelItem && isLastItem && isEmpty) { + this.getOutOfList(); + + return; + } else if (isLastItem && isEmpty) { + this.unshiftItem(); + + return; + } + + /** + * On other Enters, get content from caret till the end of the block + * And move it to the new item + */ + const endingFragment = Caret.extractFragmentFromCaretPositionTillTheEnd(); + if (!endingFragment) { + return; + } + const endingHTML = Dom.fragmentToString(endingFragment); + const itemChildren = currentItem?.querySelector( + `.${this.CSS.itemChildren}` + ); + + /** + * Create the new list item + */ + const itemEl = this.createItem(endingHTML, undefined); + + /** + * Check if child items exist + * + * @type {boolean} + */ + const childrenExist = + itemChildren && + Array.from(itemChildren.querySelectorAll(`.${this.CSS.item}`)).length > 0; + + /** + * If item has children, prepend to them + * Otherwise, insert the new item after current + */ + if (childrenExist) { + itemChildren.prepend(itemEl); + } else { + currentItem?.after(itemEl); + } + + this.focusItem(itemEl); + } + + /** + * Decrease indentation of the current item + * + * @returns {void} + */ + unshiftItem(): void { + const currentItem = this.currentItem; + if (!currentItem) { + return; + } + if (!currentItem.parentNode) { + return; + } + if (!isHtmlElement(currentItem.parentNode)) { + return; + } + + const parentItem = currentItem.parentNode.closest(`.${this.CSS.item}`); + + /** + * If item in the first-level list then no need to do anything + */ + if (!parentItem) { + return; + } + + this.caret.save(); + + parentItem.after(currentItem); + + this.caret.restore(); + + /** + * If previous parent's children list is now empty, remove it. + */ + const prevParentChildrenList = parentItem.querySelector( + `.${this.CSS.itemChildren}` + ); + if (!prevParentChildrenList) { + return; + } + const isPrevParentChildrenEmpty = + prevParentChildrenList.children.length === 0; + + if (isPrevParentChildrenEmpty) { + prevParentChildrenList.remove(); + } + } + + /** + * Return the item content + * + * @param {Element} item - item wrapper (
      1. ) + * @returns {string} + */ + getItemContent(item: Element): string { + const contentNode = item.querySelector(`.${this.CSS.itemContent}`); + if (!contentNode) { + return ''; + } + + if (Dom.isEmpty(contentNode)) { + return ''; + } + + return contentNode.innerHTML; + } + + /** + * Sets focus to the item's content + * + * @param {Element} item - item (
      2. ) to select + * @param {boolean} atStart - where to set focus: at the start or at the end + * @returns {void} + */ + focusItem(item: Element, atStart: boolean = true): void { + const itemContent = item.querySelector( + `.${this.CSS.itemContent}` + ); + if (!itemContent) { + return; + } + + Caret.focus(itemContent, atStart); + } + + /** + * Get out from List Tool by Enter on the empty last item + * + * @returns {void} + */ + getOutOfList(): void { + this.currentItem?.remove(); + + this.api.blocks.insert(); + this.api.caret.setToBlock(this.api.blocks.getCurrentBlockIndex()); + } + + /** + * Handle backspace + * + * @param {KeyboardEvent} event - keydown + */ + backspace(event: KeyboardEvent): void { + /** + * Caret is not at start of the item + * Then backspace button should remove letter as usual + */ + if (!Caret.isAtStart()) { + return; + } + + /** + * Prevent default backspace behaviour + */ + event.preventDefault(); + + const currentItem = this.currentItem; + if (!currentItem) { + return; + } + const previousItem = currentItem.previousSibling; + if (!currentItem.parentNode) { + return; + } + if (!isHtmlElement(currentItem.parentNode)) { + return; + } + const parentItem = currentItem.parentNode.closest(`.${this.CSS.item}`); + + /** + * Do nothing with the first item in the first-level list. + * No previous sibling means that this is the first item in the list. + * No parent item means that this is a first-level list. + * + * Before: + * 1. |Hello + * 2. World! + * + * After: + * 1. |Hello + * 2. World! + * + * If it this item and the while list is empty then editor.js should + * process this behaviour and remove the block completely + * + * Before: + * 1. | + * + * After: block has been removed + * + */ + if (!previousItem && !parentItem) { + return; + } + + // make sure previousItem is an HTMLElement + if (previousItem && !isHtmlElement(previousItem)) { + return; + } + + /** + * Prevent editor.js behaviour + */ + event.stopPropagation(); + + /** + * Lets compute the item which will be merged with current item text + */ + let targetItem: Element | null; + + /** + * If there is a previous item then we get a deepest item in its sublists + * + * Otherwise we will use the parent item + */ + if (previousItem) { + const childrenOfPreviousItem = previousItem.querySelectorAll( + `.${this.CSS.item}` + ); + + targetItem = Array.from(childrenOfPreviousItem).pop() || previousItem; + } else { + targetItem = parentItem; + } + + /** + * Get content from caret till the end of the block to move it to the new item + */ + const endingFragment = Caret.extractFragmentFromCaretPositionTillTheEnd(); + if (!endingFragment) { + return; + } + const endingHTML = Dom.fragmentToString(endingFragment); + + /** + * Get the target item content element + */ + if (!targetItem) { + return; + } + const targetItemContent = targetItem.querySelector( + `.${this.CSS.itemContent}` + ); + + /** + * Set a new place for caret + */ + if (!targetItemContent) { + return; + } + Caret.focus(targetItemContent, false); + + /** + * Save the caret position + */ + this.caret.save(); + + /** + * Update target item content by merging with current item html content + */ + targetItemContent.insertAdjacentHTML('beforeend', endingHTML); + + /** + * Get the sublist first-level items for current item + */ + let currentItemSublistItems: NodeListOf | Element[] = + currentItem.querySelectorAll( + `.${this.CSS.itemChildren} > .${this.CSS.item}` + ); + + /** + * Create an array from current item sublist items + */ + currentItemSublistItems = Array.from(currentItemSublistItems); + + /** + * Filter items for sublist first-level + * No need to move deeper items + */ + currentItemSublistItems = currentItemSublistItems.filter((node) => { + // make sure node.parentNode is an HTMLElement + if (!node.parentNode) { + return false; + } + if (!isHtmlElement(node.parentNode)) { + return false; + } + return node.parentNode.closest(`.${this.CSS.item}`) === currentItem; + }); + + /** + * Reverse the array to insert items + */ + currentItemSublistItems.reverse().forEach((item) => { + /** + * Check if we need to save the indent for current item children + * + * If this is the first item in the list then place its children to the same level as currentItem. + * Same as shift+tab for all of these children. + * + * If there is a previous sibling then place children right after target item + */ + if (!previousItem) { + /** + * The first item in the list + * + * Before: + * 1. Hello + * 1.1. |My + * 1.1.1. Wonderful + * 1.1.2. World + * + * After: + * 1. Hello|My + * 1.1. Wonderful + * 1.2. World + */ + currentItem.after(item); + } else { + /** + * Not the first item + * + * Before: + * 1. Hello + * 1.1. My + * 1.2. |Dear + * 1.2.1. Wonderful + * 1.2.2. World + * + * After: + * 1. Hello + * 1.1. My|Dear + * 1.2. Wonderful + * 1.3. World + */ + targetItem.after(item); + } + }); + + /** + * Remove current item element + */ + currentItem.remove(); + + /** + * Restore the caret position + */ + this.caret.restore(); + } + + /** + * Add indentation to current item + * + * @param {KeyboardEvent} event - keydown + */ + addTab(event: KeyboardEvent): void { + /** + * Prevent editor.js behaviour + */ + event.stopPropagation(); + + /** + * Prevent browser tab behaviour + */ + event.preventDefault(); + + const currentItem = this.currentItem; + if (!currentItem) { + return; + } + const prevItem = currentItem.previousSibling; + if (!prevItem) { + return; + } + if (!isHtmlElement(prevItem)) { + return; + } + const isFirstChild = !prevItem; + + /** + * In the first item we should not handle Tabs (because there is no parent item above) + */ + if (isFirstChild) { + return; + } + + const prevItemChildrenList = prevItem.querySelector( + `.${this.CSS.itemChildren}` + ); + + this.caret.save(); + + /** + * If prev item has child items, just append current to them + */ + if (prevItemChildrenList) { + prevItemChildrenList.appendChild(currentItem); + } else { + /** + * If prev item has no child items + * - Create and append children wrapper to the previous item + * - Append current item to it + */ + const sublistWrapper = this.makeListWrapper(undefined, [ + this.CSS.itemChildren, + ]); + const prevItemBody = prevItem.querySelector(`.${this.CSS.itemBody}`); + + sublistWrapper.appendChild(currentItem); + prevItemBody?.appendChild(sublistWrapper); + } + + this.caret.restore(); + } + + /** + * Reduce indentation for current item + * + * @param {KeyboardEvent} event - keydown + * @returns {void} + */ + shiftTab(event: KeyboardEvent): void { + /** + * Prevent editor.js behaviour + */ + event.stopPropagation(); + + /** + * Prevent browser tab behaviour + */ + event.preventDefault(); + + /** + * Move item from current list to parent list + */ + this.unshiftItem(); + } + + /** + * Convert from list to text for conversionConfig + * + * @param {ListData} data + * @returns {string} + */ + static joinRecursive(data: ListData | ListItem): string { + return data.items + .map((item) => `${item.content} ${NestedList.joinRecursive(item)}`) + .join(''); + } + + /** + * Convert from text to list with import and export list to text + */ + static get conversionConfig(): { + export: (data: ListData) => string; + import: (content: string) => ListData; + } { + return { + export: (data) => { + return NestedList.joinRecursive(data); + }, + import: (content) => { + return { + items: [ + { + content, + items: [], + }, + ], + style: 'unordered', + }; + }, + }; + } +} diff --git a/src/index.ts b/src/index.ts index f134d52a..8c5f16a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,46 +5,46 @@ import type { TunesMenuConfig, } from '@editorjs/editorjs/types/tools'; -import { isHtmlElement } from './utils/type-guards'; - import * as Dom from './utils/dom'; import Caret from './utils/caret'; import { IconListBulleted, IconListNumbered } from '@codexteam/icons'; import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/listParams'; +import Tabulator from './Tabulator'; /** * Build styles */ import './../styles/index.pcss'; -/** - * Constructor Params for Nested List Tool, use to pass initial data and settings - */ -export type NestedListParams = BlockToolConstructorOptions< - ListData, - NestedListConfig ->; - /** * CSS classes for the Nested List Tool */ interface NestedListCssClasses { - baseBlock: string; wrapper: string; wrapperOrdered: string; wrapperUnordered: string; + wrapperChecklist: string; item: string; itemBody: string; itemContent: string; itemChildren: string; settingsWrapper: string; - settingsButton: string; - settingsButtonActive: string; + itemChecked: string; + noHover: string; + checkbox: string; + checkboxContainer: string; } /** - * NestedList Tool for EditorJS + * Constructor Params for Nested List Tool, use to pass initial data and settings */ +export type NestedListParams = BlockToolConstructorOptions< + ListData, + NestedListConfig +>; + +type NestedListStyle = 'ordered' | 'unordered' | 'checklist'; + export default class NestedList { /** * Notify core that read-only mode is supported @@ -114,6 +114,8 @@ export default class NestedList { */ private caret: Caret; + private style: NestedListStyle; + /** * Render plugin`s main Element and fill it with saved data * @@ -138,8 +140,9 @@ export default class NestedList { /** * Set the default list style from the config. */ - this.defaultListStyle = - this.config?.defaultStyle === 'ordered' ? 'ordered' : 'unordered'; + this.defaultListStyle = 'checklist'; + + this.style = this.defaultListStyle; const initialData = { style: this.defaultListStyle, @@ -153,295 +156,11 @@ export default class NestedList { this.caret = new Caret(); } - /** - * Returns list tag with items - * - * @returns {Element} - * @public - */ - render(): Element { - this.nodes.wrapper = this.makeListWrapper(this.data.style, [ - this.CSS.baseBlock, - ]); - - // fill with data - if (this.data.items.length) { - this.appendItems(this.data.items, this.nodes.wrapper); - } else { - this.appendItems( - [ - { - content: '', - items: [], - }, - ], - this.nodes.wrapper - ); - } - - if (!this.readOnly) { - // detect keydown on the last item to escape List - this.nodes.wrapper.addEventListener( - 'keydown', - (event) => { - switch (event.key) { - case 'Enter': - this.enterPressed(event); - break; - case 'Backspace': - this.backspace(event); - break; - case 'Tab': - if (event.shiftKey) { - this.shiftTab(event); - } else { - this.addTab(event); - } - break; - } - }, - false - ); - } - - return this.nodes.wrapper; - } - - /** - * Creates Block Tune allowing to change the list style - * - * @public - * @returns {Array} - */ - renderSettings(): TunesMenuConfig { - const tunes = [ - { - name: 'unordered' as const, - label: this.api.i18n.t('Unordered'), - icon: IconListBulleted, - }, - { - name: 'ordered' as const, - label: this.api.i18n.t('Ordered'), - icon: IconListNumbered, - }, - ]; - - return tunes.map((tune) => ({ - name: tune.name, - icon: tune.icon, - label: tune.label, - isActive: this.data.style === tune.name, - closeOnActivate: true, - onActivate: () => { - this.listStyle = tune.name; - }, - })); - } - - /** - * On paste sanitzation config. Allow only tags that are allowed in the Tool. - * - * @returns {PasteConfig} - paste config. - */ - static get pasteConfig(): PasteConfig { - return { - tags: ['OL', 'UL', 'LI'], - }; - } - - /** - * On paste callback that is fired from Editor. - * - * @param {PasteEvent} event - event with pasted data - */ - onPaste(event: PasteEvent): void { - const list = event.detail.data; - - this.data = this.pasteHandler(list); - - // render new list - const oldView = this.nodes.wrapper; - - if (oldView && oldView.parentNode) { - oldView.parentNode.replaceChild(this.render(), oldView); - } - } - - /** - * Handle UL, OL and LI tags paste and returns List data - * - * @param {HTMLUListElement|HTMLOListElement|HTMLLIElement} element - * @returns {ListData} - */ - pasteHandler(element: PasteEvent['detail']['data']): ListData { - const { tagName: tag } = element; - let style: ListDataStyle = 'unordered'; - let tagToSearch: string; - - // set list style and tag to search. - switch (tag) { - case 'OL': - style = 'ordered'; - tagToSearch = 'ol'; - break; - case 'UL': - case 'LI': - style = 'unordered'; - tagToSearch = 'ul'; - } - - const data: ListData = { - style, - items: [], - }; - - // get pasted items from the html. - const getPastedItems = (parent: Element): ListItem[] => { - // get first level li elements. - const children = Array.from(parent.querySelectorAll(`:scope > li`)); - - return children.map((child) => { - // get subitems if they exist. - const subItemsWrapper = child.querySelector(`:scope > ${tagToSearch}`); - // get subitems. - const subItems = subItemsWrapper ? getPastedItems(subItemsWrapper) : []; - // get text content of the li element. - const content = child?.firstChild?.textContent || ''; - - return { - content, - items: subItems, - }; - }); - }; - - // get pasted items. - data.items = getPastedItems(element); - - return data; - } - - /** - * Renders children list - * - * @param {ListItem[]} items - items data to append - * @param {Element} parentItem - where to append - * @returns {void} - */ - appendItems(items: ListItem[], parentItem: Element): void { - items.forEach((item) => { - const itemEl = this.createItem(item.content, item.items); - - parentItem.appendChild(itemEl); - }); - } - - /** - * Renders the single item - * - * @param {string} content - item content to render - * @param {ListItem[]} [items] - children - * @returns {Element} - */ - createItem(content: string, items: ListItem[] = []): Element { - const itemWrapper = Dom.make('li', this.CSS.item); - const itemBody = Dom.make('div', this.CSS.itemBody); - const itemContent = Dom.make('div', this.CSS.itemContent, { - innerHTML: content, - contentEditable: (!this.readOnly).toString(), - }); - - itemBody.appendChild(itemContent); - itemWrapper.appendChild(itemBody); - - /** - * Append children if we have some - */ - if (items && items.length > 0) { - this.addChildrenList(itemWrapper, items); - } + render() { + const list = new Tabulator(this.data, this.style, this.config); + const rendered = list.render(); - return itemWrapper; - } - - /** - * Extracts tool's data from the DOM - * - * @returns {ListData} - */ - save(): ListData { - /** - * The method for recursive collecting of the child items - * - * @param {Element} parent - where to find items - * @returns {ListItem[]} - */ - const getItems = (parent: Element): ListItem[] => { - const children = Array.from( - parent.querySelectorAll(`:scope > .${this.CSS.item}`) - ); - - return children.map((el) => { - const subItemsWrapper = el.querySelector(`.${this.CSS.itemChildren}`); - const content = this.getItemContent(el); - const subItems = subItemsWrapper ? getItems(subItemsWrapper) : []; - - return { - content, - items: subItems, - }; - }); - }; - - return { - style: this.data.style, - items: this.nodes.wrapper ? getItems(this.nodes.wrapper) : [], - }; - } - - /** - * Append children list to passed item - * - * @param {Element} parentItem - item that should contain passed sub-items - * @param {ListItem[]} items - sub items to append - */ - addChildrenList(parentItem: Element, items: ListItem[]): void { - const itemBody = parentItem.querySelector(`.${this.CSS.itemBody}`); - const sublistWrapper = this.makeListWrapper(undefined, [ - this.CSS.itemChildren, - ]); - - this.appendItems(items, sublistWrapper); - - if (!itemBody) { - return; - } - - itemBody.appendChild(sublistWrapper); - } - - /** - * Creates main
          or
            tag depended on style - * - * @param {string} [style] - 'ordered' or 'unordered' - * @param {string[]} [classes] - additional classes to append - * @returns {HTMLOListElement|HTMLUListElement} - */ - makeListWrapper( - style: string = this.listStyle, - classes: string[] = [] - ): HTMLOListElement | HTMLUListElement { - const tag = style === 'ordered' ? 'ol' : 'ul'; - const styleClass = - style === 'ordered' ? this.CSS.wrapperOrdered : this.CSS.wrapperUnordered; - - classes.push(styleClass); - - // since tag is either 'ol' or 'ul' we can safely cast it to HTMLOListElement | HTMLUListElement - return Dom.make(tag, [this.CSS.wrapper, ...classes]) as - | HTMLOListElement - | HTMLUListElement; + return rendered; } /** @@ -452,619 +171,19 @@ export default class NestedList { */ get CSS(): NestedListCssClasses { return { - baseBlock: this.api.styles.block, wrapper: 'cdx-nested-list', wrapperOrdered: 'cdx-nested-list--ordered', wrapperUnordered: 'cdx-nested-list--unordered', + wrapperChecklist: 'cdx-nested-list--checklist', item: 'cdx-nested-list__item', itemBody: 'cdx-nested-list__item-body', itemContent: 'cdx-nested-list__item-content', itemChildren: 'cdx-nested-list__item-children', settingsWrapper: 'cdx-nested-list__settings', - settingsButton: this.api.styles.settingsButton, - settingsButtonActive: this.api.styles.settingsButtonActive, - }; - } - - /** - * Get list style name - * - * @returns {string} - */ - get listStyle(): string { - return this.data.style || this.defaultListStyle; - } - - /** - * Set list style - * - * @param {ListDataStyle} style - new style to set - */ - set listStyle(style: ListDataStyle) { - if (!this.nodes) { - return; - } - if (!this.nodes.wrapper) { - return; - } - /** - * Get lists elements - * - * @type {Element[]} - */ - const lists: Element[] = Array.from( - this.nodes.wrapper.querySelectorAll(`.${this.CSS.wrapper}`) - ); - - /** - * Add main wrapper to the list - */ - lists.push(this.nodes.wrapper); - - /** - * For each list we need to update classes - */ - lists.forEach((list) => { - list.classList.toggle(this.CSS.wrapperUnordered, style === 'unordered'); - list.classList.toggle(this.CSS.wrapperOrdered, style === 'ordered'); - }); - - /** - * Update the style in data - * - * @type {ListDataStyle} - */ - this.data.style = style; - } - - /** - * Returns current List item by the caret position - * - * @returns {Element} - */ - get currentItem(): Element | null { - const selection = window.getSelection(); - - if (!selection) { - return null; - } - let currentNode = selection.anchorNode; - - if (!currentNode) { - return null; - } - - if (!isHtmlElement(currentNode)) { - currentNode = currentNode.parentNode; - } - if (!currentNode) { - return null; - } - if (!isHtmlElement(currentNode)) { - return null; - } - - return currentNode.closest(`.${this.CSS.item}`); - } - - /** - * Handles Enter keypress - * - * @param {KeyboardEvent} event - keydown - * @returns {void} - */ - enterPressed(event: KeyboardEvent): void { - const currentItem = this.currentItem; - - /** - * Prevent editor.js behaviour - */ - event.stopPropagation(); - - /** - * Prevent browser behaviour - */ - event.preventDefault(); - - /** - * Prevent duplicated event in Chinese, Japanese and Korean languages - */ - if (event.isComposing) { - return; - } - - /** - * On Enter in the last empty item, get out of list - */ - const isEmpty = currentItem - ? this.getItemContent(currentItem).trim().length === 0 - : true; - const isFirstLevelItem = currentItem?.parentNode === this.nodes.wrapper; - const isLastItem = currentItem?.nextElementSibling === null; - - if (isFirstLevelItem && isLastItem && isEmpty) { - this.getOutOfList(); - - return; - } else if (isLastItem && isEmpty) { - this.unshiftItem(); - - return; - } - - /** - * On other Enters, get content from caret till the end of the block - * And move it to the new item - */ - const endingFragment = Caret.extractFragmentFromCaretPositionTillTheEnd(); - if (!endingFragment) { - return; - } - const endingHTML = Dom.fragmentToString(endingFragment); - const itemChildren = currentItem?.querySelector( - `.${this.CSS.itemChildren}` - ); - - /** - * Create the new list item - */ - const itemEl = this.createItem(endingHTML, undefined); - - /** - * Check if child items exist - * - * @type {boolean} - */ - const childrenExist = - itemChildren && - Array.from(itemChildren.querySelectorAll(`.${this.CSS.item}`)).length > 0; - - /** - * If item has children, prepend to them - * Otherwise, insert the new item after current - */ - if (childrenExist) { - itemChildren.prepend(itemEl); - } else { - currentItem?.after(itemEl); - } - - this.focusItem(itemEl); - } - - /** - * Decrease indentation of the current item - * - * @returns {void} - */ - unshiftItem(): void { - const currentItem = this.currentItem; - if (!currentItem) { - return; - } - if (!currentItem.parentNode) { - return; - } - if (!isHtmlElement(currentItem.parentNode)) { - return; - } - - const parentItem = currentItem.parentNode.closest(`.${this.CSS.item}`); - - /** - * If item in the first-level list then no need to do anything - */ - if (!parentItem) { - return; - } - - this.caret.save(); - - parentItem.after(currentItem); - - this.caret.restore(); - - /** - * If previous parent's children list is now empty, remove it. - */ - const prevParentChildrenList = parentItem.querySelector( - `.${this.CSS.itemChildren}` - ); - if (!prevParentChildrenList) { - return; - } - const isPrevParentChildrenEmpty = - prevParentChildrenList.children.length === 0; - - if (isPrevParentChildrenEmpty) { - prevParentChildrenList.remove(); - } - } - - /** - * Return the item content - * - * @param {Element} item - item wrapper (
          1. ) - * @returns {string} - */ - getItemContent(item: Element): string { - const contentNode = item.querySelector(`.${this.CSS.itemContent}`); - if (!contentNode) { - return ''; - } - - if (Dom.isEmpty(contentNode)) { - return ''; - } - - return contentNode.innerHTML; - } - - /** - * Sets focus to the item's content - * - * @param {Element} item - item (
          2. ) to select - * @param {boolean} atStart - where to set focus: at the start or at the end - * @returns {void} - */ - focusItem(item: Element, atStart: boolean = true): void { - const itemContent = item.querySelector( - `.${this.CSS.itemContent}` - ); - if (!itemContent) { - return; - } - - Caret.focus(itemContent, atStart); - } - - /** - * Get out from List Tool by Enter on the empty last item - * - * @returns {void} - */ - getOutOfList(): void { - this.currentItem?.remove(); - - this.api.blocks.insert(); - this.api.caret.setToBlock(this.api.blocks.getCurrentBlockIndex()); - } - - /** - * Handle backspace - * - * @param {KeyboardEvent} event - keydown - */ - backspace(event: KeyboardEvent): void { - /** - * Caret is not at start of the item - * Then backspace button should remove letter as usual - */ - if (!Caret.isAtStart()) { - return; - } - - /** - * Prevent default backspace behaviour - */ - event.preventDefault(); - - const currentItem = this.currentItem; - if (!currentItem) { - return; - } - const previousItem = currentItem.previousSibling; - if (!currentItem.parentNode) { - return; - } - if (!isHtmlElement(currentItem.parentNode)) { - return; - } - const parentItem = currentItem.parentNode.closest(`.${this.CSS.item}`); - - /** - * Do nothing with the first item in the first-level list. - * No previous sibling means that this is the first item in the list. - * No parent item means that this is a first-level list. - * - * Before: - * 1. |Hello - * 2. World! - * - * After: - * 1. |Hello - * 2. World! - * - * If it this item and the while list is empty then editor.js should - * process this behaviour and remove the block completely - * - * Before: - * 1. | - * - * After: block has been removed - * - */ - if (!previousItem && !parentItem) { - return; - } - - // make sure previousItem is an HTMLElement - if (previousItem && !isHtmlElement(previousItem)) { - return; - } - - /** - * Prevent editor.js behaviour - */ - event.stopPropagation(); - - /** - * Lets compute the item which will be merged with current item text - */ - let targetItem: Element | null; - - /** - * If there is a previous item then we get a deepest item in its sublists - * - * Otherwise we will use the parent item - */ - if (previousItem) { - const childrenOfPreviousItem = previousItem.querySelectorAll( - `.${this.CSS.item}` - ); - - targetItem = Array.from(childrenOfPreviousItem).pop() || previousItem; - } else { - targetItem = parentItem; - } - - /** - * Get content from caret till the end of the block to move it to the new item - */ - const endingFragment = Caret.extractFragmentFromCaretPositionTillTheEnd(); - if (!endingFragment) { - return; - } - const endingHTML = Dom.fragmentToString(endingFragment); - - /** - * Get the target item content element - */ - if (!targetItem) { - return; - } - const targetItemContent = targetItem.querySelector( - `.${this.CSS.itemContent}` - ); - - /** - * Set a new place for caret - */ - if (!targetItemContent) { - return; - } - Caret.focus(targetItemContent, false); - - /** - * Save the caret position - */ - this.caret.save(); - - /** - * Update target item content by merging with current item html content - */ - targetItemContent.insertAdjacentHTML('beforeend', endingHTML); - - /** - * Get the sublist first-level items for current item - */ - let currentItemSublistItems: NodeListOf | Element[] = - currentItem.querySelectorAll( - `.${this.CSS.itemChildren} > .${this.CSS.item}` - ); - - /** - * Create an array from current item sublist items - */ - currentItemSublistItems = Array.from(currentItemSublistItems); - - /** - * Filter items for sublist first-level - * No need to move deeper items - */ - currentItemSublistItems = currentItemSublistItems.filter((node) => { - // make sure node.parentNode is an HTMLElement - if (!node.parentNode) { - return false; - } - if (!isHtmlElement(node.parentNode)) { - return false; - } - return node.parentNode.closest(`.${this.CSS.item}`) === currentItem; - }); - - /** - * Reverse the array to insert items - */ - currentItemSublistItems.reverse().forEach((item) => { - /** - * Check if we need to save the indent for current item children - * - * If this is the first item in the list then place its children to the same level as currentItem. - * Same as shift+tab for all of these children. - * - * If there is a previous sibling then place children right after target item - */ - if (!previousItem) { - /** - * The first item in the list - * - * Before: - * 1. Hello - * 1.1. |My - * 1.1.1. Wonderful - * 1.1.2. World - * - * After: - * 1. Hello|My - * 1.1. Wonderful - * 1.2. World - */ - currentItem.after(item); - } else { - /** - * Not the first item - * - * Before: - * 1. Hello - * 1.1. My - * 1.2. |Dear - * 1.2.1. Wonderful - * 1.2.2. World - * - * After: - * 1. Hello - * 1.1. My|Dear - * 1.2. Wonderful - * 1.3. World - */ - targetItem.after(item); - } - }); - - /** - * Remove current item element - */ - currentItem.remove(); - - /** - * Restore the caret position - */ - this.caret.restore(); - } - - /** - * Add indentation to current item - * - * @param {KeyboardEvent} event - keydown - */ - addTab(event: KeyboardEvent): void { - /** - * Prevent editor.js behaviour - */ - event.stopPropagation(); - - /** - * Prevent browser tab behaviour - */ - event.preventDefault(); - - const currentItem = this.currentItem; - if (!currentItem) { - return; - } - const prevItem = currentItem.previousSibling; - if (!prevItem) { - return; - } - if (!isHtmlElement(prevItem)) { - return; - } - const isFirstChild = !prevItem; - - /** - * In the first item we should not handle Tabs (because there is no parent item above) - */ - if (isFirstChild) { - return; - } - - const prevItemChildrenList = prevItem.querySelector( - `.${this.CSS.itemChildren}` - ); - - this.caret.save(); - - /** - * If prev item has child items, just append current to them - */ - if (prevItemChildrenList) { - prevItemChildrenList.appendChild(currentItem); - } else { - /** - * If prev item has no child items - * - Create and append children wrapper to the previous item - * - Append current item to it - */ - const sublistWrapper = this.makeListWrapper(undefined, [ - this.CSS.itemChildren, - ]); - const prevItemBody = prevItem.querySelector(`.${this.CSS.itemBody}`); - - sublistWrapper.appendChild(currentItem); - prevItemBody?.appendChild(sublistWrapper); - } - - this.caret.restore(); - } - - /** - * Reduce indentation for current item - * - * @param {KeyboardEvent} event - keydown - * @returns {void} - */ - shiftTab(event: KeyboardEvent): void { - /** - * Prevent editor.js behaviour - */ - event.stopPropagation(); - - /** - * Prevent browser tab behaviour - */ - event.preventDefault(); - - /** - * Move item from current list to parent list - */ - this.unshiftItem(); - } - - /** - * Convert from list to text for conversionConfig - * - * @param {ListData} data - * @returns {string} - */ - static joinRecursive(data: ListData | ListItem): string { - return data.items - .map((item) => `${item.content} ${NestedList.joinRecursive(item)}`) - .join(''); - } - - /** - * Convert from text to list with import and export list to text - */ - static get conversionConfig(): { - export: (data: ListData) => string; - import: (content: string) => ListData; - } { - return { - export: (data) => { - return NestedList.joinRecursive(data); - }, - import: (content) => { - return { - items: [ - { - content, - items: [], - }, - ], - style: 'unordered', - }; - }, + itemChecked: 'cdx-nested-list__item--checked', + noHover: 'cdx-nested-list__item-checkbox--no-hover', + checkbox: 'cdx-nested-list__item-checkbox-check', + checkboxContainer: 'cdx-nested-list__item-checkbox' }; } } diff --git a/src/types/listParams.ts b/src/types/listParams.ts index 196791b7..f691c4aa 100644 --- a/src/types/listParams.ts +++ b/src/types/listParams.ts @@ -1,7 +1,7 @@ /** * list style to make list as ordered or unordered */ -export type ListDataStyle = 'ordered' | 'unordered'; +export type ListDataStyle = 'ordered' | 'unordered' | 'checklist'; /** * Output data diff --git a/styles/index.pcss b/styles/index.pcss index 86b5cda2..b872b06c 100644 --- a/styles/index.pcss +++ b/styles/index.pcss @@ -4,11 +4,21 @@ outline: none; counter-reset: item; list-style: none; + --radius-border: 5px; + --checkbox-background: #fff; + --color-border: #C9C9C9; + --color-bg-checked: #369FFF; + --line-height: 1.57em; + --color-bg-checked-hover: #0059AB; + --color-tick: #fff; + --width-checkbox: 22px; + --height-checkbox: 22px; &__item { line-height: 1.6em; display: flex; margin: 2px 0; + display: flex; [contenteditable]{ outline: none; @@ -139,6 +149,10 @@ content: "•"; } + &--checklist > &__item::before { + content: ""; + } + &__settings { display: flex; diff --git a/yarn.lock b/yarn.lock index 7d6a7796..95902792 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25,10 +25,10 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== -"@codexteam/icons@^0.0.2": - version "0.0.2" - resolved "https://registry.yarnpkg.com/@codexteam/icons/-/icons-0.0.2.tgz#9183996a38b75a93506890373a015e3a2a369264" - integrity sha512-KdeKj3TwaTHqM3IXd5YjeJP39PBUZTb+dtHjGlf5+b0VgsxYD4qzsZkb11lzopZbAuDsHaZJmAYQ8LFligIT6Q== +"@codexteam/icons@^0.3.2": + 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 d46b8dda790a6fbff73ac3dcd0b9389eee2d5592 Mon Sep 17 00:00:00 2001 From: e11sy Date: Fri, 2 Aug 2024 20:53:28 +0300 Subject: [PATCH 11/43] list types switch implemented --- index.html | 2 +- src/ListRenderer/checklistRenderer.ts | 6 + src/ListRenderer/listRenderer.ts | 2 +- src/ListRenderer/orderedListRenderer.ts | 10 +- src/ListRenderer/unorderedListRenderer.ts | 15 +- src/Tabulator/index.ts | 90 ++++++++---- src/index.ts | 168 ++++++++++++++++------ src/types/icons.d.ts | 2 +- 8 files changed, 213 insertions(+), 82 deletions(-) diff --git a/index.html b/index.html index 700f9f03..08ffa742 100644 --- a/index.html +++ b/index.html @@ -258,7 +258,7 @@ ] }, ], - style: 'ordered' + style: 'checklist' } }, { diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index 566d0d11..a45583aa 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -29,6 +29,12 @@ export class CheckListRenderer extends ListRenderer { return Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; } + renderSublistWrapper(): HTMLElement { + const divElement = Dom.make('ul', [this.CSS.wrapperChecklist, this.CSS.itemChildren]) as HTMLElement; + + return divElement; + } + /** * Redners list item element * @param content - content of the list item diff --git a/src/ListRenderer/listRenderer.ts b/src/ListRenderer/listRenderer.ts index 9050404b..b06ec684 100644 --- a/src/ListRenderer/listRenderer.ts +++ b/src/ListRenderer/listRenderer.ts @@ -28,7 +28,7 @@ export abstract class ListRenderer { * @returns {NestedListCssClasses} - CSS classes names by keys * @private */ - protected get CSS(): NestedListCssClasses { + get CSS(): NestedListCssClasses { return { wrapper: 'cdx-nested-list', wrapperOrdered: 'cdx-nested-list--ordered', diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts index 1f4210e3..d9e6677a 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/orderedListRenderer.ts @@ -27,6 +27,12 @@ export class OrderedListRenderer extends ListRenderer { return Dom.make('ol', [this.CSS.wrapper, ...classes]) as HTMLOListElement; } + renderSublistWrapper(): HTMLElement { + const divElement = Dom.make('ol', [this.CSS.wrapperOrdered, this.CSS.itemChildren]) as HTMLElement; + + return divElement; + } + /** * Redners list item element * @param content - content of the list item @@ -39,13 +45,9 @@ export class OrderedListRenderer extends ListRenderer { innerHTML: content, }); - console.log(itemContent, itemContent instanceof Node); itemBody.appendChild(itemContent); - console.log(itemBody, itemBody instanceof Node); itemWrapper.appendChild(itemBody); - console.log('created item wrapper', itemWrapper, typeof itemWrapper) - return itemWrapper as HTMLLIElement; } diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts index 2c66d7ed..71183281 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -22,11 +22,17 @@ export class UnorderedListRenderer extends ListRenderer { * @returns - created html ol element */ renderWrapper(classes: string[] = []): HTMLOListElement { - classes.push(this.CSS.wrapperOrdered); + classes.push(this.CSS.wrapperUnordered); - const olElement = Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; + const ulElement = Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; - return olElement; + return ulElement; + } + + renderSublistWrapper(): HTMLElement { + const divElement = Dom.make('ul', [this.CSS.wrapperUnordered, this.CSS.itemChildren]) as HTMLElement; + + return divElement; } /** @@ -41,10 +47,7 @@ export class UnorderedListRenderer extends ListRenderer { innerHTML: content, }); - - console.log(itemContent, itemContent instanceof Node); itemBody.appendChild(itemContent); - console.log(itemBody, itemBody instanceof Node); itemWrapper.appendChild(itemBody); return itemWrapper as HTMLLIElement; diff --git a/src/Tabulator/index.ts b/src/Tabulator/index.ts index eaaf1f52..dfa922e0 100644 --- a/src/Tabulator/index.ts +++ b/src/Tabulator/index.ts @@ -27,6 +27,16 @@ export default class Tabulator { */ data: ListData; + /** + * Rendered list of items + */ + list: ListRendererTypes | undefined; + + /** + * Wrapper of the whole list + */ + listWrapper: HTMLElement | undefined; + constructor(data: ListData, style: NestedListStyle, config?: NestedListConfig) { this.config = config; this.data = data; @@ -34,38 +44,38 @@ export default class Tabulator { } render() { - let list; - - console.log('style', this.style) + console.log(this.style); switch (this.style) { case 'ordered': - list = new OrderedListRenderer(this.config); + this.list = new OrderedListRenderer(this.config); + break case 'unordered': - list = new UnorderedListRenderer(this.config); + this.list = new UnorderedListRenderer(this.config); + break case 'checklist': - list = new CheckListRenderer(this.config); + this.list = new CheckListRenderer(this.config); + break } - const listWrapper = list.renderWrapper(); + this.listWrapper = this.list.renderWrapper(); // fill with data if (this.data.items.length) { - this.appendItems(list, this.data.items, listWrapper); + this.appendItems(this.data.items, this.listWrapper); } else { this.appendItems( - list, [ { content: '', items: [], }, ], - listWrapper, + this.listWrapper, ); } - return listWrapper; + return this.listWrapper; } /** @@ -76,18 +86,50 @@ export default class Tabulator { * @param {Element} parentItem - where to append * @returns {void} */ - appendItems(list: ListRendererTypes, items: ListItem[], parentItem: Element): void { - items.forEach((item) => { - const itemEl = list.renderItem(item.content); - - console.log('rendered item', itemEl, itemEl instanceof HTMLLIElement) - - if (itemEl instanceof Node) { - console.log(parentItem instanceof Node, itemEl instanceof Node); - parentItem.appendChild(itemEl); - } else { - console.log(itemEl) - } - }); + appendItems(items: ListItem[], parentItem: Element): void { + if (this.list !== undefined) { + items.forEach((item) => { + const itemEl = this.list?.renderItem(item.content); + + parentItem.appendChild(itemEl!); + + if (item.items.length) { + const sublistWrapper = this.list?.renderSublistWrapper() + this.appendItems(item.items, sublistWrapper!); + + parentItem.appendChild(sublistWrapper!); + } + }); + } + } + + save(): ListData { + /** + * The method for recursive collecting of the child items + * + * @param {Element} parent - where to find items + * @returns {ListItem[]} + */ + const getItems = (parent: Element): ListItem[] => { + const children = Array.from( + parent.querySelectorAll(`:scope > .cdx-nested-list__item`) + ); + + return children.map((el) => { + const subItemsWrapper = el.querySelector(`.cdx-nested-list__item-children`); + const content = this.list!.getItemContent(el); + const subItems = subItemsWrapper ? getItems(subItemsWrapper) : []; + + return { + content, + items: subItems, + }; + }); + }; + + return { + style: this.data.style, + items: this.listWrapper ? getItems(this.listWrapper) : [], + }; } } diff --git a/src/index.ts b/src/index.ts index 8c5f16a9..8daf8d21 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import type { import * as Dom from './utils/dom'; import Caret from './utils/caret'; -import { IconListBulleted, IconListNumbered } from '@codexteam/icons'; +import { IconListBulleted, IconListNumbered, IconChecklist } from '@codexteam/icons'; import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/listParams'; import Tabulator from './Tabulator'; @@ -16,25 +16,6 @@ import Tabulator from './Tabulator'; */ import './../styles/index.pcss'; -/** - * CSS classes for the Nested List Tool - */ -interface NestedListCssClasses { - wrapper: string; - wrapperOrdered: string; - wrapperUnordered: string; - wrapperChecklist: string; - item: string; - itemBody: string; - itemContent: string; - itemChildren: string; - settingsWrapper: string; - itemChecked: string; - noHover: string; - checkbox: string; - checkboxContainer: string; -} - /** * Constructor Params for Nested List Tool, use to pass initial data and settings */ @@ -43,7 +24,6 @@ export type NestedListParams = BlockToolConstructorOptions< NestedListConfig >; -type NestedListStyle = 'ordered' | 'unordered' | 'checklist'; export default class NestedList { /** @@ -79,6 +59,35 @@ export default class NestedList { }; } + /** + * Get list style name + * + * @returns {string} + */ + get listStyle(): ListDataStyle { + return this.data.style || this.defaultListStyle; + } + + /** + * Set list style + * + * @param {ListDataStyle} style - new style to set + */ + set listStyle(style: ListDataStyle) { + this.data.style = style; + + /** + * Rerender list item + */ + this.list = new Tabulator(this.data, this.listStyle, this.config); + + const newListElement = this.list.render() + + this.listElement?.replaceWith(newListElement); + + this.listElement = newListElement; + } + /** * The Editor.js API */ @@ -114,7 +123,13 @@ export default class NestedList { */ private caret: Caret; - private style: NestedListStyle; + list: Tabulator | undefined; + + /** + * Main constant wrapper of the whole list + */ + listElement: HTMLElement | undefined; + /** * Render plugin`s main Element and fill it with saved data @@ -140,14 +155,13 @@ export default class NestedList { /** * Set the default list style from the config. */ - this.defaultListStyle = 'checklist'; - - this.style = this.defaultListStyle; + this.defaultListStyle = 'ordered'; const initialData = { style: this.defaultListStyle, items: [], }; + this.data = data && Object.keys(data).length ? data : initialData; /** @@ -157,33 +171,97 @@ export default class NestedList { } render() { - const list = new Tabulator(this.data, this.style, this.config); - const rendered = list.render(); + this.list = new Tabulator(this.data, this.listStyle, this.config); + this.listElement = this.list.render(); + + return this.listElement; + } + + save() { + return this.list?.save(); + } + + /** + * Creates Block Tune allowing to change the list style + * + * @public + * @returns {Array} + */ + renderSettings(): TunesMenuConfig { + const tunes = [ + { + name: 'unordered' as const, + label: this.api.i18n.t('Unordered'), + icon: IconListBulleted, + }, + { + name: 'ordered' as const, + label: this.api.i18n.t('Ordered'), + icon: IconListNumbered, + }, + { + name: 'checklist' as const, + label: this.api.i18n.t('Checklist'), + icon: IconChecklist, + } + ]; - return rendered; + return tunes.map((tune) => ({ + name: tune.name, + icon: tune.icon, + label: tune.label, + isActive: this.data.style === tune.name, + closeOnActivate: true, + onActivate: () => { + this.listStyle = tune.name; + }, + })); + } + /** + * On paste sanitzation config. Allow only tags that are allowed in the Tool. + * + * @returns {PasteConfig} - paste config. + */ + static get pasteConfig(): PasteConfig { + return { + tags: ['OL', 'UL', 'LI'], + }; } /** - * Styles + * Convert from list to text for conversionConfig * - * @returns {NestedListCssClasses} - CSS classes names by keys - * @private + * @param {ListData} data + * @returns {string} + */ + static joinRecursive(data: ListData | ListItem): string { + return data.items + .map((item) => `${item.content} ${NestedList.joinRecursive(item)}`) + .join(''); + } + + /** + * Convert from text to list with import and export list to text */ - get CSS(): NestedListCssClasses { + static get conversionConfig(): { + export: (data: ListData) => string; + import: (content: string) => ListData; + } { return { - wrapper: 'cdx-nested-list', - wrapperOrdered: 'cdx-nested-list--ordered', - wrapperUnordered: 'cdx-nested-list--unordered', - wrapperChecklist: 'cdx-nested-list--checklist', - item: 'cdx-nested-list__item', - itemBody: 'cdx-nested-list__item-body', - itemContent: 'cdx-nested-list__item-content', - itemChildren: 'cdx-nested-list__item-children', - settingsWrapper: 'cdx-nested-list__settings', - itemChecked: 'cdx-nested-list__item--checked', - noHover: 'cdx-nested-list__item-checkbox--no-hover', - checkbox: 'cdx-nested-list__item-checkbox-check', - checkboxContainer: 'cdx-nested-list__item-checkbox' + export: (data) => { + return NestedList.joinRecursive(data); + }, + import: (content) => { + return { + items: [ + { + content, + items: [], + }, + ], + style: 'unordered', + }; + }, }; } } diff --git a/src/types/icons.d.ts b/src/types/icons.d.ts index 708e058b..5548abcc 100644 --- a/src/types/icons.d.ts +++ b/src/types/icons.d.ts @@ -2,6 +2,6 @@ declare module '@codexteam/icons' { export const IconListBulleted: string; export const IconListNumbered: string; - // export const IconCheckList: string + export const IconChecklist: string export const IconCheck: string; } From 5bae2c0e4b0846ea2b75893ff795a446cdf87a6b Mon Sep 17 00:00:00 2001 From: e11sy Date: Fri, 2 Aug 2024 22:04:04 +0300 Subject: [PATCH 12/43] content is editable --- index.html | 5 +- src/ListRenderer/checklistRenderer.ts | 36 +- src/ListRenderer/listRenderer.ts | 2 +- src/ListRenderer/orderedListRenderer.ts | 20 +- src/ListRenderer/unorderedListRenderer.ts | 20 +- src/Tabulator/index.ts | 637 +++++++++++++++++++++- src/index.ts | 30 +- 7 files changed, 689 insertions(+), 61 deletions(-) diff --git a/index.html b/index.html index 08ffa742..a8b5b765 100644 --- a/index.html +++ b/index.html @@ -124,7 +124,10 @@ nestedList: { class: NestedList, inlineToolbar: true, - shortcut: 'CMD+SHIFT+L' + shortcut: 'CMD+SHIFT+L', + config: { + defaultStyle: 'checklist' + } }, // list: { diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index a45583aa..678bf574 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -13,9 +13,12 @@ export class CheckListRenderer extends ListRenderer { */ protected config?: NestedListConfig; - constructor(config?: NestedListConfig) { + readOnly: boolean; + + constructor(readonly: boolean, config?: NestedListConfig) { super(); this.config = config; + this.readOnly = readonly; } /** @@ -24,13 +27,13 @@ export class CheckListRenderer extends ListRenderer { * @returns - created html ol element */ renderWrapper(classes: string[] = []): HTMLOListElement { - classes.push(this.CSS.wrapperChecklist); + classes.push(ListRenderer.CSS.wrapperChecklist); - return Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; + return Dom.make('ul', [ListRenderer.CSS.wrapper, ...classes]) as HTMLOListElement; } renderSublistWrapper(): HTMLElement { - const divElement = Dom.make('ul', [this.CSS.wrapperChecklist, this.CSS.itemChildren]) as HTMLElement; + const divElement = Dom.make('ul', [ListRenderer.CSS.wrapperChecklist, ListRenderer.CSS.itemChildren]) as HTMLElement; return divElement; } @@ -41,14 +44,15 @@ export class CheckListRenderer extends ListRenderer { * @returns - created html list item element */ renderItem(content: string): HTMLLIElement { - const itemWrapper = Dom.make('li', [this.CSS.item, this.CSS.item]); - const itemBody = Dom.make('div', this.CSS.itemBody); - const itemContent = Dom.make('div', this.CSS.itemContent, { + const itemWrapper = Dom.make('li', [ListRenderer.CSS.item, ListRenderer.CSS.item]); + const itemBody = Dom.make('div', ListRenderer.CSS.itemBody); + const itemContent = Dom.make('div', ListRenderer.CSS.itemContent, { innerHTML: content, + contentEditable: (!this.readOnly).toString(), }); - const checkbox = Dom.make('span', this.CSS.checkbox); - const checkboxContainer = Dom.make('div', this.CSS.checkboxContainer); + const checkbox = Dom.make('span', ListRenderer.CSS.checkbox); + const checkboxContainer = Dom.make('div', ListRenderer.CSS.checkboxContainer); checkbox.innerHTML = IconCheck; checkboxContainer.appendChild(checkbox); @@ -71,7 +75,7 @@ export class CheckListRenderer extends ListRenderer { * @returns {string} */ getItemContent(item: Element): string { - const contentNode = item.querySelector(`.${this.CSS.itemContent}`); + const contentNode = item.querySelector(`.${ListRenderer.CSS.itemContent}`); if (!contentNode) { return ''; } @@ -85,7 +89,7 @@ export class CheckListRenderer extends ListRenderer { getItemMeta(item: HTMLElement): ItemMeta { return { - checked: item.classList.contains(this.CSS.itemChecked) + checked: item.classList.contains(ListRenderer.CSS.itemChecked) } } @@ -97,12 +101,12 @@ export class CheckListRenderer extends ListRenderer { * @returns {void} */ private toggleCheckbox(event: any): void { - const checkListItem = event.target.closest(`.${this.CSS.item}`); - const checkbox = checkListItem.querySelector(`.${this.CSS.checkboxContainer}`); + const checkListItem = event.target.closest(`.${ListRenderer.CSS.item}`); + const checkbox = checkListItem.querySelector(`.${ListRenderer.CSS.checkboxContainer}`); if (checkbox.contains(event.target)) { - checkListItem.classList.toggle(this.CSS.itemChecked); - checkbox.classList.add(this.CSS.noHover); + checkListItem.classList.toggle(ListRenderer.CSS.itemChecked); + checkbox.classList.add(ListRenderer.CSS.noHover); checkbox.addEventListener('mouseleave', () => this.removeSpecialHoverBehavior(checkbox), { once: true }); } } @@ -115,7 +119,7 @@ export class CheckListRenderer extends ListRenderer { * @returns {Element} */ private removeSpecialHoverBehavior(el: HTMLElement) { - el.classList.remove(this.CSS.noHover); + el.classList.remove(ListRenderer.CSS.noHover); } } diff --git a/src/ListRenderer/listRenderer.ts b/src/ListRenderer/listRenderer.ts index b06ec684..4add3afd 100644 --- a/src/ListRenderer/listRenderer.ts +++ b/src/ListRenderer/listRenderer.ts @@ -28,7 +28,7 @@ export abstract class ListRenderer { * @returns {NestedListCssClasses} - CSS classes names by keys * @private */ - get CSS(): NestedListCssClasses { + static get CSS(): NestedListCssClasses { return { wrapper: 'cdx-nested-list', wrapperOrdered: 'cdx-nested-list--ordered', diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts index d9e6677a..e5110348 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/orderedListRenderer.ts @@ -11,9 +11,12 @@ export class OrderedListRenderer extends ListRenderer { */ protected config?: NestedListConfig; - constructor(config?: NestedListConfig) { + readOnly: boolean; + + constructor(readonly: boolean, config?: NestedListConfig) { super(); this.config = config; + this.readOnly = readonly; } /** @@ -22,13 +25,13 @@ export class OrderedListRenderer extends ListRenderer { * @returns - created html ol element */ renderWrapper(classes: string[] = []): HTMLOListElement { - classes.push(this.CSS.wrapperOrdered); + classes.push(ListRenderer.CSS.wrapperOrdered); - return Dom.make('ol', [this.CSS.wrapper, ...classes]) as HTMLOListElement; + return Dom.make('ol', [ListRenderer.CSS.wrapper, ...classes]) as HTMLOListElement; } renderSublistWrapper(): HTMLElement { - const divElement = Dom.make('ol', [this.CSS.wrapperOrdered, this.CSS.itemChildren]) as HTMLElement; + const divElement = Dom.make('ol', [ListRenderer.CSS.wrapperOrdered, ListRenderer.CSS.itemChildren]) as HTMLElement; return divElement; } @@ -39,10 +42,11 @@ export class OrderedListRenderer extends ListRenderer { * @returns - created html list item element */ renderItem(content: string): HTMLLIElement { - const itemWrapper = Dom.make('li', this.CSS.item); - const itemBody = Dom.make('div', this.CSS.itemBody); - const itemContent = Dom.make('div', this.CSS.itemContent, { + const itemWrapper = Dom.make('li', ListRenderer.CSS.item); + const itemBody = Dom.make('div', ListRenderer.CSS.itemBody); + const itemContent = Dom.make('div', ListRenderer.CSS.itemContent, { innerHTML: content, + contentEditable: (!this.readOnly).toString(), }); itemBody.appendChild(itemContent); @@ -58,7 +62,7 @@ export class OrderedListRenderer extends ListRenderer { * @returns {string} */ getItemContent(item: Element): string { - const contentNode = item.querySelector(`.${this.CSS.itemContent}`); + const contentNode = item.querySelector(`.${ListRenderer.CSS.itemContent}`); if (!contentNode) { return ''; } diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts index 71183281..8072a1ee 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -11,9 +11,12 @@ export class UnorderedListRenderer extends ListRenderer { */ protected config?: NestedListConfig; - constructor(config?: NestedListConfig) { + readOnly: boolean; + + constructor(readonly: boolean, config?: NestedListConfig) { super(); this.config = config; + this.readOnly = readonly; } /** @@ -22,15 +25,15 @@ export class UnorderedListRenderer extends ListRenderer { * @returns - created html ol element */ renderWrapper(classes: string[] = []): HTMLOListElement { - classes.push(this.CSS.wrapperUnordered); + classes.push(ListRenderer.CSS.wrapperUnordered); - const ulElement = Dom.make('ul', [this.CSS.wrapper, ...classes]) as HTMLOListElement; + const ulElement = Dom.make('ul', [ListRenderer.CSS.wrapper, ...classes]) as HTMLOListElement; return ulElement; } renderSublistWrapper(): HTMLElement { - const divElement = Dom.make('ul', [this.CSS.wrapperUnordered, this.CSS.itemChildren]) as HTMLElement; + const divElement = Dom.make('ul', [ListRenderer.CSS.wrapperUnordered, ListRenderer.CSS.itemChildren]) as HTMLElement; return divElement; } @@ -41,10 +44,11 @@ export class UnorderedListRenderer extends ListRenderer { * @returns - created html list item element */ renderItem(content: string): HTMLLIElement { - const itemWrapper = Dom.make('li', this.CSS.item); - const itemBody = Dom.make('div', this.CSS.itemBody); - const itemContent = Dom.make('div', this.CSS.itemContent, { + const itemWrapper = Dom.make('li', ListRenderer.CSS.item); + const itemBody = Dom.make('div', ListRenderer.CSS.itemBody); + const itemContent = Dom.make('div', ListRenderer.CSS.itemContent, { innerHTML: content, + contentEditable: (!this.readOnly).toString(), }); itemBody.appendChild(itemContent); @@ -60,7 +64,7 @@ export class UnorderedListRenderer extends ListRenderer { * @returns {string} */ getItemContent(item: Element): string { - const contentNode = item.querySelector(`.${this.CSS.itemContent}`); + const contentNode = item.querySelector(`.${ListRenderer.CSS.itemContent}`); if (!contentNode) { return ''; } diff --git a/src/Tabulator/index.ts b/src/Tabulator/index.ts index dfa922e0..3f24bd12 100644 --- a/src/Tabulator/index.ts +++ b/src/Tabulator/index.ts @@ -1,8 +1,14 @@ import { CheckListRenderer } from "../ListRenderer/checklistRenderer"; import { OrderedListRenderer } from "../ListRenderer/orderedListRenderer"; import { UnorderedListRenderer } from "../ListRenderer/unorderedListRenderer"; -import { NestedListConfig, ListData } from "../types/listParams" +import { NestedListConfig, ListData, ListDataStyle } from "../types/listParams" import { ListItem } from "../types/listParams"; +import { isHtmlElement } from '../utils/type-guards'; +import Caret from '../utils/caret'; +import { ListRenderer } from "../ListRenderer"; +import * as Dom from '../utils/dom' +import type { PasteEvent } from '../types'; +import type { API, PasteConfig } from '@editorjs/editorjs'; type NestedListStyle = 'ordered' | 'unordered' | 'checklist'; @@ -37,10 +43,57 @@ export default class Tabulator { */ listWrapper: HTMLElement | undefined; - constructor(data: ListData, style: NestedListStyle, config?: NestedListConfig) { + /** + * Caret helper + */ + private caret: Caret; + + + readOnly: boolean; + + api: API; + + /** + * Returns current List item by the caret position + * + * @returns {Element} + */ + get currentItem(): Element | null { + const selection = window.getSelection(); + + if (!selection) { + return null; + } + let currentNode = selection.anchorNode; + + if (!currentNode) { + return null; + } + + if (!isHtmlElement(currentNode)) { + currentNode = currentNode.parentNode; + } + if (!currentNode) { + return null; + } + if (!isHtmlElement(currentNode)) { + return null; + } + + return currentNode.closest(`.cdx-nested-list__item`); + } + + constructor(data: ListData, style: NestedListStyle, readonly: boolean, api: API, config?: NestedListConfig) { this.config = config; this.data = data; this.style = style; + this.readOnly = readonly; + this.api = api; + + /** + * Instantiate caret helper + */ + this.caret = new Caret(); } render() { @@ -48,13 +101,13 @@ export default class Tabulator { switch (this.style) { case 'ordered': - this.list = new OrderedListRenderer(this.config); + this.list = new OrderedListRenderer(this.readOnly, this.config); break case 'unordered': - this.list = new UnorderedListRenderer(this.config); + this.list = new UnorderedListRenderer(this.readOnly, this.config); break case 'checklist': - this.list = new CheckListRenderer(this.config); + this.list = new CheckListRenderer(this.readOnly, this.config); break } @@ -75,6 +128,31 @@ export default class Tabulator { ); } + if (!this.readOnly) { + // detect keydown on the last item to escape List + this.listWrapper.addEventListener( + 'keydown', + (event) => { + switch (event.key) { + case 'Enter': + this.enterPressed(event); + break; + case 'Backspace': + this.backspace(event); + break; + case 'Tab': + if (event.shiftKey) { + this.shiftTab(event); + } else { + this.addTab(event); + } + break; + } + }, + false + ); + } + return this.listWrapper; } @@ -132,4 +210,553 @@ export default class Tabulator { items: this.listWrapper ? getItems(this.listWrapper) : [], }; } + + /** + * On paste sanitzation config. Allow only tags that are allowed in the Tool. + * + * @returns {PasteConfig} - paste config. + */ + static get pasteConfig(): PasteConfig { + return { + tags: ['OL', 'UL', 'LI'], + }; + } + + /** + * On paste callback that is fired from Editor. + * + * @param {PasteEvent} event - event with pasted data + */ + onPaste(event: PasteEvent): void { + const list = event.detail.data; + + this.data = this.pasteHandler(list); + + // render new list + const oldView = this.listWrapper; + + if (oldView && oldView.parentNode) { + oldView.parentNode.replaceChild(this.render(), oldView); + } + } + + /** + * Handle UL, OL and LI tags paste and returns List data + * + * @param {HTMLUListElement|HTMLOListElement|HTMLLIElement} element + * @returns {ListData} + */ + pasteHandler(element: PasteEvent['detail']['data']): ListData { + const { tagName: tag } = element; + let style: ListDataStyle = 'unordered'; + let tagToSearch: string; + + // set list style and tag to search. + switch (tag) { + case 'OL': + style = 'ordered'; + tagToSearch = 'ol'; + break; + case 'UL': + case 'LI': + style = 'unordered'; + tagToSearch = 'ul'; + } + + const data: ListData = { + style, + items: [], + }; + + // get pasted items from the html. + const getPastedItems = (parent: Element): ListItem[] => { + // get first level li elements. + const children = Array.from(parent.querySelectorAll(`:scope > li`)); + + return children.map((child) => { + // get subitems if they exist. + const subItemsWrapper = child.querySelector(`:scope > ${tagToSearch}`); + // get subitems. + const subItems = subItemsWrapper ? getPastedItems(subItemsWrapper) : []; + // get text content of the li element. + const content = child?.firstChild?.textContent || ''; + + return { + content, + items: subItems, + }; + }); + }; + + // get pasted items. + data.items = getPastedItems(element); + + return data; + } + + /** + * Handles Enter keypress + * + * @param {KeyboardEvent} event - keydown + * @returns {void} + */ + enterPressed(event: KeyboardEvent): void { + const currentItem = this.currentItem; + + /** + * Prevent editor.js behaviour + */ + event.stopPropagation(); + + /** + * Prevent browser behaviour + */ + event.preventDefault(); + + /** + * Prevent duplicated event in Chinese, Japanese and Korean languages + */ + if (event.isComposing) { + return; + } + + /** + * On Enter in the last empty item, get out of list + */ + const isEmpty = currentItem + ? this.list?.getItemContent(currentItem).trim().length === 0 + : true; + const isFirstLevelItem = currentItem?.parentNode === this.listWrapper; + const isLastItem = currentItem?.nextElementSibling === null; + + if (isFirstLevelItem && isLastItem && isEmpty) { + this.getOutOfList(); + + return; + } else if (isLastItem && isEmpty) { + this.unshiftItem(); + + return; + } + + /** + * On other Enters, get content from caret till the end of the block + * And move it to the new item + */ + const endingFragment = Caret.extractFragmentFromCaretPositionTillTheEnd(); + if (!endingFragment) { + return; + } + const endingHTML = Dom.fragmentToString(endingFragment); + const itemChildren = currentItem?.querySelector( + `.${ListRenderer.CSS.itemChildren}` + ); + + /** + * Create the new list item + */ + const itemEl = this.list!.renderItem(endingHTML); + + /** + * Check if child items exist + * + * @type {boolean} + */ + const childrenExist = + itemChildren && + Array.from(itemChildren.querySelectorAll(`.${ListRenderer.CSS.item}`)).length > 0; + + /** + * If item has children, prepend to them + * Otherwise, insert the new item after current + */ + if (childrenExist) { + itemChildren.prepend(itemEl); + } else { + currentItem?.after(itemEl); + } + + this.focusItem(itemEl); + } + + /** + * Handle backspace + * + * @param {KeyboardEvent} event - keydown + */ + backspace(event: KeyboardEvent): void { + /** + * Caret is not at start of the item + * Then backspace button should remove letter as usual + */ + if (!Caret.isAtStart()) { + return; + } + + /** + * Prevent default backspace behaviour + */ + event.preventDefault(); + + const currentItem = this.currentItem; + if (!currentItem) { + return; + } + const previousItem = currentItem.previousSibling; + if (!currentItem.parentNode) { + return; + } + if (!isHtmlElement(currentItem.parentNode)) { + return; + } + const parentItem = currentItem.parentNode.closest(`.${ListRenderer.CSS.item}`); + + /** + * Do nothing with the first item in the first-level list. + * No previous sibling means that this is the first item in the list. + * No parent item means that this is a first-level list. + * + * Before: + * 1. |Hello + * 2. World! + * + * After: + * 1. |Hello + * 2. World! + * + * If it this item and the while list is empty then editor.js should + * process this behaviour and remove the block completely + * + * Before: + * 1. | + * + * After: block has been removed + * + */ + if (!previousItem && !parentItem) { + return; + } + + // make sure previousItem is an HTMLElement + if (previousItem && !isHtmlElement(previousItem)) { + return; + } + + /** + * Prevent editor.js behaviour + */ + event.stopPropagation(); + + /** + * Lets compute the item which will be merged with current item text + */ + let targetItem: Element | null; + + /** + * If there is a previous item then we get a deepest item in its sublists + * + * Otherwise we will use the parent item + */ + if (previousItem) { + const childrenOfPreviousItem = previousItem.querySelectorAll( + `.${ListRenderer.CSS.item}` + ); + + targetItem = Array.from(childrenOfPreviousItem).pop() || previousItem; + } else { + targetItem = parentItem; + } + + /** + * Get content from caret till the end of the block to move it to the new item + */ + const endingFragment = Caret.extractFragmentFromCaretPositionTillTheEnd(); + if (!endingFragment) { + return; + } + const endingHTML = Dom.fragmentToString(endingFragment); + + /** + * Get the target item content element + */ + if (!targetItem) { + return; + } + const targetItemContent = targetItem.querySelector( + `.${ListRenderer.CSS.itemContent}` + ); + + /** + * Set a new place for caret + */ + if (!targetItemContent) { + return; + } + Caret.focus(targetItemContent, false); + + /** + * Save the caret position + */ + this.caret.save(); + + /** + * Update target item content by merging with current item html content + */ + targetItemContent.insertAdjacentHTML('beforeend', endingHTML); + + /** + * Get the sublist first-level items for current item + */ + let currentItemSublistItems: NodeListOf | Element[] = + currentItem.querySelectorAll( + `.${ListRenderer.CSS.itemChildren} > .${ListRenderer.CSS.item}` + ); + + /** + * Create an array from current item sublist items + */ + currentItemSublistItems = Array.from(currentItemSublistItems); + + /** + * Filter items for sublist first-level + * No need to move deeper items + */ + currentItemSublistItems = currentItemSublistItems.filter((node) => { + // make sure node.parentNode is an HTMLElement + if (!node.parentNode) { + return false; + } + if (!isHtmlElement(node.parentNode)) { + return false; + } + return node.parentNode.closest(`.${ListRenderer.CSS.item}`) === currentItem; + }); + + /** + * Reverse the array to insert items + */ + currentItemSublistItems.reverse().forEach((item) => { + /** + * Check if we need to save the indent for current item children + * + * If this is the first item in the list then place its children to the same level as currentItem. + * Same as shift+tab for all of these children. + * + * If there is a previous sibling then place children right after target item + */ + if (!previousItem) { + /** + * The first item in the list + * + * Before: + * 1. Hello + * 1.1. |My + * 1.1.1. Wonderful + * 1.1.2. World + * + * After: + * 1. Hello|My + * 1.1. Wonderful + * 1.2. World + */ + currentItem.after(item); + } else { + /** + * Not the first item + * + * Before: + * 1. Hello + * 1.1. My + * 1.2. |Dear + * 1.2.1. Wonderful + * 1.2.2. World + * + * After: + * 1. Hello + * 1.1. My|Dear + * 1.2. Wonderful + * 1.3. World + */ + targetItem.after(item); + } + }); + + /** + * Remove current item element + */ + currentItem.remove(); + + /** + * Restore the caret position + */ + this.caret.restore(); + } + + + /** + * Reduce indentation for current item + * + * @param {KeyboardEvent} event - keydown + * @returns {void} + */ + shiftTab(event: KeyboardEvent): void { + /** + * Prevent editor.js behaviour + */ + event.stopPropagation(); + + /** + * Prevent browser tab behaviour + */ + event.preventDefault(); + + /** + * Move item from current list to parent list + */ + this.unshiftItem(); + } + + + /** + * Decrease indentation of the current item + * + * @returns {void} + */ + unshiftItem(): void { + const currentItem = this.currentItem; + if (!currentItem) { + return; + } + if (!currentItem.parentNode) { + return; + } + if (!isHtmlElement(currentItem.parentNode)) { + return; + } + + const parentItem = currentItem.parentNode.closest(`.${ListRenderer.CSS.item}`); + + /** + * If item in the first-level list then no need to do anything + */ + if (!parentItem) { + return; + } + + this.caret.save(); + + parentItem.after(currentItem); + + this.caret.restore(); + + /** + * If previous parent's children list is now empty, remove it. + */ + const prevParentChildrenList = parentItem.querySelector( + `.${ListRenderer.CSS.itemChildren}` + ); + if (!prevParentChildrenList) { + return; + } + const isPrevParentChildrenEmpty = + prevParentChildrenList.children.length === 0; + + if (isPrevParentChildrenEmpty) { + prevParentChildrenList.remove(); + } + } + + + /** + * Add indentation to current item + * + * @param {KeyboardEvent} event - keydown + */ + addTab(event: KeyboardEvent): void { + /** + * Prevent editor.js behaviour + */ + event.stopPropagation(); + + /** + * Prevent browser tab behaviour + */ + event.preventDefault(); + + const currentItem = this.currentItem; + if (!currentItem) { + return; + } + const prevItem = currentItem.previousSibling; + if (!prevItem) { + return; + } + if (!isHtmlElement(prevItem)) { + return; + } + const isFirstChild = !prevItem; + + /** + * In the first item we should not handle Tabs (because there is no parent item above) + */ + if (isFirstChild) { + return; + } + + const prevItemChildrenList = prevItem.querySelector( + `.${ListRenderer.CSS.itemChildren}` + ); + + this.caret.save(); + + /** + * If prev item has child items, just append current to them + */ + if (prevItemChildrenList) { + prevItemChildrenList.appendChild(currentItem); + } else { + /** + * If prev item has no child items + * - Create and append children wrapper to the previous item + * - Append current item to it + */ + const sublistWrapper = this.list!.renderSublistWrapper(); + const prevItemBody = prevItem.querySelector(`.${ListRenderer.CSS.itemBody}`); + + sublistWrapper.appendChild(currentItem); + prevItemBody?.appendChild(sublistWrapper); + } + + this.caret.restore(); + } + + /** + * Sets focus to the item's content + * + * @param {Element} item - item (
          3. ) to select + * @param {boolean} atStart - where to set focus: at the start or at the end + * @returns {void} + */ + focusItem(item: Element, atStart: boolean = true): void { + const itemContent = item.querySelector( + `.${ListRenderer.CSS.itemContent}` + ); + if (!itemContent) { + return; + } + + Caret.focus(itemContent, atStart); + } + + /** + * Get out from List Tool by Enter on the empty last item + * + * @returns {void} + */ + getOutOfList(): void { + this.currentItem?.remove(); + + this.api.blocks.insert(); + this.api.caret.setToBlock(this.api.blocks.getCurrentBlockIndex()); + } } diff --git a/src/index.ts b/src/index.ts index 8daf8d21..a83b6416 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,8 +4,6 @@ import type { BlockToolConstructorOptions, TunesMenuConfig, } from '@editorjs/editorjs/types/tools'; - -import * as Dom from './utils/dom'; import Caret from './utils/caret'; import { IconListBulleted, IconListNumbered, IconChecklist } from '@codexteam/icons'; import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/listParams'; @@ -19,12 +17,11 @@ import './../styles/index.pcss'; /** * Constructor Params for Nested List Tool, use to pass initial data and settings */ -export type NestedListParams = BlockToolConstructorOptions< - ListData, - NestedListConfig ->; - +export type NestedListParams = BlockToolConstructorOptions; +/** + * Default class of the component used in editor + */ export default class NestedList { /** * Notify core that read-only mode is supported @@ -79,7 +76,7 @@ export default class NestedList { /** * Rerender list item */ - this.list = new Tabulator(this.data, this.listStyle, this.config); + this.list = new Tabulator(this.data, this.listStyle, this.readOnly, this.api, this.config); const newListElement = this.list.render() @@ -108,11 +105,6 @@ export default class NestedList { */ private defaultListStyle?: NestedListConfig['defaultStyle']; - /** - * Corresponds to UiNodes type from Editor.js but with wrapper being nullable - */ - private nodes: { wrapper: HTMLElement | null }; - /** * Tool's data */ @@ -141,13 +133,6 @@ export default class NestedList { * @param {boolean} params.readOnly - read-only mode flag */ constructor({ data, config, api, readOnly }: NestedListParams) { - /** - * HTML nodes used in tool - */ - this.nodes = { - wrapper: null, - }; - this.api = api; this.readOnly = readOnly; this.config = config; @@ -155,7 +140,7 @@ export default class NestedList { /** * Set the default list style from the config. */ - this.defaultListStyle = 'ordered'; + this.defaultListStyle = this.config?.defaultStyle || 'ordered'; const initialData = { style: this.defaultListStyle, @@ -171,7 +156,8 @@ export default class NestedList { } render() { - this.list = new Tabulator(this.data, this.listStyle, this.config); + this.list = new Tabulator(this.data, this.listStyle, this.readOnly, this.api, this.config); + this.listElement = this.list.render(); return this.listElement; From dd576b9f7df53101e278998c01d1f9430a27cac7 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 16:01:44 +0300 Subject: [PATCH 13/43] ordered list item numbering improved --- styles/index.pcss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/styles/index.pcss b/styles/index.pcss index b872b06c..4fcd4fb5 100644 --- a/styles/index.pcss +++ b/styles/index.pcss @@ -142,7 +142,11 @@ } &--ordered > &__item::before { - content: counters(item, ".") ". "; + content: counters(item, ".") " " + } + + &--ordered { + counter-reset: item; } &--unordered > &__item::before { From 0da5fd18235f51309c19bdcd1eea4389db12b91e Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 16:55:28 +0300 Subject: [PATCH 14/43] children styles updated --- src/Tabulator/index.ts | 20 ++++++++++++-------- styles/index.pcss | 12 ++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Tabulator/index.ts b/src/Tabulator/index.ts index 3f24bd12..3194524c 100644 --- a/src/Tabulator/index.ts +++ b/src/Tabulator/index.ts @@ -97,8 +97,6 @@ export default class Tabulator { } render() { - console.log(this.style); - switch (this.style) { case 'ordered': this.list = new OrderedListRenderer(this.readOnly, this.config); @@ -167,15 +165,19 @@ export default class Tabulator { appendItems(items: ListItem[], parentItem: Element): void { if (this.list !== undefined) { items.forEach((item) => { - const itemEl = this.list?.renderItem(item.content); + const itemEl = this.list!.renderItem(item.content); - parentItem.appendChild(itemEl!); + parentItem.appendChild(itemEl); if (item.items.length) { const sublistWrapper = this.list?.renderSublistWrapper() this.appendItems(item.items, sublistWrapper!); - parentItem.appendChild(sublistWrapper!); + const itemBody = itemEl.querySelector(`.${ListRenderer.CSS.itemBody}`); + + if (itemBody) { + itemBody.appendChild(sublistWrapper!); + } } }); } @@ -190,11 +192,11 @@ export default class Tabulator { */ const getItems = (parent: Element): ListItem[] => { const children = Array.from( - parent.querySelectorAll(`:scope > .cdx-nested-list__item`) + parent.querySelectorAll(`:scope > .${ListRenderer.CSS.item}`) ); return children.map((el) => { - const subItemsWrapper = el.querySelector(`.cdx-nested-list__item-children`); + const subItemsWrapper = el.querySelector(`.${ListRenderer.CSS.itemChildren}`); const content = this.list!.getItemContent(el); const subItems = subItemsWrapper ? getItems(subItemsWrapper) : []; @@ -205,10 +207,12 @@ export default class Tabulator { }); }; - return { + const re = { style: this.data.style, items: this.listWrapper ? getItems(this.listWrapper) : [], }; + + return re; } /** diff --git a/styles/index.pcss b/styles/index.pcss index 4fcd4fb5..b792ab96 100644 --- a/styles/index.pcss +++ b/styles/index.pcss @@ -18,7 +18,10 @@ line-height: 1.6em; display: flex; margin: 2px 0; - display: flex; + + &-children { + padding-inline-start: 0; + } [contenteditable]{ outline: none; @@ -28,11 +31,6 @@ flex-grow: 2; } - &-content, - &-children { - flex-basis: 100%; - } - &-content { word-break: break-word; white-space: pre-wrap; @@ -132,8 +130,6 @@ } } - &-children {} - &::before { counter-increment: item; margin-right: 5px; From 3227a52dfa75b33bb53647c2e63f08c0b68409d0 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 16:55:40 +0300 Subject: [PATCH 15/43] fixed saving and content format --- src/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index a83b6416..7e071cd5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -164,7 +164,9 @@ export default class NestedList { } save() { - return this.list?.save(); + this.data = this.list!.save(); + + return this.data } /** From d484ff879bb09aa3101af55f20339b38f4d737f0 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 18:00:51 +0300 Subject: [PATCH 16/43] refactored checkbox styles --- src/ListRenderer/checklistRenderer.ts | 17 +-- src/ListRenderer/listRenderer.ts | 8 +- src/Tabulator/index.ts | 4 +- styles/index.pcss | 189 +++++++++++++------------- 4 files changed, 105 insertions(+), 113 deletions(-) diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index 678bf574..7ab4be70 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -29,7 +29,13 @@ export class CheckListRenderer extends ListRenderer { renderWrapper(classes: string[] = []): HTMLOListElement { classes.push(ListRenderer.CSS.wrapperChecklist); - return Dom.make('ul', [ListRenderer.CSS.wrapper, ...classes]) as HTMLOListElement; + const listWrapper = Dom.make('ul', [ListRenderer.CSS.wrapper, ...classes]) as HTMLOListElement; + + listWrapper.addEventListener('click', (event) => { + this.toggleCheckbox(event); + }); + + return listWrapper; } renderSublistWrapper(): HTMLElement { @@ -57,10 +63,6 @@ export class CheckListRenderer extends ListRenderer { checkbox.innerHTML = IconCheck; checkboxContainer.appendChild(checkbox); - checkboxContainer.addEventListener('click', (event) => { - this.toggleCheckbox(event); - }); - itemBody.appendChild(itemContent); itemWrapper.appendChild(checkboxContainer); itemWrapper.appendChild(itemBody); @@ -101,11 +103,10 @@ export class CheckListRenderer extends ListRenderer { * @returns {void} */ private toggleCheckbox(event: any): void { - const checkListItem = event.target.closest(`.${ListRenderer.CSS.item}`); - const checkbox = checkListItem.querySelector(`.${ListRenderer.CSS.checkboxContainer}`); + const checkbox = event.target.closest(`.${ListRenderer.CSS.checkboxContainer}`); if (checkbox.contains(event.target)) { - checkListItem.classList.toggle(ListRenderer.CSS.itemChecked); + checkbox.classList.toggle(ListRenderer.CSS.itemChecked); checkbox.classList.add(ListRenderer.CSS.noHover); checkbox.addEventListener('mouseleave', () => this.removeSpecialHoverBehavior(checkbox), { once: true }); } diff --git a/src/ListRenderer/listRenderer.ts b/src/ListRenderer/listRenderer.ts index 4add3afd..91c111cf 100644 --- a/src/ListRenderer/listRenderer.ts +++ b/src/ListRenderer/listRenderer.ts @@ -39,10 +39,10 @@ export abstract class ListRenderer { itemContent: 'cdx-nested-list__item-content', itemChildren: 'cdx-nested-list__item-children', settingsWrapper: 'cdx-nested-list__settings', - itemChecked: 'cdx-nested-list__item--checked', - noHover: 'cdx-nested-list__item-checkbox--no-hover', - checkbox: 'cdx-nested-list__item-checkbox-check', - checkboxContainer: 'cdx-nested-list__item-checkbox' + itemChecked: 'cdx-nested-list__checkbox--checked', + noHover: 'cdx-nested-list__checkbox--no-hover', + checkbox: 'cdx-nested-list__checkbox-check', + checkboxContainer: 'cdx-nested-list__checkbox' }; } } diff --git a/src/Tabulator/index.ts b/src/Tabulator/index.ts index 3194524c..8bb431a0 100644 --- a/src/Tabulator/index.ts +++ b/src/Tabulator/index.ts @@ -207,12 +207,10 @@ export default class Tabulator { }); }; - const re = { + return { style: this.data.style, items: this.listWrapper ? getItems(this.listWrapper) : [], }; - - return re; } /** diff --git a/styles/index.pcss b/styles/index.pcss index b792ab96..69bd9fcd 100644 --- a/styles/index.pcss +++ b/styles/index.pcss @@ -11,8 +11,8 @@ --line-height: 1.57em; --color-bg-checked-hover: #0059AB; --color-tick: #fff; - --width-checkbox: 22px; - --height-checkbox: 22px; + --width-checkbox: 20px; + --height-checkbox: 20px; &__item { line-height: 1.6em; @@ -32,104 +32,11 @@ } &-content { + margin-left: 8px; word-break: break-word; white-space: pre-wrap; } - &-checkbox { - width: var(--width-checkbox); - height: var(--height-checkbox); - display: flex; - align-items: center; - margin-right: 8px; - margin-top: calc(var(--line-height)/2 - var(--height-checkbox)/2); - cursor: pointer; - - svg { - opacity: 0; - height: 20px; - width: 20px; - position: absolute; - left: -1px; - top: -1px; - max-height: 20px; - } - - @media (hover: hover) { - &:not(&--no-hover):hover { - ^&-check { - svg { - opacity: 1; - } - } - } - } - - &-check { - cursor: pointer; - display: inline-block; - flex-shrink: 0; - position: relative; - width: 20px; - height: 20px; - box-sizing: border-box; - margin-left: 0; - border-radius: var(--radius-border); - border: 1px solid var(--color-border); - background: var(--checkbox-background); - - &::before { - content: ''; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - border-radius: 100%; - background-color: var(--color-bg-checked); - visibility: hidden; - pointer-events: none; - transform: scale(1); - transition: transform 400ms ease-out, opacity 400ms; - } - } - } - - &--checked { - line-height: 1.6em; - display: flex; - margin: 2px 0; - ^&-checkbox { - @media (hover: hover) { - &:not(&--no-hover):hover { - .cdx-checklist__item-checkbox-check { - background: var(--color-bg-checked-hover); - border-color: var(--color-bg-checked-hover); - } - } - } - - &-check { - background: var(--color-bg-checked); - border-color: var(--color-bg-checked); - - svg { - opacity: 1; - - path { - stroke: var(--color-tick); - } - } - - &::before { - opacity: 0; - visibility: visible; - transform: scale(2.5); - } - } - } - } - &::before { counter-increment: item; margin-right: 5px; @@ -154,10 +61,96 @@ } &__settings { - display: flex; - .cdx-settings-button { width: 50%; } } + + &__checkbox { + width: var(--width-checkbox); + height: var(--height-checkbox); + display: flex; + align-items: center; + cursor: pointer; + + svg { + opacity: 0; + height: 20px; + width: 20px; + left: -1px; + top: -1px; + position: absolute; + max-height: 20px; + } + + @media (hover: hover) { + &:not(&--no-hover):hover { + ^&-check { + svg { + opacity: 1; + } + } + } + } + + &--checked { + line-height: 1.6em; + + @media (hover: hover) { + &:not(&--no-hover):hover { + .cdx-checklist__checkbox-check { + background: var(--color-bg-checked-hover); + border-color: var(--color-bg-checked-hover); + } + } + } + + + ^&-check { + background: var(--color-bg-checked); + border-color: var(--color-bg-checked); + + svg { + opacity: 1; + + path { + stroke: var(--color-tick); + } + } + + &::before { + opacity: 0; + visibility: visible; + transform: scale(2.5); + } + } + } + &-check { + cursor: pointer; + display: inline-block; + position: relative; + margin: 0 auto; + width: 20px; + height: 20px; + box-sizing: border-box; + border-radius: var(--radius-border); + border: 1px solid var(--color-border); + background: var(--checkbox-background); + + &::before { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + border-radius: 100%; + background-color: var(--color-bg-checked); + visibility: hidden; + pointer-events: none; + transform: scale(1); + transition: transform 400ms ease-out, opacity 400ms; + } + } + } } From e04e8dfbbf81afccd07f80428e203ec95e462a76 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 18:32:59 +0300 Subject: [PATCH 17/43] now tool saving data with meta field --- src/ListRenderer/checklistRenderer.ts | 14 ++++++++++---- src/ListRenderer/orderedListRenderer.ts | 7 +++++++ src/ListRenderer/unorderedListRenderer.ts | 7 +++++++ src/Tabulator/index.ts | 8 ++++++-- src/index.ts | 3 +++ src/types/listParams.ts | 5 +++++ 6 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index 7ab4be70..25200305 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -49,7 +49,7 @@ export class CheckListRenderer extends ListRenderer { * @param content - content of the list item * @returns - created html list item element */ - renderItem(content: string): HTMLLIElement { + renderItem(content: string, meta: ItemMeta): HTMLLIElement { const itemWrapper = Dom.make('li', [ListRenderer.CSS.item, ListRenderer.CSS.item]); const itemBody = Dom.make('div', ListRenderer.CSS.itemBody); const itemContent = Dom.make('div', ListRenderer.CSS.itemContent, { @@ -60,6 +60,10 @@ export class CheckListRenderer extends ListRenderer { const checkbox = Dom.make('span', ListRenderer.CSS.checkbox); const checkboxContainer = Dom.make('div', ListRenderer.CSS.checkboxContainer); + if (meta && meta.checked === true) { + checkboxContainer.classList.add(ListRenderer.CSS.itemChecked); + } + checkbox.innerHTML = IconCheck; checkboxContainer.appendChild(checkbox); @@ -89,9 +93,11 @@ export class CheckListRenderer extends ListRenderer { return contentNode.innerHTML; } - getItemMeta(item: HTMLElement): ItemMeta { + getItemMeta(item: Element): ItemMeta { + const checkbox = item.querySelector(`.${ListRenderer.CSS.checkboxContainer}`); + return { - checked: item.classList.contains(ListRenderer.CSS.itemChecked) + checked: checkbox ? checkbox.classList.contains(ListRenderer.CSS.itemChecked) : undefined, } } @@ -105,7 +111,7 @@ export class CheckListRenderer extends ListRenderer { private toggleCheckbox(event: any): void { const checkbox = event.target.closest(`.${ListRenderer.CSS.checkboxContainer}`); - if (checkbox.contains(event.target)) { + if (checkbox && checkbox.contains(event.target)) { checkbox.classList.toggle(ListRenderer.CSS.itemChecked); checkbox.classList.add(ListRenderer.CSS.noHover); checkbox.addEventListener('mouseleave', () => this.removeSpecialHoverBehavior(checkbox), { once: true }); diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts index e5110348..ec3f933f 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/orderedListRenderer.ts @@ -1,3 +1,4 @@ +import ItemMeta from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './listRenderer'; @@ -73,4 +74,10 @@ export class OrderedListRenderer extends ListRenderer { return contentNode.innerHTML; } + + getItemMeta(item: Element): ItemMeta { + return { + checked: undefined, + } + } } diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts index 8072a1ee..021001ea 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -1,3 +1,4 @@ +import ItemMeta from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './listRenderer'; @@ -75,4 +76,10 @@ export class UnorderedListRenderer extends ListRenderer { return contentNode.innerHTML; } + + getItemMeta(item: Element): ItemMeta { + return { + checked: undefined, + } + } } diff --git a/src/Tabulator/index.ts b/src/Tabulator/index.ts index 8bb431a0..8ea29787 100644 --- a/src/Tabulator/index.ts +++ b/src/Tabulator/index.ts @@ -119,6 +119,7 @@ export default class Tabulator { [ { content: '', + meta: {}, items: [], }, ], @@ -165,7 +166,7 @@ export default class Tabulator { appendItems(items: ListItem[], parentItem: Element): void { if (this.list !== undefined) { items.forEach((item) => { - const itemEl = this.list!.renderItem(item.content); + const itemEl = this.list!.renderItem(item.content, item.meta); parentItem.appendChild(itemEl); @@ -198,10 +199,12 @@ export default class Tabulator { return children.map((el) => { const subItemsWrapper = el.querySelector(`.${ListRenderer.CSS.itemChildren}`); const content = this.list!.getItemContent(el); + const meta = this.list!.getItemMeta(el); const subItems = subItemsWrapper ? getItems(subItemsWrapper) : []; return { content, + meta, items: subItems, }; }); @@ -285,6 +288,7 @@ export default class Tabulator { return { content, + meta: {}, items: subItems, }; }); @@ -357,7 +361,7 @@ export default class Tabulator { /** * Create the new list item */ - const itemEl = this.list!.renderItem(endingHTML); + const itemEl = this.list!.renderItem(endingHTML, {}); /** * Check if child items exist diff --git a/src/index.ts b/src/index.ts index 7e071cd5..8775b515 100644 --- a/src/index.ts +++ b/src/index.ts @@ -166,6 +166,8 @@ export default class NestedList { save() { this.data = this.list!.save(); + console.log(this.data); + return this.data } @@ -244,6 +246,7 @@ export default class NestedList { items: [ { content, + meta: {}, items: [], }, ], diff --git a/src/types/listParams.ts b/src/types/listParams.ts index f691c4aa..2e0f24db 100644 --- a/src/types/listParams.ts +++ b/src/types/listParams.ts @@ -1,3 +1,5 @@ +import ItemMeta from "./itemMeta"; + /** * list style to make list as ordered or unordered */ @@ -25,6 +27,9 @@ export interface ListItem { * list item text content */ content: string; + + meta: ItemMeta; + /** * sublist items */ From a180404fd59788bfc415f4ca6e88a96ec74769af Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 19:06:56 +0300 Subject: [PATCH 18/43] comments improved --- src/ListRenderer/checklistRenderer.ts | 19 ++++++--- src/ListRenderer/orderedListRenderer.ts | 20 ++++++--- src/ListRenderer/unorderedListRenderer.ts | 20 ++++++--- src/{Tabulator => ListTabulator}/index.ts | 22 ++++++++-- src/index.ts | 49 +++++++++++++++-------- 5 files changed, 93 insertions(+), 37 deletions(-) rename src/{Tabulator => ListTabulator}/index.ts (97%) diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index 25200305..0b5653c0 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -13,6 +13,9 @@ export class CheckListRenderer extends ListRenderer { */ protected config?: NestedListConfig; + /** + * Is NestedList Tool read-only option + */ readOnly: boolean; constructor(readonly: boolean, config?: NestedListConfig) { @@ -23,13 +26,10 @@ export class CheckListRenderer extends ListRenderer { /** * Renders ol wrapper for list - * @param classes - * @returns - created html ol element */ - renderWrapper(classes: string[] = []): HTMLOListElement { - classes.push(ListRenderer.CSS.wrapperChecklist); - - const listWrapper = Dom.make('ul', [ListRenderer.CSS.wrapper, ...classes]) as HTMLOListElement; + renderWrapper(): HTMLOListElement { + const listWrapper = Dom.make('ul', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperChecklist]) as HTMLOListElement; listWrapper.addEventListener('click', (event) => { this.toggleCheckbox(event); @@ -38,6 +38,10 @@ export class CheckListRenderer extends ListRenderer { return listWrapper; } + /** + * Render wrapper of child list + * @returns wrapper element of the child list + */ renderSublistWrapper(): HTMLElement { const divElement = Dom.make('ul', [ListRenderer.CSS.wrapperChecklist, ListRenderer.CSS.itemChildren]) as HTMLElement; @@ -93,6 +97,11 @@ export class CheckListRenderer extends ListRenderer { return contentNode.innerHTML; } + /** + * Return meta object of certain element + * @param {Element} item - item of the list to get meta from + * @returns {ItemMeta} Item meta object + */ getItemMeta(item: Element): ItemMeta { const checkbox = item.querySelector(`.${ListRenderer.CSS.checkboxContainer}`); diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts index ec3f933f..b4b8ef9d 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/orderedListRenderer.ts @@ -12,6 +12,9 @@ export class OrderedListRenderer extends ListRenderer { */ protected config?: NestedListConfig; + /** + * Is NestedList Tool read-only option + */ readOnly: boolean; constructor(readonly: boolean, config?: NestedListConfig) { @@ -22,15 +25,16 @@ export class OrderedListRenderer extends ListRenderer { /** * Renders ol wrapper for list - * @param classes - * @returns - created html ol element */ - renderWrapper(classes: string[] = []): HTMLOListElement { - classes.push(ListRenderer.CSS.wrapperOrdered); - - return Dom.make('ol', [ListRenderer.CSS.wrapper, ...classes]) as HTMLOListElement; + renderWrapper(): HTMLOListElement { + return Dom.make('ol', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperOrdered]) as HTMLOListElement; } + /** + * Render wrapper of child list + * @returns wrapper element of the child list + */ renderSublistWrapper(): HTMLElement { const divElement = Dom.make('ol', [ListRenderer.CSS.wrapperOrdered, ListRenderer.CSS.itemChildren]) as HTMLElement; @@ -75,7 +79,11 @@ export class OrderedListRenderer extends ListRenderer { return contentNode.innerHTML; } - getItemMeta(item: Element): ItemMeta { + /** + * Returns item meta, for ordered list checked will be always undefined + * @returns Item meta object + */ + getItemMeta(): ItemMeta { return { checked: undefined, } diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts index 021001ea..cdee8fac 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -12,6 +12,9 @@ export class UnorderedListRenderer extends ListRenderer { */ protected config?: NestedListConfig; + /** + * Is NestedList Tool read-only option + */ readOnly: boolean; constructor(readonly: boolean, config?: NestedListConfig) { @@ -22,17 +25,19 @@ export class UnorderedListRenderer extends ListRenderer { /** * Renders ol wrapper for list - * @param classes - * @returns - created html ol element */ - renderWrapper(classes: string[] = []): HTMLOListElement { - classes.push(ListRenderer.CSS.wrapperUnordered); + renderWrapper(): HTMLOListElement { - const ulElement = Dom.make('ul', [ListRenderer.CSS.wrapper, ...classes]) as HTMLOListElement; + const ulElement = Dom.make('ul', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperUnordered]) as HTMLOListElement; return ulElement; } + /** + * Render wrapper of child list + * @returns wrapper element of the child list + */ renderSublistWrapper(): HTMLElement { const divElement = Dom.make('ul', [ListRenderer.CSS.wrapperUnordered, ListRenderer.CSS.itemChildren]) as HTMLElement; @@ -77,7 +82,12 @@ export class UnorderedListRenderer extends ListRenderer { return contentNode.innerHTML; } - getItemMeta(item: Element): ItemMeta { + + /** + * Returns item meta, for undered list checked will be always undefined + * @returns Item meta object + */ + getItemMeta(): ItemMeta { return { checked: undefined, } diff --git a/src/Tabulator/index.ts b/src/ListTabulator/index.ts similarity index 97% rename from src/Tabulator/index.ts rename to src/ListTabulator/index.ts index 8ea29787..84df292e 100644 --- a/src/Tabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -9,6 +9,7 @@ import { ListRenderer } from "../ListRenderer"; import * as Dom from '../utils/dom' import type { PasteEvent } from '../types'; import type { API, PasteConfig } from '@editorjs/editorjs'; +import { NestedListParams } from ".."; type NestedListStyle = 'ordered' | 'unordered' | 'checklist'; @@ -17,7 +18,7 @@ type ListRendererTypes = OrderedListRenderer | UnorderedListRenderer | CheckList /** * Class that is responsible for list tabulation */ -export default class Tabulator { +export default class ListTabulator { /** * Tool's configuration */ @@ -48,9 +49,14 @@ export default class Tabulator { */ private caret: Caret; - + /** + * Is NestedList Tool read-only option + */ readOnly: boolean; + /** + * The Editor.js API + */ api: API; /** @@ -83,11 +89,11 @@ export default class Tabulator { return currentNode.closest(`.cdx-nested-list__item`); } - constructor(data: ListData, style: NestedListStyle, readonly: boolean, api: API, config?: NestedListConfig) { + constructor({data, config, api, readOnly}: NestedListParams, style: NestedListStyle) { this.config = config; this.data = data; this.style = style; - this.readOnly = readonly; + this.readOnly = readOnly; this.api = api; /** @@ -96,6 +102,10 @@ export default class Tabulator { this.caret = new Caret(); } + /** + * Function that is responsible for rendering nested list with contents + * @returns Filled with content wrapper element of the list + */ render() { switch (this.style) { case 'ordered': @@ -184,6 +194,10 @@ export default class Tabulator { } } + /** + * Function that is responsible for list content saving + * @returns saved list data + */ save(): ListData { /** * The method for recursive collecting of the child items diff --git a/src/index.ts b/src/index.ts index 8775b515..f0ae786c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import type { import Caret from './utils/caret'; import { IconListBulleted, IconListNumbered, IconChecklist } from '@codexteam/icons'; import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/listParams'; -import Tabulator from './Tabulator'; +import ListTabulator from './ListTabulator'; /** * Build styles @@ -74,9 +74,17 @@ export default class NestedList { this.data.style = style; /** - * Rerender list item + * Create new instance of list */ - this.list = new Tabulator(this.data, this.listStyle, this.readOnly, this.api, this.config); + this.list = new ListTabulator( + { + data: this.data, + api: this.api, + readOnly: this.readOnly, + config: this.config, + }, + this.listStyle + ); const newListElement = this.list.render() @@ -111,11 +119,9 @@ export default class NestedList { private data: ListData; /** - * Caret helper + * Class that is responsible for list complete list rendering and saving */ - private caret: Caret; - - list: Tabulator | undefined; + list: ListTabulator | undefined; /** * Main constant wrapper of the whole list @@ -138,7 +144,7 @@ export default class NestedList { this.config = config; /** - * Set the default list style from the config. + * Set the default list style from the config or presetted 'ordered'. */ this.defaultListStyle = this.config?.defaultStyle || 'ordered'; @@ -148,26 +154,34 @@ export default class NestedList { }; this.data = data && Object.keys(data).length ? data : initialData; - - /** - * Instantiate caret helper - */ - this.caret = new Caret(); } + /** + * Function that is responsible for content rendering + * @returns rendered list wrapper with all contents + */ render() { - this.list = new Tabulator(this.data, this.listStyle, this.readOnly, this.api, this.config); + this.list = new ListTabulator({ + data: this.data, + readOnly: this.readOnly, + api: this.api, + config: this.config, + }, + this.listStyle + ); this.listElement = this.list.render(); return this.listElement; } + /** + * Function that is responsible for content saving + * @returns formatted content used in editor + */ save() { this.data = this.list!.save(); - console.log(this.data); - return this.data } @@ -175,7 +189,7 @@ export default class NestedList { * Creates Block Tune allowing to change the list style * * @public - * @returns {Array} + * @returns {Array} array of tune configs */ renderSettings(): TunesMenuConfig { const tunes = [ @@ -207,6 +221,7 @@ export default class NestedList { }, })); } + /** * On paste sanitzation config. Allow only tags that are allowed in the Tool. * From 3d1ef9da12ce646c8e097301232f8b863407b3c6 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 19:07:11 +0300 Subject: [PATCH 19/43] get rid of ex.ts --- src/ex.ts | 1070 ----------------------------------------------------- 1 file changed, 1070 deletions(-) delete mode 100644 src/ex.ts diff --git a/src/ex.ts b/src/ex.ts deleted file mode 100644 index f134d52a..00000000 --- a/src/ex.ts +++ /dev/null @@ -1,1070 +0,0 @@ -import type { API, PasteConfig, ToolboxConfig } from '@editorjs/editorjs'; -import type { PasteEvent } from './types'; -import type { - BlockToolConstructorOptions, - TunesMenuConfig, -} from '@editorjs/editorjs/types/tools'; - -import { isHtmlElement } from './utils/type-guards'; - -import * as Dom from './utils/dom'; -import Caret from './utils/caret'; -import { IconListBulleted, IconListNumbered } from '@codexteam/icons'; -import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/listParams'; - -/** - * Build styles - */ -import './../styles/index.pcss'; - -/** - * Constructor Params for Nested List Tool, use to pass initial data and settings - */ -export type NestedListParams = BlockToolConstructorOptions< - ListData, - NestedListConfig ->; - -/** - * CSS classes for the Nested List Tool - */ -interface NestedListCssClasses { - baseBlock: string; - wrapper: string; - wrapperOrdered: string; - wrapperUnordered: string; - item: string; - itemBody: string; - itemContent: string; - itemChildren: string; - settingsWrapper: string; - settingsButton: string; - settingsButtonActive: string; -} - -/** - * NestedList Tool for EditorJS - */ -export default class NestedList { - /** - * Notify core that read-only mode is supported - * - * @returns {boolean} - */ - static get isReadOnlySupported(): boolean { - return true; - } - - /** - * Allow to use native Enter behaviour - * - * @returns {boolean} - * @public - */ - static get enableLineBreaks(): boolean { - return true; - } - - /** - * Get Tool toolbox settings - * icon - Tool icon's SVG - * title - title to show in toolbox - * - * @returns {ToolboxConfig} - */ - static get toolbox(): ToolboxConfig { - return { - icon: IconListNumbered, - title: 'List', - }; - } - - /** - * The Editor.js API - */ - private api: API; - - /** - * Is NestedList Tool read-only - */ - private readOnly: boolean; - - /** - * Tool's configuration - */ - private config?: NestedListConfig; - - /** - * Default list style - */ - private defaultListStyle?: NestedListConfig['defaultStyle']; - - /** - * Corresponds to UiNodes type from Editor.js but with wrapper being nullable - */ - private nodes: { wrapper: HTMLElement | null }; - - /** - * Tool's data - */ - private data: ListData; - - /** - * Caret helper - */ - private caret: Caret; - - /** - * Render plugin`s main Element and fill it with saved data - * - * @param {object} params - tool constructor options - * @param {ListData} params.data - previously saved data - * @param {object} params.config - user config for Tool - * @param {object} params.api - Editor.js API - * @param {boolean} params.readOnly - read-only mode flag - */ - constructor({ data, config, api, readOnly }: NestedListParams) { - /** - * HTML nodes used in tool - */ - this.nodes = { - wrapper: null, - }; - - this.api = api; - this.readOnly = readOnly; - this.config = config; - - /** - * Set the default list style from the config. - */ - this.defaultListStyle = - this.config?.defaultStyle === 'ordered' ? 'ordered' : 'unordered'; - - const initialData = { - style: this.defaultListStyle, - items: [], - }; - this.data = data && Object.keys(data).length ? data : initialData; - - /** - * Instantiate caret helper - */ - this.caret = new Caret(); - } - - /** - * Returns list tag with items - * - * @returns {Element} - * @public - */ - render(): Element { - this.nodes.wrapper = this.makeListWrapper(this.data.style, [ - this.CSS.baseBlock, - ]); - - // fill with data - if (this.data.items.length) { - this.appendItems(this.data.items, this.nodes.wrapper); - } else { - this.appendItems( - [ - { - content: '', - items: [], - }, - ], - this.nodes.wrapper - ); - } - - if (!this.readOnly) { - // detect keydown on the last item to escape List - this.nodes.wrapper.addEventListener( - 'keydown', - (event) => { - switch (event.key) { - case 'Enter': - this.enterPressed(event); - break; - case 'Backspace': - this.backspace(event); - break; - case 'Tab': - if (event.shiftKey) { - this.shiftTab(event); - } else { - this.addTab(event); - } - break; - } - }, - false - ); - } - - return this.nodes.wrapper; - } - - /** - * Creates Block Tune allowing to change the list style - * - * @public - * @returns {Array} - */ - renderSettings(): TunesMenuConfig { - const tunes = [ - { - name: 'unordered' as const, - label: this.api.i18n.t('Unordered'), - icon: IconListBulleted, - }, - { - name: 'ordered' as const, - label: this.api.i18n.t('Ordered'), - icon: IconListNumbered, - }, - ]; - - return tunes.map((tune) => ({ - name: tune.name, - icon: tune.icon, - label: tune.label, - isActive: this.data.style === tune.name, - closeOnActivate: true, - onActivate: () => { - this.listStyle = tune.name; - }, - })); - } - - /** - * On paste sanitzation config. Allow only tags that are allowed in the Tool. - * - * @returns {PasteConfig} - paste config. - */ - static get pasteConfig(): PasteConfig { - return { - tags: ['OL', 'UL', 'LI'], - }; - } - - /** - * On paste callback that is fired from Editor. - * - * @param {PasteEvent} event - event with pasted data - */ - onPaste(event: PasteEvent): void { - const list = event.detail.data; - - this.data = this.pasteHandler(list); - - // render new list - const oldView = this.nodes.wrapper; - - if (oldView && oldView.parentNode) { - oldView.parentNode.replaceChild(this.render(), oldView); - } - } - - /** - * Handle UL, OL and LI tags paste and returns List data - * - * @param {HTMLUListElement|HTMLOListElement|HTMLLIElement} element - * @returns {ListData} - */ - pasteHandler(element: PasteEvent['detail']['data']): ListData { - const { tagName: tag } = element; - let style: ListDataStyle = 'unordered'; - let tagToSearch: string; - - // set list style and tag to search. - switch (tag) { - case 'OL': - style = 'ordered'; - tagToSearch = 'ol'; - break; - case 'UL': - case 'LI': - style = 'unordered'; - tagToSearch = 'ul'; - } - - const data: ListData = { - style, - items: [], - }; - - // get pasted items from the html. - const getPastedItems = (parent: Element): ListItem[] => { - // get first level li elements. - const children = Array.from(parent.querySelectorAll(`:scope > li`)); - - return children.map((child) => { - // get subitems if they exist. - const subItemsWrapper = child.querySelector(`:scope > ${tagToSearch}`); - // get subitems. - const subItems = subItemsWrapper ? getPastedItems(subItemsWrapper) : []; - // get text content of the li element. - const content = child?.firstChild?.textContent || ''; - - return { - content, - items: subItems, - }; - }); - }; - - // get pasted items. - data.items = getPastedItems(element); - - return data; - } - - /** - * Renders children list - * - * @param {ListItem[]} items - items data to append - * @param {Element} parentItem - where to append - * @returns {void} - */ - appendItems(items: ListItem[], parentItem: Element): void { - items.forEach((item) => { - const itemEl = this.createItem(item.content, item.items); - - parentItem.appendChild(itemEl); - }); - } - - /** - * Renders the single item - * - * @param {string} content - item content to render - * @param {ListItem[]} [items] - children - * @returns {Element} - */ - createItem(content: string, items: ListItem[] = []): Element { - const itemWrapper = Dom.make('li', this.CSS.item); - const itemBody = Dom.make('div', this.CSS.itemBody); - const itemContent = Dom.make('div', this.CSS.itemContent, { - innerHTML: content, - contentEditable: (!this.readOnly).toString(), - }); - - itemBody.appendChild(itemContent); - itemWrapper.appendChild(itemBody); - - /** - * Append children if we have some - */ - if (items && items.length > 0) { - this.addChildrenList(itemWrapper, items); - } - - return itemWrapper; - } - - /** - * Extracts tool's data from the DOM - * - * @returns {ListData} - */ - save(): ListData { - /** - * The method for recursive collecting of the child items - * - * @param {Element} parent - where to find items - * @returns {ListItem[]} - */ - const getItems = (parent: Element): ListItem[] => { - const children = Array.from( - parent.querySelectorAll(`:scope > .${this.CSS.item}`) - ); - - return children.map((el) => { - const subItemsWrapper = el.querySelector(`.${this.CSS.itemChildren}`); - const content = this.getItemContent(el); - const subItems = subItemsWrapper ? getItems(subItemsWrapper) : []; - - return { - content, - items: subItems, - }; - }); - }; - - return { - style: this.data.style, - items: this.nodes.wrapper ? getItems(this.nodes.wrapper) : [], - }; - } - - /** - * Append children list to passed item - * - * @param {Element} parentItem - item that should contain passed sub-items - * @param {ListItem[]} items - sub items to append - */ - addChildrenList(parentItem: Element, items: ListItem[]): void { - const itemBody = parentItem.querySelector(`.${this.CSS.itemBody}`); - const sublistWrapper = this.makeListWrapper(undefined, [ - this.CSS.itemChildren, - ]); - - this.appendItems(items, sublistWrapper); - - if (!itemBody) { - return; - } - - itemBody.appendChild(sublistWrapper); - } - - /** - * Creates main
              or
                tag depended on style - * - * @param {string} [style] - 'ordered' or 'unordered' - * @param {string[]} [classes] - additional classes to append - * @returns {HTMLOListElement|HTMLUListElement} - */ - makeListWrapper( - style: string = this.listStyle, - classes: string[] = [] - ): HTMLOListElement | HTMLUListElement { - const tag = style === 'ordered' ? 'ol' : 'ul'; - const styleClass = - style === 'ordered' ? this.CSS.wrapperOrdered : this.CSS.wrapperUnordered; - - classes.push(styleClass); - - // since tag is either 'ol' or 'ul' we can safely cast it to HTMLOListElement | HTMLUListElement - return Dom.make(tag, [this.CSS.wrapper, ...classes]) as - | HTMLOListElement - | HTMLUListElement; - } - - /** - * Styles - * - * @returns {NestedListCssClasses} - CSS classes names by keys - * @private - */ - get CSS(): NestedListCssClasses { - return { - baseBlock: this.api.styles.block, - wrapper: 'cdx-nested-list', - wrapperOrdered: 'cdx-nested-list--ordered', - wrapperUnordered: 'cdx-nested-list--unordered', - item: 'cdx-nested-list__item', - itemBody: 'cdx-nested-list__item-body', - itemContent: 'cdx-nested-list__item-content', - itemChildren: 'cdx-nested-list__item-children', - settingsWrapper: 'cdx-nested-list__settings', - settingsButton: this.api.styles.settingsButton, - settingsButtonActive: this.api.styles.settingsButtonActive, - }; - } - - /** - * Get list style name - * - * @returns {string} - */ - get listStyle(): string { - return this.data.style || this.defaultListStyle; - } - - /** - * Set list style - * - * @param {ListDataStyle} style - new style to set - */ - set listStyle(style: ListDataStyle) { - if (!this.nodes) { - return; - } - if (!this.nodes.wrapper) { - return; - } - /** - * Get lists elements - * - * @type {Element[]} - */ - const lists: Element[] = Array.from( - this.nodes.wrapper.querySelectorAll(`.${this.CSS.wrapper}`) - ); - - /** - * Add main wrapper to the list - */ - lists.push(this.nodes.wrapper); - - /** - * For each list we need to update classes - */ - lists.forEach((list) => { - list.classList.toggle(this.CSS.wrapperUnordered, style === 'unordered'); - list.classList.toggle(this.CSS.wrapperOrdered, style === 'ordered'); - }); - - /** - * Update the style in data - * - * @type {ListDataStyle} - */ - this.data.style = style; - } - - /** - * Returns current List item by the caret position - * - * @returns {Element} - */ - get currentItem(): Element | null { - const selection = window.getSelection(); - - if (!selection) { - return null; - } - let currentNode = selection.anchorNode; - - if (!currentNode) { - return null; - } - - if (!isHtmlElement(currentNode)) { - currentNode = currentNode.parentNode; - } - if (!currentNode) { - return null; - } - if (!isHtmlElement(currentNode)) { - return null; - } - - return currentNode.closest(`.${this.CSS.item}`); - } - - /** - * Handles Enter keypress - * - * @param {KeyboardEvent} event - keydown - * @returns {void} - */ - enterPressed(event: KeyboardEvent): void { - const currentItem = this.currentItem; - - /** - * Prevent editor.js behaviour - */ - event.stopPropagation(); - - /** - * Prevent browser behaviour - */ - event.preventDefault(); - - /** - * Prevent duplicated event in Chinese, Japanese and Korean languages - */ - if (event.isComposing) { - return; - } - - /** - * On Enter in the last empty item, get out of list - */ - const isEmpty = currentItem - ? this.getItemContent(currentItem).trim().length === 0 - : true; - const isFirstLevelItem = currentItem?.parentNode === this.nodes.wrapper; - const isLastItem = currentItem?.nextElementSibling === null; - - if (isFirstLevelItem && isLastItem && isEmpty) { - this.getOutOfList(); - - return; - } else if (isLastItem && isEmpty) { - this.unshiftItem(); - - return; - } - - /** - * On other Enters, get content from caret till the end of the block - * And move it to the new item - */ - const endingFragment = Caret.extractFragmentFromCaretPositionTillTheEnd(); - if (!endingFragment) { - return; - } - const endingHTML = Dom.fragmentToString(endingFragment); - const itemChildren = currentItem?.querySelector( - `.${this.CSS.itemChildren}` - ); - - /** - * Create the new list item - */ - const itemEl = this.createItem(endingHTML, undefined); - - /** - * Check if child items exist - * - * @type {boolean} - */ - const childrenExist = - itemChildren && - Array.from(itemChildren.querySelectorAll(`.${this.CSS.item}`)).length > 0; - - /** - * If item has children, prepend to them - * Otherwise, insert the new item after current - */ - if (childrenExist) { - itemChildren.prepend(itemEl); - } else { - currentItem?.after(itemEl); - } - - this.focusItem(itemEl); - } - - /** - * Decrease indentation of the current item - * - * @returns {void} - */ - unshiftItem(): void { - const currentItem = this.currentItem; - if (!currentItem) { - return; - } - if (!currentItem.parentNode) { - return; - } - if (!isHtmlElement(currentItem.parentNode)) { - return; - } - - const parentItem = currentItem.parentNode.closest(`.${this.CSS.item}`); - - /** - * If item in the first-level list then no need to do anything - */ - if (!parentItem) { - return; - } - - this.caret.save(); - - parentItem.after(currentItem); - - this.caret.restore(); - - /** - * If previous parent's children list is now empty, remove it. - */ - const prevParentChildrenList = parentItem.querySelector( - `.${this.CSS.itemChildren}` - ); - if (!prevParentChildrenList) { - return; - } - const isPrevParentChildrenEmpty = - prevParentChildrenList.children.length === 0; - - if (isPrevParentChildrenEmpty) { - prevParentChildrenList.remove(); - } - } - - /** - * Return the item content - * - * @param {Element} item - item wrapper (
              1. ) - * @returns {string} - */ - getItemContent(item: Element): string { - const contentNode = item.querySelector(`.${this.CSS.itemContent}`); - if (!contentNode) { - return ''; - } - - if (Dom.isEmpty(contentNode)) { - return ''; - } - - return contentNode.innerHTML; - } - - /** - * Sets focus to the item's content - * - * @param {Element} item - item (
              2. ) to select - * @param {boolean} atStart - where to set focus: at the start or at the end - * @returns {void} - */ - focusItem(item: Element, atStart: boolean = true): void { - const itemContent = item.querySelector( - `.${this.CSS.itemContent}` - ); - if (!itemContent) { - return; - } - - Caret.focus(itemContent, atStart); - } - - /** - * Get out from List Tool by Enter on the empty last item - * - * @returns {void} - */ - getOutOfList(): void { - this.currentItem?.remove(); - - this.api.blocks.insert(); - this.api.caret.setToBlock(this.api.blocks.getCurrentBlockIndex()); - } - - /** - * Handle backspace - * - * @param {KeyboardEvent} event - keydown - */ - backspace(event: KeyboardEvent): void { - /** - * Caret is not at start of the item - * Then backspace button should remove letter as usual - */ - if (!Caret.isAtStart()) { - return; - } - - /** - * Prevent default backspace behaviour - */ - event.preventDefault(); - - const currentItem = this.currentItem; - if (!currentItem) { - return; - } - const previousItem = currentItem.previousSibling; - if (!currentItem.parentNode) { - return; - } - if (!isHtmlElement(currentItem.parentNode)) { - return; - } - const parentItem = currentItem.parentNode.closest(`.${this.CSS.item}`); - - /** - * Do nothing with the first item in the first-level list. - * No previous sibling means that this is the first item in the list. - * No parent item means that this is a first-level list. - * - * Before: - * 1. |Hello - * 2. World! - * - * After: - * 1. |Hello - * 2. World! - * - * If it this item and the while list is empty then editor.js should - * process this behaviour and remove the block completely - * - * Before: - * 1. | - * - * After: block has been removed - * - */ - if (!previousItem && !parentItem) { - return; - } - - // make sure previousItem is an HTMLElement - if (previousItem && !isHtmlElement(previousItem)) { - return; - } - - /** - * Prevent editor.js behaviour - */ - event.stopPropagation(); - - /** - * Lets compute the item which will be merged with current item text - */ - let targetItem: Element | null; - - /** - * If there is a previous item then we get a deepest item in its sublists - * - * Otherwise we will use the parent item - */ - if (previousItem) { - const childrenOfPreviousItem = previousItem.querySelectorAll( - `.${this.CSS.item}` - ); - - targetItem = Array.from(childrenOfPreviousItem).pop() || previousItem; - } else { - targetItem = parentItem; - } - - /** - * Get content from caret till the end of the block to move it to the new item - */ - const endingFragment = Caret.extractFragmentFromCaretPositionTillTheEnd(); - if (!endingFragment) { - return; - } - const endingHTML = Dom.fragmentToString(endingFragment); - - /** - * Get the target item content element - */ - if (!targetItem) { - return; - } - const targetItemContent = targetItem.querySelector( - `.${this.CSS.itemContent}` - ); - - /** - * Set a new place for caret - */ - if (!targetItemContent) { - return; - } - Caret.focus(targetItemContent, false); - - /** - * Save the caret position - */ - this.caret.save(); - - /** - * Update target item content by merging with current item html content - */ - targetItemContent.insertAdjacentHTML('beforeend', endingHTML); - - /** - * Get the sublist first-level items for current item - */ - let currentItemSublistItems: NodeListOf | Element[] = - currentItem.querySelectorAll( - `.${this.CSS.itemChildren} > .${this.CSS.item}` - ); - - /** - * Create an array from current item sublist items - */ - currentItemSublistItems = Array.from(currentItemSublistItems); - - /** - * Filter items for sublist first-level - * No need to move deeper items - */ - currentItemSublistItems = currentItemSublistItems.filter((node) => { - // make sure node.parentNode is an HTMLElement - if (!node.parentNode) { - return false; - } - if (!isHtmlElement(node.parentNode)) { - return false; - } - return node.parentNode.closest(`.${this.CSS.item}`) === currentItem; - }); - - /** - * Reverse the array to insert items - */ - currentItemSublistItems.reverse().forEach((item) => { - /** - * Check if we need to save the indent for current item children - * - * If this is the first item in the list then place its children to the same level as currentItem. - * Same as shift+tab for all of these children. - * - * If there is a previous sibling then place children right after target item - */ - if (!previousItem) { - /** - * The first item in the list - * - * Before: - * 1. Hello - * 1.1. |My - * 1.1.1. Wonderful - * 1.1.2. World - * - * After: - * 1. Hello|My - * 1.1. Wonderful - * 1.2. World - */ - currentItem.after(item); - } else { - /** - * Not the first item - * - * Before: - * 1. Hello - * 1.1. My - * 1.2. |Dear - * 1.2.1. Wonderful - * 1.2.2. World - * - * After: - * 1. Hello - * 1.1. My|Dear - * 1.2. Wonderful - * 1.3. World - */ - targetItem.after(item); - } - }); - - /** - * Remove current item element - */ - currentItem.remove(); - - /** - * Restore the caret position - */ - this.caret.restore(); - } - - /** - * Add indentation to current item - * - * @param {KeyboardEvent} event - keydown - */ - addTab(event: KeyboardEvent): void { - /** - * Prevent editor.js behaviour - */ - event.stopPropagation(); - - /** - * Prevent browser tab behaviour - */ - event.preventDefault(); - - const currentItem = this.currentItem; - if (!currentItem) { - return; - } - const prevItem = currentItem.previousSibling; - if (!prevItem) { - return; - } - if (!isHtmlElement(prevItem)) { - return; - } - const isFirstChild = !prevItem; - - /** - * In the first item we should not handle Tabs (because there is no parent item above) - */ - if (isFirstChild) { - return; - } - - const prevItemChildrenList = prevItem.querySelector( - `.${this.CSS.itemChildren}` - ); - - this.caret.save(); - - /** - * If prev item has child items, just append current to them - */ - if (prevItemChildrenList) { - prevItemChildrenList.appendChild(currentItem); - } else { - /** - * If prev item has no child items - * - Create and append children wrapper to the previous item - * - Append current item to it - */ - const sublistWrapper = this.makeListWrapper(undefined, [ - this.CSS.itemChildren, - ]); - const prevItemBody = prevItem.querySelector(`.${this.CSS.itemBody}`); - - sublistWrapper.appendChild(currentItem); - prevItemBody?.appendChild(sublistWrapper); - } - - this.caret.restore(); - } - - /** - * Reduce indentation for current item - * - * @param {KeyboardEvent} event - keydown - * @returns {void} - */ - shiftTab(event: KeyboardEvent): void { - /** - * Prevent editor.js behaviour - */ - event.stopPropagation(); - - /** - * Prevent browser tab behaviour - */ - event.preventDefault(); - - /** - * Move item from current list to parent list - */ - this.unshiftItem(); - } - - /** - * Convert from list to text for conversionConfig - * - * @param {ListData} data - * @returns {string} - */ - static joinRecursive(data: ListData | ListItem): string { - return data.items - .map((item) => `${item.content} ${NestedList.joinRecursive(item)}`) - .join(''); - } - - /** - * Convert from text to list with import and export list to text - */ - static get conversionConfig(): { - export: (data: ListData) => string; - import: (content: string) => ListData; - } { - return { - export: (data) => { - return NestedList.joinRecursive(data); - }, - import: (content) => { - return { - items: [ - { - content, - items: [], - }, - ], - style: 'unordered', - }; - }, - }; - } -} From 9064c1b9a0fb6858271833f19f141cc1af5ee858 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 19:08:36 +0300 Subject: [PATCH 20/43] comments improved in types --- src/types/itemMeta.ts | 3 +++ src/types/listParams.ts | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/types/itemMeta.ts b/src/types/itemMeta.ts index 7675b5eb..04ce56f1 100644 --- a/src/types/itemMeta.ts +++ b/src/types/itemMeta.ts @@ -1,3 +1,6 @@ +/** + * Meta information of each list item + */ export default interface ItemMeta { /** * State of the item diff --git a/src/types/listParams.ts b/src/types/listParams.ts index 2e0f24db..beaea1b9 100644 --- a/src/types/listParams.ts +++ b/src/types/listParams.ts @@ -28,6 +28,9 @@ export interface ListItem { */ content: string; + /** + * Meta information of each list item + */ meta: ItemMeta; /** @@ -45,8 +48,4 @@ export interface NestedListConfig { * default is unordered */ defaultStyle?: ListDataStyle; - - starts?: number; - - maxLevel?: number; } From d9110c9d1c97c9e5081f6dbdfbc746a946dc80cf Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 19:33:53 +0300 Subject: [PATCH 21/43] removed unneded for dev mode tools from index --- index.html | 174 ----------------------------------------------------- 1 file changed, 174 deletions(-) diff --git a/index.html b/index.html index a8b5b765..c371189c 100644 --- a/index.html +++ b/index.html @@ -55,20 +55,6 @@ Read more at Tools Connection doc: https://editorjs.io/getting-started#tools-connection --> - - - - - - - - - - - - - - @@ -107,20 +93,6 @@ /** * Each Tool is a Plugin. Pass them via 'class' option with necessary settings {@link docs/tools.md} */ - header: { - class: Header, - inlineToolbar: ['marker', 'link'], - config: { - placeholder: 'Header' - }, - shortcut: 'CMD+SHIFT+H' - }, - - /** - * Or pass class directly without any configuration - */ - image: SimpleImage, - nestedList: { class: NestedList, inlineToolbar: true, @@ -129,57 +101,6 @@ defaultStyle: 'checklist' } }, - - // list: { - // class: List, - // inlineToolbar: true, - // shortcut: 'CMD+SHIFT+L' - // }, - - checklist: { - class: Checklist, - inlineToolbar: true, - }, - - quote: { - class: Quote, - inlineToolbar: true, - config: { - quotePlaceholder: 'Enter a quote', - captionPlaceholder: 'Quote\'s author', - }, - shortcut: 'CMD+SHIFT+O' - }, - - warning: Warning, - - marker: { - class: Marker, - shortcut: 'CMD+SHIFT+M' - }, - - code: { - class: CodeTool, - shortcut: 'CMD+SHIFT+C' - }, - - delimiter: Delimiter, - - inlineCode: { - class: InlineCode, - shortcut: 'CMD+SHIFT+C' - }, - - linkTool: LinkTool, - - embed: Embed, - - table: { - class: Table, - inlineToolbar: true, - shortcut: 'CMD+ALT+T' - }, - }, /** @@ -264,101 +185,6 @@ style: 'checklist' } }, - { - type: "header", - data: { - text: "Editor.js", - level: 2 - } - }, - { - type : 'paragraph', - data : { - text : 'Hey. Meet the new Editor. On this page you can see it in action — try to edit this text. Source code of the page contains the example of connection and configuration.' - } - }, - { - type: "header", - data: { - text: "Key features", - level: 3 - } - }, - { - type : 'list', - data : { - items : [ - 'It is a block-styled editor', - 'It returns clean data output in JSON', - 'Designed to be extendable and pluggable with a simple API', - ], - style: 'unordered' - } - }, - { - type: "header", - data: { - text: "What does it mean «block-styled editor»", - level: 3 - } - }, - { - type : 'paragraph', - data : { - text : 'Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor\'s Core.' - } - }, - { - type : 'paragraph', - data : { - text : `There are dozens of ready-to-use Blocks and the simple API for creation any Block you need. For example, you can implement Blocks for Tweets, Instagram posts, surveys and polls, CTA-buttons and even games.` - } - }, - { - type: "header", - data: { - text: "What does it mean clean data output", - level: 3 - } - }, - { - type : 'paragraph', - data : { - text : 'Classic WYSIWYG-editors produce raw HTML-markup with both content data and content appearance. On the contrary, Editor.js outputs JSON object with data of each Block. You can see an example below' - } - }, - { - type : 'paragraph', - data : { - text : `Given data can be used as you want: render with HTML for Web clients, render natively for mobile apps, create markup for Facebook Instant Articles or Google AMP, generate an audio version and so on.` - } - }, - { - type : 'paragraph', - data : { - text : 'Clean data is useful to sanitize, validate and process on the backend.' - } - }, - { - type : 'delimiter', - data : {} - }, - { - type : 'paragraph', - data : { - text : 'We have been working on this project more than three years. Several large media projects help us to test and debug the Editor, to make its core more stable. At the same time we significantly improved the API. Now, it can be used to create any plugin for any task. Hope you enjoy. 😏' - } - }, - { - type: 'image', - data: { - url: '/example/assets/codex2x.png', - caption: '', - stretched: false, - withBorder: true, - withBackground: false, - } - }, ] }, onReady: function(){ From f899714655683ccb712357f1346628605678d46b Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 19:45:38 +0300 Subject: [PATCH 22/43] for ordered and unordered list item meta would be {} --- src/ListRenderer/orderedListRenderer.ts | 4 +--- src/ListRenderer/unorderedListRenderer.ts | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts index b4b8ef9d..e80c8b4d 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/orderedListRenderer.ts @@ -84,8 +84,6 @@ export class OrderedListRenderer extends ListRenderer { * @returns Item meta object */ getItemMeta(): ItemMeta { - return { - checked: undefined, - } + return {} } } diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts index cdee8fac..9053aa1a 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -88,8 +88,6 @@ export class UnorderedListRenderer extends ListRenderer { * @returns Item meta object */ getItemMeta(): ItemMeta { - return { - checked: undefined, - } + return {} } } From fdac5a2cb1462f18507f861bef9e1f1bc8999b51 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 20:01:55 +0300 Subject: [PATCH 23/43] added private class properties --- src/ListRenderer/checklistRenderer.ts | 4 +-- src/ListRenderer/index.ts | 8 ++--- src/ListRenderer/orderedListRenderer.ts | 4 +-- src/ListRenderer/unorderedListRenderer.ts | 4 +-- src/ListTabulator/index.ts | 38 +++++++++++------------ styles/index.pcss | 16 +++++----- 6 files changed, 36 insertions(+), 38 deletions(-) diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index 0b5653c0..9be3a4a6 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -2,7 +2,7 @@ import { IconCheck } from '@codexteam/icons' import ItemMeta from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; -import { ListRenderer } from './listRenderer'; +import { ListRenderer } from './ListRenderer'; /** * Class that is responsible for checklist rendering @@ -16,7 +16,7 @@ export class CheckListRenderer extends ListRenderer { /** * Is NestedList Tool read-only option */ - readOnly: boolean; + private readOnly: boolean; constructor(readonly: boolean, config?: NestedListConfig) { super(); diff --git a/src/ListRenderer/index.ts b/src/ListRenderer/index.ts index 6f3d423b..72b35547 100644 --- a/src/ListRenderer/index.ts +++ b/src/ListRenderer/index.ts @@ -1,6 +1,6 @@ -import { CheckListRenderer } from "./checklistRenderer"; -import { OrderedListRenderer } from "./orderedListRenderer"; -import { UnorderedListRenderer } from "./unorderedListRenderer"; -import { ListRenderer } from './listRenderer'; +import { CheckListRenderer } from "./ChecklistRenderer"; +import { OrderedListRenderer } from "./OrderedListRenderer"; +import { UnorderedListRenderer } from "./UnorderedListRenderer"; +import { ListRenderer } from './ListRenderer'; export { CheckListRenderer, OrderedListRenderer, UnorderedListRenderer, ListRenderer }; diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts index e80c8b4d..2b2e4e81 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/orderedListRenderer.ts @@ -1,7 +1,7 @@ import ItemMeta from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; -import { ListRenderer } from './listRenderer'; +import { ListRenderer } from './ListRenderer'; /** * Class that is responsible for ordered list rendering @@ -15,7 +15,7 @@ export class OrderedListRenderer extends ListRenderer { /** * Is NestedList Tool read-only option */ - readOnly: boolean; + private readOnly: boolean; constructor(readonly: boolean, config?: NestedListConfig) { super(); diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts index 9053aa1a..78164cc1 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -1,7 +1,7 @@ import ItemMeta from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; -import { ListRenderer } from './listRenderer'; +import { ListRenderer } from './ListRenderer'; /** * Class that is responsible for unordered list rendering @@ -15,7 +15,7 @@ export class UnorderedListRenderer extends ListRenderer { /** * Is NestedList Tool read-only option */ - readOnly: boolean; + private readOnly: boolean; constructor(readonly: boolean, config?: NestedListConfig) { super(); diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index 84df292e..d4824d2c 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -1,6 +1,6 @@ -import { CheckListRenderer } from "../ListRenderer/checklistRenderer"; -import { OrderedListRenderer } from "../ListRenderer/orderedListRenderer"; -import { UnorderedListRenderer } from "../ListRenderer/unorderedListRenderer"; +import { CheckListRenderer } from "../ListRenderer/ChecklistRenderer"; +import { OrderedListRenderer } from "../ListRenderer/OrderedListRenderer"; +import { UnorderedListRenderer } from "../ListRenderer/UnorderedListRenderer"; import { NestedListConfig, ListData, ListDataStyle } from "../types/listParams" import { ListItem } from "../types/listParams"; import { isHtmlElement } from '../utils/type-guards'; @@ -20,44 +20,44 @@ type ListRendererTypes = OrderedListRenderer | UnorderedListRenderer | CheckList */ export default class ListTabulator { /** - * Tool's configuration + * The Editor.js API */ - config?: NestedListConfig; + private api: API; /** - * Style of the nested list + * Caret helper */ - style: NestedListStyle; + private caret: Caret; /** - * Full content of the list + * Is NestedList Tool read-only option */ - data: ListData; + private readOnly: boolean; /** - * Rendered list of items + * Tool's configuration */ - list: ListRendererTypes | undefined; + private config?: NestedListConfig; /** - * Wrapper of the whole list + * Full content of the list */ - listWrapper: HTMLElement | undefined; + private data: ListData; /** - * Caret helper + * Style of the nested list */ - private caret: Caret; + style: NestedListStyle; /** - * Is NestedList Tool read-only option + * Rendered list of items */ - readOnly: boolean; + list: ListRendererTypes | undefined; /** - * The Editor.js API + * Wrapper of the whole list */ - api: API; + listWrapper: HTMLElement | undefined; /** * Returns current List item by the caret position diff --git a/styles/index.pcss b/styles/index.pcss index 69bd9fcd..9df172fa 100644 --- a/styles/index.pcss +++ b/styles/index.pcss @@ -11,8 +11,7 @@ --line-height: 1.57em; --color-bg-checked-hover: #0059AB; --color-tick: #fff; - --width-checkbox: 20px; - --height-checkbox: 20px; + --size-checkbox: 20px; &__item { line-height: 1.6em; @@ -67,20 +66,19 @@ } &__checkbox { - width: var(--width-checkbox); - height: var(--height-checkbox); + width: var(--size-checkbox); + height: var(--size-checkbox); display: flex; align-items: center; cursor: pointer; svg { opacity: 0; - height: 20px; - width: 20px; + height: var(--size-checkbox); + width: var(--size-checkbox); left: -1px; top: -1px; position: absolute; - max-height: 20px; } @media (hover: hover) { @@ -130,8 +128,8 @@ display: inline-block; position: relative; margin: 0 auto; - width: 20px; - height: 20px; + width: var(--size-checkbox); + height: var(--size-checkbox); box-sizing: border-box; border-radius: var(--radius-border); border: 1px solid var(--color-border); From 78d8f3211c0e96e79f7a5a8c3c049980e5cab50b Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 20:07:40 +0300 Subject: [PATCH 24/43] moved click event delegation from `toggleCheckbox()` --- src/ListRenderer/checklistRenderer.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index 9be3a4a6..bc6c7439 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -31,8 +31,18 @@ export class CheckListRenderer extends ListRenderer { renderWrapper(): HTMLOListElement { const listWrapper = Dom.make('ul', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperChecklist]) as HTMLOListElement; + /** + * Listen to clicks on checkbox + */ listWrapper.addEventListener('click', (event) => { - this.toggleCheckbox(event); + const target = event.target as Element; + if (target){ + const checkbox = target.closest(`.${ListRenderer.CSS.checkboxContainer}`); + + if (checkbox && checkbox.contains(target)) { + this.toggleCheckbox(checkbox); + } + } }); return listWrapper; @@ -117,15 +127,11 @@ export class CheckListRenderer extends ListRenderer { * @param {MouseEvent} event - click * @returns {void} */ - private toggleCheckbox(event: any): void { - const checkbox = event.target.closest(`.${ListRenderer.CSS.checkboxContainer}`); - - if (checkbox && checkbox.contains(event.target)) { - checkbox.classList.toggle(ListRenderer.CSS.itemChecked); - checkbox.classList.add(ListRenderer.CSS.noHover); - checkbox.addEventListener('mouseleave', () => this.removeSpecialHoverBehavior(checkbox), { once: true }); + private toggleCheckbox(checkbox: Element): void { + checkbox.classList.toggle(ListRenderer.CSS.itemChecked); + checkbox.classList.add(ListRenderer.CSS.noHover); + checkbox.addEventListener('mouseleave', () => this.removeSpecialHoverBehavior(checkbox), { once: true }); } - } /** * Removes class responsible for special hover behavior on an item @@ -134,7 +140,7 @@ export class CheckListRenderer extends ListRenderer { * @param {Element} el - item wrapper * @returns {Element} */ - private removeSpecialHoverBehavior(el: HTMLElement) { + private removeSpecialHoverBehavior(el: Element) { el.classList.remove(ListRenderer.CSS.noHover); } } From 8de6984b3f49e31b0ee57cf0c24849051283f585 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 20:15:17 +0300 Subject: [PATCH 25/43] separated types --- src/ListRenderer/checklistRenderer.ts | 8 ++++---- src/ListRenderer/orderedListRenderer.ts | 6 +++--- src/ListRenderer/unorderedListRenderer.ts | 6 +++--- src/ListTabulator/index.ts | 2 +- src/types/itemMeta.ts | 17 +++++++++++++---- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index bc6c7439..6b5347c5 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -1,5 +1,5 @@ import { IconCheck } from '@codexteam/icons' -import ItemMeta from "../types/itemMeta"; +import type { ChecklistItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; @@ -63,7 +63,7 @@ export class CheckListRenderer extends ListRenderer { * @param content - content of the list item * @returns - created html list item element */ - renderItem(content: string, meta: ItemMeta): HTMLLIElement { + renderItem(content: string, meta: ChecklistItemMeta ): HTMLLIElement { const itemWrapper = Dom.make('li', [ListRenderer.CSS.item, ListRenderer.CSS.item]); const itemBody = Dom.make('div', ListRenderer.CSS.itemBody); const itemContent = Dom.make('div', ListRenderer.CSS.itemContent, { @@ -112,11 +112,11 @@ export class CheckListRenderer extends ListRenderer { * @param {Element} item - item of the list to get meta from * @returns {ItemMeta} Item meta object */ - getItemMeta(item: Element): ItemMeta { + getItemMeta(item: Element): ChecklistItemMeta { const checkbox = item.querySelector(`.${ListRenderer.CSS.checkboxContainer}`); return { - checked: checkbox ? checkbox.classList.contains(ListRenderer.CSS.itemChecked) : undefined, + checked: checkbox ? checkbox.classList.contains(ListRenderer.CSS.itemChecked) : false, } } diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts index 2b2e4e81..a5a5451b 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/orderedListRenderer.ts @@ -1,4 +1,4 @@ -import ItemMeta from "../types/itemMeta"; +import type { OrderedListItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; @@ -80,10 +80,10 @@ export class OrderedListRenderer extends ListRenderer { } /** - * Returns item meta, for ordered list checked will be always undefined + * Returns item meta, for ordered list * @returns Item meta object */ - getItemMeta(): ItemMeta { + getItemMeta(): OrderedListItemMeta { return {} } } diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts index 78164cc1..5e299e1c 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -1,4 +1,4 @@ -import ItemMeta from "../types/itemMeta"; +import type { UnorderedListItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; @@ -84,10 +84,10 @@ export class UnorderedListRenderer extends ListRenderer { /** - * Returns item meta, for undered list checked will be always undefined + * Returns item meta, for unordered list * @returns Item meta object */ - getItemMeta(): ItemMeta { + getItemMeta(): UnorderedListItemMeta { return {} } } diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index d4824d2c..949f910f 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -375,7 +375,7 @@ export default class ListTabulator { /** * Create the new list item */ - const itemEl = this.list!.renderItem(endingHTML, {}); + const itemEl = this.list!.renderItem(endingHTML, { checked: false }); /** * Check if child items exist diff --git a/src/types/itemMeta.ts b/src/types/itemMeta.ts index 04ce56f1..c02696e5 100644 --- a/src/types/itemMeta.ts +++ b/src/types/itemMeta.ts @@ -1,9 +1,18 @@ /** * Meta information of each list item */ -export default interface ItemMeta { +interface ItemMeta {}; + +/** + * Meta information of checklist item + */ +export interface ChecklistItemMeta extends ItemMeta { /** - * State of the item + * State of the checkbox of the item */ - checked?: boolean; -} + checked: boolean; +}; + +export interface OrderedListItemMeta extends ItemMeta {}; + +export interface UnorderedListItemMeta extends ItemMeta {}; From b4cf7388dbf63315f9a7fa3da778f1708609e7b3 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 20:30:01 +0300 Subject: [PATCH 26/43] added `currentLevel` property for ListTabulator --- src/ListRenderer/checklistRenderer.ts | 48 +++++++++++------------ src/ListRenderer/orderedListRenderer.ts | 23 +++++------ src/ListRenderer/unorderedListRenderer.ts | 30 +++++++------- src/ListTabulator/index.ts | 16 +++++++- 4 files changed, 64 insertions(+), 53 deletions(-) diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index 6b5347c5..ee7779fc 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -26,36 +26,36 @@ export class CheckListRenderer extends ListRenderer { /** * Renders ol wrapper for list - * @returns - created html ol element + * @param level - level of nesting (0 for the rool level) + * @returns - created html ul element */ - renderWrapper(): HTMLOListElement { - const listWrapper = Dom.make('ul', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperChecklist]) as HTMLOListElement; + renderWrapper(level: number): HTMLUListElement { + let wrapperElement: HTMLUListElement; /** - * Listen to clicks on checkbox + * Check if it's root level */ - listWrapper.addEventListener('click', (event) => { - const target = event.target as Element; - if (target){ - const checkbox = target.closest(`.${ListRenderer.CSS.checkboxContainer}`); - - if (checkbox && checkbox.contains(target)) { - this.toggleCheckbox(checkbox); + if (level === 0) { + wrapperElement = Dom.make('ul', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperChecklist]) as HTMLUListElement; + + /** + * Listen to clicks on checkbox + */ + wrapperElement.addEventListener('click', (event) => { + const target = event.target as Element; + if (target){ + const checkbox = target.closest(`.${ListRenderer.CSS.checkboxContainer}`); + + if (checkbox && checkbox.contains(target)) { + this.toggleCheckbox(checkbox); + } } - } - }); - - return listWrapper; - } - - /** - * Render wrapper of child list - * @returns wrapper element of the child list - */ - renderSublistWrapper(): HTMLElement { - const divElement = Dom.make('ul', [ListRenderer.CSS.wrapperChecklist, ListRenderer.CSS.itemChildren]) as HTMLElement; + }); + } else { + wrapperElement = Dom.make('ul', [ListRenderer.CSS.wrapperChecklist, ListRenderer.CSS.itemChildren]) as HTMLUListElement; + } - return divElement; + return wrapperElement; } /** diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts index a5a5451b..146a554b 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/orderedListRenderer.ts @@ -25,22 +25,23 @@ export class OrderedListRenderer extends ListRenderer { /** * Renders ol wrapper for list + * @param level - level of nesting (0 for the rool level) * @returns - created html ol element */ - renderWrapper(): HTMLOListElement { - return Dom.make('ol', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperOrdered]) as HTMLOListElement; - } + renderWrapper(level: number): HTMLOListElement { + let wrapperElement: HTMLOListElement; - /** - * Render wrapper of child list - * @returns wrapper element of the child list - */ - renderSublistWrapper(): HTMLElement { - const divElement = Dom.make('ol', [ListRenderer.CSS.wrapperOrdered, ListRenderer.CSS.itemChildren]) as HTMLElement; + /** + * Check if it's root level + */ + if (level === 0) { + wrapperElement = Dom.make('ol', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperUnordered]) as HTMLOListElement; + } else { + wrapperElement = Dom.make('ol', [ListRenderer.CSS.wrapperUnordered, ListRenderer.CSS.itemChildren]) as HTMLOListElement; + } - return divElement; + return wrapperElement; } - /** * Redners list item element * @param content - content of the list item diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts index 5e299e1c..1fc6f5f7 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -25,23 +25,22 @@ export class UnorderedListRenderer extends ListRenderer { /** * Renders ol wrapper for list - * @returns - created html ol element + * @param level - level of nesting (0 for the rool level) + * @returns - created html ul element */ - renderWrapper(): HTMLOListElement { - - const ulElement = Dom.make('ul', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperUnordered]) as HTMLOListElement; - - return ulElement; - } - - /** - * Render wrapper of child list - * @returns wrapper element of the child list - */ - renderSublistWrapper(): HTMLElement { - const divElement = Dom.make('ul', [ListRenderer.CSS.wrapperUnordered, ListRenderer.CSS.itemChildren]) as HTMLElement; + renderWrapper(level: number): HTMLUListElement { + let wrapperElement: HTMLUListElement; + + /** + * Check if it's root level + */ + if (level === 0) { + wrapperElement = Dom.make('ul', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperUnordered]) as HTMLUListElement; + } else { + wrapperElement = Dom.make('ul', [ListRenderer.CSS.wrapperUnordered, ListRenderer.CSS.itemChildren]) as HTMLUListElement; + } - return divElement; + return wrapperElement; } /** @@ -82,7 +81,6 @@ export class UnorderedListRenderer extends ListRenderer { return contentNode.innerHTML; } - /** * Returns item meta, for unordered list * @returns Item meta object diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index 949f910f..fe45a642 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -44,6 +44,11 @@ export default class ListTabulator { */ private data: ListData; + /** + * Current level of nesting for dynamyc updates + */ + private currentLevel: number; + /** * Style of the nested list */ @@ -95,6 +100,7 @@ export default class ListTabulator { this.style = style; this.readOnly = readOnly; this.api = api; + this.currentLevel = 0; /** * Instantiate caret helper @@ -119,7 +125,7 @@ export default class ListTabulator { break } - this.listWrapper = this.list.renderWrapper(); + this.listWrapper = this.list.renderWrapper(this.currentLevel); // fill with data if (this.data.items.length) { @@ -174,6 +180,11 @@ export default class ListTabulator { * @returns {void} */ appendItems(items: ListItem[], parentItem: Element): void { + /** + * Update current nesting level + */ + this.currentLevel += 1; + if (this.list !== undefined) { items.forEach((item) => { const itemEl = this.list!.renderItem(item.content, item.meta); @@ -181,8 +192,9 @@ export default class ListTabulator { parentItem.appendChild(itemEl); if (item.items.length) { - const sublistWrapper = this.list?.renderSublistWrapper() + const sublistWrapper = this.list?.renderWrapper(this.currentLevel) this.appendItems(item.items, sublistWrapper!); + this.currentLevel -= 1; const itemBody = itemEl.querySelector(`.${ListRenderer.CSS.itemBody}`); From 91b7cfa00a9a42bfdcf4fbfeb246aab1130acdb6 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 20:35:17 +0300 Subject: [PATCH 27/43] minor fix --- src/ListTabulator/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index fe45a642..792224df 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -752,7 +752,7 @@ export default class ListTabulator { * - Create and append children wrapper to the previous item * - Append current item to it */ - const sublistWrapper = this.list!.renderSublistWrapper(); + const sublistWrapper = this.list!.renderWrapper(1); const prevItemBody = prevItem.querySelector(`.${ListRenderer.CSS.itemBody}`); sublistWrapper.appendChild(currentItem); From cf251668279f318c6c2623b706de44fbfba91823 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 21:42:28 +0300 Subject: [PATCH 28/43] separated css styles for different classes --- src/ListRenderer/checklistRenderer.ts | 56 +++++++++++++++++------ src/ListRenderer/listRenderer.ts | 20 +------- src/ListRenderer/orderedListRenderer.ts | 35 +++++++++++--- src/ListRenderer/unorderedListRenderer.ts | 33 ++++++++++--- src/ListTabulator/index.ts | 13 +++++- src/types/listParams.ts | 4 +- 6 files changed, 111 insertions(+), 50 deletions(-) diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/checklistRenderer.ts index ee7779fc..694065e8 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/checklistRenderer.ts @@ -4,6 +4,19 @@ import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; +interface ChecklistCssClasses { + wrapper: string; + wrapperChecklist: string; + item: string; + itemBody: string; + itemContent: string; + itemChildren: string; + itemChecked: string; + noHover: string; + checkbox: string; + checkboxContainer: string; +} + /** * Class that is responsible for checklist rendering */ @@ -18,6 +31,19 @@ export class CheckListRenderer extends ListRenderer { */ private readOnly: boolean; + static get CSS(): ChecklistCssClasses { + const listCssClasses = super.CSS; + + return { + ...listCssClasses, + wrapperChecklist: 'cdx-nested-list--checklist', + itemChecked: 'cdx-nested-list__checkbox--checked', + noHover: 'cdx-nested-list__checkbox--no-hover', + checkbox: 'cdx-nested-list__checkbox-check', + checkboxContainer: 'cdx-nested-list__checkbox' + } + } + constructor(readonly: boolean, config?: NestedListConfig) { super(); this.config = config; @@ -36,7 +62,7 @@ export class CheckListRenderer extends ListRenderer { * Check if it's root level */ if (level === 0) { - wrapperElement = Dom.make('ul', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperChecklist]) as HTMLUListElement; + wrapperElement = Dom.make('ul', [CheckListRenderer.CSS.wrapper, CheckListRenderer.CSS.wrapperChecklist]) as HTMLUListElement; /** * Listen to clicks on checkbox @@ -44,7 +70,7 @@ export class CheckListRenderer extends ListRenderer { wrapperElement.addEventListener('click', (event) => { const target = event.target as Element; if (target){ - const checkbox = target.closest(`.${ListRenderer.CSS.checkboxContainer}`); + const checkbox = target.closest(`.${CheckListRenderer.CSS.checkboxContainer}`); if (checkbox && checkbox.contains(target)) { this.toggleCheckbox(checkbox); @@ -52,7 +78,7 @@ export class CheckListRenderer extends ListRenderer { } }); } else { - wrapperElement = Dom.make('ul', [ListRenderer.CSS.wrapperChecklist, ListRenderer.CSS.itemChildren]) as HTMLUListElement; + wrapperElement = Dom.make('ul', [CheckListRenderer.CSS.wrapperChecklist, CheckListRenderer.CSS.itemChildren]) as HTMLUListElement; } return wrapperElement; @@ -64,18 +90,18 @@ export class CheckListRenderer extends ListRenderer { * @returns - created html list item element */ renderItem(content: string, meta: ChecklistItemMeta ): HTMLLIElement { - const itemWrapper = Dom.make('li', [ListRenderer.CSS.item, ListRenderer.CSS.item]); - const itemBody = Dom.make('div', ListRenderer.CSS.itemBody); - const itemContent = Dom.make('div', ListRenderer.CSS.itemContent, { + const itemWrapper = Dom.make('li', [CheckListRenderer.CSS.item, CheckListRenderer.CSS.item]); + const itemBody = Dom.make('div', CheckListRenderer.CSS.itemBody); + const itemContent = Dom.make('div', CheckListRenderer.CSS.itemContent, { innerHTML: content, contentEditable: (!this.readOnly).toString(), }); - const checkbox = Dom.make('span', ListRenderer.CSS.checkbox); - const checkboxContainer = Dom.make('div', ListRenderer.CSS.checkboxContainer); + const checkbox = Dom.make('span', CheckListRenderer.CSS.checkbox); + const checkboxContainer = Dom.make('div', CheckListRenderer.CSS.checkboxContainer); if (meta && meta.checked === true) { - checkboxContainer.classList.add(ListRenderer.CSS.itemChecked); + checkboxContainer.classList.add(CheckListRenderer.CSS.itemChecked); } checkbox.innerHTML = IconCheck; @@ -95,7 +121,7 @@ export class CheckListRenderer extends ListRenderer { * @returns {string} */ getItemContent(item: Element): string { - const contentNode = item.querySelector(`.${ListRenderer.CSS.itemContent}`); + const contentNode = item.querySelector(`.${CheckListRenderer.CSS.itemContent}`); if (!contentNode) { return ''; } @@ -113,10 +139,10 @@ export class CheckListRenderer extends ListRenderer { * @returns {ItemMeta} Item meta object */ getItemMeta(item: Element): ChecklistItemMeta { - const checkbox = item.querySelector(`.${ListRenderer.CSS.checkboxContainer}`); + const checkbox = item.querySelector(`.${CheckListRenderer.CSS.checkboxContainer}`); return { - checked: checkbox ? checkbox.classList.contains(ListRenderer.CSS.itemChecked) : false, + checked: checkbox ? checkbox.classList.contains(CheckListRenderer.CSS.itemChecked) : false, } } @@ -128,8 +154,8 @@ export class CheckListRenderer extends ListRenderer { * @returns {void} */ private toggleCheckbox(checkbox: Element): void { - checkbox.classList.toggle(ListRenderer.CSS.itemChecked); - checkbox.classList.add(ListRenderer.CSS.noHover); + checkbox.classList.toggle(CheckListRenderer.CSS.itemChecked); + checkbox.classList.add(CheckListRenderer.CSS.noHover); checkbox.addEventListener('mouseleave', () => this.removeSpecialHoverBehavior(checkbox), { once: true }); } @@ -141,7 +167,7 @@ export class CheckListRenderer extends ListRenderer { * @returns {Element} */ private removeSpecialHoverBehavior(el: Element) { - el.classList.remove(ListRenderer.CSS.noHover); + el.classList.remove(CheckListRenderer.CSS.noHover); } } diff --git a/src/ListRenderer/listRenderer.ts b/src/ListRenderer/listRenderer.ts index 91c111cf..c426e707 100644 --- a/src/ListRenderer/listRenderer.ts +++ b/src/ListRenderer/listRenderer.ts @@ -1,20 +1,12 @@ /** * CSS classes for the Nested List Tool */ -interface NestedListCssClasses { +interface ListCssClasses { wrapper: string; - wrapperOrdered: string; - wrapperUnordered: string; - wrapperChecklist: string; item: string; itemBody: string; itemContent: string; itemChildren: string; - settingsWrapper: string; - itemChecked: string; - noHover: string; - checkbox: string; - checkboxContainer: string; } /** @@ -28,21 +20,13 @@ export abstract class ListRenderer { * @returns {NestedListCssClasses} - CSS classes names by keys * @private */ - static get CSS(): NestedListCssClasses { + static get CSS(): ListCssClasses { return { wrapper: 'cdx-nested-list', - wrapperOrdered: 'cdx-nested-list--ordered', - wrapperUnordered: 'cdx-nested-list--unordered', - wrapperChecklist: 'cdx-nested-list--checklist', item: 'cdx-nested-list__item', itemBody: 'cdx-nested-list__item-body', itemContent: 'cdx-nested-list__item-content', itemChildren: 'cdx-nested-list__item-children', - settingsWrapper: 'cdx-nested-list__settings', - itemChecked: 'cdx-nested-list__checkbox--checked', - noHover: 'cdx-nested-list__checkbox--no-hover', - checkbox: 'cdx-nested-list__checkbox-check', - checkboxContainer: 'cdx-nested-list__checkbox' }; } } diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/orderedListRenderer.ts index 146a554b..b67bb521 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/orderedListRenderer.ts @@ -3,6 +3,18 @@ import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; +/** + * CSS classes for the Ordered list + */ +interface OrderedListCssClasses { + wrapper: string; + wrapperOrdered: string; + item: string; + itemBody: string; + itemContent: string; + itemChildren: string; +} + /** * Class that is responsible for ordered list rendering */ @@ -17,6 +29,15 @@ export class OrderedListRenderer extends ListRenderer { */ private readOnly: boolean; + static get CSS(): OrderedListCssClasses { + const listCssClasses = super.CSS; + + return { + ...listCssClasses, + wrapperOrdered: 'cdx-nested-list--ordered', + } + } + constructor(readonly: boolean, config?: NestedListConfig) { super(); this.config = config; @@ -35,9 +56,9 @@ export class OrderedListRenderer extends ListRenderer { * Check if it's root level */ if (level === 0) { - wrapperElement = Dom.make('ol', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperUnordered]) as HTMLOListElement; + wrapperElement = Dom.make('ol', [OrderedListRenderer.CSS.wrapper, OrderedListRenderer.CSS.wrapperOrdered]) as HTMLOListElement; } else { - wrapperElement = Dom.make('ol', [ListRenderer.CSS.wrapperUnordered, ListRenderer.CSS.itemChildren]) as HTMLOListElement; + wrapperElement = Dom.make('ol', [OrderedListRenderer.CSS.wrapperOrdered, OrderedListRenderer.CSS.itemChildren]) as HTMLOListElement; } return wrapperElement; @@ -47,10 +68,10 @@ export class OrderedListRenderer extends ListRenderer { * @param content - content of the list item * @returns - created html list item element */ - renderItem(content: string): HTMLLIElement { - const itemWrapper = Dom.make('li', ListRenderer.CSS.item); - const itemBody = Dom.make('div', ListRenderer.CSS.itemBody); - const itemContent = Dom.make('div', ListRenderer.CSS.itemContent, { + renderItem(content: string, meta: OrderedListItemMeta): HTMLLIElement { + const itemWrapper = Dom.make('li', OrderedListRenderer.CSS.item); + const itemBody = Dom.make('div', OrderedListRenderer.CSS.itemBody); + const itemContent = Dom.make('div', OrderedListRenderer.CSS.itemContent, { innerHTML: content, contentEditable: (!this.readOnly).toString(), }); @@ -68,7 +89,7 @@ export class OrderedListRenderer extends ListRenderer { * @returns {string} */ getItemContent(item: Element): string { - const contentNode = item.querySelector(`.${ListRenderer.CSS.itemContent}`); + const contentNode = item.querySelector(`.${OrderedListRenderer.CSS.itemContent}`); if (!contentNode) { return ''; } diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/unorderedListRenderer.ts index 1fc6f5f7..8a627260 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/unorderedListRenderer.ts @@ -3,6 +3,15 @@ import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; +interface UnoderedListCssClasses { + wrapper: string; + wrapperUnordered: string; + item: string; + itemBody: string; + itemContent: string; + itemChildren: string; +} + /** * Class that is responsible for unordered list rendering */ @@ -17,6 +26,16 @@ export class UnorderedListRenderer extends ListRenderer { */ private readOnly: boolean; + + static get CSS(): UnoderedListCssClasses { + const listCssClasses = super.CSS; + + return { + ...listCssClasses, + wrapperUnordered: 'cdx-nested-list--unordered', + } + } + constructor(readonly: boolean, config?: NestedListConfig) { super(); this.config = config; @@ -35,9 +54,9 @@ export class UnorderedListRenderer extends ListRenderer { * Check if it's root level */ if (level === 0) { - wrapperElement = Dom.make('ul', [ListRenderer.CSS.wrapper, ListRenderer.CSS.wrapperUnordered]) as HTMLUListElement; + wrapperElement = Dom.make('ul', [UnorderedListRenderer.CSS.wrapper, UnorderedListRenderer.CSS.wrapperUnordered]) as HTMLUListElement; } else { - wrapperElement = Dom.make('ul', [ListRenderer.CSS.wrapperUnordered, ListRenderer.CSS.itemChildren]) as HTMLUListElement; + wrapperElement = Dom.make('ul', [UnorderedListRenderer.CSS.wrapperUnordered, UnorderedListRenderer.CSS.itemChildren]) as HTMLUListElement; } return wrapperElement; @@ -48,10 +67,10 @@ export class UnorderedListRenderer extends ListRenderer { * @param content - content of the list item * @returns - created html list item element */ - renderItem(content: string): HTMLLIElement { - const itemWrapper = Dom.make('li', ListRenderer.CSS.item); - const itemBody = Dom.make('div', ListRenderer.CSS.itemBody); - const itemContent = Dom.make('div', ListRenderer.CSS.itemContent, { + renderItem(content: string, meta: UnorderedListItemMeta): HTMLLIElement { + const itemWrapper = Dom.make('li', UnorderedListRenderer.CSS.item); + const itemBody = Dom.make('div', UnorderedListRenderer.CSS.itemBody); + const itemContent = Dom.make('div', UnorderedListRenderer.CSS.itemContent, { innerHTML: content, contentEditable: (!this.readOnly).toString(), }); @@ -69,7 +88,7 @@ export class UnorderedListRenderer extends ListRenderer { * @returns {string} */ getItemContent(item: Element): string { - const contentNode = item.querySelector(`.${ListRenderer.CSS.itemContent}`); + const contentNode = item.querySelector(`.${UnorderedListRenderer.CSS.itemContent}`); if (!contentNode) { return ''; } diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index 792224df..df2aea08 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -10,6 +10,7 @@ import * as Dom from '../utils/dom' import type { PasteEvent } from '../types'; import type { API, PasteConfig } from '@editorjs/editorjs'; import { NestedListParams } from ".."; +import { ChecklistItemMeta, OrderedListItemMeta, UnorderedListItemMeta } from "../types/itemMeta"; type NestedListStyle = 'ordered' | 'unordered' | 'checklist'; @@ -187,7 +188,17 @@ export default class ListTabulator { if (this.list !== undefined) { items.forEach((item) => { - const itemEl = this.list!.renderItem(item.content, item.meta); + let itemEl: Element; + + if (this.list instanceof OrderedListRenderer) { + itemEl = this.list!.renderItem(item.content, item.meta as OrderedListItemMeta); + } + else if (this.list instanceof UnorderedListRenderer) { + itemEl = this.list!.renderItem(item.content, item.meta as UnorderedListItemMeta); + } + else { + itemEl = this.list!.renderItem(item.content, item.meta as ChecklistItemMeta); + } parentItem.appendChild(itemEl); diff --git a/src/types/listParams.ts b/src/types/listParams.ts index beaea1b9..182c56d6 100644 --- a/src/types/listParams.ts +++ b/src/types/listParams.ts @@ -1,4 +1,4 @@ -import ItemMeta from "./itemMeta"; +import { ChecklistItemMeta, OrderedListItemMeta, UnorderedListItemMeta } from "./itemMeta"; /** * list style to make list as ordered or unordered @@ -31,7 +31,7 @@ export interface ListItem { /** * Meta information of each list item */ - meta: ItemMeta; + meta: OrderedListItemMeta | UnorderedListItemMeta | ChecklistItemMeta; /** * sublist items From 717f079b6c0949e1ddd116a55809ac74f26d8e61 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 21:44:22 +0300 Subject: [PATCH 29/43] comments improved --- src/types/itemMeta.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/types/itemMeta.ts b/src/types/itemMeta.ts index c02696e5..f4af71f4 100644 --- a/src/types/itemMeta.ts +++ b/src/types/itemMeta.ts @@ -13,6 +13,12 @@ export interface ChecklistItemMeta extends ItemMeta { checked: boolean; }; +/** + * Meta information of ordered list item + */ export interface OrderedListItemMeta extends ItemMeta {}; +/** + * Meta information of unordered list item + */ export interface UnorderedListItemMeta extends ItemMeta {}; From 99eebd67783b1123478586fecb2dfbb0f00c26fd Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 21:46:50 +0300 Subject: [PATCH 30/43] files renaming --- .../{checklistRenderer.ts => Checklist__Renderer.ts} | 2 +- src/ListRenderer/{listRenderer.ts => List_Renderer.ts} | 0 .../{orderedListRenderer.ts => Order_edListRenderer.ts} | 2 +- ...unorderedListRenderer.ts => UnorderedListRendere_r.ts} | 2 +- src/ListRenderer/index.ts | 8 ++++---- src/ListTabulator/index.ts | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) rename src/ListRenderer/{checklistRenderer.ts => Checklist__Renderer.ts} (99%) rename src/ListRenderer/{listRenderer.ts => List_Renderer.ts} (100%) rename src/ListRenderer/{orderedListRenderer.ts => Order_edListRenderer.ts} (98%) rename src/ListRenderer/{unorderedListRenderer.ts => UnorderedListRendere_r.ts} (98%) diff --git a/src/ListRenderer/checklistRenderer.ts b/src/ListRenderer/Checklist__Renderer.ts similarity index 99% rename from src/ListRenderer/checklistRenderer.ts rename to src/ListRenderer/Checklist__Renderer.ts index 694065e8..faf11f75 100644 --- a/src/ListRenderer/checklistRenderer.ts +++ b/src/ListRenderer/Checklist__Renderer.ts @@ -2,7 +2,7 @@ import { IconCheck } from '@codexteam/icons' import type { ChecklistItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; -import { ListRenderer } from './ListRenderer'; +import { ListRenderer } from './List_Renderer'; interface ChecklistCssClasses { wrapper: string; diff --git a/src/ListRenderer/listRenderer.ts b/src/ListRenderer/List_Renderer.ts similarity index 100% rename from src/ListRenderer/listRenderer.ts rename to src/ListRenderer/List_Renderer.ts diff --git a/src/ListRenderer/orderedListRenderer.ts b/src/ListRenderer/Order_edListRenderer.ts similarity index 98% rename from src/ListRenderer/orderedListRenderer.ts rename to src/ListRenderer/Order_edListRenderer.ts index b67bb521..734ebc4f 100644 --- a/src/ListRenderer/orderedListRenderer.ts +++ b/src/ListRenderer/Order_edListRenderer.ts @@ -1,7 +1,7 @@ import type { OrderedListItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; -import { ListRenderer } from './ListRenderer'; +import { ListRenderer } from './List_Renderer'; /** * CSS classes for the Ordered list diff --git a/src/ListRenderer/unorderedListRenderer.ts b/src/ListRenderer/UnorderedListRendere_r.ts similarity index 98% rename from src/ListRenderer/unorderedListRenderer.ts rename to src/ListRenderer/UnorderedListRendere_r.ts index 8a627260..a09eace7 100644 --- a/src/ListRenderer/unorderedListRenderer.ts +++ b/src/ListRenderer/UnorderedListRendere_r.ts @@ -1,7 +1,7 @@ import type { UnorderedListItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; -import { ListRenderer } from './ListRenderer'; +import { ListRenderer } from './List_Renderer'; interface UnoderedListCssClasses { wrapper: string; diff --git a/src/ListRenderer/index.ts b/src/ListRenderer/index.ts index 72b35547..4ea17d9d 100644 --- a/src/ListRenderer/index.ts +++ b/src/ListRenderer/index.ts @@ -1,6 +1,6 @@ -import { CheckListRenderer } from "./ChecklistRenderer"; -import { OrderedListRenderer } from "./OrderedListRenderer"; -import { UnorderedListRenderer } from "./UnorderedListRenderer"; -import { ListRenderer } from './ListRenderer'; +import { CheckListRenderer } from "./Checklist__Renderer"; +import { OrderedListRenderer } from "./Order_edListRenderer"; +import { UnorderedListRenderer } from "./UnorderedListRendere_r"; +import { ListRenderer } from './List_Renderer'; export { CheckListRenderer, OrderedListRenderer, UnorderedListRenderer, ListRenderer }; diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index df2aea08..f14fcf0c 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -1,6 +1,6 @@ -import { CheckListRenderer } from "../ListRenderer/ChecklistRenderer"; -import { OrderedListRenderer } from "../ListRenderer/OrderedListRenderer"; -import { UnorderedListRenderer } from "../ListRenderer/UnorderedListRenderer"; +import { CheckListRenderer } from "../ListRenderer/Checklist__Renderer"; +import { OrderedListRenderer } from "../ListRenderer/Order_edListRenderer"; +import { UnorderedListRenderer } from "../ListRenderer/UnorderedListRendere_r"; import { NestedListConfig, ListData, ListDataStyle } from "../types/listParams" import { ListItem } from "../types/listParams"; import { isHtmlElement } from '../utils/type-guards'; From a9e250777c30b43c550d5170197d0c82d049a682 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 21:47:29 +0300 Subject: [PATCH 31/43] files renaming --- .../{Checklist__Renderer.ts => ChecklistRenderer.ts} | 2 +- src/ListRenderer/{List_Renderer.ts => ListRenderer.ts} | 0 .../{Order_edListRenderer.ts => OrderedListRenderer.ts} | 2 +- ...UnorderedListRendere_r.ts => UnorderedListRenderer.ts} | 2 +- src/ListRenderer/index.ts | 8 ++++---- src/ListTabulator/index.ts | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) rename src/ListRenderer/{Checklist__Renderer.ts => ChecklistRenderer.ts} (99%) rename src/ListRenderer/{List_Renderer.ts => ListRenderer.ts} (100%) rename src/ListRenderer/{Order_edListRenderer.ts => OrderedListRenderer.ts} (98%) rename src/ListRenderer/{UnorderedListRendere_r.ts => UnorderedListRenderer.ts} (98%) diff --git a/src/ListRenderer/Checklist__Renderer.ts b/src/ListRenderer/ChecklistRenderer.ts similarity index 99% rename from src/ListRenderer/Checklist__Renderer.ts rename to src/ListRenderer/ChecklistRenderer.ts index faf11f75..694065e8 100644 --- a/src/ListRenderer/Checklist__Renderer.ts +++ b/src/ListRenderer/ChecklistRenderer.ts @@ -2,7 +2,7 @@ import { IconCheck } from '@codexteam/icons' import type { ChecklistItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; -import { ListRenderer } from './List_Renderer'; +import { ListRenderer } from './ListRenderer'; interface ChecklistCssClasses { wrapper: string; diff --git a/src/ListRenderer/List_Renderer.ts b/src/ListRenderer/ListRenderer.ts similarity index 100% rename from src/ListRenderer/List_Renderer.ts rename to src/ListRenderer/ListRenderer.ts diff --git a/src/ListRenderer/Order_edListRenderer.ts b/src/ListRenderer/OrderedListRenderer.ts similarity index 98% rename from src/ListRenderer/Order_edListRenderer.ts rename to src/ListRenderer/OrderedListRenderer.ts index 734ebc4f..b67bb521 100644 --- a/src/ListRenderer/Order_edListRenderer.ts +++ b/src/ListRenderer/OrderedListRenderer.ts @@ -1,7 +1,7 @@ import type { OrderedListItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; -import { ListRenderer } from './List_Renderer'; +import { ListRenderer } from './ListRenderer'; /** * CSS classes for the Ordered list diff --git a/src/ListRenderer/UnorderedListRendere_r.ts b/src/ListRenderer/UnorderedListRenderer.ts similarity index 98% rename from src/ListRenderer/UnorderedListRendere_r.ts rename to src/ListRenderer/UnorderedListRenderer.ts index a09eace7..8a627260 100644 --- a/src/ListRenderer/UnorderedListRendere_r.ts +++ b/src/ListRenderer/UnorderedListRenderer.ts @@ -1,7 +1,7 @@ import type { UnorderedListItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; -import { ListRenderer } from './List_Renderer'; +import { ListRenderer } from './ListRenderer'; interface UnoderedListCssClasses { wrapper: string; diff --git a/src/ListRenderer/index.ts b/src/ListRenderer/index.ts index 4ea17d9d..72b35547 100644 --- a/src/ListRenderer/index.ts +++ b/src/ListRenderer/index.ts @@ -1,6 +1,6 @@ -import { CheckListRenderer } from "./Checklist__Renderer"; -import { OrderedListRenderer } from "./Order_edListRenderer"; -import { UnorderedListRenderer } from "./UnorderedListRendere_r"; -import { ListRenderer } from './List_Renderer'; +import { CheckListRenderer } from "./ChecklistRenderer"; +import { OrderedListRenderer } from "./OrderedListRenderer"; +import { UnorderedListRenderer } from "./UnorderedListRenderer"; +import { ListRenderer } from './ListRenderer'; export { CheckListRenderer, OrderedListRenderer, UnorderedListRenderer, ListRenderer }; diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index f14fcf0c..df2aea08 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -1,6 +1,6 @@ -import { CheckListRenderer } from "../ListRenderer/Checklist__Renderer"; -import { OrderedListRenderer } from "../ListRenderer/Order_edListRenderer"; -import { UnorderedListRenderer } from "../ListRenderer/UnorderedListRendere_r"; +import { CheckListRenderer } from "../ListRenderer/ChecklistRenderer"; +import { OrderedListRenderer } from "../ListRenderer/OrderedListRenderer"; +import { UnorderedListRenderer } from "../ListRenderer/UnorderedListRenderer"; import { NestedListConfig, ListData, ListDataStyle } from "../types/listParams" import { ListItem } from "../types/listParams"; import { isHtmlElement } from '../utils/type-guards'; From 5903be0ef92fc3e9a5322bd535dd37d12301372b Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 21:59:55 +0300 Subject: [PATCH 32/43] list css classes interface now extended --- src/ListRenderer/ChecklistRenderer.ts | 8 ++------ src/ListRenderer/ListRenderer.ts | 2 +- src/ListRenderer/OrderedListRenderer.ts | 8 ++------ src/ListRenderer/UnorderedListRenderer.ts | 8 ++------ 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/ListRenderer/ChecklistRenderer.ts b/src/ListRenderer/ChecklistRenderer.ts index 694065e8..65adadc9 100644 --- a/src/ListRenderer/ChecklistRenderer.ts +++ b/src/ListRenderer/ChecklistRenderer.ts @@ -3,14 +3,10 @@ import type { ChecklistItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; +import type { ListCssClasses } from './ListRenderer'; -interface ChecklistCssClasses { - wrapper: string; +interface ChecklistCssClasses extends ListCssClasses{ wrapperChecklist: string; - item: string; - itemBody: string; - itemContent: string; - itemChildren: string; itemChecked: string; noHover: string; checkbox: string; diff --git a/src/ListRenderer/ListRenderer.ts b/src/ListRenderer/ListRenderer.ts index c426e707..5501a83a 100644 --- a/src/ListRenderer/ListRenderer.ts +++ b/src/ListRenderer/ListRenderer.ts @@ -1,7 +1,7 @@ /** * CSS classes for the Nested List Tool */ -interface ListCssClasses { +export interface ListCssClasses { wrapper: string; item: string; itemBody: string; diff --git a/src/ListRenderer/OrderedListRenderer.ts b/src/ListRenderer/OrderedListRenderer.ts index b67bb521..151b4a0c 100644 --- a/src/ListRenderer/OrderedListRenderer.ts +++ b/src/ListRenderer/OrderedListRenderer.ts @@ -2,17 +2,13 @@ import type { OrderedListItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; +import type { ListCssClasses } from './ListRenderer'; /** * CSS classes for the Ordered list */ -interface OrderedListCssClasses { - wrapper: string; +interface OrderedListCssClasses extends ListCssClasses { wrapperOrdered: string; - item: string; - itemBody: string; - itemContent: string; - itemChildren: string; } /** diff --git a/src/ListRenderer/UnorderedListRenderer.ts b/src/ListRenderer/UnorderedListRenderer.ts index 8a627260..36228bf6 100644 --- a/src/ListRenderer/UnorderedListRenderer.ts +++ b/src/ListRenderer/UnorderedListRenderer.ts @@ -2,14 +2,10 @@ import type { UnorderedListItemMeta } from "../types/itemMeta"; import { NestedListConfig } from "../types/listParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; +import type { ListCssClasses } from './ListRenderer'; -interface UnoderedListCssClasses { - wrapper: string; +interface UnoderedListCssClasses extends ListCssClasses { wrapperUnordered: string; - item: string; - itemBody: string; - itemContent: string; - itemChildren: string; } /** From 7314e07c3af7e7f72103939d6ce46e980b77d1d3 Mon Sep 17 00:00:00 2001 From: e11sy Date: Sat, 3 Aug 2024 22:24:53 +0300 Subject: [PATCH 33/43] style fix --- styles/index.pcss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/styles/index.pcss b/styles/index.pcss index 9df172fa..1a2dd427 100644 --- a/styles/index.pcss +++ b/styles/index.pcss @@ -44,7 +44,7 @@ } &--ordered > &__item::before { - content: counters(item, ".") " " + content: counters(item, ".") "." } &--ordered { @@ -66,10 +66,10 @@ } &__checkbox { + padding-top: 2px; width: var(--size-checkbox); height: var(--size-checkbox); display: flex; - align-items: center; cursor: pointer; svg { From 348da48a824ce14d490c5ccdad6e01d930562862 Mon Sep 17 00:00:00 2001 From: e11sy Date: Tue, 6 Aug 2024 00:00:59 +0300 Subject: [PATCH 34/43] removed itemBody --- src/ListRenderer/ChecklistRenderer.ts | 6 ++-- src/ListTabulator/index.ts | 6 ++-- styles/i.html | 47 +++++++++++++++++++++++++++ styles/index.pcss | 17 ++++++---- 4 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 styles/i.html diff --git a/src/ListRenderer/ChecklistRenderer.ts b/src/ListRenderer/ChecklistRenderer.ts index 65adadc9..74e65163 100644 --- a/src/ListRenderer/ChecklistRenderer.ts +++ b/src/ListRenderer/ChecklistRenderer.ts @@ -87,7 +87,7 @@ export class CheckListRenderer extends ListRenderer { */ renderItem(content: string, meta: ChecklistItemMeta ): HTMLLIElement { const itemWrapper = Dom.make('li', [CheckListRenderer.CSS.item, CheckListRenderer.CSS.item]); - const itemBody = Dom.make('div', CheckListRenderer.CSS.itemBody); + // const itemBody = Dom.make('div', CheckListRenderer.CSS.itemBody); const itemContent = Dom.make('div', CheckListRenderer.CSS.itemContent, { innerHTML: content, contentEditable: (!this.readOnly).toString(), @@ -103,9 +103,9 @@ export class CheckListRenderer extends ListRenderer { checkbox.innerHTML = IconCheck; checkboxContainer.appendChild(checkbox); - itemBody.appendChild(itemContent); itemWrapper.appendChild(checkboxContainer); - itemWrapper.appendChild(itemBody); + itemWrapper.appendChild(itemContent); + // itemWrapper.appendChild(itemBody); return itemWrapper as HTMLLIElement; } diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index df2aea08..251b486e 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -207,10 +207,10 @@ export default class ListTabulator { this.appendItems(item.items, sublistWrapper!); this.currentLevel -= 1; - const itemBody = itemEl.querySelector(`.${ListRenderer.CSS.itemBody}`); + const itemBody = itemEl.querySelector(`.${ListRenderer.CSS.item}`); - if (itemBody) { - itemBody.appendChild(sublistWrapper!); + if (itemEl) { + itemEl.appendChild(sublistWrapper!); } } }); diff --git a/styles/i.html b/styles/i.html new file mode 100644 index 00000000..c53bb05d --- /dev/null +++ b/styles/i.html @@ -0,0 +1,47 @@ +
              3. +
                checkbox
                +
                +
                content
                +
                  +
                • +
                  checkbox2
                  +
                  +
                  content3
                  +
                    +
                    +
                  • +
                  • +
                    checkbox3
                    +
                    +
                    content3
                    +
                      +
                      +
                    • +
                    +
                    +
                  • + + diff --git a/styles/index.pcss b/styles/index.pcss index 1a2dd427..ebf3b196 100644 --- a/styles/index.pcss +++ b/styles/index.pcss @@ -15,25 +15,27 @@ &__item { line-height: 1.6em; - display: flex; + display: grid; + grid-template-columns: auto 1fr; + grid-template-rows: auto auto; + grid-template-areas: + "checkbox content" + ". child"; margin: 2px 0; &-children { - padding-inline-start: 0; + grid-area: child; } [contenteditable]{ outline: none; } - &-body { - flex-grow: 2; - } - &-content { margin-left: 8px; word-break: break-word; white-space: pre-wrap; + grid-area: content; } &::before { @@ -66,7 +68,8 @@ } &__checkbox { - padding-top: 2px; + padding-top: 0.1em; + grid-area: checkbox; width: var(--size-checkbox); height: var(--size-checkbox); display: flex; From 1a012b266afa9fed03e909451e32a41f93cb2d73 Mon Sep 17 00:00:00 2001 From: e11sy Date: Tue, 6 Aug 2024 18:01:02 +0300 Subject: [PATCH 35/43] changed addTab behaviour --- src/ListRenderer/ChecklistRenderer.ts | 2 -- src/ListRenderer/ListRenderer.ts | 2 -- src/ListRenderer/OrderedListRenderer.ts | 4 +--- src/ListRenderer/UnorderedListRenderer.ts | 4 +--- src/ListTabulator/index.ts | 28 ++++++++++++++++++----- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/ListRenderer/ChecklistRenderer.ts b/src/ListRenderer/ChecklistRenderer.ts index 74e65163..f4d78702 100644 --- a/src/ListRenderer/ChecklistRenderer.ts +++ b/src/ListRenderer/ChecklistRenderer.ts @@ -87,7 +87,6 @@ export class CheckListRenderer extends ListRenderer { */ renderItem(content: string, meta: ChecklistItemMeta ): HTMLLIElement { const itemWrapper = Dom.make('li', [CheckListRenderer.CSS.item, CheckListRenderer.CSS.item]); - // const itemBody = Dom.make('div', CheckListRenderer.CSS.itemBody); const itemContent = Dom.make('div', CheckListRenderer.CSS.itemContent, { innerHTML: content, contentEditable: (!this.readOnly).toString(), @@ -105,7 +104,6 @@ export class CheckListRenderer extends ListRenderer { itemWrapper.appendChild(checkboxContainer); itemWrapper.appendChild(itemContent); - // itemWrapper.appendChild(itemBody); return itemWrapper as HTMLLIElement; } diff --git a/src/ListRenderer/ListRenderer.ts b/src/ListRenderer/ListRenderer.ts index 5501a83a..9811a737 100644 --- a/src/ListRenderer/ListRenderer.ts +++ b/src/ListRenderer/ListRenderer.ts @@ -4,7 +4,6 @@ export interface ListCssClasses { wrapper: string; item: string; - itemBody: string; itemContent: string; itemChildren: string; } @@ -24,7 +23,6 @@ export abstract class ListRenderer { return { wrapper: 'cdx-nested-list', item: 'cdx-nested-list__item', - itemBody: 'cdx-nested-list__item-body', itemContent: 'cdx-nested-list__item-content', itemChildren: 'cdx-nested-list__item-children', }; diff --git a/src/ListRenderer/OrderedListRenderer.ts b/src/ListRenderer/OrderedListRenderer.ts index 151b4a0c..d405a684 100644 --- a/src/ListRenderer/OrderedListRenderer.ts +++ b/src/ListRenderer/OrderedListRenderer.ts @@ -66,14 +66,12 @@ export class OrderedListRenderer extends ListRenderer { */ renderItem(content: string, meta: OrderedListItemMeta): HTMLLIElement { const itemWrapper = Dom.make('li', OrderedListRenderer.CSS.item); - const itemBody = Dom.make('div', OrderedListRenderer.CSS.itemBody); const itemContent = Dom.make('div', OrderedListRenderer.CSS.itemContent, { innerHTML: content, contentEditable: (!this.readOnly).toString(), }); - itemBody.appendChild(itemContent); - itemWrapper.appendChild(itemBody); + itemWrapper.appendChild(itemContent); return itemWrapper as HTMLLIElement; } diff --git a/src/ListRenderer/UnorderedListRenderer.ts b/src/ListRenderer/UnorderedListRenderer.ts index 36228bf6..989ae5cc 100644 --- a/src/ListRenderer/UnorderedListRenderer.ts +++ b/src/ListRenderer/UnorderedListRenderer.ts @@ -65,14 +65,12 @@ export class UnorderedListRenderer extends ListRenderer { */ renderItem(content: string, meta: UnorderedListItemMeta): HTMLLIElement { const itemWrapper = Dom.make('li', UnorderedListRenderer.CSS.item); - const itemBody = Dom.make('div', UnorderedListRenderer.CSS.itemBody); const itemContent = Dom.make('div', UnorderedListRenderer.CSS.itemContent, { innerHTML: content, contentEditable: (!this.readOnly).toString(), }); - itemBody.appendChild(itemContent); - itemWrapper.appendChild(itemBody); + itemWrapper.appendChild(itemContent); return itemWrapper as HTMLLIElement; } diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index 251b486e..38c0c001 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -207,8 +207,6 @@ export default class ListTabulator { this.appendItems(item.items, sublistWrapper!); this.currentLevel -= 1; - const itemBody = itemEl.querySelector(`.${ListRenderer.CSS.item}`); - if (itemEl) { itemEl.appendChild(sublistWrapper!); } @@ -727,6 +725,7 @@ export default class ListTabulator { event.preventDefault(); const currentItem = this.currentItem; + if (!currentItem) { return; } @@ -737,6 +736,9 @@ export default class ListTabulator { if (!isHtmlElement(prevItem)) { return; } + if (currentItem.querySelector(`.${ListRenderer.CSS.itemChildren}`) !== null) { + return; + } const isFirstChild = !prevItem; /** @@ -756,18 +758,32 @@ export default class ListTabulator { * If prev item has child items, just append current to them */ if (prevItemChildrenList) { - prevItemChildrenList.appendChild(currentItem); + /** + * CurrentItem would not be removed soon (it should be cleared content and checkbox would be removed) + * after that elements with child items would be moveable too + */ + currentItem.remove(); + const newSublistItem = this.list!.renderItem(this.list!.getItemContent(currentItem), {checked: false}); + prevItemChildrenList.appendChild(newSublistItem); } else { + /** + * CurrentItem would not be removed soon (it should be cleared content and checkbox would be removed) + * after that elements with child items would be moveable too + */ + currentItem.remove(); /** * If prev item has no child items * - Create and append children wrapper to the previous item * - Append current item to it */ const sublistWrapper = this.list!.renderWrapper(1); - const prevItemBody = prevItem.querySelector(`.${ListRenderer.CSS.itemBody}`); + const newSublistItem = this.list!.renderItem(this.list!.getItemContent(currentItem), {checked: false}); + + sublistWrapper.appendChild(newSublistItem); + + console.log(prevItem, sublistWrapper) - sublistWrapper.appendChild(currentItem); - prevItemBody?.appendChild(sublistWrapper); + prevItem?.appendChild(sublistWrapper); } this.caret.restore(); From 20e60732188175f84a90f1e70372ae56d0e971a1 Mon Sep 17 00:00:00 2001 From: e11sy Date: Wed, 7 Aug 2024 20:15:55 +0300 Subject: [PATCH 36/43] file renaming --- src/ListRenderer/ChecklistRenderer.ts | 4 +- src/ListRenderer/OrderedListRenderer.ts | 4 +- src/ListRenderer/UnorderedListRenderer.ts | 4 +- src/ListTabulator/index.ts | 6 +-- src/index.ts | 2 +- src/types/{itemMeta.ts => IItemMeta.ts} | 0 src/types/{listParams.ts => LiistParams.ts} | 2 +- styles/i.html | 47 --------------------- styles/index.pcss | 9 ++-- 9 files changed, 15 insertions(+), 63 deletions(-) rename src/types/{itemMeta.ts => IItemMeta.ts} (100%) rename src/types/{listParams.ts => LiistParams.ts} (96%) delete mode 100644 styles/i.html diff --git a/src/ListRenderer/ChecklistRenderer.ts b/src/ListRenderer/ChecklistRenderer.ts index f4d78702..634a93c8 100644 --- a/src/ListRenderer/ChecklistRenderer.ts +++ b/src/ListRenderer/ChecklistRenderer.ts @@ -1,6 +1,6 @@ import { IconCheck } from '@codexteam/icons' -import type { ChecklistItemMeta } from "../types/itemMeta"; -import { NestedListConfig } from "../types/listParams"; +import type { ChecklistItemMeta } from "../types/IItemMeta"; +import { NestedListConfig } from "../types/LiistParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; diff --git a/src/ListRenderer/OrderedListRenderer.ts b/src/ListRenderer/OrderedListRenderer.ts index d405a684..0d92a89c 100644 --- a/src/ListRenderer/OrderedListRenderer.ts +++ b/src/ListRenderer/OrderedListRenderer.ts @@ -1,5 +1,5 @@ -import type { OrderedListItemMeta } from "../types/itemMeta"; -import { NestedListConfig } from "../types/listParams"; +import type { OrderedListItemMeta } from "../types/IItemMeta"; +import { NestedListConfig } from "../types/LiistParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; diff --git a/src/ListRenderer/UnorderedListRenderer.ts b/src/ListRenderer/UnorderedListRenderer.ts index 989ae5cc..b5277c56 100644 --- a/src/ListRenderer/UnorderedListRenderer.ts +++ b/src/ListRenderer/UnorderedListRenderer.ts @@ -1,5 +1,5 @@ -import type { UnorderedListItemMeta } from "../types/itemMeta"; -import { NestedListConfig } from "../types/listParams"; +import type { UnorderedListItemMeta } from "../types/IItemMeta"; +import { NestedListConfig } from "../types/LiistParams"; import * as Dom from '../utils/dom'; import { ListRenderer } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index 38c0c001..7b44a09c 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -1,8 +1,8 @@ import { CheckListRenderer } from "../ListRenderer/ChecklistRenderer"; import { OrderedListRenderer } from "../ListRenderer/OrderedListRenderer"; import { UnorderedListRenderer } from "../ListRenderer/UnorderedListRenderer"; -import { NestedListConfig, ListData, ListDataStyle } from "../types/listParams" -import { ListItem } from "../types/listParams"; +import { NestedListConfig, ListData, ListDataStyle } from "../types/LiistParams" +import { ListItem } from "../types/LiistParams"; import { isHtmlElement } from '../utils/type-guards'; import Caret from '../utils/caret'; import { ListRenderer } from "../ListRenderer"; @@ -10,7 +10,7 @@ import * as Dom from '../utils/dom' import type { PasteEvent } from '../types'; import type { API, PasteConfig } from '@editorjs/editorjs'; import { NestedListParams } from ".."; -import { ChecklistItemMeta, OrderedListItemMeta, UnorderedListItemMeta } from "../types/itemMeta"; +import { ChecklistItemMeta, OrderedListItemMeta, UnorderedListItemMeta } from "../types/IItemMeta"; type NestedListStyle = 'ordered' | 'unordered' | 'checklist'; diff --git a/src/index.ts b/src/index.ts index f0ae786c..2b851493 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import type { } from '@editorjs/editorjs/types/tools'; import Caret from './utils/caret'; import { IconListBulleted, IconListNumbered, IconChecklist } from '@codexteam/icons'; -import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/listParams'; +import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/LiistParams'; import ListTabulator from './ListTabulator'; /** diff --git a/src/types/itemMeta.ts b/src/types/IItemMeta.ts similarity index 100% rename from src/types/itemMeta.ts rename to src/types/IItemMeta.ts diff --git a/src/types/listParams.ts b/src/types/LiistParams.ts similarity index 96% rename from src/types/listParams.ts rename to src/types/LiistParams.ts index 182c56d6..0bdad795 100644 --- a/src/types/listParams.ts +++ b/src/types/LiistParams.ts @@ -1,4 +1,4 @@ -import { ChecklistItemMeta, OrderedListItemMeta, UnorderedListItemMeta } from "./itemMeta"; +import { ChecklistItemMeta, OrderedListItemMeta, UnorderedListItemMeta } from "./IItemMeta"; /** * list style to make list as ordered or unordered diff --git a/styles/i.html b/styles/i.html deleted file mode 100644 index c53bb05d..00000000 --- a/styles/i.html +++ /dev/null @@ -1,47 +0,0 @@ -
                  • -
                    checkbox
                    -
                    -
                    content
                    -
                      -
                    • -
                      checkbox2
                      -
                      -
                      content3
                      -
                        -
                        -
                      • -
                      • -
                        checkbox3
                        -
                        -
                        content3
                        -
                          -
                          -
                        • -
                        -
                        -
                      • - - diff --git a/styles/index.pcss b/styles/index.pcss index ebf3b196..7462bc5a 100644 --- a/styles/index.pcss +++ b/styles/index.pcss @@ -8,13 +8,13 @@ --checkbox-background: #fff; --color-border: #C9C9C9; --color-bg-checked: #369FFF; - --line-height: 1.57em; + --line-height: 1.6em; --color-bg-checked-hover: #0059AB; --color-tick: #fff; - --size-checkbox: 20px; + --size-checkbox: 1.2em; &__item { - line-height: 1.6em; + line-height: var(--line-height); display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; @@ -68,7 +68,6 @@ } &__checkbox { - padding-top: 0.1em; grid-area: checkbox; width: var(--size-checkbox); height: var(--size-checkbox); @@ -95,7 +94,7 @@ } &--checked { - line-height: 1.6em; + line-height: var(--line-height); @media (hover: hover) { &:not(&--no-hover):hover { From e2a8d6f68593e25022a44af670865eddadcb7598 Mon Sep 17 00:00:00 2001 From: e11sy Date: Wed, 7 Aug 2024 20:16:21 +0300 Subject: [PATCH 37/43] file renaming --- src/types/{icons.d.ts => IIcons.d.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/types/{icons.d.ts => IIcons.d.ts} (100%) diff --git a/src/types/icons.d.ts b/src/types/IIcons.d.ts similarity index 100% rename from src/types/icons.d.ts rename to src/types/IIcons.d.ts From a9abfa37eb23f4b1af7013579471620ea4aa223a Mon Sep 17 00:00:00 2001 From: e11sy Date: Wed, 7 Aug 2024 20:19:06 +0300 Subject: [PATCH 38/43] file renaming --- src/ListRenderer/ChecklistRenderer.ts | 2 +- src/ListRenderer/OrderedListRenderer.ts | 2 +- src/ListRenderer/UnorderedListRenderer.ts | 2 +- src/ListTabulator/index.ts | 4 ++-- src/index.ts | 2 +- src/types/IIcons.d.ts | 7 ------- src/utils/{dom.ts => DDom.ts} | 0 src/utils/caret.ts | 2 +- 8 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 src/types/IIcons.d.ts rename src/utils/{dom.ts => DDom.ts} (100%) diff --git a/src/ListRenderer/ChecklistRenderer.ts b/src/ListRenderer/ChecklistRenderer.ts index 634a93c8..339d4e54 100644 --- a/src/ListRenderer/ChecklistRenderer.ts +++ b/src/ListRenderer/ChecklistRenderer.ts @@ -1,7 +1,7 @@ import { IconCheck } from '@codexteam/icons' import type { ChecklistItemMeta } from "../types/IItemMeta"; import { NestedListConfig } from "../types/LiistParams"; -import * as Dom from '../utils/dom'; +import * as Dom from '../utils/DDom'; import { ListRenderer } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; diff --git a/src/ListRenderer/OrderedListRenderer.ts b/src/ListRenderer/OrderedListRenderer.ts index 0d92a89c..ceb7beec 100644 --- a/src/ListRenderer/OrderedListRenderer.ts +++ b/src/ListRenderer/OrderedListRenderer.ts @@ -1,6 +1,6 @@ import type { OrderedListItemMeta } from "../types/IItemMeta"; import { NestedListConfig } from "../types/LiistParams"; -import * as Dom from '../utils/dom'; +import * as Dom from '../utils/DDom'; import { ListRenderer } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; diff --git a/src/ListRenderer/UnorderedListRenderer.ts b/src/ListRenderer/UnorderedListRenderer.ts index b5277c56..c640b9dc 100644 --- a/src/ListRenderer/UnorderedListRenderer.ts +++ b/src/ListRenderer/UnorderedListRenderer.ts @@ -1,6 +1,6 @@ import type { UnorderedListItemMeta } from "../types/IItemMeta"; import { NestedListConfig } from "../types/LiistParams"; -import * as Dom from '../utils/dom'; +import * as Dom from '../utils/DDom'; import { ListRenderer } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index 7b44a09c..4b94b00c 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -4,9 +4,9 @@ import { UnorderedListRenderer } from "../ListRenderer/UnorderedListRenderer"; import { NestedListConfig, ListData, ListDataStyle } from "../types/LiistParams" import { ListItem } from "../types/LiistParams"; import { isHtmlElement } from '../utils/type-guards'; -import Caret from '../utils/caret'; +import Caret from '../utils/Caret'; import { ListRenderer } from "../ListRenderer"; -import * as Dom from '../utils/dom' +import * as Dom from '../utils/DDom' import type { PasteEvent } from '../types'; import type { API, PasteConfig } from '@editorjs/editorjs'; import { NestedListParams } from ".."; diff --git a/src/index.ts b/src/index.ts index 2b851493..a4e540e3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import type { BlockToolConstructorOptions, TunesMenuConfig, } from '@editorjs/editorjs/types/tools'; -import Caret from './utils/caret'; +import Caret from './utils/Caret'; import { IconListBulleted, IconListNumbered, IconChecklist } from '@codexteam/icons'; import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/LiistParams'; import ListTabulator from './ListTabulator'; diff --git a/src/types/IIcons.d.ts b/src/types/IIcons.d.ts deleted file mode 100644 index 5548abcc..00000000 --- a/src/types/IIcons.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -// temporary fix for the missing types -declare module '@codexteam/icons' { - export const IconListBulleted: string; - export const IconListNumbered: string; - export const IconChecklist: string - export const IconCheck: string; -} diff --git a/src/utils/dom.ts b/src/utils/DDom.ts similarity index 100% rename from src/utils/dom.ts rename to src/utils/DDom.ts diff --git a/src/utils/caret.ts b/src/utils/caret.ts index f26d1a33..30efa835 100644 --- a/src/utils/caret.ts +++ b/src/utils/caret.ts @@ -1,4 +1,4 @@ -import * as dom from './dom'; +import * as dom from './DDom'; import { isHtmlElement } from './type-guards'; /** From c60e9fea0c6d9147e146ee66f3fa7726f8cab50d Mon Sep 17 00:00:00 2001 From: e11sy Date: Wed, 7 Aug 2024 20:19:49 +0300 Subject: [PATCH 39/43] renaming --- src/ListRenderer/ChecklistRenderer.ts | 6 +++--- src/ListRenderer/OrderedListRenderer.ts | 6 +++--- src/ListRenderer/UnorderedListRenderer.ts | 6 +++--- src/ListTabulator/index.ts | 10 +++++----- src/index.ts | 4 ++-- src/types/{IItemMeta.ts => ItemMeta.ts} | 0 src/types/{LiistParams.ts => ListParams.ts} | 2 +- src/utils/{DDom.ts => Dom.ts} | 0 src/utils/{caret.ts => t.ts} | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) rename src/types/{IItemMeta.ts => ItemMeta.ts} (100%) rename src/types/{LiistParams.ts => ListParams.ts} (96%) rename src/utils/{DDom.ts => Dom.ts} (100%) rename src/utils/{caret.ts => t.ts} (99%) diff --git a/src/ListRenderer/ChecklistRenderer.ts b/src/ListRenderer/ChecklistRenderer.ts index 339d4e54..64742093 100644 --- a/src/ListRenderer/ChecklistRenderer.ts +++ b/src/ListRenderer/ChecklistRenderer.ts @@ -1,7 +1,7 @@ import { IconCheck } from '@codexteam/icons' -import type { ChecklistItemMeta } from "../types/IItemMeta"; -import { NestedListConfig } from "../types/LiistParams"; -import * as Dom from '../utils/DDom'; +import type { ChecklistItemMeta } from "../types/ItemMeta"; +import { NestedListConfig } from "../types/ListParams"; +import * as Dom from '../utils/Dom'; import { ListRenderer } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; diff --git a/src/ListRenderer/OrderedListRenderer.ts b/src/ListRenderer/OrderedListRenderer.ts index ceb7beec..05f33b3d 100644 --- a/src/ListRenderer/OrderedListRenderer.ts +++ b/src/ListRenderer/OrderedListRenderer.ts @@ -1,6 +1,6 @@ -import type { OrderedListItemMeta } from "../types/IItemMeta"; -import { NestedListConfig } from "../types/LiistParams"; -import * as Dom from '../utils/DDom'; +import type { OrderedListItemMeta } from "../types/ItemMeta"; +import { NestedListConfig } from "../types/ListParams"; +import * as Dom from '../utils/Dom'; import { ListRenderer } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; diff --git a/src/ListRenderer/UnorderedListRenderer.ts b/src/ListRenderer/UnorderedListRenderer.ts index c640b9dc..dd453e8b 100644 --- a/src/ListRenderer/UnorderedListRenderer.ts +++ b/src/ListRenderer/UnorderedListRenderer.ts @@ -1,6 +1,6 @@ -import type { UnorderedListItemMeta } from "../types/IItemMeta"; -import { NestedListConfig } from "../types/LiistParams"; -import * as Dom from '../utils/DDom'; +import type { UnorderedListItemMeta } from "../types/ItemMeta"; +import { NestedListConfig } from "../types/ListParams"; +import * as Dom from '../utils/Dom'; import { ListRenderer } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index 4b94b00c..8a0b15d3 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -1,16 +1,16 @@ import { CheckListRenderer } from "../ListRenderer/ChecklistRenderer"; import { OrderedListRenderer } from "../ListRenderer/OrderedListRenderer"; import { UnorderedListRenderer } from "../ListRenderer/UnorderedListRenderer"; -import { NestedListConfig, ListData, ListDataStyle } from "../types/LiistParams" -import { ListItem } from "../types/LiistParams"; +import { NestedListConfig, ListData, ListDataStyle } from "../types/ListParams" +import { ListItem } from "../types/ListParams"; import { isHtmlElement } from '../utils/type-guards'; -import Caret from '../utils/Caret'; +import Caret from '../utils/t'; import { ListRenderer } from "../ListRenderer"; -import * as Dom from '../utils/DDom' +import * as Dom from '../utils/Dom' import type { PasteEvent } from '../types'; import type { API, PasteConfig } from '@editorjs/editorjs'; import { NestedListParams } from ".."; -import { ChecklistItemMeta, OrderedListItemMeta, UnorderedListItemMeta } from "../types/IItemMeta"; +import { ChecklistItemMeta, OrderedListItemMeta, UnorderedListItemMeta } from "../types/ItemMeta"; type NestedListStyle = 'ordered' | 'unordered' | 'checklist'; diff --git a/src/index.ts b/src/index.ts index a4e540e3..9f457a11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,9 +4,9 @@ import type { BlockToolConstructorOptions, TunesMenuConfig, } from '@editorjs/editorjs/types/tools'; -import Caret from './utils/Caret'; +import Caret from './utils/t'; import { IconListBulleted, IconListNumbered, IconChecklist } from '@codexteam/icons'; -import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/LiistParams'; +import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/ListParams'; import ListTabulator from './ListTabulator'; /** diff --git a/src/types/IItemMeta.ts b/src/types/ItemMeta.ts similarity index 100% rename from src/types/IItemMeta.ts rename to src/types/ItemMeta.ts diff --git a/src/types/LiistParams.ts b/src/types/ListParams.ts similarity index 96% rename from src/types/LiistParams.ts rename to src/types/ListParams.ts index 0bdad795..9316df25 100644 --- a/src/types/LiistParams.ts +++ b/src/types/ListParams.ts @@ -1,4 +1,4 @@ -import { ChecklistItemMeta, OrderedListItemMeta, UnorderedListItemMeta } from "./IItemMeta"; +import { ChecklistItemMeta, OrderedListItemMeta, UnorderedListItemMeta } from "./ItemMeta"; /** * list style to make list as ordered or unordered diff --git a/src/utils/DDom.ts b/src/utils/Dom.ts similarity index 100% rename from src/utils/DDom.ts rename to src/utils/Dom.ts diff --git a/src/utils/caret.ts b/src/utils/t.ts similarity index 99% rename from src/utils/caret.ts rename to src/utils/t.ts index 30efa835..69e982f0 100644 --- a/src/utils/caret.ts +++ b/src/utils/t.ts @@ -1,4 +1,4 @@ -import * as dom from './DDom'; +import * as dom from './Dom'; import { isHtmlElement } from './type-guards'; /** From ac07f3839c5db669c8158cda2390c2447effca33 Mon Sep 17 00:00:00 2001 From: e11sy Date: Wed, 7 Aug 2024 20:20:01 +0300 Subject: [PATCH 40/43] renaming --- src/ListTabulator/index.ts | 2 +- src/index.ts | 2 +- src/utils/{t.ts => Caret.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/utils/{t.ts => Caret.ts} (100%) diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index 8a0b15d3..cf9bdeb3 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -4,7 +4,7 @@ import { UnorderedListRenderer } from "../ListRenderer/UnorderedListRenderer"; import { NestedListConfig, ListData, ListDataStyle } from "../types/ListParams" import { ListItem } from "../types/ListParams"; import { isHtmlElement } from '../utils/type-guards'; -import Caret from '../utils/t'; +import Caret from '../utils/Caret'; import { ListRenderer } from "../ListRenderer"; import * as Dom from '../utils/Dom' import type { PasteEvent } from '../types'; diff --git a/src/index.ts b/src/index.ts index 9f457a11..1d19adf1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ import type { BlockToolConstructorOptions, TunesMenuConfig, } from '@editorjs/editorjs/types/tools'; -import Caret from './utils/t'; +import Caret from './utils/Caret'; import { IconListBulleted, IconListNumbered, IconChecklist } from '@codexteam/icons'; import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/ListParams'; import ListTabulator from './ListTabulator'; diff --git a/src/utils/t.ts b/src/utils/Caret.ts similarity index 100% rename from src/utils/t.ts rename to src/utils/Caret.ts From 3bb7736baee9e78b131fc46b8390b86d304f2b76 Mon Sep 17 00:00:00 2001 From: e11sy Date: Wed, 7 Aug 2024 20:32:37 +0300 Subject: [PATCH 41/43] List renderer now interface --- src/ListRenderer/ChecklistRenderer.ts | 21 ++++++------- src/ListRenderer/ListRenderer.ts | 38 ++++++++++++----------- src/ListRenderer/OrderedListRenderer.ts | 11 +++---- src/ListRenderer/UnorderedListRenderer.ts | 15 +++++---- src/ListRenderer/index.ts | 4 +-- src/ListTabulator/index.ts | 32 +++++++++---------- src/index.ts | 2 -- styles/index.pcss | 2 +- 8 files changed, 59 insertions(+), 66 deletions(-) diff --git a/src/ListRenderer/ChecklistRenderer.ts b/src/ListRenderer/ChecklistRenderer.ts index 64742093..df2a069d 100644 --- a/src/ListRenderer/ChecklistRenderer.ts +++ b/src/ListRenderer/ChecklistRenderer.ts @@ -2,7 +2,7 @@ import { IconCheck } from '@codexteam/icons' import type { ChecklistItemMeta } from "../types/ItemMeta"; import { NestedListConfig } from "../types/ListParams"; import * as Dom from '../utils/Dom'; -import { ListRenderer } from './ListRenderer'; +import { ListRendererInterface, DefaultListCssClasses, CssPrefix } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; interface ChecklistCssClasses extends ListCssClasses{ @@ -16,7 +16,7 @@ interface ChecklistCssClasses extends ListCssClasses{ /** * Class that is responsible for checklist rendering */ -export class CheckListRenderer extends ListRenderer { +export class CheckListRenderer implements ListRendererInterface { /** * Tool's configuration */ @@ -28,20 +28,17 @@ export class CheckListRenderer extends ListRenderer { private readOnly: boolean; static get CSS(): ChecklistCssClasses { - const listCssClasses = super.CSS; - return { - ...listCssClasses, - wrapperChecklist: 'cdx-nested-list--checklist', - itemChecked: 'cdx-nested-list__checkbox--checked', - noHover: 'cdx-nested-list__checkbox--no-hover', - checkbox: 'cdx-nested-list__checkbox-check', - checkboxContainer: 'cdx-nested-list__checkbox' + ...DefaultListCssClasses, + wrapperChecklist: `${CssPrefix}--checklist`, + itemChecked: `${CssPrefix}__checkbox--checked`, + noHover: `${CssPrefix}__checkbox--no-hover`, + checkbox: `${CssPrefix}__checkbox-check`, + checkboxContainer: `${CssPrefix}__checkbox` } } constructor(readonly: boolean, config?: NestedListConfig) { - super(); this.config = config; this.readOnly = readonly; } @@ -61,7 +58,7 @@ export class CheckListRenderer extends ListRenderer { wrapperElement = Dom.make('ul', [CheckListRenderer.CSS.wrapper, CheckListRenderer.CSS.wrapperChecklist]) as HTMLUListElement; /** - * Listen to clicks on checkbox + * Delegate clicks from wrapper to items */ wrapperElement.addEventListener('click', (event) => { const target = event.target as Element; diff --git a/src/ListRenderer/ListRenderer.ts b/src/ListRenderer/ListRenderer.ts index 9811a737..1139c14a 100644 --- a/src/ListRenderer/ListRenderer.ts +++ b/src/ListRenderer/ListRenderer.ts @@ -1,5 +1,20 @@ /** - * CSS classes for the Nested List Tool + * Default css prefix for list + */ +export const CssPrefix = 'cdx-list'; + +/** + * CSS classes for the List Tool + */ +export const DefaultListCssClasses = { + wrapper: CssPrefix, + item: `${CssPrefix}__item`, + itemContent: `${CssPrefix}__item-content`, + itemChildren: `${CssPrefix}__item-children`, +} + +/** + * Interface that represents default list css classes */ export interface ListCssClasses { wrapper: string; @@ -9,22 +24,9 @@ export interface ListCssClasses { } /** - * List renderer class - * Used for storing css classes and + * List renderer interface + * It is implemented by all renderer classes */ -export abstract class ListRenderer { - /** - * Styles - * - * @returns {NestedListCssClasses} - CSS classes names by keys - * @private - */ - static get CSS(): ListCssClasses { - return { - wrapper: 'cdx-nested-list', - item: 'cdx-nested-list__item', - itemContent: 'cdx-nested-list__item-content', - itemChildren: 'cdx-nested-list__item-children', - }; - } +export interface ListRendererInterface { + } diff --git a/src/ListRenderer/OrderedListRenderer.ts b/src/ListRenderer/OrderedListRenderer.ts index 05f33b3d..f81f721a 100644 --- a/src/ListRenderer/OrderedListRenderer.ts +++ b/src/ListRenderer/OrderedListRenderer.ts @@ -1,7 +1,7 @@ import type { OrderedListItemMeta } from "../types/ItemMeta"; import { NestedListConfig } from "../types/ListParams"; import * as Dom from '../utils/Dom'; -import { ListRenderer } from './ListRenderer'; +import { ListRendererInterface, DefaultListCssClasses, CssPrefix } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; /** @@ -14,7 +14,7 @@ interface OrderedListCssClasses extends ListCssClasses { /** * Class that is responsible for ordered list rendering */ -export class OrderedListRenderer extends ListRenderer { +export class OrderedListRenderer implements ListRendererInterface { /** * Tool's configuration */ @@ -26,16 +26,13 @@ export class OrderedListRenderer extends ListRenderer { private readOnly: boolean; static get CSS(): OrderedListCssClasses { - const listCssClasses = super.CSS; - return { - ...listCssClasses, - wrapperOrdered: 'cdx-nested-list--ordered', + ...DefaultListCssClasses, + wrapperOrdered: `${CssPrefix}--ordered`, } } constructor(readonly: boolean, config?: NestedListConfig) { - super(); this.config = config; this.readOnly = readonly; } diff --git a/src/ListRenderer/UnorderedListRenderer.ts b/src/ListRenderer/UnorderedListRenderer.ts index dd453e8b..8e1b74a7 100644 --- a/src/ListRenderer/UnorderedListRenderer.ts +++ b/src/ListRenderer/UnorderedListRenderer.ts @@ -1,7 +1,7 @@ import type { UnorderedListItemMeta } from "../types/ItemMeta"; import { NestedListConfig } from "../types/ListParams"; import * as Dom from '../utils/Dom'; -import { ListRenderer } from './ListRenderer'; +import { ListRendererInterface, DefaultListCssClasses } from './ListRenderer'; import type { ListCssClasses } from './ListRenderer'; interface UnoderedListCssClasses extends ListCssClasses { @@ -11,7 +11,7 @@ interface UnoderedListCssClasses extends ListCssClasses { /** * Class that is responsible for unordered list rendering */ -export class UnorderedListRenderer extends ListRenderer { +export class UnorderedListRenderer implements ListRendererInterface { /** * Tool's configuration */ @@ -22,18 +22,17 @@ export class UnorderedListRenderer extends ListRenderer { */ private readOnly: boolean; - + /** + * Getter for all CSS classes used in unordered list rendering + */ static get CSS(): UnoderedListCssClasses { - const listCssClasses = super.CSS; - return { - ...listCssClasses, - wrapperUnordered: 'cdx-nested-list--unordered', + ...DefaultListCssClasses, + wrapperUnordered: '${CssPrefix}--unordered', } } constructor(readonly: boolean, config?: NestedListConfig) { - super(); this.config = config; this.readOnly = readonly; } diff --git a/src/ListRenderer/index.ts b/src/ListRenderer/index.ts index 72b35547..477fb0ac 100644 --- a/src/ListRenderer/index.ts +++ b/src/ListRenderer/index.ts @@ -1,6 +1,6 @@ import { CheckListRenderer } from "./ChecklistRenderer"; import { OrderedListRenderer } from "./OrderedListRenderer"; import { UnorderedListRenderer } from "./UnorderedListRenderer"; -import { ListRenderer } from './ListRenderer'; +import { DefaultListCssClasses, CssPrefix } from './ListRenderer'; -export { CheckListRenderer, OrderedListRenderer, UnorderedListRenderer, ListRenderer }; +export { CheckListRenderer, OrderedListRenderer, UnorderedListRenderer, DefaultListCssClasses, CssPrefix }; diff --git a/src/ListTabulator/index.ts b/src/ListTabulator/index.ts index cf9bdeb3..326b73c2 100644 --- a/src/ListTabulator/index.ts +++ b/src/ListTabulator/index.ts @@ -5,7 +5,7 @@ import { NestedListConfig, ListData, ListDataStyle } from "../types/ListParams" import { ListItem } from "../types/ListParams"; import { isHtmlElement } from '../utils/type-guards'; import Caret from '../utils/Caret'; -import { ListRenderer } from "../ListRenderer"; +import { DefaultListCssClasses } from "../ListRenderer"; import * as Dom from '../utils/Dom' import type { PasteEvent } from '../types'; import type { API, PasteConfig } from '@editorjs/editorjs'; @@ -92,7 +92,7 @@ export default class ListTabulator { return null; } - return currentNode.closest(`.cdx-nested-list__item`); + return currentNode.closest(DefaultListCssClasses.item); } constructor({data, config, api, readOnly}: NestedListParams, style: NestedListStyle) { @@ -228,11 +228,11 @@ export default class ListTabulator { */ const getItems = (parent: Element): ListItem[] => { const children = Array.from( - parent.querySelectorAll(`:scope > .${ListRenderer.CSS.item}`) + parent.querySelectorAll(`:scope > .${DefaultListCssClasses.item}`) ); return children.map((el) => { - const subItemsWrapper = el.querySelector(`.${ListRenderer.CSS.itemChildren}`); + const subItemsWrapper = el.querySelector(`.${DefaultListCssClasses.itemChildren}`); const content = this.list!.getItemContent(el); const meta = this.list!.getItemMeta(el); const subItems = subItemsWrapper ? getItems(subItemsWrapper) : []; @@ -390,7 +390,7 @@ export default class ListTabulator { } const endingHTML = Dom.fragmentToString(endingFragment); const itemChildren = currentItem?.querySelector( - `.${ListRenderer.CSS.itemChildren}` + `.${DefaultListCssClasses.itemChildren}` ); /** @@ -405,7 +405,7 @@ export default class ListTabulator { */ const childrenExist = itemChildren && - Array.from(itemChildren.querySelectorAll(`.${ListRenderer.CSS.item}`)).length > 0; + Array.from(itemChildren.querySelectorAll(`.${DefaultListCssClasses.item}`)).length > 0; /** * If item has children, prepend to them @@ -450,7 +450,7 @@ export default class ListTabulator { if (!isHtmlElement(currentItem.parentNode)) { return; } - const parentItem = currentItem.parentNode.closest(`.${ListRenderer.CSS.item}`); + const parentItem = currentItem.parentNode.closest(`.${DefaultListCssClasses.item}`); /** * Do nothing with the first item in the first-level list. @@ -500,7 +500,7 @@ export default class ListTabulator { */ if (previousItem) { const childrenOfPreviousItem = previousItem.querySelectorAll( - `.${ListRenderer.CSS.item}` + `.${DefaultListCssClasses.item}` ); targetItem = Array.from(childrenOfPreviousItem).pop() || previousItem; @@ -524,7 +524,7 @@ export default class ListTabulator { return; } const targetItemContent = targetItem.querySelector( - `.${ListRenderer.CSS.itemContent}` + `.${DefaultListCssClasses.itemContent}` ); /** @@ -550,7 +550,7 @@ export default class ListTabulator { */ let currentItemSublistItems: NodeListOf | Element[] = currentItem.querySelectorAll( - `.${ListRenderer.CSS.itemChildren} > .${ListRenderer.CSS.item}` + `.${DefaultListCssClasses.itemChildren} > .${DefaultListCssClasses.item}` ); /** @@ -570,7 +570,7 @@ export default class ListTabulator { if (!isHtmlElement(node.parentNode)) { return false; } - return node.parentNode.closest(`.${ListRenderer.CSS.item}`) === currentItem; + return node.parentNode.closest(`.${DefaultListCssClasses.item}`) === currentItem; }); /** @@ -675,7 +675,7 @@ export default class ListTabulator { return; } - const parentItem = currentItem.parentNode.closest(`.${ListRenderer.CSS.item}`); + const parentItem = currentItem.parentNode.closest(`.${DefaultListCssClasses.item}`); /** * If item in the first-level list then no need to do anything @@ -694,7 +694,7 @@ export default class ListTabulator { * If previous parent's children list is now empty, remove it. */ const prevParentChildrenList = parentItem.querySelector( - `.${ListRenderer.CSS.itemChildren}` + `.${DefaultListCssClasses.itemChildren}` ); if (!prevParentChildrenList) { return; @@ -736,7 +736,7 @@ export default class ListTabulator { if (!isHtmlElement(prevItem)) { return; } - if (currentItem.querySelector(`.${ListRenderer.CSS.itemChildren}`) !== null) { + if (currentItem.querySelector(`.${DefaultListCssClasses.itemChildren}`) !== null) { return; } const isFirstChild = !prevItem; @@ -749,7 +749,7 @@ export default class ListTabulator { } const prevItemChildrenList = prevItem.querySelector( - `.${ListRenderer.CSS.itemChildren}` + `.${DefaultListCssClasses.itemChildren}` ); this.caret.save(); @@ -798,7 +798,7 @@ export default class ListTabulator { */ focusItem(item: Element, atStart: boolean = true): void { const itemContent = item.querySelector( - `.${ListRenderer.CSS.itemContent}` + `.${DefaultListCssClasses.itemContent}` ); if (!itemContent) { return; diff --git a/src/index.ts b/src/index.ts index 1d19adf1..4a69cdfe 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,8 @@ import type { API, PasteConfig, ToolboxConfig } from '@editorjs/editorjs'; -import type { PasteEvent } from './types'; import type { BlockToolConstructorOptions, TunesMenuConfig, } from '@editorjs/editorjs/types/tools'; -import Caret from './utils/Caret'; import { IconListBulleted, IconListNumbered, IconChecklist } from '@codexteam/icons'; import { NestedListConfig, ListData, ListDataStyle, ListItem } from './types/ListParams'; import ListTabulator from './ListTabulator'; diff --git a/styles/index.pcss b/styles/index.pcss index 7462bc5a..99d15619 100644 --- a/styles/index.pcss +++ b/styles/index.pcss @@ -1,4 +1,4 @@ -.cdx-nested-list { +.${CssPrefix} { margin: 0; padding: 0; outline: none; From 3d87c88572bf2544ef7b8c3912d0a504a3249035 Mon Sep 17 00:00:00 2001 From: e11sy Date: Wed, 7 Aug 2024 21:34:54 +0300 Subject: [PATCH 42/43] filled list renderer interface --- index.html | 8 +++--- src/ListRenderer/ChecklistRenderer.ts | 13 ++++----- src/ListRenderer/ListRenderer.ts | 34 ++++++++++++++++++++--- src/ListRenderer/OrderedListRenderer.ts | 10 +++---- src/ListRenderer/UnorderedListRenderer.ts | 12 ++++---- src/ListTabulator/index.ts | 17 ++++++++---- src/index.ts | 4 +-- src/types/ItemMeta.ts | 2 +- styles/index.pcss | 13 +++++---- 9 files changed, 72 insertions(+), 41 deletions(-) diff --git a/index.html b/index.html index c371189c..8e083bd1 100644 --- a/index.html +++ b/index.html @@ -61,7 +61,7 @@