2
0
mirror of https://github.com/Ionaru/easy-markdown-editor synced 2025-06-30 22:51:01 -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... ### I'm submitting a...
- [x] Bug report - [x] Bug report
- [ ] Feature request - [ ] Feature request
### Reproduction steps ### Reproduction steps

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**
@ -25,9 +25,9 @@ If applicable, add screenshots to help explain your problem.
**Version information** **Version information**
- OS: [e.g. Windows, MacOS] - OS: [e.g. Windows, MacOS]
- Browser: [e.g. Chrome 72] - Browser: [e.g. Chrome 72]
- EasyMDE version: [e.g. 2.5.1] - EasyMDE version: [e.g. 2.5.1]
**Additional context** **Additional context**
Add any other context about the problem here. Add any other context about the problem here.

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

@ -1,48 +1,48 @@
name: Test & Deploy name: Test & Deploy
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
branches: branches:
- master - master
tags: tags:
- '*' - "*"
pull_request: pull_request:
branches: branches:
- master - master
jobs: jobs:
audit: audit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: current node-version: current
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- run: npm audit --omit=dev - run: npm audit --omit=dev
test: test:
needs: [audit] needs: [audit]
runs-on: ubuntu-latest runs-on: ubuntu-latest
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
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- run: npm ci - run: npm ci
- name: Test - name: Test
run: npm test run: npm test
# - uses: actions/upload-artifact@v3 # - uses: actions/upload-artifact@v3
# if: failure() # if: failure()
# with: # 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 ### 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 ## [2.18.0] - 2022-09-20
### Added ### 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 ## [2.17.0] - 2022-08-20
### Added ### Added
- Improved CSRF support for uploading images (Thanks to [@ZsgsDesign], [#394]). - Improved CSRF support for uploading images (Thanks to [@ZsgsDesign], [#394]).
- Option to register an image preview handler: `imagesPreviewHandler` (Thanks to [@diego-gw], [#411]). - Option to register an image preview handler: `imagesPreviewHandler` (Thanks to [@diego-gw], [#411]).
- Support for `.webp` image formats (Thanks to [@sghoweri], [#417]). - Support for `.webp` image formats (Thanks to [@sghoweri], [#417]).
- `toggleHeading` methods for headings 4, 5 and 6 (Thanks to [@vanillajonathan], [#449] and [#492]). - `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]). - Support for `.gif`, `.avif` and `.apng` image formats (Thanks to [@vanillajonathan], [#450], [#458] and [#459]).
- Keyboard shortcuts for `toggleHeading` methods (Thanks to [@vanillajonathan], [#451]). - Keyboard shortcuts for `toggleHeading` methods (Thanks to [@vanillajonathan], [#451]).
- `iconClassMap` option to specify icon class names for toolbar buttons (Thanks to [@vanillajonathan], [#454]). - `iconClassMap` option to specify icon class names for toolbar buttons (Thanks to [@vanillajonathan], [#454]).
- `role="toolbar"` on the toolbar element (Thanks to [@vanillajonathan], [#455]). - `role="toolbar"` on the toolbar element (Thanks to [@vanillajonathan], [#455]).
- Support for text buttons in the toolbar (Thanks to [@vanillajonathan], [#461]). - 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]). - `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]). - `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]). - Option to not overwrite the preview HTML by returning `null` from `previewRender` (Thanks to [@LevitatingOrange], [#471]).
### Fixed ### Fixed
- Hyperlink doubling (Thanks to [@danielok1993], [#95]). - Hyperlink doubling (Thanks to [@danielok1993], [#95]).
- URLs with certain characters entered through prompts causing invalid markdown (Thanks to [@Zignature], [#393]). - URLs with certain characters entered through prompts causing invalid markdown (Thanks to [@Zignature], [#393]).
- Autofocus option not working properly ([#399]). - Autofocus option not working properly ([#399]).
- Inconsistent border colour (Thanks to [@vanillajonathan], [#466]). - Inconsistent border colour (Thanks to [@vanillajonathan], [#466]).
- Invalid regex on Safari browsers ([#478]) - Invalid regex on Safari browsers ([#478])
- `hideIcons` option type (Thanks to [@LoyalPotato], [#488]). - `hideIcons` option type (Thanks to [@LoyalPotato], [#488]).
### Changed ### Changed
- Editor now uses responsive font sizes (Thanks to [@vanillajonathan], [#452]). - Editor now uses responsive font sizes (Thanks to [@vanillajonathan], [#452]).
### Documentation ### Documentation
- Added several improvements to README (Thanks to [@vanillajonathan], [#436], [#438], [#440], [#444]). - Added several improvements to README (Thanks to [@vanillajonathan], [#436], [#438], [#440], [#444]).
- Fixed typo in README (Thanks to [@kicksent], [#484]). - Fixed typo in README (Thanks to [@kicksent], [#484]).
- Added missing icon for `upload-image` in README (Thanks to [@hlf20010508], [#486]). - Added missing icon for `upload-image` in README (Thanks to [@hlf20010508], [#486]).
## [2.16.1] - 2022-01-14 ## [2.16.1] - 2022-01-14
### Fixed ### Fixed
- Incorrect initial line and column count in status bar. - Incorrect initial line and column count in status bar.
- Security issue in `marked` dependency. - Security issue in `marked` dependency.
## [2.16.0] - 2022-01-11 ## [2.16.0] - 2022-01-11
### Added ### Added
- `direction` option to enable RTL mode (Thanks to [@souljuse], [#358]). - `direction` option to enable RTL mode (Thanks to [@souljuse], [#358]).
- `attributes` option to add custom attributes to toolbar buttons (Thanks to [@Zignature], [#388]). - `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]). - `unorderedListStyle` option to change the character used for unordered lists (Thanks to [@Zignature], [#389]).
### Fixed ### Fixed
- Image extension detection when extension is uppercase (Thanks to [@ukjinjang], [#357]). - 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]). - 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]). - Incorrect line and column count in status bar (Thanks to [@Zignature], [#384]).
## [2.15.0] - 2021-04-22 ## [2.15.0] - 2021-04-22
### Added ### 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 ### Fixed
- `ToolbarIcon` typings, added `icon` (Thanks to [@ChronosMasterOfAllTime], [#308]). - `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]). - 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]). - 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]). - Required typings not being included in `dependencies` (Thanks to [@marekdedic], [#322]).
## [2.14.0] - 2021-02-14 ## [2.14.0] - 2021-02-14
### Added ### 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 ### Fixed
- Issues with images not displaying correctly in the preview screen (Thanks to [@ivictbor], [#253]). - 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]). - 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]) - Editor trying to display non-image files (Thanks to [@Juupaa], [#277])
- Unneeded call to `window.removeEventListener` (Thanks to [@emirotin], [#280]) - Unneeded call to `window.removeEventListener` (Thanks to [@emirotin], [#280])
- Spell checker (Thanks to [@Fanvadar], [#284]). - Spell checker (Thanks to [@Fanvadar], [#284]).
- Focus issues with toolbar dropdown menus (Thanks to [@Situphen], [#285]). - Focus issues with toolbar dropdown menus (Thanks to [@Situphen], [#285]).
- Interaction between the `sideBySideFullscreen` and the preview state (Thanks to [@smundro], [#286]) - 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]). - Refactored strange method of padding the toolbar into regular padding (Thanks to [@sn3p], [#293]).
- Security issue in `marked` dependency (Thanks to [@dependabot], [#298]). - Security issue in `marked` dependency (Thanks to [@dependabot], [#298]).
## [2.13.0] - 2020-11-11 ## [2.13.0] - 2020-11-11
### Added ### Added
- CodeMirror autorefresh plugin and autoRefresh option (Thanks to [@mbolli], [#249]). - CodeMirror autorefresh plugin and autoRefresh option (Thanks to [@mbolli], [#249]).
- `lineNumbers` option to display line numbers in the editor (Thanks to [@nhymxu], [#267]). - `lineNumbers` option to display line numbers in the editor (Thanks to [@nhymxu], [#267]).
### Fixed ### 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 ## [2.12.1] - 2020-10-06
### Changed ### Changed
- Set `previewImagesInEditor` option to `false` by default ([#251]). - Set `previewImagesInEditor` option to `false` by default ([#251]).
## [2.12.0] - 2020-09-29 ## [2.12.0] - 2020-09-29
### Added ### Added
- `this` context in imageUploadFunction (Thanks to [@JoshuaLicense], [#225]). - `this` context in imageUploadFunction (Thanks to [@JoshuaLicense], [#225]).
- `previewImagesInEditor` option to display images in editor mode (Thanks to [@ivictbor], [#235]). - `previewImagesInEditor` option to display images in editor mode (Thanks to [@ivictbor], [#235]).
- `overlayMode` options to supply an additional codemirror mode (Thanks to [@czynskee], [#244]). - `overlayMode` options to supply an additional codemirror mode (Thanks to [@czynskee], [#244]).
### Fixed ### Fixed
- Corrected default size units from `b,Kb,Mb` to ` B, KB, MB` ([#239]). - Corrected default size units from `b,Kb,Mb` to ` B, KB, MB` ([#239]).
- Max height less than min height (Thanks to [@nick-denry], [#222]). - Max height less than min height (Thanks to [@nick-denry], [#222]).
- toTextArea issue (Thanks to [@nick-denry], [#223]). - 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]). - Error when updateStatusBar was called during image upload, but the status bar is disabled (Thanks to [@JoshuaLicense], [#224]).
## [2.11.0] - 2020-07-16 ## [2.11.0] - 2020-07-16
### Added ### Added
- Support for Node.js 14. - Support for Node.js 14.
- Preview without fullscreen (Thanks to [@nick-denry], [#196]). - Preview without fullscreen (Thanks to [@nick-denry], [#196]).
### Fixed ### Fixed
- Fix cursor displayed position on activity (Thanks to [@firm1], [#184]). - Fix cursor displayed position on activity (Thanks to [@firm1], [#184]).
- Checkboxes always have bullets in front of them ([#136]). - 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]). - Save the text only when modifying the content of the easymde instance (Thanks to [@firm1], [#181]).
## [2.10.1] - 2020-04-06 ## [2.10.1] - 2020-04-06
### Fixed ### 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 ## [2.10.0] - 2020-04-02
### Added ### Added
- `inputStyle` and `nativeSpellcheck` options to manage the native language of the browser (Thanks to [@firm1], [#143]). - `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]). - 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]). - `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]). - Time formatting and custom text options for the autosave message (Thanks to [@dima-bzz], [#170]).
### Changed ### Changed
- Delay before assuming that submit of the form as failed is `autosave.submit_delay` instead of `autosave.delay` (Thanks to [@Situphen], [#139]). - 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]). - Add `watch` task for gulp (Thanks to [@A-312], [#150]).
### Fixed ### Fixed
- Issue with Marked when using IE11 and webpack (Thanks to [@felipefdl], [#169]). - Issue with Marked when using IE11 and webpack (Thanks to [@felipefdl], [#169]).
- Updated codemirror to version 5.52.2 (Thanks to [@A-312], [#173]). - 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]). - Editor displaying on top of other elements on a webpage (Thanks to [@StefKors], [#175]).
## [2.9.0] - 2020-01-13 ## [2.9.0] - 2020-01-13
### Added ### Added
- Missing minHeight option in type definition (Thanks to [@t49tran], [#123]). - Missing minHeight option in type definition (Thanks to [@t49tran], [#123]).
- Other missing type definitions ([#126]). - Other missing type definitions ([#126]).
### Changed ### 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 ## [2.8.0] - 2019-08-20
### Added ### Added
- Upload images functionality (Thanks to [@roipoussiere] and [@JeroenvO], [#71], [#101]). - Upload images functionality (Thanks to [@roipoussiere] and [@JeroenvO], [#71], [#101]).
- Allow custom image upload function (Thanks to [@sperezp], [#106]). - Allow custom image upload function (Thanks to [@sperezp], [#106]).
- More polish to the upload images functionality (Thanks to [@jfly], [#109]). - More polish to the upload images functionality (Thanks to [@jfly], [#109]).
- Improved React compatibility (Thanks to [@richtera], [#97]). - Improved React compatibility (Thanks to [@richtera], [#97]).
### Fixed ### Fixed
- Missing link in dist file header. - Missing link in dist file header.
## [2.7.0] - 2019-07-13 ## [2.7.0] - 2019-07-13
### Added ### Added
- `previewClass` option for overwriting the preview screen class ([#99]). - `previewClass` option for overwriting the preview screen class ([#99]).
### Fixed ### Fixed
- Updated dependencies to resolve potential security issue. - Updated dependencies to resolve potential security issue.
- Resolved small code style issues shown by new eslint rules. - Resolved small code style issues shown by new eslint rules.
## [2.6.1] - 2019-06-17 ## [2.6.1] - 2019-06-17
### Fixed ### Fixed
- Error when toggling between ordered and unordered lists (Thanks to [@roryok], [#93]). - Error when toggling between ordered and unordered lists (Thanks to [@roryok], [#93]).
- Keyboard shortcuts for custom actions not working (Thanks to [@ysykzheng], [#75]). - Keyboard shortcuts for custom actions not working (Thanks to [@ysykzheng], [#75]).
## [2.6.0] - 2019-04-15 ## [2.6.0] - 2019-04-15
### Added ### Added
- Contributing guide (Thanks to [@roipoussiere], [#54]). - Contributing guide (Thanks to [@roipoussiere], [#54]).
- Issue templates. - Issue templates.
- Standardized changelog file. - Standardized changelog file.
### Changed ### Changed
- Finish rewrite of README (Thanks to [@roipoussiere], [#54]). - Finish rewrite of README (Thanks to [@roipoussiere], [#54]).
- Image and link prompt fill with "https://" by default. - Image and link prompt fill with "https://" by default.
- Link to markdown guide to <https://www.markdownguide.org/basic-syntax/>. - Link to markdown guide to <https://www.markdownguide.org/basic-syntax/>.
### Fixed ### Fixed
- Backwards compatibility in the API with SimpleMDE 1.0.0 ([#41]). - Backwards compatibility in the API with SimpleMDE 1.0.0 ([#41]).
- Automatic publish of master branch to `@next` - Automatic publish of master branch to `@next`
### Removed ### Removed
- Distribution files from source-control. - Distribution files from source-control.
## [2.5.1] - 2019-01-17 ## [2.5.1] - 2019-01-17
### Fixed ### Fixed
- `role="button"` needed to be `type="button"` ([#45]). - `role="button"` needed to be `type="button"` ([#45]).
## [2.5.0] - 2019-01-17 ## [2.5.0] - 2019-01-17
### Added ### Added
- Typescript support (Thanks to [@FranklinWhale], [#44]). - Typescript support (Thanks to [@FranklinWhale], [#44]).
- `role="button"` to toolbar buttons ([#38]). - `role="button"` to toolbar buttons ([#38]).
### Fixed ### Fixed
- Eraser icon not working with FontAwesome 5. - Eraser icon not working with FontAwesome 5.
## [2.4.2] - 2018-11-09 ## [2.4.2] - 2018-11-09
### Added ### Added
- Node.js 11 support. - Node.js 11 support.
### Fixed ### Fixed
- Header button icons not showing sub-icons with FontAwesome 5. - Header button icons not showing sub-icons with FontAwesome 5.
- Inconsistent autosave behaviour when submitting a form (Thanks to [@Furgas] and [@adamb70], [#31]). - Inconsistent autosave behaviour when submitting a form (Thanks to [@Furgas] and [@adamb70], [#31]).
## [2.4.1] - 2018-10-15 ## [2.4.1] - 2018-10-15
### Added ### 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 ## [2.4.0] - 2018-10-15
### Added ### Added
- Theming support (Thanks to [@LeviticusMB], [#17]). - Theming support (Thanks to [@LeviticusMB], [#17]).
- onToggleFullscreen event hook (Thanks to [@n-3-0], [#16]). - onToggleFullscreen event hook (Thanks to [@n-3-0], [#16]).
### Fixed ### 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 ## [2.2.2] - 2019-07-03
### Fixed ### Fixed
- Automatic publish only publishing tags. - Automatic publish only publishing tags.
## [2.2.1] - 2019-06-29 ## [2.2.1] - 2019-06-29
### Changed ### Changed
- Attempt automatic publish `@next` version on npm. - Attempt automatic publish `@next` version on npm.
- Links in the preview window will open in a new tab by default. - Links in the preview window will open in a new tab by default.
### Fixed ### Fixed
- Multi-text select issue by disabling multi-select in the editor ([#10]). - Multi-text select issue by disabling multi-select in the editor ([#10]).
- `main` file in package.json (Thanks to [@sne11ius], [#11]). - `main` file in package.json (Thanks to [@sne11ius], [#11]).
## [2.0.1] - 2018-05-13 ## [2.0.1] - 2018-05-13
### Changed ### Changed
- Rewrote part of the documentation for EasyMDE. - Rewrote part of the documentation for EasyMDE.
- Updated gulp to version 4.0.0. - Updated gulp to version 4.0.0.
### Fixed ### 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 ## [2.0.0] - 2018-04-23
@ -321,23 +321,23 @@ Project forked from [SimpleMDE](https://github.com/sparksuite/simplemde-markdown
### BREAKING CHANGES ### BREAKING CHANGES
- Dropped Bower support. - Dropped Bower support.
- Dropped support for older Node.js versions. - Dropped support for older Node.js versions.
### Added ### Added
- FontAwesome 5 support. - FontAwesome 5 support.
- Support for newer Node.js versions. - Support for newer Node.js versions.
### Changed ### Changed
- Packages are now version-locked. - Packages are now version-locked.
- Simplified build script. - Simplified build script.
- Markdown guide button is no longer disabled in preview mode. - Markdown guide button is no longer disabled in preview mode.
### Fixed ### 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 --> <!-- Linked issues -->

View File

@ -22,14 +22,14 @@ The editor is entirely customizable, from theming to toolbar buttons and javascr
## Quick access ## Quick access
- [EasyMDE - Markdown Editor](#easymde---markdown-editor) - [EasyMDE - Markdown Editor](#easymde---markdown-editor)
- [Quick access](#quick-access) - [Quick access](#quick-access)
- [Install EasyMDE](#install-easymde) - [Install EasyMDE](#install-easymde)
- [How to use](#how-to-use) - [How to use](#how-to-use)
- [How it works](#how-it-works) - [How it works](#how-it-works)
- [SimpleMDE fork](#simplemde-fork) - [SimpleMDE fork](#simplemde-fork)
- [Contributing](#contributing) - [Contributing](#contributing)
- [License](#license) - [License](#license)
## Install EasyMDE ## Install EasyMDE
@ -57,14 +57,14 @@ I originally made this fork to implement FontAwesome 5 compatibility into Simple
Changes include: Changes include:
- FontAwesome 5 compatibility - FontAwesome 5 compatibility
- Guide button works when editor is in preview mode - Guide button works when editor is in preview mode
- Links are now `https://` by default - Links are now `https://` by default
- Small styling changes - Small styling changes
- Support for Node 8 and beyond - Support for Node 8 and beyond
- Lots of refactored code - Lots of refactored code
- Links in preview will open in a new tab by default - Links in preview will open in a new tab by default
- TypeScript support - TypeScript support
My intention is to continue development on this project, improving it and keeping it alive. 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). This project is released under the [MIT License](./LICENSE).
- Copyright (c) 2015 Sparksuite, Inc. - Copyright (c) 2015 Sparksuite, Inc.
- Copyright (c) 2017 Jeroen Akkerman. - 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: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
: undefined; ? "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 { 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,
}); });
@ -71,9 +71,11 @@ This is a longer sentence!
// window.x.destruct(); // window.x.destruct();
</script> </script>
<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
</script> .querySelector("easy-markdown-editor")
.setAttribute("name", "Everyone");
</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"],
}, },
}); });