在构建 Pika 的帖子流布局时,我们需要添加在 Pika 编辑器中管理摘录的功能。这些摘录将用于在帖子流中显示帖子的一小部分,同时提供“继续阅读”链接供读者点击阅读帖子的其余部分。为了添加此功能,我们必须深入研究扩展编辑器Tiptap的基础开源库。
首先是 Tiptap 部分
以下是扩展的完整代码。该扩展主要检测用户是否输入 、
或 WordPress 的
<!--more-->
并将该文本替换为:
< div data-type = " excerpt " class = " post-excerpt " contenteditable = " false " draggable = " true " > ↑ Excerpt ↑ </ div >
通过该 HTML,我们可以使用 CSS 来设置样式,如下所示:
该扩展足够智能,可以知道编辑器中是否已存在摘录,在这种情况下,不允许创建另一个摘录。
扩展名:
import { Node , InputRule , nodeInputRule , mergeAttributes } from '@tiptap/core'
/**
* Look for on a line by itself, with all the whitespace permutations
*/
export const excerptInputRegex = / ^\s*\s*$ / i
/**
* Look for on a line by itself, with all the whitespace permutations
*/
export const moreInputRegex = / ^\s*\s*$ / i
/**
* Look for classic WordPress <!--more--> tag
*/
export const wpMoreInputRegex = / ^\s*<*![-—]+\s*more\s*[-—]+>\s*$ / i
const excerptText = '↑ Excerpt ↑'
/**
* Used to detect if an excerpt already exists in the editor
*/
const hasExistingExcerpt = ( state ) => {
let hasExcerpt = false
state . doc . descendants ( node => {
if ( node . type . name === 'excerpt' ) {
hasExcerpt = true
return false // stop traversing
}
} )
return hasExcerpt
}
/**
* Disable excerpt button in toolbar if excerpt already exists in
* the editor. Note that we use Tiptap with the Rhino editor for
* Rails, which explains the rhino-editor selectors. Rhino:
* https://rhino-editor.vercel.app/
*/
const setExcerptButtonState = ( editor ) => {
const button = editor . view . dom . closest ( 'rhino-editor' ) . querySelector ( 'rhino-editor button[data-excerpt]' )
if ( button ) {
button . classList . toggle ( 'toolbar__button--disable' , hasExistingExcerpt ( editor . state ) )
button . disabled = hasExistingExcerpt ( editor . state )
}
}
/**
* This custom InputRule allows us to make a singleton excerpt node
* that short-circuits if an excerpt node already exists.
*/
export function excerptInputRule ( config ) {
return new InputRule ( {
find : config . find ,
handler : ( { state , range , match } ) => {
if ( hasExistingExcerpt ( state ) ) {
return
}
const delegate = nodeInputRule ( {
find : config . find ,
type : config . type ,
} )
delegate . handler ( { state , range , match } )
} ,
} )
}
export const Excerpt = Node . create ( {
name : 'excerpt' ,
group : 'block' ,
content : 'inline+' ,
inline : false ,
isolating : true ,
atom : true ,
draggable : true ,
selectable : true ,
onCreate ( ) {
setExcerptButtonState ( this . editor )
} ,
onUpdate ( ) {
setExcerptButtonState ( this . editor )
} ,
parseHTML ( ) {
return [ { tag : 'div[data-type="excerpt"]' } ]
} ,
renderHTML ( { HTMLAttributes } ) {
return [ 'div' , mergeAttributes ( { 'data-type' : 'excerpt' , class : 'post-excerpt' } , HTMLAttributes ) , excerptText ]
} ,
/**
* Add insertExcerpt command that we can call from our custom
* toolbar buttons. This command checks for an existing excerpt
* before inserting a new one.
*/
addCommands ( ) {
return {
insertExcerpt : ( ) => ( { state , commands } ) => {
if ( hasExistingExcerpt ( state ) ) {
return false
}
return commands . insertContent ( {
type : this . name ,
content : [ {
type : 'text' ,
text : excerptText
} ]
} )
} ,
}
} ,
/**
* Set up various detection for etc text.
*/
addInputRules ( ) {
return [
excerptInputRule ( {
find : excerptInputRegex ,
type : this . type ,
} ) ,
excerptInputRule ( {
find : moreInputRegex ,
type : this . type ,
} ) ,
excerptInputRule ( {
find : wpMoreInputRegex ,
type : this . type ,
} ) ,
]
} ,
} )
现在是 Rails 部分
这显然需要根据您的 Tiptap 环境进行修改。在我们的例子中,使用Rhino 编辑器和 Rails,这就是我们所做的……
app/javascript/controllers/extensions/excerpt.js
是我们的扩展目录所在的位置。我们使用 importmaps 来管理 JavaScript,因此我们需要将扩展目录固定在那里:
pin_all_from "app/javascript/extensions" , under : "extensions"
我们为所有 Rhino 增强功能提供了一个刺激控制器。我们需要在那里导入我们的扩展:
import { Excerpt } from "extensions/excerpt"
要添加扩展,我们在用于执行所有 Rhino 初始化的函数中执行此操作(我们将 Rhino 编辑器传递到此函数中):
initializeEditor ( rhino ) {
// snip
rhino . addExtensions ( Excerpt )
// snip
}
我们添加一个函数,稍后我们将使用新的 Rhino 工具栏按钮调用该函数。该函数调用我们在扩展中定义的insertExcerpt
命令。
insertExcerpt ( ) {
this . element . editor . chain ( ) . focus ( ) . insertExcerpt ( ) . run ( )
}
最后,这是我们添加到 Rhino 工具栏的按钮。注意data-action="click->rhino#insertExcerpt"
,它正在调用上面的函数:
< button slot = " after-increase-indentation-button " class = " rhino-toolbar-button toolbar__button--excerpt " type = " button " data-role-tooltip = " rhino-insert-excerpt " data-action = " click->rhino#insertExcerpt " data-excerpt data-role = " toolbar-item " tabindex = " -1 " >
< role-tooltip class = " toolbar__tooltip " id = " rhino-insert-excerpt " part = " toolbar__tooltip toolbar__tooltip--create-excerpt " exportparts = " base:toolbar__tooltip__base, arrow:toolbar__tooltip__arrow " >
Create Excerpt
</ role-tooltip >
< svg xmlns = " http://www.w3.org/2000/svg " aria-hidden = " true " fill = " currentColor " part = " toolbar__icon " viewBox = " 0 0 24 24 " width = " 24 " height = " 24 " style = " pointer-events : none ; " >
< path d = " SNIP " />
</ svg >
</ button >
这绝不是一个嵌入式扩展,但希望它可以帮助其他想要将此摘录功能添加到他们的 Tiptap 编辑器中的人。
原文: https://goodenough.us/blog/2025-01-16-til-tiptap-excerpt-extension-with-rails/