Skip to content

Commit

Permalink
feature(censor): Add slate-censor-plugin (#12)
Browse files Browse the repository at this point in the history
* init censor plugin

* add censoring plugin

* add censor plugin story

* update story

* .

Co-authored-by: surya darma <budi.surya@kumparan.com>
  • Loading branch information
imdbsd and surya darma authored Jun 1, 2021
1 parent 1a4d76c commit 7322b74
Show file tree
Hide file tree
Showing 10 changed files with 295 additions and 0 deletions.
8 changes: 8 additions & 0 deletions packages/slate-censor-plugin/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"bracketSpacing": false
}
62 changes: 62 additions & 0 deletions packages/slate-censor-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Slate Censor Plugin

slate.js plugin for censoring text in slate.js document

## Instal

```
$ yarn add @imdbsd/slate-censor-plugin
```

## Usage

Define list of the censored word in an array and past it to the decorate function

```
import {useState, useMemo, FC} from 'react'
import {createEditor, Node} from 'slate'
import {Slate, Editable, withReact} from 'slate-react'
import {decorate, renderLeaf} from '@imdbsd/slate-censor-plugin'
const blacklist = ['fuck']
const Editor: FC = (props) => {
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState<Node[]>([
{
type: 'paragraph',
children: [
{
text: `This fuck word will getting censored`,
},
],
},
])
return (
<Slate
editor={editor}
value={value}
onChange={(newValue) => setValue(newValue)}
>
<Editable decorate={decorate(blacklist)} renderLeaf={renderLeaf('')} />
</Slate>
)
}
export default Editor
```

## Options

### decorate

| options | type | Description |
| --------- | :------------------------: | ---------------------------: |
| blacklist | `required` `Array<string>` | List of the blacklisted word |

### renderLeaf / CensorLeaf

| props/args | type | Description |
| ---------- | :-----------------: | -----------------------------------------------------: |
| censorChar | `optional` `string` | Char that will used to censor the word, default is `*` |
22 changes: 22 additions & 0 deletions packages/slate-censor-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "@imdbsd/slate-censor-plugin",
"version": "1.0.0",
"description": "slate censor text plugin",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"repository": "https://github.com/imdbsd/slate-plugin/tree/master/packages/slate-censor-plugin",
"private": false,
"author": "imdbsd",
"license": "MIT",
"scripts": {
"build": "tsc -p ."
},
"peerDependencies": {
"react": "^17.0.2",
"slate": "^0.63.0",
"slate-react": "^0.64.0"
},
"devDependencies": {
"typescript": "^4.3.2"
}
}
42 changes: 42 additions & 0 deletions packages/slate-censor-plugin/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as React from 'react'
import {NodeEntry, Range, Text} from 'slate'
import {RenderLeafProps, DefaultLeaf} from 'slate-react'
import {CENSOR_LEAF_TYPE, getRangeFromString} from './utils'

export type CensorLeafProps = RenderLeafProps & {
leaf: Text & {type: string}
censorChar?: string
}

export const CensorLeaf = (props: CensorLeafProps) => {
const censorChar = props.censorChar?.trim() || '*'
if (props.leaf.type === CENSOR_LEAF_TYPE) {
return (
<span {...props.attributes}>
<span data-slate-string>
{censorChar.repeat(props.leaf.text.length)}
</span>
</span>
)
}
return <DefaultLeaf {...props} />
}
export const renderLeaf = (censorChar?: string) => (props: CensorLeafProps) => (
<CensorLeaf {...props} censorChar={censorChar} />
)

export const decorate = (blacklists: string[]) => ([
node,
path,
]: NodeEntry): Range[] => {
if (Text.isText(node)) {
const {text} = node
const decorates = blacklists.reduce<Range[]>((accDecorate, blacklist) => {
return [...accDecorate, ...getRangeFromString(text, blacklist, path)]
}, [])
return decorates
}
return []
}

export {CENSOR_LEAF_TYPE}
28 changes: 28 additions & 0 deletions packages/slate-censor-plugin/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Range, Path} from 'slate'

export const CENSOR_LEAF_TYPE = 'censored'

const getAllAnchorOffset = (text: string, search: string): Array<number> => {
let start = 0
const offset: Array<number> = []
while (text.indexOf(search, start) !== -1) {
const index = text.indexOf(search, start)
offset.push(index)
start += index + search.length
}
return offset
}

export const getRangeFromString = (
text: string,
search: string,
path: Path
): Range[] => {
const anchorsOffset = getAllAnchorOffset(text, search)
const textLength = search.length
return anchorsOffset.map((offset) => ({
type: CENSOR_LEAF_TYPE,
anchor: {path, offset},
focus: {path, offset: offset + textLength},
}))
}
71 changes: 71 additions & 0 deletions packages/slate-censor-plugin/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */

/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
"declaration": true /* Generates corresponding '.d.ts' file. */,
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist" /* Redirect output structure to the directory. */,
"rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
"removeComments": true /* Do not emit comments to output. */,
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */

/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true /* Report errors on unused parameters. */,
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */

/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */

/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */

/* Advanced Options */
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"exclude": ["node_modules", "__tests__", "dist"]
}
1 change: 1 addition & 0 deletions playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@imdbsd/slate-censor-plugin": "1.0.0",
"@imdbsd/slate-paste-url-plugin": "0.0.1",
"@imdbsd/slate-stabilo-plugin": "1.0.0",
"@imdbsd/slate-string-deserialize": "1.0.4",
Expand Down
16 changes: 16 additions & 0 deletions playground/src/SlateCensor.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react'
import {Story, Meta} from '@storybook/react/types-6-0'
import Editor, {Props} from './SlateCensor'

export default {
component: Editor,
title: 'slate-censor-plugin',
} as Meta

const Template: Story<Props> = (args) => <Editor {...args} />

export const Default = Template.bind({})
Default.args = {
blacklist: 'fuck;bitch;slut',
censorChar: '*',
}
40 changes: 40 additions & 0 deletions playground/src/SlateCensor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {useState, useMemo, FC} from 'react'
import {createEditor, Node} from 'slate'
import {Slate, Editable, withReact} from 'slate-react'
import {decorate, renderLeaf} from '@imdbsd/slate-censor-plugin'

export type Props = {
blacklist?: string
censorChar?: string
}

const Editor: FC<Props> = (props) => {
const blacklist = props.blacklist ? props.blacklist.split(';') : []
const censorChar = props.censorChar || '*'
const editor = useMemo(() => withReact(createEditor()), [])
const [value, setValue] = useState<Node[]>([
{
type: 'paragraph',
children: [
{
text: `Try typing the blacklisted word that appear on adon`,
},
],
},
])

return (
<Slate
editor={editor}
value={value}
onChange={(newValue) => setValue(newValue)}
>
<Editable
decorate={decorate(blacklist)}
renderLeaf={renderLeaf(censorChar)}
/>
</Slate>
)
}

export default Editor
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -14008,6 +14008,11 @@ typescript@^4.2.4:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961"
integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg==

typescript@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==

unfetch@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be"
Expand Down

0 comments on commit 7322b74

Please sign in to comment.