Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(extension-count): add ability to count characters/words and set …
…a soft max
- Loading branch information
Showing
30 changed files
with
856 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# @remirror/extension-count | ||
|
||
## 1.0.1 | ||
|
||
> 2021-07-17 | ||
### Patch Changes | ||
|
||
- [#1002](https://github.com/remirror/remirror/pull/1002) [`b3ea6f10d`](https://github.com/remirror/remirror/commit/b3ea6f10d4917f933971236be936731f75a69a70) Thanks [@ifiokjr](https://github.com/ifiokjr)! - Use carets `^` for versioning of `remirror` packages. | ||
|
||
- Updated dependencies [[`b3ea6f10d`](https://github.com/remirror/remirror/commit/b3ea6f10d4917f933971236be936731f75a69a70)]: | ||
- @remirror/core@1.0.1 | ||
- @remirror/pm@1.0.1 | ||
|
||
## 1.0.0 | ||
|
||
> 2021-07-17 | ||
### Patch Changes | ||
|
||
- Updated dependencies [[`8202b65ef`](https://github.com/remirror/remirror/commit/8202b65efbce5a8338c45fd34b3efb676b7e54e7), [`adfb12a4c`](https://github.com/remirror/remirror/commit/adfb12a4cee7031eec4baa10830b0fc0134ebdc8), [`7f3569729`](https://github.com/remirror/remirror/commit/7f3569729c0d843b7745a490feda383b31aa2b7e), [`270edd91b`](https://github.com/remirror/remirror/commit/270edd91ba6badf9468721e35fa0ddc6a21c6dd2), [`b4dfcad36`](https://github.com/remirror/remirror/commit/b4dfcad364a0b41d321fbd26a97377f2b6d4047c), [`e9b10fa5a`](https://github.com/remirror/remirror/commit/e9b10fa5a50dd3e342b75b0a852627db99f22dc2), [`6ab7d2224`](https://github.com/remirror/remirror/commit/6ab7d2224d16ba821d8510e0498aaa9c420922c4), [`270edd91b`](https://github.com/remirror/remirror/commit/270edd91ba6badf9468721e35fa0ddc6a21c6dd2), [`270edd91b`](https://github.com/remirror/remirror/commit/270edd91ba6badf9468721e35fa0ddc6a21c6dd2), [`7024de573`](https://github.com/remirror/remirror/commit/7024de5738a968f2914a999e570d723899815611), [`03d0ae485`](https://github.com/remirror/remirror/commit/03d0ae485079a166a223b902ea72cbe62504b0f0)]: | ||
- @remirror/core@1.0.0 | ||
- @remirror/pm@1.0.0 |
5 changes: 5 additions & 0 deletions
5
packages/remirror__extension-count/__tests__/count-extension.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { extensionValidityTest } from 'jest-remirror'; | ||
|
||
import { CountExtension } from '../'; | ||
|
||
extensionValidityTest(CountExtension); |
38 changes: 38 additions & 0 deletions
38
packages/remirror__extension-count/__tests__/tsconfig.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"__AUTO_GENERATED__": [ | ||
"To update the configuration edit the following field.", | ||
"`package.json > @remirror > tsconfigs > '__tests__'`", | ||
"", | ||
"Then run: `pnpm -w generate:ts`" | ||
], | ||
"extends": "../../../support/tsconfig.base.json", | ||
"compilerOptions": { | ||
"types": [ | ||
"jest", | ||
"jest-extended", | ||
"jest-axe", | ||
"@testing-library/jest-dom", | ||
"snapshot-diff", | ||
"node" | ||
], | ||
"declaration": false, | ||
"noEmit": true, | ||
"skipLibCheck": true, | ||
"importsNotUsedAsValues": "remove" | ||
}, | ||
"include": ["./"], | ||
"references": [ | ||
{ | ||
"path": "../src" | ||
}, | ||
{ | ||
"path": "../../testing/src" | ||
}, | ||
{ | ||
"path": "../../remirror/src" | ||
}, | ||
{ | ||
"path": "../../remirror__core/src" | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
{ | ||
"name": "@remirror/extension-count", | ||
"version": "1.0.1", | ||
"description": "Count characters or words in your editor, and set a soft max length", | ||
"keywords": [ | ||
"remirror", | ||
"extension" | ||
], | ||
"homepage": "https://github.com/remirror/remirror/tree/HEAD/packages/remirror__extension-count", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/remirror/remirror.git", | ||
"directory": "packages/remirror__extension-count" | ||
}, | ||
"license": "MIT", | ||
"contributors": [ | ||
"Will Hawker <w.hawker@hotmail.co.uk>" | ||
], | ||
"sideEffects": false, | ||
"exports": { | ||
".": { | ||
"import": "./dist/remirror-extension-count.esm.js", | ||
"require": "./dist/remirror-extension-count.cjs.js", | ||
"browser": "./dist/remirror-extension-count.browser.esm.js", | ||
"types": "./dist/remirror-extension-count.cjs.d.ts", | ||
"default": "./dist/remirror-extension-count.esm.js" | ||
}, | ||
"./package.json": "./package.json", | ||
"./types/*": "./dist/declarations/src/*.d.ts" | ||
}, | ||
"main": "./dist/remirror-extension-count.cjs.js", | ||
"module": "./dist/remirror-extension-count.esm.js", | ||
"browser": { | ||
"./dist/remirror-extension-count.cjs.js": "./dist/remirror-extension-count.browser.cjs.js", | ||
"./dist/remirror-extension-count.esm.js": "./dist/remirror-extension-count.browser.esm.js" | ||
}, | ||
"types": "./dist/remirror-extension-count.cjs.d.ts", | ||
"files": [ | ||
"dist" | ||
], | ||
"dependencies": { | ||
"@babel/runtime": "^7.13.10", | ||
"@remirror/core": "^1.3.5" | ||
}, | ||
"devDependencies": { | ||
"@remirror/pm": "^1.0.11" | ||
}, | ||
"peerDependencies": { | ||
"@remirror/pm": "^1.0.1" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"@remirror": { | ||
"sizeLimit": "5 KB" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# @remirror/extension-count | ||
|
||
> **Count words or characters in your editor, and set a soft max length** | ||
[![Version][version]][npm] [![Weekly Downloads][downloads-badge]][npm] [![Bundled size][size-badge]][size] [![Typed Codebase][typescript]](#) [![MIT License][license]](#) | ||
|
||
[version]: https://flat.badgen.net/npm/v/@remirror/extension-count | ||
[npm]: https://npmjs.com/package/@remirror/extension-count | ||
[license]: https://flat.badgen.net/badge/license/MIT/purple | ||
[size]: https://bundlephobia.com/result?p=@remirror/extension-count | ||
[size-badge]: https://flat.badgen.net/bundlephobia/minzip/@remirror/extension-count | ||
[typescript]: https://flat.badgen.net/badge/icon/TypeScript?icon=typescript&label | ||
[downloads-badge]: https://badgen.net/npm/dw/@remirror/extension-count/red?icon=npm | ||
|
||
## Installation | ||
|
||
```bash | ||
# yarn | ||
yarn add @remirror/extension-count | ||
|
||
# pnpm | ||
pnpm add @remirror/extension-count | ||
|
||
# npm | ||
npm install @remirror/extension-count | ||
``` | ||
|
||
## Usage | ||
|
||
The following code creates an instance of this extension. | ||
|
||
```ts | ||
import { CountExtension } from '@remirror/extension-count'; | ||
|
||
const extension = new CountExtension(); | ||
``` |
193 changes: 193 additions & 0 deletions
193
packages/remirror__extension-count/src/count-extension.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
import type { | ||
CreateExtensionPlugin, | ||
EditorState, | ||
Helper, | ||
Static, | ||
Transaction, | ||
} from '@remirror/core'; | ||
import { extension, findMatches, helper, PlainExtension } from '@remirror/core'; | ||
import { Decoration, DecorationSet } from '@remirror/pm/view'; | ||
|
||
import { | ||
getCharacterExceededPosition, | ||
getTextLength, | ||
getWordExceededPosition, | ||
WORDS_REGEX, | ||
} from './count-utils'; | ||
|
||
export enum CountStrategy { | ||
CHARACTERS = 'CHARACTERS', | ||
WORDS = 'WORDS', | ||
} | ||
|
||
export interface CountOptions { | ||
/** | ||
* An optional soft limit. Text that exceeds this limit will be highlighted. | ||
* | ||
* @default -1 | ||
*/ | ||
maximum?: Static<number>; | ||
|
||
/** | ||
* The classname to use when highlighting text that exceed the given maximum. | ||
* | ||
* @default 'remirror-max-count-exceeded' | ||
*/ | ||
maximumExceededClassName?: Static<string>; | ||
|
||
/** | ||
* The counting strategy to use. Either CountStrategy.CHARACTERS or CountStrategy.WORDS | ||
* | ||
* @default CountStrategy.CHARACTERS | ||
*/ | ||
maximumStrategy?: Static<CountStrategy>; | ||
} | ||
|
||
interface CountPluginState { | ||
decorationSet: DecorationSet; | ||
} | ||
|
||
/** | ||
* Count words or characters in your editor, and set a soft max length | ||
*/ | ||
@extension<CountOptions>({ | ||
defaultOptions: { | ||
maximum: -1, | ||
maximumExceededClassName: 'remirror-max-count-exceeded', | ||
maximumStrategy: CountStrategy.CHARACTERS, | ||
}, | ||
staticKeys: ['maximum', 'maximumStrategy', 'maximumExceededClassName'], | ||
}) | ||
export class CountExtension extends PlainExtension<CountOptions> { | ||
get name() { | ||
return 'count' as const; | ||
} | ||
|
||
/** | ||
* Get the configured maximum characters/words. | ||
*/ | ||
@helper() | ||
getCountMaximum(): Helper<number> { | ||
return this.options.maximum; | ||
} | ||
|
||
/** | ||
* Get the count of characters in the document. | ||
* | ||
* @param state | ||
*/ | ||
@helper() | ||
getCharacterCount(state: EditorState = this.store.getState()): Helper<number> { | ||
let count = 0; | ||
|
||
state.doc.nodesBetween(0, state.doc.nodeSize - 2, (node) => { | ||
count += getTextLength(node); | ||
return true; | ||
}); | ||
|
||
// Remove the last line break character | ||
return Math.max(count - 1, 0); | ||
} | ||
|
||
/** | ||
* Get the count of words in the document. | ||
* | ||
* @param state | ||
*/ | ||
@helper() | ||
getWordCount(state: EditorState = this.store.getState()): Helper<number> { | ||
const text = this.store.helpers.getText({ lineBreakDivider: ' ', state }); | ||
return findMatches(text, WORDS_REGEX).length; | ||
} | ||
|
||
/** | ||
* Is the current number of characters/words valid in the current strategy. | ||
* | ||
* @param state | ||
*/ | ||
@helper() | ||
isCountValid(state: EditorState = this.store.getState()): Helper<boolean> { | ||
const { maximumStrategy, maximum } = this.options; | ||
|
||
if (maximum < 1) { | ||
return true; | ||
} | ||
|
||
if (maximumStrategy === CountStrategy.CHARACTERS) { | ||
const count = this.store.helpers.getCharacterCount(state); | ||
return count <= maximum; | ||
} | ||
|
||
return this.store.helpers.getWordCount(state) <= maximum; | ||
} | ||
|
||
protected createDecorationSet(state: EditorState): DecorationSet { | ||
const { maximum = -1, maximumStrategy, maximumExceededClassName } = this.options; | ||
|
||
const isCharacterCountStrategy = maximumStrategy === CountStrategy.CHARACTERS; | ||
const posStrategy = isCharacterCountStrategy | ||
? getCharacterExceededPosition | ||
: getWordExceededPosition; | ||
|
||
const pos = posStrategy(state, maximum); | ||
|
||
return DecorationSet.create(state.doc, [ | ||
Decoration.inline(pos, state.doc.nodeSize - 2, { | ||
class: maximumExceededClassName, | ||
}), | ||
]); | ||
} | ||
|
||
createPlugin(): CreateExtensionPlugin<CountPluginState> { | ||
const { maximum } = this.options; | ||
|
||
return { | ||
state: { | ||
init: (_, state: EditorState) => { | ||
if (this.isCountValid(state)) { | ||
return { | ||
decorationSet: DecorationSet.empty, | ||
}; | ||
} | ||
|
||
return { | ||
decorationSet: this.createDecorationSet(state), | ||
}; | ||
}, | ||
apply: ( | ||
tr: Transaction, | ||
pluginState: CountPluginState, | ||
_: EditorState, | ||
state: EditorState, | ||
) => { | ||
if (!tr.docChanged || maximum < 1) { | ||
return pluginState; | ||
} | ||
|
||
if (this.isCountValid(state)) { | ||
return { | ||
decorationSet: DecorationSet.empty, | ||
}; | ||
} | ||
|
||
return { | ||
decorationSet: this.createDecorationSet(state), | ||
}; | ||
}, | ||
}, | ||
props: { | ||
decorations(state: EditorState) { | ||
return this.getState(state).decorationSet; | ||
}, | ||
}, | ||
}; | ||
} | ||
} | ||
|
||
declare global { | ||
namespace Remirror { | ||
interface AllExtensions { | ||
count: CountExtension; | ||
} | ||
} | ||
} |
Oops, something went wrong.