2
0
mirror of https://github.com/Ionaru/easy-markdown-editor synced 2025-07-01 07:01:04 -06:00

Update everything & fix prettier/eslint errors

This commit is contained in:
Jeroen akkerman 2025-05-01 17:22:17 +02:00
parent 1e9986c0e3
commit cbf0f6e387
37 changed files with 2903 additions and 2831 deletions

View File

@ -1,3 +0,0 @@
coverage
dist
node_modules

View File

@ -1,14 +0,0 @@
{
"root": true,
"extends": ["@ionaru", "prettier"],
"rules": {
"jest/no-deprecated-functions": "off",
"jest/unbound-method": "off",
"jest/require-hook": "off",
"import/extensions": "off",
"import/no-unresolved": "off",
"unicorn/no-null": "off",
"@typescript-eslint/member-ordering": "off",
"@typescript-eslint/explicit-member-accessibility": "off"
}
}

View File

@ -1,9 +1,9 @@
--- ---
name: Bug report name: Bug report
about: Create a report to help us improve about: Create a report to help us improve
title: '' title: ""
labels: Bug labels: Bug
assignees: '' assignees: ""
--- ---
**Describe the bug** **Describe the bug**

View File

@ -1,9 +1,9 @@
--- ---
name: Feature request name: Feature request
about: Suggest an idea for this project about: Suggest an idea for this project
title: '' title: ""
labels: Feature labels: Feature
assignees: '' assignees: ""
--- ---
**Is your feature request related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**

View File

@ -1,9 +1,9 @@
--- ---
name: Question name: Question
about: Ask a question if anything is unclear about: Ask a question if anything is unclear
title: '' title: ""
labels: Question labels: Question
assignees: '' assignees: ""
--- ---
**Describe your question** **Describe your question**

View File

@ -6,7 +6,7 @@ on:
branches: branches:
- master - master
tags: tags:
- '*' - "*"
pull_request: pull_request:
branches: branches:
- master - master
@ -30,7 +30,7 @@ jobs:
strategy: strategy:
matrix: matrix:
node-version: ['14', '16', '18'] node-version: ["14", "16", "18"]
steps: steps:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3

View File

@ -1,7 +1 @@
{ {}
"printWidth": 80,
"singleQuote": true,
"quoteProps": "consistent",
"tabWidth": 4,
"trailingComma": "all"
}

82
eslint.config.js Normal file
View File

@ -0,0 +1,82 @@
// Core ESLint packages
import eslint from "@eslint/js";
import typescriptEslint from "typescript-eslint";
// Plugins
import eslintConfigPrettier from "eslint-config-prettier";
import eslintConfigUnicorn from "eslint-plugin-unicorn";
import eslintPluginImport from "eslint-plugin-import";
import eslintPluginSonarJS from "eslint-plugin-sonarjs";
export default typescriptEslint.config(
{
ignores: [
"dist/**",
"node_modules/**",
"**/*.spec.ts",
"**/*.test.ts",
"vitest.config.ts",
],
},
{
files: ["**/*.ts"],
extends: [
eslint.configs.recommended,
eslintPluginImport.flatConfigs.recommended,
eslintPluginImport.flatConfigs.typescript,
...typescriptEslint.configs.recommendedTypeChecked,
...typescriptEslint.configs.stylisticTypeChecked,
eslintPluginSonarJS.configs.recommended,
eslintConfigUnicorn.configs.recommended,
eslintConfigPrettier,
],
languageOptions: {
ecmaVersion: "latest",
sourceType: "module",
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
// Override specific rules for TypeScript files (these will take priority over the extended configs above)
rules: {
"unicorn/no-null": "off",
"import/no-unresolved": "off",
"unicorn/prefer-top-level-await": "off",
"import/order": [
"error",
{
alphabetize: {
caseInsensitive: true,
order: "asc",
orderImportKind: "asc",
},
"newlines-between": "always",
},
],
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
},
],
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-misused-promises": [
"error",
{
checksVoidReturn: false,
},
],
},
},
{
files: ["**.*.spec.ts", "apps/client-e2e/**/*.ts"],
rules: {
"@typescript-eslint/no-unsafe-assignment": "off",
},
},
);

4428
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -27,43 +27,38 @@
"test:unit": "vitest run", "test:unit": "vitest run",
"test:e2e": "cypress run", "test:e2e": "cypress run",
"lint": "prettier --check . && eslint .", "lint": "prettier --check . && eslint .",
"format": "prettier --write . && eslint --fix ." "fix": "prettier --write . && eslint --fix ."
}, },
"dependencies": { "dependencies": {
"@codemirror/lang-markdown": "^6.2.5", "@codemirror/lang-markdown": "^6.3.2",
"@codemirror/language": "^6.10.2", "@codemirror/language": "^6.11.0",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.27.0", "@codemirror/view": "^6.36.6",
"@lezer/highlight": "^1.2.0", "@lezer/highlight": "^1.2.1",
"@lezer/markdown": "^1.3.0", "@lezer/markdown": "^1.4.3",
"escape-string-regexp": "^5.0.0", "escape-string-regexp": "^5.0.0",
"marked": "^12.0.2" "marked": "^15.0.11"
}, },
"devDependencies": { "devDependencies": {
"@ionaru/eslint-config": "^9.2.1-53.0", "@eslint/js": "^9.25.1",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.1.6", "@types/node": "^22.15.3",
"@types/node": "^20.14.2", "@vitest/coverage-v8": "^3.1.2",
"@typescript-eslint/eslint-plugin": "^5.59.1", "@vitest/ui": "^3.1.2",
"@vitest/coverage-v8": "^1.6.0", "cypress": "^14.3.2",
"@vitest/ui": "^1.6.0", "eslint": "^9.25.1",
"cypress": "^12.11.0", "eslint-config-prettier": "^10.1.2",
"eslint": "^8.39.0", "eslint-plugin-import": "^2.31.0",
"eslint-config-prettier": "^8.8.0", "eslint-plugin-sonarjs": "^3.0.2",
"eslint-plugin-import": "^2.27.5", "eslint-plugin-unicorn": "^59.0.0",
"eslint-plugin-jest": "^27.2.1", "jsdom": "^26.1.0",
"eslint-plugin-prefer-arrow": "^1.2.3", "prettier": "^3.5.3",
"eslint-plugin-sonarjs": "^0.19.0", "rollup": "^4.40.1",
"eslint-plugin-unicorn": "^46.0.0",
"jsdom": "^24.1.0",
"prettier": "^3.3.1",
"rollup": "^4.18.0",
"rollup-plugin-cleaner": "^1.0.0", "rollup-plugin-cleaner": "^1.0.0",
"rollup-plugin-scss": "^4.0.0", "tslib": "^2.8.1",
"sass": "^1.77.4", "typescript": "^5.8.3",
"tslib": "^2.6.3", "typescript-eslint": "^8.31.1",
"typescript": "^5.4.5", "vitest": "^3.1.2"
"vitest": "^1.6.0"
} }
} }

View File

@ -1,28 +1,28 @@
import { nodeResolve } from '@rollup/plugin-node-resolve'; import { nodeResolve } from "@rollup/plugin-node-resolve";
import terser from '@rollup/plugin-terser'; import terser from "@rollup/plugin-terser";
import typescript from '@rollup/plugin-typescript'; import typescript from "@rollup/plugin-typescript";
import cleaner from 'rollup-plugin-cleaner'; import cleaner from "rollup-plugin-cleaner";
import scss from 'rollup-plugin-scss'; import scss from "rollup-plugin-scss";
export default [ export default [
// Browser configuration // Browser configuration
{ {
input: 'src/index.ts', input: "src/index.ts",
output: { output: {
file: 'dist/browser/easymde.min.js', file: "dist/browser/easymde.min.js",
inlineDynamicImports: true, inlineDynamicImports: true,
sourcemap: true, sourcemap: true,
}, },
plugins: [ plugins: [
cleaner({ cleaner({
targets: ['./dist/'], targets: ["./dist/"],
}), }),
nodeResolve(), nodeResolve(),
scss({ scss({
fileName: 'easymde.css', fileName: "easymde.css",
}), }),
typescript(), typescript(),
terser(), terser(),
], ],
} },
]; ];

