Merge pull request #1492 from sereneinserenade/main

Adding types to Linter and making the structure a bit easier
This commit is contained in:
Hans Pagel
2021-07-20 13:54:24 +02:00
committed by GitHub
6 changed files with 50 additions and 32 deletions

View File

@@ -1,10 +1,15 @@
// @ts-nocheck
import { Extension } from '@tiptap/core' import { Extension } from '@tiptap/core'
import { Decoration, DecorationSet } from 'prosemirror-view' import { Decoration, DecorationSet } from 'prosemirror-view'
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state' import { Plugin, PluginKey, TextSelection } from 'prosemirror-state'
import { Node as ProsemirrorNode } from 'prosemirror-model'
import LinterPlugin, { Result as Issue } from './LinterPlugin'
function renderIcon(issue) { interface IconDivElement extends HTMLDivElement {
const icon = document.createElement('div') issue?: Issue
}
function renderIcon(issue: Issue) {
const icon: IconDivElement = document.createElement('div')
icon.className = 'lint-icon' icon.className = 'lint-icon'
icon.title = issue.message icon.title = issue.message
@@ -13,11 +18,11 @@ function renderIcon(issue) {
return icon return icon
} }
function runAllLinterPlugins(doc, plugins) { function runAllLinterPlugins(doc: ProsemirrorNode, plugins: Array<typeof LinterPlugin>) {
const decorations: [any?] = [] const decorations: [any?] = []
const results = plugins.map(LinterPlugin => { const results = plugins.map(RegisteredLinterPlugin => {
return new LinterPlugin(doc).scan().getResults() return new RegisteredLinterPlugin(doc).scan().getResults()
}).flat() }).flat()
results.forEach(issue => { results.forEach(issue => {
@@ -31,7 +36,7 @@ function runAllLinterPlugins(doc, plugins) {
} }
export interface LinterOptions { export interface LinterOptions {
plugins: [any], plugins: Array<typeof LinterPlugin>,
} }
export const Linter = Extension.create({ export const Linter = Extension.create({
@@ -62,8 +67,9 @@ export const Linter = Extension.create({
return this.getState(state) return this.getState(state)
}, },
handleClick(view, _, event) { handleClick(view, _, event) {
if (/lint-icon/.test(event.target.className)) { const target = (event.target as IconDivElement)
const { from, to } = event.target.issue if (/lint-icon/.test(target.className) && target.issue) {
const { from, to } = target.issue
view.dispatch( view.dispatch(
view.state.tr view.state.tr
@@ -73,17 +79,22 @@ export const Linter = Extension.create({
return true return true
} }
return false
}, },
handleDoubleClick(view, _, event) { handleDoubleClick(view, _, event) {
if (/lint-icon/.test(event.target.className)) { const target = (event.target as IconDivElement)
const prob = event.target.issue if (/lint-icon/.test((event.target as HTMLElement).className) && target.issue) {
const prob = target.issue
if (prob.fix) { if (prob.fix) {
prob.fix(view) prob.fix(view, prob)
view.focus() view.focus()
return true return true
} }
} }
return false
}, },
}, },
}), }),

View File

@@ -1,8 +1,10 @@
interface Result { import { Node as ProsemirrorNode } from 'prosemirror-model'
export interface Result {
message: string, message: string,
from: number, from: number,
to: number, to: number,
fix?: null fix?: Function
} }
export default class LinterPlugin { export default class LinterPlugin {
@@ -10,11 +12,11 @@ export default class LinterPlugin {
private results: Array<Result> = [] private results: Array<Result> = []
constructor(doc: any) { constructor(doc: ProsemirrorNode) {
this.doc = doc this.doc = doc
} }
record(message: string, from: number, to: number, fix?: null) { record(message: string, from: number, to: number, fix?: Function) {
this.results.push({ this.results.push({
message, message,
from, from,
@@ -23,6 +25,10 @@ export default class LinterPlugin {
}) })
} }
scan() {
return this
}
getResults() { getResults() {
return this.results return this.results
} }

View File

@@ -1,4 +1,3 @@
// @ts-nocheck
import LinterPlugin from '../LinterPlugin' import LinterPlugin from '../LinterPlugin'
export class BadWords extends LinterPlugin { export class BadWords extends LinterPlugin {
@@ -6,7 +5,7 @@ export class BadWords extends LinterPlugin {
public regex = /\b(obviously|clearly|evidently|simply)\b/ig public regex = /\b(obviously|clearly|evidently|simply)\b/ig
scan() { scan() {
this.doc.descendants((node: any, position: any) => { this.doc.descendants((node: any, position: number) => {
if (!node.isText) { if (!node.isText) {
return return
} }

View File

@@ -1,15 +1,15 @@
// @ts-nocheck import { EditorView } from 'prosemirror-view'
import LinterPlugin from '../LinterPlugin' import LinterPlugin, { Result as Issue } from '../LinterPlugin'
export class HeadingLevel extends LinterPlugin { export class HeadingLevel extends LinterPlugin {
fixHeader(level) { fixHeader(level: number) {
return function ({ state, dispatch }) { return function ({ state, dispatch }: EditorView, issue: Issue) {
dispatch(state.tr.setNodeMarkup(this.from - 1, null, { level })) dispatch(state.tr.setNodeMarkup(issue.from - 1, undefined, { level }))
} }
} }
scan() { scan() {
let lastHeadLevel = null let lastHeadLevel: number | null = null
this.doc.descendants((node, position) => { this.doc.descendants((node, position) => {
if (node.type.name === 'heading') { if (node.type.name === 'heading') {

View File

@@ -1,14 +1,14 @@
// @ts-nocheck import { EditorView } from 'prosemirror-view'
import LinterPlugin from '../LinterPlugin' import LinterPlugin, { Result as Issue } from '../LinterPlugin'
export class Punctuation extends LinterPlugin { export class Punctuation extends LinterPlugin {
public regex = / ([,.!?:]) ?/g public regex = / ([,.!?:]) ?/g
fix(replacement: any) { fix(replacement: any) {
return function ({ state, dispatch }) { return function ({ state, dispatch }: EditorView, issue: Issue) {
dispatch( dispatch(
state.tr.replaceWith( state.tr.replaceWith(
this.from, this.to, issue.from, issue.to,
state.schema.text(replacement), state.schema.text(replacement),
), ),
) )
@@ -17,9 +17,9 @@ export class Punctuation extends LinterPlugin {
scan() { scan() {
this.doc.descendants((node, position) => { this.doc.descendants((node, position) => {
if (!node.isText) { if (!node.isText) return
return
} if (!node.text) return
const matches = this.regex.exec(node.text) const matches = this.regex.exec(node.text)

View File

@@ -1,6 +1,8 @@
# Linter # Linter
⚠️ Experiment ⚠️ Experiment, currently not supported or maintained
Linter can be used to check the content as per your wish and highlight it to the user. Linter extension can have multiple plugins for each task you want to achieve.
## Issues ## Issues
* There is no decoration API in tiptap, thats why this is a lot of ProseMirror work. Before well publish that example, wed need to find a few ways to make it more tiptap-like. For example, it would be great to use Vue/React components for the widget. * There is no decoration API in tiptap, thats why this is a lot of ProseMirror work. Before well publish that example, wed need to find a few ways to make it more tiptap-like. For example, it would be great to use Vue/React components for the widget.