Skip to content

Commit

Permalink
Merge pull request #506 from thematters/develop
Browse files Browse the repository at this point in the history
Release: v0.3.0-alpha.1
  • Loading branch information
robertu7 authored Aug 4, 2024
2 parents 1840810 + e48592b commit 146e0f8
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 152 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@matters/matters-editor",
"version": "0.3.0-alpha.0",
"version": "0.3.0-alpha.1",
"description": "Editor for matters.news",
"author": "https://github.com/thematters",
"homepage": "https://github.com/thematters/matters-editor",
Expand Down
138 changes: 138 additions & 0 deletions src/editors/extensions/figcaptionKit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { type Editor } from '@tiptap/core'
import { Node } from '@tiptap/core'
import { Plugin, PluginKey } from '@tiptap/pm/state'

/**
* FigcaptionKit extension works with FigureAudio,
* FigureEmbed and FigureImage extensions to:
* - limit figcaption length
* - handle enter key event to insert a new paragraph
* - handle backspace key event to remove the figcaption if it's empty
* - handle click event to select the figcaption
*
* @see {https://github.com/ueberdosis/tiptap/issues/629}
*/

type FigcaptionKitOptions = {
maxCaptionLength?: number
}

const pluginName = 'figcaptionKit'

export const makeFigcaptionEventHandlerPlugin = ({
editor,
}: {
editor: Editor
}) => {
return new Plugin({
key: new PluginKey('figcaptionEventHandler'),
props: {
handleClickOn(view, pos, node, nodePos, event) {
const isFigcaption =
event.target instanceof HTMLElement
? event.target.tagName.toUpperCase() === 'FIGCAPTION'
: false

if (!isFigcaption) return

// set the selection to the figcaption node
editor.commands.setTextSelection(pos)

// to prevent the default behavior which is to select the whole node
// @see {@url https://discuss.prosemirror.net/t/prevent-nodeview-selection-on-click/3193}
return true
},
handleKeyDown(view, event) {
const isBackSpace = event.key.toLowerCase() === 'backspace'
const isEnter = event.key.toLowerCase() === 'enter'

if (!isBackSpace && !isEnter) {
return
}

const anchorParent = view.state.selection.$anchor.parent
const isCurrentPlugin = anchorParent.type.name === pluginName
const isEmptyFigcaption = anchorParent.content.size <= 0

if (!isCurrentPlugin) {
return
}

// backSpace to remove if the figcaption is empty
if (isBackSpace && isEmptyFigcaption) {
// FIXME: setTimeOut to avoid repetitive deletion
setTimeout(() => {
editor.commands.deleteNode(pluginName)
})
return
}

// insert a new paragraph
if (isEnter) {
const { $from, $to } = editor.state.selection
const isTextAfter = $to.nodeAfter?.type?.name === 'text'

// skip if figcaption text is selected
// or has text after current selection
if ($from !== $to || isTextAfter) {
return
}

// FIXME: setTimeOut to avoid repetitive paragraph insertion
setTimeout(() => {
editor.commands.insertContentAt($to.pos + 1, {
type: 'paragraph',
})
})
}
},
},
})
}

export const FigcaptionKit = Node.create<FigcaptionKitOptions>({
name: pluginName,

addOptions() {
return {
maxCaptionLength: undefined,
}
},

addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey('figcaptionLimit'),
filterTransaction: (transaction) => {
// Nothing has changed, ignore it.
if (!transaction.docChanged || !this.options.maxCaptionLength) {
return true
}

try {
// skip if not in a figure
const anchorParent = transaction.selection.$anchor.parent
const isFigure = anchorParent.type.name.includes('figure')
if (!isFigure) {
return true
}

// limit figcaption length
if (anchorParent.content.size <= 0) return true

const figcaptionText = anchorParent.content.child(0).text || ''
if (figcaptionText.length > this.options.maxCaptionLength) {
return false
}
} catch (e) {
console.error(e)
}

return true
},
}),

makeFigcaptionEventHandlerPlugin({ editor: this.editor }),
]
},
})
52 changes: 2 additions & 50 deletions src/editors/extensions/figureAudio.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Editor, Node } from '@tiptap/core'
import { Node } from '@tiptap/core'
import { Plugin, PluginKey } from '@tiptap/pm/state'