View File

@ -1,21 +1,20 @@
/* eslint-disable sort-keys,@typescript-eslint/member-ordering */ import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
import { import {
HighlightStyle, HighlightStyle,
defaultHighlightStyle, defaultHighlightStyle,
syntaxHighlighting, syntaxHighlighting,
} from '@codemirror/language'; } from "@codemirror/language";
import { EditorState } from '@codemirror/state'; import { EditorState } from "@codemirror/state";
import { drawSelection, EditorView } from '@codemirror/view'; import { drawSelection, EditorView } from "@codemirror/view";
import { tags } from '@lezer/highlight'; import { tags } from "@lezer/highlight";
import { marked } from 'marked'; import { marked } from "marked";
import { AlreadyConstructedError } from './errors/already-constructed-error'; import { AlreadyConstructedError } from "./errors/already-constructed-error";
import { NotConstructedError } from './errors/not-constructed-error'; import { NotConstructedError } from "./errors/not-constructed-error";
import { importDefaultToolbar, importToolbar } from './imports'; import { importDefaultToolbar, importToolbar } from "./imports";
import { InputOptions, Options } from './options'; import { InputOptions, Options } from "./options";
import './styles.scss'; import "./styles.scss";
export class EasyMDE { export class EasyMDE {
private readonly element: HTMLTextAreaElement; private readonly element: HTMLTextAreaElement;
@ -30,15 +29,16 @@ export class EasyMDE {
this.#options = { this.#options = {
...options, ...options,
blockStyles: { blockStyles: {
bold: '**', bold: "**",
italic: '*', italic: "*",
strikethrough: '~~', strikethrough: "~~",
code: '`', code: "`",
}, },
}; };
this.element = EasyMDE.verifyAndReturnElement(options.element); this.element = EasyMDE.verifyAndReturnElement(options.element);
marked.parse('# EasyMDE'); marked.parse("# EasyMDE", { async: false });
this.construct(); // eslint-disable-next-line sonarjs/no-async-constructor
void this.construct();
} }
public get container(): HTMLDivElement { public get container(): HTMLDivElement {
@ -84,45 +84,45 @@ export class EasyMDE {
const highlightStyle = HighlightStyle.define([ const highlightStyle = HighlightStyle.define([
{ {
tag: tags.heading1, tag: tags.heading1,
fontSize: '200%', fontSize: "200%",
lineHeight: '200%', lineHeight: "200%",
textDecoration: 'none', textDecoration: "none",
}, },
{ {
tag: tags.heading2, tag: tags.heading2,
fontSize: '160%', fontSize: "160%",
lineHeight: '160%', lineHeight: "160%",
textDecoration: 'none', textDecoration: "none",
}, },
{ {
tag: tags.heading3, tag: tags.heading3,
fontSize: '125%', fontSize: "125%",
lineHeight: '125%', lineHeight: "125%",
textDecoration: 'none', textDecoration: "none",
}, },
{ {
tag: tags.heading4, tag: tags.heading4,
fontSize: '110%', fontSize: "110%",
lineHeight: '110%', lineHeight: "110%",
textDecoration: 'none', textDecoration: "none",
}, },
{ {
tag: tags.heading5, tag: tags.heading5,
fontSize: '105%', fontSize: "105%",
lineHeight: '105%', lineHeight: "105%",
textDecoration: 'none', textDecoration: "none",
}, },
{ {
tag: tags.heading6, tag: tags.heading6,
fontSize: '100%', fontSize: "100%",
lineHeight: '100%', lineHeight: "100%",
textDecoration: 'none', textDecoration: "none",
}, },
{ {
tag: tags.monospace, tag: tags.monospace,
fontFamily: 'monospace', fontFamily: "monospace",
textDecoration: 'none', textDecoration: "none",
background: 'rgba(0, 0, 0, 0.05)', background: "rgba(0, 0, 0, 0.05)",
}, },
]); ]);
@ -158,7 +158,7 @@ export class EasyMDE {
easyMDEContainer.append(await this.createStatusBar()); easyMDEContainer.append(await this.createStatusBar());
} }
this.element.insertAdjacentElement('afterend', easyMDEContainer); this.element.insertAdjacentElement("afterend", easyMDEContainer);
this.codemirror.focus(); this.codemirror.focus();
@ -169,7 +169,7 @@ export class EasyMDE {
this.element.value = this.codemirror.state.doc.toString(); this.element.value = this.codemirror.state.doc.toString();
for (const plugin of this.plugins) { for (const plugin of this.plugins) {
plugin.destroy(); void plugin.destroy();
} }
this.codemirror.destroy(); this.codemirror.destroy();
@ -181,7 +181,7 @@ export class EasyMDE {
this.element.hidden = false; this.element.hidden = false;
} }
public async addPlugin(plugin: IEasyMDEPlugin): Promise<IEasyMDEPlugin> { public addPlugin(plugin: IEasyMDEPlugin): IEasyMDEPlugin {
this.plugins.push(plugin); this.plugins.push(plugin);
return plugin; return plugin;
} }
@ -192,20 +192,20 @@ export class EasyMDE {
importDefaultToolbar(), importDefaultToolbar(),
]); ]);
const toolbar = new Toolbar(this, defaultToolbar); const toolbar = new Toolbar(this, defaultToolbar);
await this.addPlugin(toolbar); this.addPlugin(toolbar);
// await toolbar.build(defaultToolbar); // await toolbar.build(defaultToolbar);
return toolbar.element; return toolbar.element;
} }
private async createStatusBar(): Promise<HTMLDivElement> { private async createStatusBar(): Promise<HTMLDivElement> {
const { StatusBar } = await import('./status-bar/status-bar'); const { StatusBar } = await import("./status-bar/status-bar");
const statusBar = new StatusBar(this); const statusBar = new StatusBar(this);
return statusBar.element; return statusBar.element;
} }
private createContainer(): HTMLDivElement { private createContainer(): HTMLDivElement {
const container = document.createElement('div'); const container = document.createElement("div");
container.classList.add('easymde-container'); container.classList.add("easymde-container");
return container; return container;
} }
} }
@ -214,7 +214,7 @@ export type IEasyMDEPluginClass = new (easyMDE: EasyMDE) => IEasyMDEPlugin;
export interface IEasyMDEPlugin { export interface IEasyMDEPlugin {
// new (editor: EasyMDE, ...args: any): IEasyMDEPlugin; // new (editor: EasyMDE, ...args: any): IEasyMDEPlugin;
build(arguments_: any): Promise<void>; build(arguments_: unknown): Promise<void>;
destroy(): Promise<void>; destroy(): Promise<void>;
} }

View File

@ -1,6 +1,6 @@
export class AlreadyConstructedError extends Error { export class AlreadyConstructedError extends Error {
public constructor() { public constructor() {
super('EasyMDE is already initialized.'); super("EasyMDE is already initialized.");
this.name = 'AlreadyConstructedError'; this.name = "AlreadyConstructedError";
} }
} }

View File

@ -3,6 +3,6 @@ export class NotConstructedError extends Error {
super( super(
'EasyMDE is not initialized, run the "construct()" method to do so.', 'EasyMDE is not initialized, run the "construct()" method to do so.',
); );
this.name = 'NotConstructedError'; this.name = "NotConstructedError";
} }
} }

