2
0
mirror of https://github.com/Ionaru/easy-markdown-editor synced 2025-06-29 22:21: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

@ -2,8 +2,8 @@
### I'm submitting a...
- [x] Bug report
- [ ] Feature request
- [x] Bug report
- [ ] Feature request
### Reproduction steps

View File

@ -1,9 +1,9 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
title: ""
labels: Bug
assignees: ''
assignees: ""
---
**Describe the bug**
@ -25,9 +25,9 @@ If applicable, add screenshots to help explain your problem.
**Version information**
- OS: [e.g. Windows, MacOS]
- Browser: [e.g. Chrome 72]
- EasyMDE version: [e.g. 2.5.1]
- OS: [e.g. Windows, MacOS]
- Browser: [e.g. Chrome 72]
- EasyMDE version: [e.g. 2.5.1]
**Additional context**
Add any other context about the problem here.

View File

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

View File

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

View File

@ -1,48 +1,48 @@
name: Test & Deploy
on:
workflow_dispatch:
push:
branches:
- master
tags:
- '*'
pull_request:
branches:
- master
workflow_dispatch:
push:
branches:
- master
tags:
- "*"
pull_request:
branches:
- master
jobs:
audit:
runs-on: ubuntu-latest
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v3
with:
node-version: current
steps:
- uses: actions/setup-node@v3
with:
node-version: current
- uses: actions/checkout@v3
- uses: actions/checkout@v3
- run: npm audit --omit=dev
- run: npm audit --omit=dev
test:
needs: [audit]
runs-on: ubuntu-latest
test:
needs: [audit]
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ['14', '16', '18']
strategy:
matrix:
node-version: ["14", "16", "18"]
steps:
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
steps:
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- uses: actions/checkout@v3
- uses: actions/checkout@v3
- run: npm ci
- run: npm ci
- name: Test
run: npm test
- name: Test
run: npm test
# - uses: actions/upload-artifact@v3
# if: failure()
# with:

View File

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

View File