/**
Expand Down Expand Up @@ -52,6 +52,7 @@ export const FigureAudio = Node.create({
content: 'text*',
draggable: true,
isolating: true,
atom: true,

// disallows all marks for figcaption
marks: '',
Expand Down Expand Up @@ -155,56 +156,7 @@ export const FigureAudio = Node.create({
new Plugin({
key: new PluginKey('removePastedFigureAudio'),
props: {
handleKeyDown(view, event) {
const isBackSpace = event.key.toLowerCase() === 'backspace'
const isEnter = event.key.toLowerCase() === 'enter'

if (!isBackSpace && !isEnter) {
return
}

const anchorParent = view.state.selection.$anchor.parent
const isCurrentPlugin = anchorParent.type.name === pluginName
const isEmptyFigcaption = anchorParent.content.size <= 0

if (!isCurrentPlugin) {
return
}

// @ts-expect-error
const editor = view.dom.editor as Editor

// backSpace to remove if the figcaption is empty
if (isBackSpace && isEmptyFigcaption) {
// FIXME: setTimeOut to avoid repetitive deletion
setTimeout(() => {
editor.commands.deleteNode(pluginName)
})
return
}

// insert a new paragraph
if (isEnter) {
const { $from, $to } = editor.state.selection
const isTextAfter = $to.nodeAfter?.type?.name === 'text'

// skip if figcaption text is selected
// or has text after current selection
if ($from !== $to || isTextAfter) {
return
}

// FIXME: setTimeOut to avoid repetitive paragraph insertion
setTimeout(() => {
editor.commands.insertContentAt($to.pos + 1, {
type: 'paragraph',
})
})
}
},

transformPastedHTML(html) {
// remove
html = html
.replace(/\n/g, '')
.replace(/<figure.*class=.audio.*[\n]*.*?<\/figure>/g, '')
Expand Down
52 changes: 2 additions & 50 deletions src/editors/extensions/figureEmbed.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Editor, Node } from '@tiptap/core'
import { Node } from '@tiptap/core'
import { Plugin, PluginKey } from '@tiptap/pm/state'

/**
Expand Down Expand Up @@ -255,6 +255,7 @@ export const FigureEmbed = Node.create({
content: 'text*',
draggable: true,
isolating: true,
atom: true,

// disallows all marks for figcaption
marks: '',
Expand Down Expand Up @@ -359,56 +360,7 @@ export const FigureEmbed = Node.create({
new Plugin({
key: new PluginKey('removePastedFigureEmbed'),
props: {
handleKeyDown(view, event) {
const isBackSpace = event.key.toLowerCase() === 'backspace'
const isEnter = event.key.toLowerCase() === 'enter'

if (!isBackSpace && !isEnter) {
return
}

const anchorParent = view.state.selection.$anchor.parent
const isCurrentPlugin = anchorParent.type.name === pluginName
const isEmptyFigcaption = anchorParent.content.size <= 0

if (!isCurrentPlugin) {
return
}

// @ts-expect-error
const editor = view.dom.editor as Editor

// backSpace to remove if the figcaption is empty
if (isBackSpace && isEmptyFigcaption) {
// FIXME: setTimeOut to avoid repetitive deletion
setTimeout(() => {
editor.commands.deleteNode(pluginName)
})
return
}

// insert a new paragraph
if (isEnter) {
const { $from, $to } = editor.state.selection
const isTextAfter = $to.nodeAfter?.type?.name === 'text'

// skip if figcaption text is selected
// or has text after current selection
if ($from !== $to || isTextAfter) {
return
}

// FIXME: setTimeOut to avoid repetitive paragraph insertion
setTimeout(() => {
editor.commands.insertContentAt($to.pos + 1, {
type: 'paragraph',
})
})
}
},

transformPastedHTML(html) {
// remove
html = html
.replace(/\n/g, '')
.replace(/<figure.*class=.embed.*[\n]*.*?<\/figure>/g, '')
Expand Down
53 changes: 3 additions & 50 deletions src/editors/extensions/figureImage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Editor, Node } from '@tiptap/core'
import { Node } from '@tiptap/core'
import { Plugin, PluginKey } from '@tiptap/pm/state'

/**
Expand Down Expand Up @@ -34,6 +34,8 @@ export const FigureImage = Node.create({
content: 'text*',
draggable: true,
isolating: true,
selectable: true,
atom: true,

// disallows all marks for figcaption
marks: '',
Expand Down Expand Up @@ -107,56 +109,7 @@ export const FigureImage = Node.create({
new Plugin({
key: new PluginKey('removePastedFigureImage'),
props: {
handleKeyDown(view, event) {
const isBackSpace = event.key.toLowerCase() === 'backspace'
const isEnter = event.key.toLowerCase() === 'enter'

if (!isBackSpace && !isEnter) {
return
}

const anchorParent = view.state.selection.$anchor.parent
const isCurrentPlugin = anchorParent.type.name === pluginName
const isEmptyFigcaption = anchorParent.content.size <= 0

if (!isCurrentPlugin) {
return
}

// @ts-expect-error
const editor = view.dom.editor as Editor

// backSpace to remove if the figcaption is empty
if (isBackSpace && isEmptyFigcaption) {
// FIXME: setTimeOut to avoid repetitive deletion
setTimeout(() => {
editor.commands.deleteNode(pluginName)
})
return
}

// insert a new paragraph
if (isEnter) {
const { $from, $to } = editor.state.selection
const isTextAfter = $to.nodeAfter?.type?.name === 'text'

// skip if figcaption text is selected
// or has text after current selection
if ($from !== $to || isTextAfter) {
return
}

// FIXME: setTimeOut to avoid repetitive paragraph insertion
setTimeout(() => {
editor.commands.insertContentAt($to.pos + 1, {
type: 'paragraph',
})
})
}
},

transformPastedHTML(html) {
// remove
html = html
.replace(/\n/g, '')
.replace(/<figure.*class=.image.*[\n]*.*?<\/figure>/g, '')
Expand Down
3 changes: 3 additions & 0 deletions src/editors/extensions/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './blockquote'
export * from './bold'
export * from './figcaptionKit'
export * from './figureAudio'
export * from './figureEmbed'
export * from './figureImage'
Expand All @@ -26,6 +27,7 @@ import Text from '@tiptap/extension-text'

import { Blockquote } from './blockquote'
import { Bold } from './bold'
import { FigcaptionKit } from './figcaptionKit'
import { FigureAudio } from './figureAudio'
import { FigureEmbed } from './figureEmbed'
import { FigureImage } from './figureImage'
Expand Down Expand Up @@ -65,6 +67,7 @@ export const articleEditorExtensions = [
FigureImage,
FigureAudio,
FigureEmbed,
FigcaptionKit,
]

export const commentEditorExtensions = [...baseEditorExtensions]
Expand Down
2 changes: 1 addition & 1 deletion src/editors/extensions/pasteDropFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@ export const PasteDropFile = Node.create<PasteDropFileOptions>({
name: pluginName,

addProseMirrorPlugins() {
return [makePlugin({ ...this.options, editor: this.editor as Editor })]
return [makePlugin({ ...this.options, editor: this.editor })]
},
})

0 comments on commit 146e0f8

Please sign in to comment.