View File

@ -1,5 +0,0 @@
import { EasyMDE } from '../easymde';
import { toggleBlock } from '../utils/toggle-block';
export const toggleBold = (editor: EasyMDE) =>
toggleBlock(editor.codemirror, '**');

View File

@ -1,5 +0,0 @@
import { EasyMDE } from '../easymde';
import { toggleBlock } from '../utils/toggle-block';
export const toggleItalic = (editor: EasyMDE) =>
toggleBlock(editor.codemirror, '*');

View File

@ -1,2 +1,2 @@
export const importToolbar = () => import('./toolbar/toolbar'); export const importToolbar = () => import("./toolbar/toolbar");
export const importDefaultToolbar = () => import('./toolbar/default-toolbar'); export const importDefaultToolbar = () => import("./toolbar/default-toolbar");

View File

@ -1,30 +1,29 @@
export { EasyMDE } from './easymde'; export { EasyMDE } from "./easymde";
export * from './imports'; export * from "./imports";
export class EasyMarkdownEditor extends HTMLElement { export class EasyMarkdownEditor extends HTMLElement {
name = "World";
name = 'World';
constructor() { constructor() {
super(); super();
this.name = 'World'; this.name = "World";
} }
connectedCallback() { connectedCallback() {
const shadow = this.attachShadow({ mode: 'closed' }); const shadow = this.attachShadow({ mode: "closed" });
shadow.innerHTML = 'Hello World!' + this.name; shadow.innerHTML = "Hello World!" + this.name;
} }
static get observedAttributes() { static get observedAttributes() {
return ['name']; return ["name"];
} }
attributeChangedCallback(name: string, oldValue: string, newValue: string) { attributeChangedCallback(name: string, oldValue: string, newValue: string) {
if (name === 'name') { if (name === "name") {
this.name = newValue; this.name = newValue;
} }
console.log('Attribute Changed', name, oldValue, newValue); console.log("Attribute Changed", name, oldValue, newValue);
} }
} }
customElements.define( 'easy-markdown-editor', EasyMarkdownEditor ); customElements.define("easy-markdown-editor", EasyMarkdownEditor);

View File

