adding types to linter extension and it's plugins
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
// @ts-nocheck
|
||||
import { Extension } from '@tiptap/core'
|
||||
import { Decoration, DecorationSet } from 'prosemirror-view'
|
||||
import { Plugin, PluginKey, TextSelection } from 'prosemirror-state'
|
||||
import { Node as ProsemirrorNode } from 'prosemirror-model'
|
||||
import LinterPlugin, { Result as Issue } from './LinterPlugin'
|
||||
|
||||
function renderIcon(issue) {
|
||||
const icon = document.createElement('div')
|
||||
interface IconDivElement extends HTMLDivElement {
|
||||
issue?: Issue
|
||||
}
|
||||
|
||||
function renderIcon(issue: Issue) {
|
||||
const icon: IconDivElement = document.createElement('div')
|
||||
|
||||
icon.className = 'lint-icon'
|
||||
icon.title = issue.message
|
||||
@@ -13,11 +18,11 @@ function renderIcon(issue) {
|
||||
return icon
|
||||
}
|
||||
|
||||
function runAllLinterPlugins(doc, plugins) {
|
||||
function runAllLinterPlugins(doc: ProsemirrorNode, plugins: Array<typeof LinterPlugin>) {
|
||||
const decorations: [any?] = []
|
||||
|
||||
const results = plugins.map(LinterPlugin => {
|
||||
return new LinterPlugin(doc).scan().getResults()
|
||||
const results = plugins.map(RegisteredLinterPlugin => {
|
||||
return new RegisteredLinterPlugin(doc).scan().getResults()
|
||||
}).flat()
|
||||
|
||||
results.forEach(issue => {
|
||||
@@ -31,7 +36,7 @@ function runAllLinterPlugins(doc, plugins) {
|
||||
}
|
||||
|
||||
export interface LinterOptions {
|
||||
plugins: [any],
|
||||
plugins: Array<typeof LinterPlugin>,
|
||||
}
|
||||
|
||||
export const Linter = Extension.create({
|
||||
@@ -62,8 +67,9 @@ export const Linter = Extension.create({
|
||||
return this.getState(state)
|
||||
},
|
||||
handleClick(view, _, event) {
|
||||
if (/lint-icon/.test(event.target.className)) {
|
||||
const { from, to } = event.target.issue
|
||||
const target = (event.target as IconDivElement)
|
||||
if (/lint-icon/.test(target.className) && target.issue) {
|
||||
const { from, to } = target.issue
|
||||
|
||||
view.dispatch(
|
||||
view.state.tr
|
||||
@@ -73,17 +79,22 @@ export const Linter = Extension.create({
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
handleDoubleClick(view, _, event) {
|
||||
if (/lint-icon/.test(event.target.className)) {
|
||||
const prob = event.target.issue
|
||||
const target = (event.target as IconDivElement)
|
||||
if (/lint-icon/.test((event.target as HTMLElement).className) && target.issue) {
|
||||
const prob = target.issue
|
||||
|
||||
if (prob.fix) {
|
||||
prob.fix(view)
|
||||
prob.fix(view, prob)
|
||||
view.focus()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
interface Result {
|
||||
import { Node as ProsemirrorNode } from 'prosemirror-model'
|
||||
|
||||
export interface Result {
|
||||
message: string,
|
||||
from: number,
|
||||
to: number,
|
||||
fix?: null
|
||||
fix?: Function
|
||||
}
|
||||
|
||||
export default class LinterPlugin {
|
||||
@@ -10,11 +12,11 @@ export default class LinterPlugin {
|
||||
|
||||
private results: Array<Result> = []
|
||||
|
||||
constructor(doc: any) {
|
||||
constructor(doc: ProsemirrorNode) {
|
||||
this.doc = doc
|
||||
}
|
||||
|
||||
record(message: string, from: number, to: number, fix?: null) {
|
||||
record(message: string, from: number, to: number, fix?: Function) {
|
||||
this.results.push({
|
||||
message,
|
||||
from,
|
||||
@@ -23,6 +25,10 @@ export default class LinterPlugin {
|
||||
})
|
||||
}
|
||||
|
||||
scan() {
|
||||
return this
|
||||
}
|
||||
|
||||
getResults() {
|
||||
return this.results
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// @ts-nocheck
|
||||
import LinterPlugin from '../LinterPlugin'
|
||||
|
||||
export class BadWords extends LinterPlugin {
|
||||
@@ -6,7 +5,7 @@ export class BadWords extends LinterPlugin {
|
||||
public regex = /\b(obviously|clearly|evidently|simply)\b/ig
|
||||
|
||||
scan() {
|
||||
this.doc.descendants((node: any, position: any) => {
|
||||
this.doc.descendants((node: any, position: number) => {
|
||||
if (!node.isText) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// @ts-nocheck
|
||||
import LinterPlugin from '../LinterPlugin'
|
||||
import { EditorView } from 'prosemirror-view'
|
||||
import LinterPlugin, { Result as Issue } from '../LinterPlugin'
|
||||
|
||||
export class HeadingLevel extends LinterPlugin {
|
||||
fixHeader(level) {
|
||||
return function ({ state, dispatch }) {
|
||||
dispatch(state.tr.setNodeMarkup(this.from - 1, null, { level }))
|
||||
fixHeader(level: number) {
|
||||
return function ({ state, dispatch }: EditorView, issue: Issue) {
|
||||
dispatch(state.tr.setNodeMarkup(issue.from - 1, undefined, { level }))
|
||||
}
|
||||
}
|
||||
|
||||
scan() {
|
||||
let lastHeadLevel = null
|
||||
let lastHeadLevel: number | null = null
|
||||
|
||||
this.doc.descendants((node, position) => {
|
||||
if (node.type.name === 'heading') {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// @ts-nocheck
|
||||
import LinterPlugin from '../LinterPlugin'
|
||||
import { EditorView } from 'prosemirror-view'
|
||||
import LinterPlugin, { Result as Issue } from '../LinterPlugin'
|
||||
|
||||
export class Punctuation extends LinterPlugin {
|
||||
public regex = / ([,.!?:]) ?/g
|
||||
|
||||
fix(replacement: any) {
|
||||
return function ({ state, dispatch }) {
|
||||
return function ({ state, dispatch }: EditorView, issue: Issue) {
|
||||
dispatch(
|
||||
state.tr.replaceWith(
|
||||
this.from, this.to,
|
||||
issue.from, issue.to,
|
||||
state.schema.text(replacement),
|
||||
),
|
||||
)
|
||||
@@ -17,9 +17,9 @@ export class Punctuation extends LinterPlugin {
|
||||
|
||||
scan() {
|
||||
this.doc.descendants((node, position) => {
|
||||
if (!node.isText) {
|
||||
return
|
||||
}
|
||||
if (!node.isText) return
|
||||
|
||||
if (!node.text) return
|
||||
|
||||
const matches = this.regex.exec(node.text)
|
||||
|
||||
|
||||
42
docs/src/demos/Marks/Subscript/index.spec.js
Normal file
42
docs/src/demos/Marks/Subscript/index.spec.js
Normal file
@@ -0,0 +1,42 @@
|
||||
context('/demos/Marks/Subscript', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Marks/Subscript')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p>Example Text</p>')
|
||||
cy.get('.ProseMirror').type('{selectall}')
|
||||
})
|
||||
})
|
||||
|
||||
it('should transform inline style to sub tags', () => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p><span style="vertical-align: middle">Example Text</span></p>')
|
||||
expect(editor.getHTML()).to.eq('<p><sub>Example Text</sub></p>')
|
||||
})
|
||||
})
|
||||
|
||||
it('sould omit inline style with a different vertical align', () => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p><b style="vertical-align: middle">Example Text</b></p>')
|
||||
expect(editor.getHTML()).to.eq('<p>Example Text</p>')
|
||||
})
|
||||
})
|
||||
|
||||
it('the button should make the selected text bold', () => {
|
||||
cy.get('button:first')
|
||||
.click()
|
||||
|
||||
cy.get('.ProseMirror')
|
||||
.find('sub')
|
||||
.should('contain', 'Example Text')
|
||||
})
|
||||
|
||||
it('the button should toggle the selected text bold', () => {
|
||||
cy.get('button:first').click()
|
||||
cy.get('.ProseMirror').type('{selectall}')
|
||||
cy.get('button:first').click()
|
||||
cy.get('.ProseMirror sub').should('not.exist')
|
||||
})
|
||||
})
|
||||
49
docs/src/demos/Marks/Subscript/index.vue
Normal file
49
docs/src/demos/Marks/Subscript/index.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<button @click="editor.chain().focus().toggleSubscript().run()" :class="{ 'is-active': editor.isActive('subscript') }">
|
||||
subscript
|
||||
</button>
|
||||
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import Subscript from '@tiptap/extension-subscript'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Subscript,
|
||||
],
|
||||
content: `
|
||||
<p>This is regular text.</p>
|
||||
<p><sub>This is subscript.</sub></p>
|
||||
<p><span style="vertical-align: sub">And this.</span></p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
42
docs/src/demos/Marks/Superscript/index.spec.js
Normal file
42
docs/src/demos/Marks/Superscript/index.spec.js
Normal file
@@ -0,0 +1,42 @@
|
||||
context('/demos/Marks/Superscript', () => {
|
||||
before(() => {
|
||||
cy.visit('/demos/Marks/Superscript')
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p>Example Text</p>')
|
||||
cy.get('.ProseMirror').type('{selectall}')
|
||||
})
|
||||
})
|
||||
|
||||
it('should transform inline style to sup tags', () => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p><span style="vertical-align: super">Example Text</span></p>')
|
||||
expect(editor.getHTML()).to.eq('<p><sup>Example Text</sup></p>')
|
||||
})
|
||||
})
|
||||
|
||||
it('sould omit inline style with a different vertical align', () => {
|
||||
cy.get('.ProseMirror').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p><span style="vertical-align: middle">Example Text</span></p>')
|
||||
expect(editor.getHTML()).to.eq('<p>Example Text</p>')
|
||||
})
|
||||
})
|
||||
|
||||
it('the button should make the selected text bold', () => {
|
||||
cy.get('button:first')
|
||||
.click()
|
||||
|
||||
cy.get('.ProseMirror')
|
||||
.find('sup')
|
||||
.should('contain', 'Example Text')
|
||||
})
|
||||
|
||||
it('the button should toggle the selected text bold', () => {
|
||||
cy.get('button:first').click()
|
||||
cy.get('.ProseMirror').type('{selectall}')
|
||||
cy.get('button:first').click()
|
||||
cy.get('.ProseMirror sup').should('not.exist')
|
||||
})
|
||||
})
|
||||
49
docs/src/demos/Marks/Superscript/index.vue
Normal file
49
docs/src/demos/Marks/Superscript/index.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div v-if="editor">
|
||||
<button @click="editor.chain().focus().toggleSuperscript().run()" :class="{ 'is-active': editor.isActive('superscript') }">
|
||||
superscript
|
||||
</button>
|
||||
|
||||
<editor-content :editor="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Editor, EditorContent } from '@tiptap/vue-2'
|
||||
import Document from '@tiptap/extension-document'
|
||||
import Paragraph from '@tiptap/extension-paragraph'
|
||||
import Text from '@tiptap/extension-text'
|
||||
import Superscript from '@tiptap/extension-superscript'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EditorContent,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.editor = new Editor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
Superscript,
|
||||
],
|
||||
content: `
|
||||
<p>This is regular text.</p>
|
||||
<p><sup>This is superscript.</sup></p>
|
||||
<p><span style="vertical-align: super">And this.</span></p>
|
||||
`,
|
||||
})
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.editor.destroy()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user