@ -9,311 +9,311 @@ and this project adheres to [Semantic Versioning](https://semver.org).
### BREAKING CHANGES
- Complete rewrite of the editor. API has changed significantly, please see the [migration guide](TBD).
- Complete rewrite of the editor. API has changed significantly, please see the [migration guide](TBD).
## [2.18.0] - 2022-09-20
### Added
- `toolbarButtonClassPrefix` option to resolve conflicts with Bootstrap classes ([#493]).
- `toolbarButtonClassPrefix` option to resolve conflicts with Bootstrap classes ([#493]).
## [2.17.0] - 2022-08-20
### Added
- Improved CSRF support for uploading images (Thanks to [@ZsgsDesign], [#394]).
- Option to register an image preview handler: `imagesPreviewHandler` (Thanks to [@diego-gw], [#411]).
- Support for `.webp` image formats (Thanks to [@sghoweri], [#417]).
- `toggleHeading` methods for headings 4, 5 and 6 (Thanks to [@vanillajonathan], [#449] and [#492]).
- Support for `.gif`, `.avif` and `.apng` image formats (Thanks to [@vanillajonathan], [#450], [#458] and [#459]).
- Keyboard shortcuts for `toggleHeading` methods (Thanks to [@vanillajonathan], [#451]).
- `iconClassMap` option to specify icon class names for toolbar buttons (Thanks to [@vanillajonathan], [#454]).
- `role="toolbar"` on the toolbar element (Thanks to [@vanillajonathan], [#455]).
- Support for text buttons in the toolbar (Thanks to [@vanillajonathan], [#461]).
- `aria-label` attribute for toolbar buttons (uses the `title` option) (Thanks to [@vanillajonathan], [#463]).
- `role="application"` on the editor container element (Thanks to [@vanillajonathan], [#464]).
- Option to not overwrite the preview HTML by returning `null` from `previewRender` (Thanks to [@LevitatingOrange], [#471]).
- Improved CSRF support for uploading images (Thanks to [@ZsgsDesign], [#394]).
- Option to register an image preview handler: `imagesPreviewHandler` (Thanks to [@diego-gw], [#411]).
- Support for `.webp` image formats (Thanks to [@sghoweri], [#417]).
- `toggleHeading` methods for headings 4, 5 and 6 (Thanks to [@vanillajonathan], [#449] and [#492]).
- Support for `.gif`, `.avif` and `.apng` image formats (Thanks to [@vanillajonathan], [#450], [#458] and [#459]).
- Keyboard shortcuts for `toggleHeading` methods (Thanks to [@vanillajonathan], [#451]).
- `iconClassMap` option to specify icon class names for toolbar buttons (Thanks to [@vanillajonathan], [#454]).
- `role="toolbar"` on the toolbar element (Thanks to [@vanillajonathan], [#455]).
- Support for text buttons in the toolbar (Thanks to [@vanillajonathan], [#461]).
- `aria-label` attribute for toolbar buttons (uses the `title` option) (Thanks to [@vanillajonathan], [#463]).
- `role="application"` on the editor container element (Thanks to [@vanillajonathan], [#464]).
- Option to not overwrite the preview HTML by returning `null` from `previewRender` (Thanks to [@LevitatingOrange], [#471]).
### Fixed
- Hyperlink doubling (Thanks to [@danielok1993], [#95]).
- URLs with certain characters entered through prompts causing invalid markdown (Thanks to [@Zignature], [#393]).
- Autofocus option not working properly ([#399]).
- Inconsistent border colour (Thanks to [@vanillajonathan], [#466]).
- Invalid regex on Safari browsers ([#478])
- `hideIcons` option type (Thanks to [@LoyalPotato], [#488]).
- Hyperlink doubling (Thanks to [@danielok1993], [#95]).
- URLs with certain characters entered through prompts causing invalid markdown (Thanks to [@Zignature], [#393]).
- Autofocus option not working properly ([#399]).
- Inconsistent border colour (Thanks to [@vanillajonathan], [#466]).
- Invalid regex on Safari browsers ([#478])
- `hideIcons` option type (Thanks to [@LoyalPotato], [#488]).
### Changed
- Editor now uses responsive font sizes (Thanks to [@vanillajonathan], [#452]).
- Editor now uses responsive font sizes (Thanks to [@vanillajonathan], [#452]).
### Documentation
- Added several improvements to README (Thanks to [@vanillajonathan], [#436], [#438], [#440], [#444]).
- Fixed typo in README (Thanks to [@kicksent], [#484]).
- Added missing icon for `upload-image` in README (Thanks to [@hlf20010508], [#486]).
- Added several improvements to README (Thanks to [@vanillajonathan], [#436], [#438], [#440], [#444]).
- Fixed typo in README (Thanks to [@kicksent], [#484]).
- Added missing icon for `upload-image` in README (Thanks to [@hlf20010508], [#486]).
## [2.16.1] - 2022-01-14
### Fixed
- Incorrect initial line and column count in status bar.
- Security issue in `marked` dependency.
- Incorrect initial line and column count in status bar.
- Security issue in `marked` dependency.
## [2.16.0] - 2022-01-11
### Added
- `direction` option to enable RTL mode (Thanks to [@souljuse], [#358]).
- `attributes` option to add custom attributes to toolbar buttons (Thanks to [@Zignature], [#388]).
- `unorderedListStyle` option to change the character used for unordered lists (Thanks to [@Zignature], [#389]).
- `direction` option to enable RTL mode (Thanks to [@souljuse], [#358]).
- `attributes` option to add custom attributes to toolbar buttons (Thanks to [@Zignature], [#388]).
- `unorderedListStyle` option to change the character used for unordered lists (Thanks to [@Zignature], [#389]).
### Fixed
- Image extension detection when extension is uppercase (Thanks to [@ukjinjang], [#357]).
- Submenu actions not working in Chromium Browsers (Thanks to [@Offerel], [@robjean9] and [@kelvinj], [#364]).
- Incorrect line and column count in status bar (Thanks to [@Zignature], [#384]).
- Image extension detection when extension is uppercase (Thanks to [@ukjinjang], [#357]).
- Submenu actions not working in Chromium Browsers (Thanks to [@Offerel], [@robjean9] and [@kelvinj], [#364]).
- Incorrect line and column count in status bar (Thanks to [@Zignature], [#384]).
## [2.15.0] - 2021-04-22
### Added
- `imagePathAbsolute` option to return the absolute path when uploading an image (Thanks to [@wwsalmon], [#313]).
- `imagePathAbsolute` option to return the absolute path when uploading an image (Thanks to [@wwsalmon], [#313]).
### Fixed
- `ToolbarIcon` typings, added `icon` (Thanks to [@ChronosMasterOfAllTime], [#308]).
- Image link extension when it was not the last part of the URL (Thanks to [@deerboy], [#311]).
- Preview mode did not stay enabled when toggling to fullscreen (Thanks to [@smundro], [#316]).
- Required typings not being included in `dependencies` (Thanks to [@marekdedic], [#322]).
- `ToolbarIcon` typings, added `icon` (Thanks to [@ChronosMasterOfAllTime], [#308]).
- Image link extension when it was not the last part of the URL (Thanks to [@deerboy], [#311]).
- Preview mode did not stay enabled when toggling to fullscreen (Thanks to [@smundro], [#316]).
- Required typings not being included in `dependencies` (Thanks to [@marekdedic], [#322]).
## [2.14.0] - 2021-02-14
### Added
- The `scrollbarStyle` option to change the style of the scrollbar (Thanks to [@danice], [#250]).
- The `scrollbarStyle` option to change the style of the scrollbar (Thanks to [@danice], [#250]).
### Fixed
- Issues with images not displaying correctly in the preview screen (Thanks to [@ivictbor], [#253]).
- An error when both `sideBySideFullscreen` and `status` were set to `false` (Thanks to [@joahim], [#272]).
- Editor trying to display non-image files (Thanks to [@Juupaa], [#277])
- Unneeded call to `window.removeEventListener` (Thanks to [@emirotin], [#280])
- Spell checker (Thanks to [@Fanvadar], [#284]).
- Focus issues with toolbar dropdown menus (Thanks to [@Situphen], [#285]).
- Interaction between the `sideBySideFullscreen` and the preview state (Thanks to [@smundro], [#286])
- Refactored strange method of padding the toolbar into regular padding (Thanks to [@sn3p], [#293]).
- Security issue in `marked` dependency (Thanks to [@dependabot], [#298]).
- Issues with images not displaying correctly in the preview screen (Thanks to [@ivictbor], [#253]).
- An error when both `sideBySideFullscreen` and `status` were set to `false` (Thanks to [@joahim], [#272]).
- Editor trying to display non-image files (Thanks to [@Juupaa], [#277])
- Unneeded call to `window.removeEventListener` (Thanks to [@emirotin], [#280])
- Spell checker (Thanks to [@Fanvadar], [#284]).
- Focus issues with toolbar dropdown menus (Thanks to [@Situphen], [#285]).
- Interaction between the `sideBySideFullscreen` and the preview state (Thanks to [@smundro], [#286])
- Refactored strange method of padding the toolbar into regular padding (Thanks to [@sn3p], [#293]).
- Security issue in `marked` dependency (Thanks to [@dependabot], [#298]).
## [2.13.0] - 2020-11-11
### Added
- CodeMirror autorefresh plugin and autoRefresh option (Thanks to [@mbolli], [#249]).
- `lineNumbers` option to display line numbers in the editor (Thanks to [@nhymxu], [#267]).
- CodeMirror autorefresh plugin and autoRefresh option (Thanks to [@mbolli], [#249]).
- `lineNumbers` option to display line numbers in the editor (Thanks to [@nhymxu], [#267]).
### Fixed
- CSS scoping issues when the editor is used in combination with other CodeMirror instances ([#252]).
- CSS scoping issues when the editor is used in combination with other CodeMirror instances ([#252]).
## [2.12.1] - 2020-10-06
### Changed
- Set `previewImagesInEditor` option to `false` by default ([#251]).
- Set `previewImagesInEditor` option to `false` by default ([#251]).
## [2.12.0] - 2020-09-29
### Added
- `this` context in imageUploadFunction (Thanks to [@JoshuaLicense], [#225]).
- `previewImagesInEditor` option to display images in editor mode (Thanks to [@ivictbor], [#235]).
- `overlayMode` options to supply an additional codemirror mode (Thanks to [@czynskee], [#244]).
- `this` context in imageUploadFunction (Thanks to [@JoshuaLicense], [#225]).
- `previewImagesInEditor` option to display images in editor mode (Thanks to [@ivictbor], [#235]).
- `overlayMode` options to supply an additional codemirror mode (Thanks to [@czynskee], [#244]).
### Fixed
- Corrected default size units from `b,Kb,Mb` to ` B, KB, MB` ([#239]).
- Max height less than min height (Thanks to [@nick-denry], [#222]).
- toTextArea issue (Thanks to [@nick-denry], [#223]).
- Error when updateStatusBar was called during image upload, but the status bar is disabled (Thanks to [@JoshuaLicense], [#224]).
- Corrected default size units from `b,Kb,Mb` to ` B, KB, MB` ([#239]).
- Max height less than min height (Thanks to [@nick-denry], [#222]).
- toTextArea issue (Thanks to [@nick-denry], [#223]).
- Error when updateStatusBar was called during image upload, but the status bar is disabled (Thanks to [@JoshuaLicense], [#224]).
## [2.11.0] - 2020-07-16
### Added
- Support for Node.js 14.
- Preview without fullscreen (Thanks to [@nick-denry], [#196]).
- Support for Node.js 14.
- Preview without fullscreen (Thanks to [@nick-denry], [#196]).
### Fixed
- Fix cursor displayed position on activity (Thanks to [@firm1], [#184]).
- Checkboxes always have bullets in front of them ([#136]).
- Save the text only when modifying the content of the easymde instance (Thanks to [@firm1], [#181]).
- Fix cursor displayed position on activity (Thanks to [@firm1], [#184]).
- Checkboxes always have bullets in front of them ([#136]).
- Save the text only when modifying the content of the easymde instance (Thanks to [@firm1], [#181]).
## [2.10.1] - 2020-04-06
### Fixed
- Typescript error when entering certain strings for toolbar buttons ([#178]).
- Typescript error when entering certain strings for toolbar buttons ([#178]).
## [2.10.0] - 2020-04-02
### Added
- `inputStyle` and `nativeSpellcheck` options to manage the native language of the browser (Thanks to [@firm1], [#143]).
- Group buttons in drop-down lists by adding a sub-option `children` for the items in the toolbar (Thanks to [@firm1], [#141]).
- `sanitizerFunction` option to allow custom HTML sanitizing in the markdown preview (Thanks to [@adamb70], [#147]).
- Time formatting and custom text options for the autosave message (Thanks to [@dima-bzz], [#170]).
- `inputStyle` and `nativeSpellcheck` options to manage the native language of the browser (Thanks to [@firm1], [#143]).
- Group buttons in drop-down lists by adding a sub-option `children` for the items in the toolbar (Thanks to [@firm1], [#141]).
- `sanitizerFunction` option to allow custom HTML sanitizing in the markdown preview (Thanks to [@adamb70], [#147]).
- Time formatting and custom text options for the autosave message (Thanks to [@dima-bzz], [#170]).
### Changed
- Delay before assuming that submit of the form as failed is `autosave.submit_delay` instead of `autosave.delay` (Thanks to [@Situphen], [#139]).
- Add `watch` task for gulp (Thanks to [@A-312], [#150]).
- Delay before assuming that submit of the form as failed is `autosave.submit_delay` instead of `autosave.delay` (Thanks to [@Situphen], [#139]).
- Add `watch` task for gulp (Thanks to [@A-312], [#150]).
### Fixed
- Issue with Marked when using IE11 and webpack (Thanks to [@felipefdl], [#169]).
- Updated codemirror to version 5.52.2 (Thanks to [@A-312], [#173]).
- Editor displaying on top of other elements on a webpage (Thanks to [@StefKors], [#175]).
- Issue with Marked when using IE11 and webpack (Thanks to [@felipefdl], [#169]).
- Updated codemirror to version 5.52.2 (Thanks to [@A-312], [#173]).
- Editor displaying on top of other elements on a webpage (Thanks to [@StefKors], [#175]).
## [2.9.0] - 2020-01-13
### Added
- Missing minHeight option in type definition (Thanks to [@t49tran], [#123]).
- Other missing type definitions ([#126]).
- Missing minHeight option in type definition (Thanks to [@t49tran], [#123]).
- Other missing type definitions ([#126]).
### Changed
- The editor will remove its saved contents when the editor is emptied, allowing to reload a default value (Thanks to [@Situphen], [#132]).
- The editor will remove its saved contents when the editor is emptied, allowing to reload a default value (Thanks to [@Situphen], [#132]).
## [2.8.0] - 2019-08-20
### Added
- Upload images functionality (Thanks to [@roipoussiere] and [@JeroenvO], [#71], [#101]).
- Allow custom image upload function (Thanks to [@sperezp], [#106]).
- More polish to the upload images functionality (Thanks to [@jfly], [#109]).
- Improved React compatibility (Thanks to [@richtera], [#97]).
- Upload images functionality (Thanks to [@roipoussiere] and [@JeroenvO], [#71], [#101]).
- Allow custom image upload function (Thanks to [@sperezp], [#106]).
- More polish to the upload images functionality (Thanks to [@jfly], [#109]).
- Improved React compatibility (Thanks to [@richtera], [#97]).
### Fixed
- Missing link in dist file header.
- Missing link in dist file header.
## [2.7.0] - 2019-07-13
### Added
- `previewClass` option for overwriting the preview screen class ([#99]).
- `previewClass` option for overwriting the preview screen class ([#99]).
### Fixed
- Updated dependencies to resolve potential security issue.
- Resolved small code style issues shown by new eslint rules.
- Updated dependencies to resolve potential security issue.
- Resolved small code style issues shown by new eslint rules.
## [2.6.1] - 2019-06-17
### Fixed
- Error when toggling between ordered and unordered lists (Thanks to [@roryok], [#93]).
- Keyboard shortcuts for custom actions not working (Thanks to [@ysykzheng], [#75]).
- Error when toggling between ordered and unordered lists (Thanks to [@roryok], [#93]).
- Keyboard shortcuts for custom actions not working (Thanks to [@ysykzheng], [#75]).
## [2.6.0] - 2019-04-15
### Added
- Contributing guide (Thanks to [@roipoussiere], [#54]).
- Issue templates.
- Standardized changelog file.
- Contributing guide (Thanks to [@roipoussiere], [#54]).
- Issue templates.
- Standardized changelog file.
### Changed
- Finish rewrite of README (Thanks to [@roipoussiere], [#54]).
- Image and link prompt fill with "https://" by default.
- Link to markdown guide to <https://www.markdownguide.org/basic-syntax/>.
- Finish rewrite of README (Thanks to [@roipoussiere], [#54]).
- Image and link prompt fill with "https://" by default.
- Link to markdown guide to <https://www.markdownguide.org/basic-syntax/>.
### Fixed
- Backwards compatibility in the API with SimpleMDE 1.0.0 ([#41]).
- Automatic publish of master branch to `@next`
- Backwards compatibility in the API with SimpleMDE 1.0.0 ([#41]).
- Automatic publish of master branch to `@next`
### Removed
- Distribution files from source-control.
- Distribution files from source-control.
## [2.5.1] - 2019-01-17
### Fixed
- `role="button"` needed to be `type="button"` ([#45]).
- `role="button"` needed to be `type="button"` ([#45]).
## [2.5.0] - 2019-01-17
### Added
- Typescript support (Thanks to [@FranklinWhale], [#44]).
- `role="button"` to toolbar buttons ([#38]).
- Typescript support (Thanks to [@FranklinWhale], [#44]).
- `role="button"` to toolbar buttons ([#38]).
### Fixed
- Eraser icon not working with FontAwesome 5.
- Eraser icon not working with FontAwesome 5.
## [2.4.2] - 2018-11-09
### Added
- Node.js 11 support.
- Node.js 11 support.
### Fixed
- Header button icons not showing sub-icons with FontAwesome 5.
- Inconsistent autosave behaviour when submitting a form (Thanks to [@Furgas] and [@adamb70], [#31]).
- Header button icons not showing sub-icons with FontAwesome 5.
- Inconsistent autosave behaviour when submitting a form (Thanks to [@Furgas] and [@adamb70], [#31]).
## [2.4.1] - 2018-10-15
### Added
- `fa-redo` class to redo button for FA5 compatibility (Thanks to [@Summon528], [#27]).
- `fa-redo` class to redo button for FA5 compatibility (Thanks to [@Summon528], [#27]).
## [2.4.0] - 2018-10-15
### Added
- Theming support (Thanks to [@LeviticusMB], [#17]).
- onToggleFullscreen event hook (Thanks to [@n-3-0], [#16]).
- Theming support (Thanks to [@LeviticusMB], [#17]).
- onToggleFullscreen event hook (Thanks to [@n-3-0], [#16]).
### Fixed
- Fullscreen not working with `toolbar: false` (Thanks to [@aphitiel], [#19]).
- Fullscreen not working with `toolbar: false` (Thanks to [@aphitiel], [#19]).
## [2.2.2] - 2019-07-03
### Fixed
- Automatic publish only publishing tags.
- Automatic publish only publishing tags.
## [2.2.1] - 2019-06-29
### Changed
- Attempt automatic publish `@next` version on npm.
- Links in the preview window will open in a new tab by default.
- Attempt automatic publish `@next` version on npm.
- Links in the preview window will open in a new tab by default.
### Fixed
- Multi-text select issue by disabling multi-select in the editor ([#10]).
- `main` file in package.json (Thanks to [@sne11ius], [#11]).
- Multi-text select issue by disabling multi-select in the editor ([#10]).
- `main` file in package.json (Thanks to [@sne11ius], [#11]).
## [2.0.1] - 2018-05-13
### Changed
- Rewrote part of the documentation for EasyMDE.
- Updated gulp to version 4.0.0.
- Rewrote part of the documentation for EasyMDE.
- Updated gulp to version 4.0.0.
### Fixed
- Icons for `heading-smaller`, `heading-bigger`, `heading-1`, `heading-2` and `heading-3` not showing ([#9]).
- Icons for `heading-smaller`, `heading-bigger`, `heading-1`, `heading-2` and `heading-3` not showing ([#9]).
## [2.0.0] - 2018-04-23
@ -321,23 +321,23 @@ Project forked from [SimpleMDE](https://github.com/sparksuite/simplemde-markdown
### BREAKING CHANGES
- Dropped Bower support.
- Dropped support for older Node.js versions.
- Dropped Bower support.
- Dropped support for older Node.js versions.
### Added
- FontAwesome 5 support.
- Support for newer Node.js versions.
- FontAwesome 5 support.
- Support for newer Node.js versions.
### Changed
- Packages are now version-locked.
- Simplified build script.
- Markdown guide button is no longer disabled in preview mode.
- Packages are now version-locked.
- Simplified build script.
- Markdown guide button is no longer disabled in preview mode.
### Fixed
- Cursor not always showing in "text" mode over the edit field
- Cursor not always showing in "text" mode over the edit field
<!-- Linked issues -->

View File

@ -22,14 +22,14 @@ The editor is entirely customizable, from theming to toolbar buttons and javascr
## Quick access
- [EasyMDE - Markdown Editor](#easymde---markdown-editor)
- [Quick access](#quick-access)
- [Install EasyMDE](#install-easymde)
- [How to use](#how-to-use)
- [How it works](#how-it-works)
- [SimpleMDE fork](#simplemde-fork)
- [Contributing](#contributing)
- [License](#license)
- [EasyMDE - Markdown Editor](#easymde---markdown-editor)
- [Quick access](#quick-access)
- [Install EasyMDE](#install-easymde)
- [How to use](#how-to-use)
- [How it works](#how-it-works)
- [SimpleMDE fork](#simplemde-fork)
- [Contributing](#contributing)
- [License](#license)
## Install EasyMDE
@ -57,14 +57,14 @@ I originally made this fork to implement FontAwesome 5 compatibility into Simple
Changes include:
- FontAwesome 5 compatibility
- Guide button works when editor is in preview mode
- Links are now `https://` by default
- Small styling changes
- Support for Node 8 and beyond
- Lots of refactored code
- Links in preview will open in a new tab by default
- TypeScript support
- FontAwesome 5 compatibility
- Guide button works when editor is in preview mode
- Links are now `https://` by default
- Small styling changes
- Support for Node 8 and beyond
- Lots of refactored code
- Links in preview will open in a new tab by default
- TypeScript support
My intention is to continue development on this project, improving it and keeping it alive.
@ -76,5 +76,5 @@ Want to contribute to EasyMDE? Thank you! We have a [contribution guide](./CONTR
This project is released under the [MIT License](./LICENSE).
- Copyright (c) 2015 Sparksuite, Inc.
- Copyright (c) 2017 Jeroen Akkerman.
- Copyright (c) 2015 Sparksuite, Inc.
- Copyright (c) 2017 Jeroen Akkerman.

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",
},
},
);

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

View File

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

View File

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

View File

@ -3,6 +3,6 @@ export class NotConstructedError extends Error {
super(
'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 importDefaultToolbar = () => import('./toolbar/default-toolbar');
export const importToolbar = () => import("./toolbar/toolbar");
export const importDefaultToolbar = () => import("./toolbar/default-toolbar");

View File

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

View File

@ -1,8 +1,8 @@
import { SelectionRange, StateEffect, Line } from '@codemirror/state';
import { ViewPlugin, ViewUpdate } from '@codemirror/view';
import { SelectionRange, StateEffect, Line } from "@codemirror/state";
import { ViewPlugin, ViewUpdate } from "@codemirror/view";
import { EasyMDE } from '../easymde';
import { countWords } from '../utils/count-words';
import { EasyMDE } from "../easymde";
import { countWords } from "../utils/count-words";
export class StatusBar {
public element: HTMLDivElement;
@ -17,8 +17,8 @@ export class StatusBar {
private selectionEnd = 0;
public constructor(private editor: EasyMDE) {
this.element = document.createElement('div');
this.element.className = 'easymde-statusbar';
this.element = document.createElement("div");
this.element.className = "easymde-statusbar";
// Initial values
this.characterCount = this.editor.codemirror.state.doc.length;
@ -51,7 +51,7 @@ export class StatusBar {
let cursorLine: Line;
if (direction === 'left') {
if (direction === "left") {
// Cursor is at the start of the selection.
cursorLine = fromLine;
this.cursorColumn =
@ -94,11 +94,12 @@ export class StatusBar {
private getSelectionDirection(
selection: SelectionRange,
): 'right' | 'left' | undefined {
): "right" | "left" | undefined {
return selection.from === this.selectionStart
? 'right'
: selection.to === this.selectionEnd
? 'left'
: undefined;
? "right"
: // eslint-disable-next-line sonarjs/no-nested-conditional
selection.to === this.selectionEnd
? "left"
: 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 { checkBlock, toggleBlock } from '../../utils/toggle-block';
import { IToolbarButtonOptions } from '../default-toolbar';
import { EasyMDE } from "../../easymde";
import { checkBlock, toggleBlock } from "../../utils/toggle-block";
import { IToolbarButtonOptions } from "../default-toolbar";
export const toggleBold = (editor: EasyMDE) =>
toggleBlock(editor.codemirror, editor.options.blockStyles.bold);
@ -13,7 +13,7 @@ export const checkBold = (editor: EasyMDE, _update: ViewUpdate) =>
export const toggleBoldButton: IToolbarButtonOptions = {
action: toggleBold,
active: checkBold,
icon: 'fas fa-bold',
name: 'bold',
title: 'Bold',
icon: "fas fa-bold",
name: "bold",
title: "Bold",
};

View File

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

View File

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

View File

@ -1,13 +1,13 @@
import { EasyMDE } from '../../easymde';
import { toggleBlock } from '../../utils/toggle-block';
import { IToolbarButtonOptions } from '../default-toolbar';
import { EasyMDE } from "../../easymde";
import { toggleBlock } from "../../utils/toggle-block";
import { IToolbarButtonOptions } from "../default-toolbar";
export const toggleStrikethrough = (editor: EasyMDE) =>
toggleBlock(editor.codemirror, editor.options.blockStyles.strikethrough);
export const toggleStrikethroughButton: IToolbarButtonOptions = {
action: toggleStrikethrough,
icon: 'fas fa-strikethrough',
name: 'strikethrough',
title: 'Strikethrough',
icon: "fas fa-strikethrough",
name: "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 { toggleCodeButton } from './buttons/toggle-code';
import { toggleItalicButton } from './buttons/toggle-italic';
// import { toggleStrikethroughButton } from "./buttons/toggle-strikethrough";
import { toggleBoldButton } from "./buttons/toggle-bold";
import { toggleCodeButton } from "./buttons/toggle-code";
import { toggleItalicButton } from "./buttons/toggle-italic";
import { toggleStrikethroughButton } from "./buttons/toggle-strikethrough";
export interface IToolbarButtonOptions {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
action?: any;
active?:
| boolean
@ -22,53 +23,53 @@ export const defaultToolbar: IToolbarButtonOptions[][] = [
[
toggleBoldButton,
toggleItalicButton,
// toggleStrikethroughButton,
toggleStrikethroughButton,
{
// action: toggleHeadingSmaller,
icon: 'fas fa-header fa-heading',
name: 'heading',
title: 'Heading',
icon: "fas fa-header fa-heading",
name: "heading",
title: "Heading",
},
],
[
toggleCodeButton,
{
// action: toggleBlockquote,
icon: 'fas fa-quote-left',
name: 'quote',
title: 'Quote',
icon: "fas fa-quote-left",
name: "quote",
title: "Quote",
},
{
// action: toggleUnorderedList,
icon: 'fas fa-list-ul',
name: 'unordered-list',
title: 'Generic List',
icon: "fas fa-list-ul",
name: "unordered-list",
title: "Generic List",
},
{
// action: toggleOrderedList,
icon: 'fas fa-list-ol',
name: 'ordered-list',
title: 'Numbered List',
icon: "fas fa-list-ol",
name: "ordered-list",
title: "Numbered List",
},
{
// action: cleanBlock,
icon: 'fas fa-eraser',
name: 'clean-block',
title: 'Clean block',
icon: "fas fa-eraser",
name: "clean-block",
title: "Clean block",
},
],
[
{
// action: drawLink,
icon: 'fas fa-link',
name: 'link',
title: 'Create Link',
icon: "fas fa-link",
name: "link",
title: "Create Link",
},
{
// action: drawImage,
icon: 'fas fa-image',
name: 'image',
title: 'Insert Image',
icon: "fas fa-image",
name: "image",
title: "Insert Image",
// }, {
// // action: drawHorizontalRule,
// icon: 'fas fa-minus',
@ -99,11 +100,11 @@ export const defaultToolbar: IToolbarButtonOptions[][] = [
],
[
{
action: 'https://simplemde.com/markdown-guide',
icon: 'fas fa-question',
name: 'guide',
action: "https://simplemde.com/markdown-guide",
icon: "fas fa-question",
name: "guide",
// noDisable: true,
title: 'Markdown Guide',
title: "Markdown Guide",
// }], [{
// action: NewMDE.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 { ViewPlugin, ViewUpdate } from '@codemirror/view';
import { StateEffect } from "@codemirror/state";
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 {
private static readonly activeClass = 'enabled';
private static readonly activeClass = "enabled";
public element: HTMLDivElement;
@ -14,12 +14,11 @@ export class Toolbar implements IEasyMDEPlugin {
private editor: EasyMDE,
toolbarLayout: IToolbarButtonOptions[][],
) {
this.element = document.createElement('div');
this.element.className = 'easymde-toolbar';
this.element = document.createElement("div");
this.element.className = "easymde-toolbar";
for (const toolBarButtonSection of toolbarLayout) {
const toolBarSection: Array<HTMLButtonElement | HTMLSpanElement> =
[];
const toolBarSection: (HTMLButtonElement | HTMLSpanElement)[] = [];
for (const toolBarButtonOptions of toolBarButtonSection) {
toolBarSection.push(
@ -52,10 +51,10 @@ export class Toolbar implements IEasyMDEPlugin {
// return toolBar;
}
// eslint-disable-next-line @typescript-eslint/require-await
public async build(toolbarLayout: IToolbarButtonOptions[][]) {
for (const toolBarButtonSection of toolbarLayout) {
const toolBarSection: Array<HTMLButtonElement | HTMLSpanElement> =
[];
const toolBarSection: (HTMLButtonElement | HTMLSpanElement)[] = [];
for (const toolBarButtonOptions of toolBarButtonSection) {
toolBarSection.push(
@ -76,14 +75,15 @@ export class Toolbar implements IEasyMDEPlugin {
}
}
}
// eslint-disable-next-line @typescript-eslint/require-await
public async destroy() {
this.element.remove();
}
private createToolBarSeparator() {
const separatorElement = document.createElement('span');
separatorElement.className = 'separator';
separatorElement.innerHTML = '|';
const separatorElement = document.createElement("span");
separatorElement.className = "separator";
separatorElement.innerHTML = "|";
return separatorElement;
}
@ -91,7 +91,7 @@ export class Toolbar implements IEasyMDEPlugin {
toolBarButtonOptions: IToolbarButtonOptions,
): HTMLButtonElement {
const buttonElement: HTMLButtonElement =
document.createElement('button');
document.createElement("button");
buttonElement.tabIndex = -1;
buttonElement.classList.add(toolBarButtonOptions.name);
@ -99,30 +99,32 @@ export class Toolbar implements IEasyMDEPlugin {
buttonElement.title = toolBarButtonOptions.title;
// Set the button onclick action.
if (typeof toolBarButtonOptions.action === 'function') {
buttonElement.addEventListener('click', () =>
if (typeof toolBarButtonOptions.action === "function") {
buttonElement.addEventListener("click", () =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call
toolBarButtonOptions.action(this.editor),
);
// buttonElement.addEventListener()
} else if (typeof toolBarButtonOptions.action === 'string') {
buttonElement.addEventListener('click', () =>
} else if (typeof toolBarButtonOptions.action === "string") {
buttonElement.addEventListener("click", () =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
window.open(toolBarButtonOptions.action),
);
}
if (typeof toolBarButtonOptions.active === 'boolean') {
if (typeof toolBarButtonOptions.active === "boolean") {
buttonElement.classList.toggle(
Toolbar.activeClass,
toolBarButtonOptions.active,
);
} else if (typeof toolBarButtonOptions.active === 'function') {
} else if (typeof toolBarButtonOptions.active === "function") {
this.editor.codemirror.dispatch({
effects: StateEffect.appendConfig.of(
ViewPlugin.define(() => ({
update: async (update: ViewUpdate) => {
if (
typeof toolBarButtonOptions.active ===
'function'
"function"
) {
const result =
await toolBarButtonOptions.active(
@ -141,7 +143,7 @@ export class Toolbar implements IEasyMDEPlugin {
}
// Set the button icon.
const buttonIcon = document.createElement('i');
const buttonIcon = document.createElement("i");
buttonIcon.className = toolBarButtonOptions.icon;
buttonElement.append(buttonIcon);

View File

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

View File

@ -1,10 +1,10 @@
import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
import { EditorState } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import { markdown, markdownLanguage } from "@codemirror/lang-markdown";
import { EditorState } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
// 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 = (
document: string,
@ -22,8 +22,8 @@ const getEditor = (
}),
});
describe.each(['*', '**', '`', '_', '__', '~~'])(
'checkBlock simple %s',
describe.each(["*", "**", "`", "_", "__", "~~"])(
"checkBlock simple %s",
(character) => {
const wordSimple = `${character}foo${character}`;
const wordSimpleWithSpace = `${character} foo ${character}`;
@ -33,7 +33,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
const wordMultiLine = `${character}foo${character}\n${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);
const anchor = Math.floor(wordSimple.length / 2);
@ -44,7 +44,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
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);
const anchor = Math.floor(wordSquished.length / 2);
@ -55,7 +55,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
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);
const anchor = Math.floor(wordSimpleWithSpace.length / 2);
@ -66,14 +66,14 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
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);
const result = checkBlock(getEditor(wordSimple), character);
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);
const anchor = wordSimple.length;
@ -84,7 +84,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
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);
const anchor = Math.floor(wordSimple.length / 2);
@ -96,7 +96,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
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);
const anchor = character.length;
@ -108,7 +108,7 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
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);
const head = wordSimple.length;
@ -119,22 +119,22 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
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);
const anchor = Math.floor(wordMultiLine.split('\n')[0].length / 2);
const anchor = Math.floor(wordMultiLine.split("\n")[0].length / 2);
const result = checkBlock(
getEditor(wordMultiLine, { anchor }),
character,
);
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);
const multiLineSplit = wordMultiLine.split('\n');
const multiLineSplit = wordMultiLine.split("\n");
const anchor =
Math.floor(multiLineSplit[1].length / 2) +
multiLineSplit[0].length;
@ -143,10 +143,10 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character,
);
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);
const anchor = wordMultiLine.length;
@ -155,25 +155,25 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character,
);
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);
const anchor = Math.floor(wordMultiWord.split(' ')[0].length / 2);
const anchor = Math.floor(wordMultiWord.split(" ")[0].length / 2);
const result = checkBlock(
getEditor(wordMultiWord, { anchor }),
character,
);
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);
const multiWordSplit = wordMultiWord.split(' ');
const multiWordSplit = wordMultiWord.split(" ");
const anchor =
Math.floor(multiWordSplit[1].length / 2) +
multiWordSplit[0].length;
@ -182,10 +182,10 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character,
);
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);
const anchor = wordMultiWord.length;
@ -194,25 +194,25 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character,
);
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);
const anchor = Math.floor(wordMultiTab.split('\t')[0].length / 2);
const anchor = Math.floor(wordMultiTab.split("\t")[0].length / 2);
const result = checkBlock(
getEditor(wordMultiTab, { anchor }),
character,
);
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);
const multiWordSplit = wordMultiTab.split('\t');
const multiWordSplit = wordMultiTab.split("\t");
const anchor =
Math.floor(multiWordSplit[1].length / 2) +
multiWordSplit[0].length;
@ -221,10 +221,10 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character,
);
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);
const anchor = wordMultiTab.length;
@ -233,25 +233,25 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character,
);
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);
const anchor = Math.floor(wordMultiWords.split(' ')[0].length / 2);
const anchor = Math.floor(wordMultiWords.split(" ")[0].length / 2);
const result = checkBlock(
getEditor(wordMultiWords, { anchor }),
character,
);
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);
const multiWordSplit = wordMultiWords.split(' ');
const multiWordSplit = wordMultiWords.split(" ");
const anchor =
Math.floor(multiWordSplit[1].length / 2) +
multiWordSplit[0].length;
@ -260,168 +260,168 @@ describe.each(['*', '**', '`', '_', '__', '~~'])(
character,
);
expect(result).toBeTruthy();
expect(result[1]).toBe('foo boo');
expect(result[1]).toBe("foo boo");
});
},
);
describe('checkBlock special cases', () => {
it('must not detect an active block when another markdown block is used with more of the same characters', () => {
describe("checkBlock special cases", () => {
it("must not detect an active block when another markdown block is used with more of the same characters", () => {
expect.assertions(1);
const result = checkBlock(getEditor('**foo**', { anchor: 4 }), '*');
const result = checkBlock(getEditor("**foo**", { anchor: 4 }), "*");
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);
const result = checkBlock(
getEditor('Some text **foo** more text', { anchor: 14 }),
'*',
getEditor("Some text **foo** more text", { anchor: 14 }),
"*",
);
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);
const result = checkBlock(
getEditor('Some text **foo** more text', { anchor: 14 }),
'**',
getEditor("Some text **foo** more text", { anchor: 14 }),
"**",
);
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);
const result = checkBlock(getEditor('__*foo*__', { anchor: 6 }), '*');
const result = checkBlock(getEditor("__*foo*__", { anchor: 6 }), "*");
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);
const result = checkBlock(getEditor('*foo*', { anchor: 3 }), '**');
const result = checkBlock(getEditor("*foo*", { anchor: 3 }), "**");
expect(result).toBeFalsy();
});
it.each(['*', '**'])(
'must detect an active block when the characters are part of another styling block',
it.each(["*", "**"])(
"must detect an active block when the characters are part of another styling block",
(c) => {
expect.assertions(1);
const result = checkBlock(getEditor('***foo***', { anchor: 5 }), c);
const result = checkBlock(getEditor("***foo***", { anchor: 5 }), c);
expect(result).toBeTruthy();
},
);
});
describe.skip('toggleBlock', () => {
it('must toggle a single word on with the selection at the start', () => {
describe.skip("toggleBlock", () => {
it("must toggle a single word on with the selection at the start", () => {
expect.assertions(1);
const editor = getEditor('Word', { anchor: 0 });
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('*Word*');
const editor = getEditor("Word", { anchor: 0 });
toggleBlock(editor, "*");
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);
const editor = getEditor('Word', { anchor: 2 });
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('*Word*');
const editor = getEditor("Word", { anchor: 2 });
toggleBlock(editor, "*");
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);
const editor = getEditor('Word', { anchor: 4 });
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('*Word*');
const editor = getEditor("Word", { anchor: 4 });
toggleBlock(editor, "*");
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);
const editor = getEditor('Many words are typed here', { anchor: 7 });
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('Many *words* are typed here');
const editor = getEditor("Many words are typed here", { anchor: 7 });
toggleBlock(editor, "*");
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);
const editor = getEditor('Many words are typed here', {
const editor = getEditor("Many words are typed here", {
anchor: 5,
head: 10,
});
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('Many *words* are typed here');
toggleBlock(editor, "*");
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);
const editor = getEditor('*Word*', { anchor: 0 });
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('Word');
const editor = getEditor("*Word*", { anchor: 0 });
toggleBlock(editor, "*");
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);
const editor = getEditor('*Word*', { anchor: 3 });
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('Word');
const editor = getEditor("*Word*", { anchor: 3 });
toggleBlock(editor, "*");
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);
const editor = getEditor('*Word*', { anchor: 6 });
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('Word');
const editor = getEditor("*Word*", { anchor: 6 });
toggleBlock(editor, "*");
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);
const editor = getEditor('Many *words* are typed here', { anchor: 8 });
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('Many words are typed here');
const editor = getEditor("Many *words* are typed here", { anchor: 8 });
toggleBlock(editor, "*");
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);
const editor = getEditor('Many *words* are typed here', {
const editor = getEditor("Many *words* are typed here", {
anchor: 6,
head: 11,
});
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('Many words are typed here');
toggleBlock(editor, "*");
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);
const editor = getEditor('*Many words are typed here*', { anchor: 8 });
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('Many words are typed here');
const editor = getEditor("*Many words are typed here*", { anchor: 8 });
toggleBlock(editor, "*");
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);
const editor = getEditor('*Many words are typed here*', {
const editor = getEditor("*Many words are typed here*", {
anchor: 6,
head: 11,
});
toggleBlock(editor, '*');
expect(editor.state.doc.toString()).toBe('Many words are typed here');
toggleBlock(editor, "*");
expect(editor.state.doc.toString()).toBe("Many words are typed here");
});
});

View File

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

View File

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

View File

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

View File

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