@ -1,36 +1,36 @@
import { MarkedOptions } from 'marked'; import { MarkedOptions } from "marked";
import { EasyMDE } from './easymde'; import { EasyMDE } from "./easymde";
interface ArrayOneOrMore<T> extends Array<T> { interface ArrayOneOrMore<T> extends Array<T> {
0: T; 0: T;
} }
type ToolbarButton = type ToolbarButton =
| 'bold' | "bold"
| 'italic' | "italic"
| 'quote' | "quote"
| 'unordered-list' | "unordered-list"
| 'ordered-list' | "ordered-list"
| 'link' | "link"
| 'image' | "image"
| 'strikethrough' | "strikethrough"
| 'code' | "code"
| 'table' | "table"
| 'redo' | "redo"
| 'heading' | "heading"
| 'undo' | "undo"
| 'heading-bigger' | "heading-bigger"
| 'heading-smaller' | "heading-smaller"
| 'heading-1' | "heading-1"
| 'heading-2' | "heading-2"
| 'heading-3' | "heading-3"
| 'clean-block' | "clean-block"
| 'horizontal-rule' | "horizontal-rule"
| 'preview' | "preview"
| 'side-by-side' | "side-by-side"
| 'fullscreen' | "fullscreen"
| 'guide'; | "guide";
interface TimeFormatOptions { interface TimeFormatOptions {
locale?: string | string[]; locale?: string | string[];
@ -53,9 +53,7 @@ interface BlockStyleOptions {
italic?: string; italic?: string;
} }
interface CustomAttributes { type CustomAttributes = Record<string, string>;
[key: string]: string;
}
interface InsertTextOptions { interface InsertTextOptions {
horizontalRule?: readonly string[]; horizontalRule?: readonly string[];
@ -77,6 +75,7 @@ interface PromptTexts {
interface RenderingOptions { interface RenderingOptions {
codeSyntaxHighlighting?: boolean; codeSyntaxHighlighting?: boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
hljs?: any; hljs?: any;
markedOptions?: MarkedOptions; markedOptions?: MarkedOptions;
sanitizerFunction?: (html: string) => string; sanitizerFunction?: (html: string) => string;
@ -149,7 +148,7 @@ interface OverlayModeOptions {
combine?: boolean; combine?: boolean;
} }
// eslint-disable-next-line @typescript-eslint/no-empty-interface // eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface SpellCheckerOptions { interface SpellCheckerOptions {
// codeMirrorInstance: CodeMirror.Editor; // codeMirrorInstance: CodeMirror.Editor;
} }
@ -183,22 +182,20 @@ export interface InputOptions {
shortcuts?: Shortcuts; shortcuts?: Shortcuts;
showIcons?: readonly ToolbarButton[]; showIcons?: readonly ToolbarButton[];
spellChecker?: boolean | ((options: SpellCheckerOptions) => void); spellChecker?: boolean | ((options: SpellCheckerOptions) => void);
inputStyle?: 'textarea' | 'contenteditable'; inputStyle?: "textarea" | "contenteditable";
nativeSpellcheck?: boolean; nativeSpellcheck?: boolean;
sideBySideFullscreen?: boolean; sideBySideFullscreen?: boolean;
status?: boolean | ReadonlyArray<string | StatusBarItem>; status?: boolean | readonly (string | StatusBarItem)[];
styleSelectedText?: boolean; styleSelectedText?: boolean;
tabSize?: number; tabSize?: number;
toolbar?: toolbar?:
| boolean | boolean
| ReadonlyArray< | readonly ("|" | ToolbarButton | ToolbarIcon | ToolbarDropdownIcon)[];
'|' | ToolbarButton | ToolbarIcon | ToolbarDropdownIcon
>;
toolbarTips?: boolean; toolbarTips?: boolean;
onToggleFullScreen?: (goingIntoFullScreen: boolean) => void; onToggleFullScreen?: (goingIntoFullScreen: boolean) => void;
theme?: string; theme?: string;
scrollbarStyle?: string; scrollbarStyle?: string;
unorderedListStyle?: '*' | '-' | '+'; unorderedListStyle?: "*" | "-" | "+";
uploadImage?: boolean; uploadImage?: boolean;
imageMaxSize?: number; imageMaxSize?: number;
@ -220,16 +217,14 @@ export interface InputOptions {
overlayMode?: OverlayModeOptions; overlayMode?: OverlayModeOptions;
direction?: 'ltr' | 'rtl'; direction?: "ltr" | "rtl";
} }
export interface Options { export interface Options {
statusbar?: boolean; statusbar?: boolean;
toolbar?: toolbar?:
| boolean | boolean
| ReadonlyArray< | readonly ("|" | ToolbarButton | ToolbarIcon | ToolbarDropdownIcon)[];
'|' | ToolbarButton | ToolbarIcon | ToolbarDropdownIcon
>;
blockStyles: { blockStyles: {
bold: string; bold: string;
code: string; code: string;

View File

@ -1,8 +1,8 @@
import { SelectionRange, StateEffect, Line } from '@codemirror/state'; import { SelectionRange, StateEffect, Line } from "@codemirror/state";
import { ViewPlugin, ViewUpdate } from '@codemirror/view'; import { ViewPlugin, ViewUpdate } from "@codemirror/view";
import { EasyMDE } from '../easymde'; import { EasyMDE } from "../easymde";
import { countWords } from '../utils/count-words'; import { countWords } from "../utils/count-words";
export class StatusBar { export class StatusBar {
public element: HTMLDivElement; public element: HTMLDivElement;
@ -17,8 +17,8 @@ export class StatusBar {
private selectionEnd = 0; private selectionEnd = 0;
public constructor(private editor: EasyMDE) { public constructor(private editor: EasyMDE) {
this.element = document.createElement('div'); this.element = document.createElement("div");
this.element.className = 'easymde-statusbar'; this.element.className = "easymde-statusbar";
// Initial values // Initial values
this.characterCount = this.editor.codemirror.state.doc.length; this.characterCount = this.editor.codemirror.state.doc.length;
@ -51,7 +51,7 @@ export class StatusBar {
let cursorLine: Line; let cursorLine: Line;
if (direction === 'left') { if (direction === "left") {
// Cursor is at the start of the selection. // Cursor is at the start of the selection.
cursorLine = fromLine; cursorLine = fromLine;
this.cursorColumn = this.cursorColumn =
@ -94,11 +94,12 @@ export class StatusBar {
private getSelectionDirection( private getSelectionDirection(
selection: SelectionRange, selection: SelectionRange,
): 'right' | 'left' | undefined { ): "right" | "left" | undefined {
return selection.from === this.selectionStart return selection.from === this.selectionStart
? 'right' ? "right"
: selection.to === this.selectionEnd : // eslint-disable-next-line sonarjs/no-nested-conditional
? 'left' selection.to === this.selectionEnd
? "left"
: undefined; : undefined;
} }
} }

View File

@ -1,19 +0,0 @@
import { ViewUpdate } from '@codemirror/view';
import { EasyMDE } from '../..';
import { IToolbarButtonOptions } from '../default-toolbar';
export class ToolbarButton implements IToolbarButtonOptions {
public action?: any;
public active?:
| boolean
| ((editor: EasyMDE, update: ViewUpdate) => boolean)
| ((editor: EasyMDE, update: ViewUpdate) => Promise<boolean>);
public icon = '';
public title = '';
private _name = '';
public get name() {
return this._name;
}
}

View File

@ -1,8 +1,8 @@
import { ViewUpdate } from '@codemirror/view'; import { ViewUpdate } from "@codemirror/view";
import { EasyMDE } from '../../easymde'; import { EasyMDE } from "../../easymde";
import { checkBlock, toggleBlock } from '../../utils/toggle-block'; import { checkBlock, toggleBlock } from "../../utils/toggle-block";
import { IToolbarButtonOptions } from '../default-toolbar'; import { IToolbarButtonOptions } from "../default-toolbar";
export const toggleBold = (editor: EasyMDE) => export const toggleBold = (editor: EasyMDE) =>
toggleBlock(editor.codemirror, editor.options.blockStyles.bold); toggleBlock(editor.codemirror, editor.options.blockStyles.bold);
@ -13,7 +13,7 @@ export const checkBold = (editor: EasyMDE, _update: ViewUpdate) =>
export const toggleBoldButton: IToolbarButtonOptions = { export const toggleBoldButton: IToolbarButtonOptions = {
action: toggleBold, action: toggleBold,
active: checkBold, active: checkBold,
icon: 'fas fa-bold', icon: "fas fa-bold",
name: 'bold', name: "bold",
title: 'Bold', title: "Bold",
}; };

View File

@ -1,13 +1,13 @@
import { EasyMDE } from '../../easymde'; import { EasyMDE } from "../../easymde";
import { toggleBlock } from '../../utils/toggle-block'; import { toggleBlock } from "../../utils/toggle-block";
import { IToolbarButtonOptions } from '../default-toolbar'; import { IToolbarButtonOptions } from "../default-toolbar";
export const toggleCode = (editor: EasyMDE) => export const toggleCode = (editor: EasyMDE) =>
toggleBlock(editor.codemirror, editor.options.blockStyles.code); toggleBlock(editor.codemirror, editor.options.blockStyles.code);
export const toggleCodeButton: IToolbarButtonOptions = { export const toggleCodeButton: IToolbarButtonOptions = {
action: toggleCode, action: toggleCode,
icon: 'fas fa-code', icon: "fas fa-code",
name: 'code', name: "code",
title: 'Code', title: "Code",
}; };

View File

@ -1,8 +1,8 @@
import { ViewUpdate } from '@codemirror/view'; import { ViewUpdate } from "@codemirror/view";
import { EasyMDE } from '../../easymde'; import { EasyMDE } from "../../easymde";
import { checkBlock, toggleBlock } from '../../utils/toggle-block'; import { checkBlock, toggleBlock } from "../../utils/toggle-block";
import { IToolbarButtonOptions } from '../default-toolbar'; import { IToolbarButtonOptions } from "../default-toolbar";
export const toggleItalic = (editor: EasyMDE) => export const toggleItalic = (editor: EasyMDE) =>
toggleBlock(editor.codemirror, editor.options.blockStyles.italic); toggleBlock(editor.codemirror, editor.options.blockStyles.italic);
@ -13,7 +13,7 @@ export const checkItalic = (editor: EasyMDE, _update: ViewUpdate) =>
export const toggleItalicButton: IToolbarButtonOptions = { export const toggleItalicButton: IToolbarButtonOptions = {
action: toggleItalic, action: toggleItalic,
active: checkItalic, active: checkItalic,
icon: 'fas fa-italic', icon: "fas fa-italic",
name: 'italic', name: "italic",
title: 'Italic', title: "Italic",
}; };

View File

@ -1,13 +1,13 @@
import { EasyMDE } from '../../easymde'; import { EasyMDE } from "../../easymde";
import { toggleBlock } from '../../utils/toggle-block'; import { toggleBlock } from "../../utils/toggle-block";
import { IToolbarButtonOptions } from '../default-toolbar'; import { IToolbarButtonOptions } from "../default-toolbar";
export const toggleStrikethrough = (editor: EasyMDE) => export const toggleStrikethrough = (editor: EasyMDE) =>
toggleBlock(editor.codemirror, editor.options.blockStyles.strikethrough); toggleBlock(editor.codemirror, editor.options.blockStyles.strikethrough);
export const toggleStrikethroughButton: IToolbarButtonOptions = { export const toggleStrikethroughButton: IToolbarButtonOptions = {
action: toggleStrikethrough, action: toggleStrikethrough,
icon: 'fas fa-strikethrough', icon: "fas fa-strikethrough",
name: 'strikethrough', name: "strikethrough",
title: 'Strikethrough', title: "Strikethrough",
}; };

View File

@ -1,13 +1,14 @@
import { ViewUpdate } from '@codemirror/view'; import { ViewUpdate } from "@codemirror/view";
import { EasyMDE } from '../easymde'; import { EasyMDE } from "../easymde";
import { toggleBoldButton } from './buttons/toggle-bold'; import { toggleBoldButton } from "./buttons/toggle-bold";
import { toggleCodeButton } from './buttons/toggle-code'; import { toggleCodeButton } from "./buttons/toggle-code";
import { toggleItalicButton } from './buttons/toggle-italic'; import { toggleItalicButton } from "./buttons/toggle-italic";
// import { toggleStrikethroughButton } from "./buttons/toggle-strikethrough"; import { toggleStrikethroughButton } from "./buttons/toggle-strikethrough";
export interface IToolbarButtonOptions { export interface IToolbarButtonOptions {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
action?: any; action?: any;
active?: active?:
| boolean | boolean
@ -22,53 +23,53 @@ export const defaultToolbar: IToolbarButtonOptions[][] = [
[ [
toggleBoldButton, toggleBoldButton,
toggleItalicButton, toggleItalicButton,
// toggleStrikethroughButton, toggleStrikethroughButton,
{ {
// action: toggleHeadingSmaller, // action: toggleHeadingSmaller,
icon: 'fas fa-header fa-heading', icon: "fas fa-header fa-heading",
name: 'heading', name: "heading",
title: 'Heading', title: "Heading",
}, },
], ],
[ [
toggleCodeButton, toggleCodeButton,
{ {
// action: toggleBlockquote, // action: toggleBlockquote,
icon: 'fas fa-quote-left', icon: "fas fa-quote-left",
name: 'quote', name: "quote",
title: 'Quote', title: "Quote",
}, },
{ {
// action: toggleUnorderedList, // action: toggleUnorderedList,
icon: 'fas fa-list-ul', icon: "fas fa-list-ul",
name: 'unordered-list', name: "unordered-list",
title: 'Generic List', title: "Generic List",
}, },
{ {
// action: toggleOrderedList, // action: toggleOrderedList,
icon: 'fas fa-list-ol', icon: "fas fa-list-ol",
name: 'ordered-list', name: "ordered-list",
title: 'Numbered List', title: "Numbered List",
}, },
{ {
// action: cleanBlock, // action: cleanBlock,
icon: 'fas fa-eraser', icon: "fas fa-eraser",
name: 'clean-block', name: "clean-block",
title: 'Clean block', title: "Clean block",
}, },
], ],
[ [
{ {
// action: drawLink, // action: drawLink,
icon: 'fas fa-link', icon: "fas fa-link",
name: 'link', name: "link",
title: 'Create Link', title: "Create Link",
}, },
{ {
// action: drawImage, // action: drawImage,
icon: 'fas fa-image', icon: "fas fa-image",
name: 'image', name: "image",
title: 'Insert Image', title: "Insert Image",
// }, { // }, {
// // action: drawHorizontalRule, // // action: drawHorizontalRule,
// icon: 'fas fa-minus', // icon: 'fas fa-minus',
@ -99,11 +100,11 @@ export const defaultToolbar: IToolbarButtonOptions[][] = [
], ],
[ [
{ {
action: 'https://simplemde.com/markdown-guide', action: "https://simplemde.com/markdown-guide",
icon: 'fas fa-question', icon: "fas fa-question",
name: 'guide', name: "guide",
// noDisable: true, // noDisable: true,
title: 'Markdown Guide', title: "Markdown Guide",
// }], [{ // }], [{
// action: NewMDE.undo, // action: NewMDE.undo,
// icon: 'fas fa-undo', // icon: 'fas fa-undo',

View File

@ -1,53 +0,0 @@
import { EasyMDE } from '../easymde';
interface CustomAttributes {
[key: string]: string;
}
interface ArrayOneOrMore<T> extends Array<T> {
0: T;
}
interface ToolbarButtonOptions {
name: string;
action: string | ((editor: EasyMDE) => void);
className: string;
title: string;
noDisable?: boolean;
noMobile?: boolean;
icon?: string;
attributes?: CustomAttributes | undefined;
}
interface ToolbarDropdownButtonOptions extends ToolbarButtonOptions {
children: ArrayOneOrMore<ToolbarButtonOptions>;
}
export class ToolbarButton {
private readonly element = document.createElement('button');
public constructor(
private options: ToolbarButtonOptions | ToolbarDropdownButtonOptions,
) {
this.setCustomAttributes();
this.element.setAttribute('type', 'button');
// Shortcuts
// Tooltip
if (options.noDisable) {
// Disable on previes
this.element.classList.add('no-disable');
}
if (options.noMobile) {
// Hide on mobile
this.element.classList.add('no-mobile');
}
this.element.tabIndex = -1;
}
private setCustomAttributes(): void {
const attributes = this.options.attributes || {};
for (const [key, value] of Object.entries(attributes)) {
this.element.setAttribute(key, value);
}
}
}

View File

@ -1,12 +1,12 @@
import { StateEffect } from '@codemirror/state'; import { StateEffect } from "@codemirror/state";
import { ViewPlugin, ViewUpdate } from '@codemirror/view'; import { ViewPlugin, ViewUpdate } from "@codemirror/view";
import { EasyMDE, IEasyMDEPlugin } from '../easymde'; import { EasyMDE, IEasyMDEPlugin } from "../easymde";
import { IToolbarButtonOptions } from './default-toolbar'; import { IToolbarButtonOptions } from "./default-toolbar";
export class Toolbar implements IEasyMDEPlugin { export class Toolbar implements IEasyMDEPlugin {
private static readonly activeClass = 'enabled'; private static readonly activeClass = "enabled";
public element: HTMLDivElement; public element: HTMLDivElement;
@ -14,12 +14,11 @@ export class Toolbar implements IEasyMDEPlugin {
private editor: EasyMDE, private editor: EasyMDE,
toolbarLayout: IToolbarButtonOptions[][], toolbarLayout: IToolbarButtonOptions[][],
) { ) {
this.element = document.createElement('div'); this.element = document.createElement("div");
this.element.className = 'easymde-toolbar'; this.element.className = "easymde-toolbar";
for (const toolBarButtonSection of toolbarLayout) { for (const toolBarButtonSection of toolbarLayout) {
const toolBarSection: Array<HTMLButtonElement | HTMLSpanElement> = const toolBarSection: (HTMLButtonElement | HTMLSpanElement)[] = [];
[];
for (const toolBarButtonOptions of toolBarButtonSection) { for (const toolBarButtonOptions of toolBarButtonSection) {
toolBarSection.push( toolBarSection.push(
@ -52,10 +51,10 @@ export class Toolbar implements IEasyMDEPlugin {
// return toolBar; // return toolBar;
} }
// eslint-disable-next-line @typescript-eslint/require-await
public async build(toolbarLayout: IToolbarButtonOptions[][]) { public async build(toolbarLayout: IToolbarButtonOptions[][]) {
for (const toolBarButtonSection of toolbarLayout) { for (const toolBarButtonSection of toolbarLayout) {
const toolBarSection: Array<HTMLButtonElement | HTMLSpanElement> = const toolBarSection: (HTMLButtonElement | HTMLSpanElement)[] = [];
[];
for (const toolBarButtonOptions of toolBarButtonSection) { for (const toolBarButtonOptions of toolBarButtonSection) {
toolBarSection.push( toolBarSection.push(
@ -76,14 +75,15 @@ export class Toolbar implements IEasyMDEPlugin {
} }
} }
} }
// eslint-disable-next-line @typescript-eslint/require-await
public async destroy() { public async destroy() {
this.element.remove(); this.element.remove();
} }
private createToolBarSeparator() { private createToolBarSeparator() {
const separatorElement = document.createElement('span'); const separatorElement = document.createElement("span");
separatorElement.className = 'separator'; separatorElement.className = "separator";
separatorElement.innerHTML = '|'; separatorElement.innerHTML = "|";
return separatorElement; return separatorElement;
} }
@ -91,7 +91,7 @@ export class Toolbar implements IEasyMDEPlugin {
toolBarButtonOptions: IToolbarButtonOptions, toolBarButtonOptions: IToolbarButtonOptions,
): HTMLButtonElement { ): HTMLButtonElement {
const buttonElement: HTMLButtonElement = const buttonElement: HTMLButtonElement =
document.createElement('button'); document.createElement("button");
buttonElement.tabIndex = -1; buttonElement.tabIndex = -1;
buttonElement.classList.add(toolBarButtonOptions.name); buttonElement.classList.add(toolBarButtonOptions.name);
@ -99,30 +99,32 @@ export class Toolbar implements IEasyMDEPlugin {
buttonElement.title = toolBarButtonOptions.title; buttonElement.title = toolBarButtonOptions.title;
// Set the button onclick action. // Set the button onclick action.
if (typeof toolBarButtonOptions.action === 'function') { if (typeof toolBarButtonOptions.action === "function") {
buttonElement.addEventListener('click', () => buttonElement.addEventListener("click", () =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
toolBarButtonOptions.action(this.editor), toolBarButtonOptions.action(this.editor),
); );
// buttonElement.addEventListener() // buttonElement.addEventListener()
} else if (typeof toolBarButtonOptions.action === 'string') { } else if (typeof toolBarButtonOptions.action === "string") {
buttonElement.addEventListener('click', () => buttonElement.addEventListener("click", () =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
window.open(toolBarButtonOptions.action), window.open(toolBarButtonOptions.action),
); );
} }
if (typeof toolBarButtonOptions.active === 'boolean') { if (typeof toolBarButtonOptions.active === "boolean") {
buttonElement.classList.toggle( buttonElement.classList.toggle(
Toolbar.activeClass, Toolbar.activeClass,
toolBarButtonOptions.active, toolBarButtonOptions.active,
); );
} else if (typeof toolBarButtonOptions.active === 'function') { } else if (typeof toolBarButtonOptions.active === "function") {
this.editor.codemirror.dispatch({ this.editor.codemirror.dispatch({
effects: StateEffect.appendConfig.of( effects: StateEffect.appendConfig.of(
ViewPlugin.define(() => ({ ViewPlugin.define(() => ({
update: async (update: ViewUpdate) => { update: async (update: ViewUpdate) => {
if ( if (
typeof toolBarButtonOptions.active === typeof toolBarButtonOptions.active ===
'function' "function"
) { ) {
const result = const result =
await toolBarButtonOptions.active( await toolBarButtonOptions.active(
@ -141,7 +143,7 @@ export class Toolbar implements IEasyMDEPlugin {
} }
// Set the button icon. // Set the button icon.
const buttonIcon = document.createElement('i'); const buttonIcon = document.createElement("i");
buttonIcon.className = toolBarButtonOptions.icon; buttonIcon.className = toolBarButtonOptions.icon;
buttonElement.append(buttonIcon); buttonElement.append(buttonIcon);

View File

@ -1,4 +1,4 @@
import { Text } from '@codemirror/state'; import { Text } from "@codemirror/state";
export const countWords = (document: Text) => export const countWords = (document: Text) =>
document document
@ -6,6 +6,6 @@ export const countWords = (document: Text) =>
.reduce( .reduce(
(previous, current) => (previous, current) =>
previous + previous +
(current ? current.split(' ').filter(Boolean).length : 0), (current ? current.split(" ").filter(Boolean).length : 0),
0, 0,
); );

View File

@ -1,10 +1,10 @@
import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
import { EditorState } from '@codemirror/state'; import { EditorState } from "@codemirror/state";
import { EditorView } from '@codemirror/view'; import { EditorView } from "@codemirror/view";
// eslint-disable-next-line @typescript-eslint/no-shadow // eslint-disable-next-line @typescript-eslint/no-shadow
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from "vitest";
import { checkBlock, toggleBlock } from './toggle-block'; import { checkBlock, toggleBlock } from "./toggle-block";
const getEditor = ( const getEditor = (
document: string, document: string,
@ -22,8 +22,8 @@ const getEditor = (
}), }),
}); });
describe.each(['*', '**', '`', '_', '__', '~~'])( describe.each(["*", "**", "`", "_", "__", "~~"])(
'checkBlock simple %s', "checkBlock simple %s",
(character) => { (character) => {
const wordSimple = `${character}foo${character}`; const wordSimple = `${character}foo${character}`;
const wordSimpleWithSpace = `${character} foo ${character}`; const wordSimpleWithSpace = `${character} foo ${character}`;
@ -33,7 +33,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
const wordMultiLine = `${character}foo${character}\n${character}boo${character}`; const wordMultiLine = `${character}foo${character}\n${character}boo${character}`;
const wordMultiTab = `${character}foo${character}\t${character}boo${character}`; const wordMultiTab = `${character}foo${character}\t${character}boo${character}`;
it('must detect an active block with selection point in the middle', () => { it("must detect an active block with selection point in the middle", () => {
expect.assertions(1); expect.assertions(1);
const anchor = Math.floor(wordSimple.length / 2); const anchor = Math.floor(wordSimple.length / 2);
@ -44,7 +44,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it('must detect an active block with selection point in the middle of a word', () => { it("must detect an active block with selection point in the middle of a word", () => {
expect.assertions(1); expect.assertions(1);
const anchor = Math.floor(wordSquished.length / 2); const anchor = Math.floor(wordSquished.length / 2);
@ -55,7 +55,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it('must not detect an active block with selection point in the middle of a word surrounded by spaces', () => { it("must not detect an active block with selection point in the middle of a word surrounded by spaces", () => {
expect.assertions(1); expect.assertions(1);
const anchor = Math.floor(wordSimpleWithSpace.length / 2); const anchor = Math.floor(wordSimpleWithSpace.length / 2);
@ -66,14 +66,14 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });
it('must detect an active block with selection point at the start', () => { it("must detect an active block with selection point at the start", () => {
expect.assertions(1); expect.assertions(1);
const result = checkBlock(getEditor(wordSimple), character); const result = checkBlock(getEditor(wordSimple), character);
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it('must detect an active block with selection point at the end', () => { it("must detect an active block with selection point at the end", () => {
expect.assertions(1); expect.assertions(1);
const anchor = wordSimple.length; const anchor = wordSimple.length;
@ -84,7 +84,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it('must detect an active block with partial selection range in the middle', () => { it("must detect an active block with partial selection range in the middle", () => {
expect.assertions(1); expect.assertions(1);
const anchor = Math.floor(wordSimple.length / 2); const anchor = Math.floor(wordSimple.length / 2);
@ -96,7 +96,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it('must detect an active block with selection range of the full word', () => { it("must detect an active block with selection range of the full word", () => {
expect.assertions(1); expect.assertions(1);
const anchor = character.length; const anchor = character.length;
@ -108,7 +108,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it('must detect an active block with selection range of the full text', () => { it("must detect an active block with selection range of the full text", () => {
expect.assertions(1); expect.assertions(1);
const head = wordSimple.length; const head = wordSimple.length;
@ -119,22 +119,22 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it('must detect an active block with selection on the first of multiple lines', () => { it("must detect an active block with selection on the first of multiple lines", () => {
expect.assertions(2); expect.assertions(2);
const anchor = Math.floor(wordMultiLine.split('\n')[0].length / 2); const anchor = Math.floor(wordMultiLine.split("\n")[0].length / 2);
const result = checkBlock( const result = checkBlock(
getEditor(wordMultiLine, { anchor }), getEditor(wordMultiLine, { anchor }),
character, character,
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('foo'); expect(result[1]).toBe("foo");
}); });
it('must detect an active block with selection on the second of multiple lines', () => { it("must detect an active block with selection on the second of multiple lines", () => {
expect.assertions(2); expect.assertions(2);
const multiLineSplit = wordMultiLine.split('\n'); const multiLineSplit = wordMultiLine.split("\n");
const anchor = const anchor =
Math.floor(multiLineSplit[1].length / 2) + Math.floor(multiLineSplit[1].length / 2) +
multiLineSplit[0].length; multiLineSplit[0].length;
@ -143,10 +143,10 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character, character,
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('boo'); expect(result[1]).toBe("boo");
}); });
it('must detect an active block with selection at the end of multiple lines', () => { it("must detect an active block with selection at the end of multiple lines", () => {
expect.assertions(2); expect.assertions(2);
const anchor = wordMultiLine.length; const anchor = wordMultiLine.length;
@ -155,25 +155,25 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character, character,
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('boo'); expect(result[1]).toBe("boo");
}); });
it('must detect an active block with selection on the first of multiple words', () => { it("must detect an active block with selection on the first of multiple words", () => {
expect.assertions(2); expect.assertions(2);
const anchor = Math.floor(wordMultiWord.split(' ')[0].length / 2); const anchor = Math.floor(wordMultiWord.split(" ")[0].length / 2);
const result = checkBlock( const result = checkBlock(
getEditor(wordMultiWord, { anchor }), getEditor(wordMultiWord, { anchor }),
character, character,
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('foo'); expect(result[1]).toBe("foo");
}); });
it('must detect an active block with selection on the second of multiple words', () => { it("must detect an active block with selection on the second of multiple words", () => {
expect.assertions(2); expect.assertions(2);
const multiWordSplit = wordMultiWord.split(' '); const multiWordSplit = wordMultiWord.split(" ");
const anchor = const anchor =
Math.floor(multiWordSplit[1].length / 2) + Math.floor(multiWordSplit[1].length / 2) +
multiWordSplit[0].length; multiWordSplit[0].length;
@ -182,10 +182,10 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character, character,
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('boo'); expect(result[1]).toBe("boo");
}); });
it('must detect an active block with selection at the end of multiple words', () => { it("must detect an active block with selection at the end of multiple words", () => {
expect.assertions(2); expect.assertions(2);
const anchor = wordMultiWord.length; const anchor = wordMultiWord.length;
@ -194,25 +194,25 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character, character,
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('boo'); expect(result[1]).toBe("boo");
}); });
it('must detect an active block with selection on the first of multiple words separated by tab', () => { it("must detect an active block with selection on the first of multiple words separated by tab", () => {
expect.assertions(2); expect.assertions(2);
const anchor = Math.floor(wordMultiTab.split('\t')[0].length / 2); const anchor = Math.floor(wordMultiTab.split("\t")[0].length / 2);
const result = checkBlock( const result = checkBlock(
getEditor(wordMultiTab, { anchor }), getEditor(wordMultiTab, { anchor }),
character, character,
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('foo'); expect(result[1]).toBe("foo");
}); });
it('must detect an active block with selection on the second of multiple words separated by tab', () => { it("must detect an active block with selection on the second of multiple words separated by tab", () => {
expect.assertions(2); expect.assertions(2);
const multiWordSplit = wordMultiTab.split('\t'); const multiWordSplit = wordMultiTab.split("\t");
const anchor = const anchor =
Math.floor(multiWordSplit[1].length / 2) + Math.floor(multiWordSplit[1].length / 2) +
multiWordSplit[0].length; multiWordSplit[0].length;
@ -221,10 +221,10 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character, character,
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('boo'); expect(result[1]).toBe("boo");
}); });
it('must detect an active block with selection at the end of multiple words separated by tab', () => { it("must detect an active block with selection at the end of multiple words separated by tab", () => {
expect.assertions(2); expect.assertions(2);
const anchor = wordMultiTab.length; const anchor = wordMultiTab.length;
@ -233,25 +233,25 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character, character,
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('boo'); expect(result[1]).toBe("boo");
}); });
it('must detect an active block with selection on the first of a multi-word text', () => { it("must detect an active block with selection on the first of a multi-word text", () => {
expect.assertions(2); expect.assertions(2);
const anchor = Math.floor(wordMultiWords.split(' ')[0].length / 2); const anchor = Math.floor(wordMultiWords.split(" ")[0].length / 2);
const result = checkBlock( const result = checkBlock(
getEditor(wordMultiWords, { anchor }), getEditor(wordMultiWords, { anchor }),
character, character,
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('foo boo'); expect(result[1]).toBe("foo boo");
}); });
it('must detect an active block with selection on the second of a multi-word text', () => { it("must detect an active block with selection on the second of a multi-word text", () => {
expect.assertions(2); expect.assertions(2);
const multiWordSplit = wordMultiWords.split(' '); const multiWordSplit = wordMultiWords.split(" ");
const anchor = const anchor =
Math.floor(multiWordSplit[1].length / 2) + Math.floor(multiWordSplit[1].length / 2) +
multiWordSplit[0].length; multiWordSplit[0].length;
@ -260,168 +260,168 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character, character,
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('foo boo'); expect(result[1]).toBe("foo boo");
}); });
}, },
); );
describe('checkBlock special cases', () => { describe("checkBlock special cases", () => {
it('must not detect an active block when another markdown block is used with more of the same characters', () => { it("must not detect an active block when another markdown block is used with more of the same characters", () => {
expect.assertions(1); expect.assertions(1);
const result = checkBlock(getEditor('**foo**', { anchor: 4 }), '*'); const result = checkBlock(getEditor("**foo**", { anchor: 4 }), "*");
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });
it('must not detect an active block when another markdown block is used with more of the same characters in a bigger context', () => { it("must not detect an active block when another markdown block is used with more of the same characters in a bigger context", () => {
expect.assertions(1); expect.assertions(1);
const result = checkBlock( const result = checkBlock(
getEditor('Some text **foo** more text', { anchor: 14 }), getEditor("Some text **foo** more text", { anchor: 14 }),
'*', "*",
); );
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });
it('must detect an active block in a bigger context', () => { it("must detect an active block in a bigger context", () => {
expect.assertions(2); expect.assertions(2);
const result = checkBlock( const result = checkBlock(
getEditor('Some text **foo** more text', { anchor: 14 }), getEditor("Some text **foo** more text", { anchor: 14 }),
'**', "**",
); );
expect(result).toBeTruthy(); expect(result).toBeTruthy();
expect(result[1]).toBe('foo'); expect(result[1]).toBe("foo");
}); });
it('must detect an active block when another markdown block is used with different characters', () => { it("must detect an active block when another markdown block is used with different characters", () => {
expect.assertions(1); expect.assertions(1);
const result = checkBlock(getEditor('__*foo*__', { anchor: 6 }), '*'); const result = checkBlock(getEditor("__*foo*__", { anchor: 6 }), "*");
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it('must not detect an active block when another markdown block is used with less of the same characters', () => { it("must not detect an active block when another markdown block is used with less of the same characters", () => {
expect.assertions(1); expect.assertions(1);
const result = checkBlock(getEditor('*foo*', { anchor: 3 }), '**'); const result = checkBlock(getEditor("*foo*", { anchor: 3 }), "**");
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });
it.each(['*', '**'])( it.each(["*", "**"])(
'must detect an active block when the characters are part of another styling block', "must detect an active block when the characters are part of another styling block",
(c) => { (c) => {
expect.assertions(1); expect.assertions(1);
const result = checkBlock(getEditor('***foo***', { anchor: 5 }), c); const result = checkBlock(getEditor("***foo***", { anchor: 5 }), c);
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}, },
); );
}); });
describe.skip('toggleBlock', () => { describe.skip("toggleBlock", () => {
it('must toggle a single word on with the selection at the start', () => { it("must toggle a single word on with the selection at the start", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('Word', { anchor: 0 }); const editor = getEditor("Word", { anchor: 0 });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('*Word*'); expect(editor.state.doc.toString()).toBe("*Word*");
}); });
it('must toggle a single word on with the selection in the middle', () => { it("must toggle a single word on with the selection in the middle", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('Word', { anchor: 2 }); const editor = getEditor("Word", { anchor: 2 });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('*Word*'); expect(editor.state.doc.toString()).toBe("*Word*");
}); });
it('must toggle a single word on with the selection at the end', () => { it("must toggle a single word on with the selection at the end", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('Word', { anchor: 4 }); const editor = getEditor("Word", { anchor: 4 });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('*Word*'); expect(editor.state.doc.toString()).toBe("*Word*");
}); });
it('must toggle a single word on in between other words', () => { it("must toggle a single word on in between other words", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('Many words are typed here', { anchor: 7 }); const editor = getEditor("Many words are typed here", { anchor: 7 });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('Many *words* are typed here'); expect(editor.state.doc.toString()).toBe("Many *words* are typed here");
}); });
it('must toggle a single word on in between other words big selection', () => { it("must toggle a single word on in between other words big selection", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('Many words are typed here', { const editor = getEditor("Many words are typed here", {
anchor: 5, anchor: 5,
head: 10, head: 10,
}); });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('Many *words* are typed here'); expect(editor.state.doc.toString()).toBe("Many *words* are typed here");
}); });
it('must toggle a single word off with the selection at the start', () => { it("must toggle a single word off with the selection at the start", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('*Word*', { anchor: 0 }); const editor = getEditor("*Word*", { anchor: 0 });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('Word'); expect(editor.state.doc.toString()).toBe("Word");
}); });
it('must toggle a single word off with the selection in the middle', () => { it("must toggle a single word off with the selection in the middle", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('*Word*', { anchor: 3 }); const editor = getEditor("*Word*", { anchor: 3 });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('Word'); expect(editor.state.doc.toString()).toBe("Word");
}); });
it('must toggle a single word off with the selection at the end', () => { it("must toggle a single word off with the selection at the end", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('*Word*', { anchor: 6 }); const editor = getEditor("*Word*", { anchor: 6 });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('Word'); expect(editor.state.doc.toString()).toBe("Word");
}); });
it('must toggle a single word off in between other words', () => { it("must toggle a single word off in between other words", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('Many *words* are typed here', { anchor: 8 }); const editor = getEditor("Many *words* are typed here", { anchor: 8 });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('Many words are typed here'); expect(editor.state.doc.toString()).toBe("Many words are typed here");
}); });
it('must toggle a single word off in between other words big selection', () => { it("must toggle a single word off in between other words big selection", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('Many *words* are typed here', { const editor = getEditor("Many *words* are typed here", {
anchor: 6, anchor: 6,
head: 11, head: 11,
}); });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('Many words are typed here'); expect(editor.state.doc.toString()).toBe("Many words are typed here");
}); });
it('must toggle a formatted sentence off', () => { it("must toggle a formatted sentence off", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('*Many words are typed here*', { anchor: 8 }); const editor = getEditor("*Many words are typed here*", { anchor: 8 });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('Many words are typed here'); expect(editor.state.doc.toString()).toBe("Many words are typed here");
}); });
it('must toggle a formatted sentence off in a big selection', () => { it("must toggle a formatted sentence off in a big selection", () => {
expect.assertions(1); expect.assertions(1);
const editor = getEditor('*Many words are typed here*', { const editor = getEditor("*Many words are typed here*", {
anchor: 6, anchor: 6,
head: 11, head: 11,
}); });
toggleBlock(editor, '*'); toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe('Many words are typed here'); expect(editor.state.doc.toString()).toBe("Many words are typed here");
}); });
}); });

View File

@ -1,6 +1,6 @@
import { EditorSelection, EditorState } from '@codemirror/state'; import { EditorSelection, EditorState } from "@codemirror/state";
import { EditorView } from '@codemirror/view'; import { EditorView } from "@codemirror/view";
import escapeStringRegexp from 'escape-string-regexp'; import escapeStringRegexp from "escape-string-regexp";
/** /**
* Checks whether the selection matches a formatted block of text. * Checks whether the selection matches a formatted block of text.
@ -18,7 +18,7 @@ export const checkBlock = (
const escapedCharacters = escapeStringRegexp(characters); const escapedCharacters = escapeStringRegexp(characters);
const regularExpression = new RegExp( const regularExpression = new RegExp(
`^${escapedCharacters}(.*)${escapedCharacters}$`, `^${escapedCharacters}(.*)${escapedCharacters}$`,
'gs', "gs",
); );
const checkResult = regularExpression.exec(text); const checkResult = regularExpression.exec(text);
@ -26,8 +26,16 @@ export const checkBlock = (
let doubleCharactersCheckResult = null; let doubleCharactersCheckResult = null;
let tripleCharactersCheckResult = null; let tripleCharactersCheckResult = null;
if (characters.length === 1) { if (characters.length === 1) {
doubleCharactersCheckResult = checkBlock(editor, characters.repeat(2), minimal); doubleCharactersCheckResult = checkBlock(
tripleCharactersCheckResult = checkBlock(editor, characters.repeat(3), minimal); editor,
characters.repeat(2),
minimal,
);
tripleCharactersCheckResult = checkBlock(
editor,
characters.repeat(3),
minimal,
);
} }
if ( if (
@ -100,13 +108,14 @@ export const getExpandedSelection = (
while (fromPosition >= 0) { while (fromPosition >= 0) {
const newText = state.sliceDoc(fromPosition, to); const newText = state.sliceDoc(fromPosition, to);
if (newText.startsWith('\n') || newText.startsWith('\t')) { if (newText.startsWith("\n") || newText.startsWith("\t")) {
fromPosition++; fromPosition++;
break; break;
} else if (minimal && newText.startsWith(' ')) { // eslint-disable-next-line sonarjs/no-duplicated-branches
} else if (minimal && newText.startsWith(" ")) {
fromPosition++; fromPosition++;
break; break;
} else if (newText.startsWith(characters + ' ')) { } else if (newText.startsWith(characters + " ")) {
fromPosition += characters.length + 1; fromPosition += characters.length + 1;
break; break;
} else if ( } else if (
@ -123,10 +132,11 @@ export const getExpandedSelection = (
let toPosition = to; let toPosition = to;
while (toPosition < state.doc.length) { while (toPosition < state.doc.length) {
const newText = state.sliceDoc(from, toPosition); const newText = state.sliceDoc(from, toPosition);
if (newText.endsWith('\n') || newText.endsWith('\t')) { if (newText.endsWith("\n") || newText.endsWith("\t")) {
toPosition--; toPosition--;
break; break;
} else if (minimal && newText.endsWith(' ')) { // eslint-disable-next-line sonarjs/no-duplicated-branches
} else if (minimal && newText.endsWith(" ")) {
toPosition--; toPosition--;
break; break;
} else if ( } else if (

View File

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@ -57,9 +57,9 @@ This is a longer sentence!
EasyMDE, EasyMDE,
importToolbar, importToolbar,
importDefaultToolbar, importDefaultToolbar,
} from '../dist/browser/easymde.min.js'; } from "../dist/browser/easymde.min.js";
window.x = new EasyMDE({ window.x = new EasyMDE({
element: document.getElementById('my-text-area'), element: document.getElementById("my-text-area"),
toolbar: true, toolbar: true,
statusbar: true, statusbar: true,
}); });
@ -73,7 +73,9 @@ This is a longer sentence!
<easy-markdown-editor name="meow"></easy-markdown-editor> <easy-markdown-editor name="meow"></easy-markdown-editor>
<script> <script>
document.querySelector('easy-markdown-editor').setAttribute('name', 'Everyone'); document
.querySelector("easy-markdown-editor")
.setAttribute("name", "Everyone");
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@ -36,7 +36,7 @@ const x = "moooo";
> >
<script type="module"> <script type="module">
window.x = new EasyMDE({ window.x = new EasyMDE({
element: document.getElementById('text'), element: document.getElementById("text"),
}); });
</script> </script>
</body> </body>

View File

@ -1,4 +1,4 @@
import { defineConfig } from 'vitest/config'; import { defineConfig } from "vitest/config";
export default defineConfig({ export default defineConfig({
test: { test: {
@ -6,11 +6,11 @@ export default defineConfig({
all: true, all: true,
clean: true, clean: true,
enabled: true, enabled: true,
include: ['src/**/*.ts'], include: ["src/**/*.ts"],
provider: 'v8', provider: "v8",
}, },
dir: 'src', dir: "src",
environment: 'jsdom', environment: "jsdom",
include: ['**/*.spec.ts'], include: ["**/*.spec.ts"],
}, },
}); });