Merge pull request #1492 from sereneinserenade/main
Adding types to Linter and making the structure a bit easier
This commit is contained in:
@@ -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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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') {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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, that’s why this is a lot of ProseMirror work. Before we’ll publish that example, we’d 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, that’s why this is a lot of ProseMirror work. Before we’ll publish that example, we’d 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.
|
||||||
|
|||||||
Reference in New Issue
Block a user