-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Search & Replace #1891
Comments
Thanks! It’ll come back at some point, but it’s not very high on our list. I think you’re the first one missing it. Let’s see if others chime in here. |
well, I mean it is not like our life depends upon it, but we use it quite a lot (with tinymce, I promise I will write a guide btw...) for instance with templates for certain how-to's, guides, reviews et cetera. So if it comes some when during the next weeks (?) that would be great. I am not sure how difficult it is to bring it from v1 to v2. |
Hey @hanspagel , I need this and was trying to translate the v1 extension to v2 but I wasn't able to do it. Here's what I have as a WIP. One thing I couldn't figure out is how do we translate these lines to the new Extension API... I've tried to do it using /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { Extension, Command, CommandProps } from '@tiptap/core'
import { Plugin, PluginKey } from 'prosemirror-state'
import { Decoration, DecorationSet } from 'prosemirror-view'
declare module '@tiptap/core' {
interface Commands {
search: {
find: (searchTerm: string | RegExp) => Command
replace: (term: string) => Command
replaceAll: (term: string) => Command
clearSearch: () => Command
}
}
}
export interface SearchOptions {
autoSelectNext: boolean
findClass: string
searching: boolean
caseSensitive: boolean
disableRegex: boolean
alwaysSearch: boolean
}
export interface Result {
from: number
to: number
class: string
}
let results: Result[] = []
let searchTerm: string | null = null
let _updating = false
const autoSelectNext = true
const findClass = 'find'
const searching = false
const caseSensitive = false
const disableRegex = true
const alwaysSearch = false
const decorations: Decoration<{ [key: string]: any }>[] = []
export const getFindRegExp = (): RegExp | void => {
if (!searchTerm) {
return
}
return RegExp(searchTerm, !caseSensitive ? 'gui' : 'gu')
}
export const getDecorations = () => {
return results.map((res) => {
return Decoration.inline(res.from, res.to, { class: res.class })
})
}
export const _search = (doc) => {
results = []
const mergedTextNodes: { text: any; pos: any }[] = []
let index = 0
if (!searchTerm) {
return
}
doc.descendants((node, pos) => {
if (node.isText) {
if (mergedTextNodes[index]) {
mergedTextNodes[index] = {
text: mergedTextNodes[index].text + node.text,
pos: mergedTextNodes[index].pos,
}
} else {
mergedTextNodes[index] = {
text: node.text,
pos,
}
}
} else {
index += 1
}
})
mergedTextNodes.forEach(({ text, pos }) => {
const search = getFindRegExp()
if (!search) {
return
}
let m
// eslint-disable-next-line no-cond-assign
while ((m = search.exec(text))) {
if (m[0] === '') {
break
}
results.push({
from: pos + m.index,
to: pos + m.index + m[0].length,
class: 'find',
})
}
})
}
export const replace = (replace, editor) => {
return (state, dispatch) => {
const firstResult = results[0]
if (!firstResult) {
return
}
const { from, to } = results[0]
dispatch(state.tr.insertText(replace, from, to))
editor.commands.find(searchTerm)
}
}
export const rebaseNextResult = (replace, index, lastOffset = 0) => {
const nextIndex = index + 1
if (!results[nextIndex]) {
return null
}
const { from: currentFrom, to: currentTo } = results[index]
const offset = currentTo - currentFrom - replace.length + lastOffset
const { from, to } = results[nextIndex]
results[nextIndex] = {
to: to - offset,
from: from - offset,
class: 'find',
}
return offset
}
export const replaceAll = (replace, editor) => {
return ({ tr }, dispatch) => {
let offset: number | null | undefined
if (!results.length) {
return
}
results.forEach(({ from, to }, index) => {
tr.insertText(replace, from, to)
offset = rebaseNextResult(replace, index, offset || 0)
})
dispatch(tr)
editor.commands.find(searchTerm)
}
}
export const find = (searchTerm) => {
return (state: { tr: any }, dispatch: any) => {
searchTerm = disableRegex ? searchTerm.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') : searchTerm
updateView(state, dispatch)
}
}
export const clear = () => {
return (state, dispatch) => {
searchTerm = null
updateView(state, dispatch)
}
}
export const updateView = ({ tr }, dispatch) => {
_updating = true
dispatch(tr)
_updating = false
}
export const createDeco = (doc) => {
_search(doc)
return decorations ? DecorationSet.create(doc, decorations) : []
}
export const Search = Extension.create<SearchOptions>({
name: 'search',
defaultOptions: {
autoSelectNext: true,
findClass: 'find',
searching: false,
caseSensitive: false,
disableRegex: true,
alwaysSearch: false,
},
addCommands() {
return {
find: (searchTerm) => () => {
find(searchTerm)
return false
},
replace:
(term) =>
({ editor }) => {
replace(term, editor)
return false
},
replaceAll:
(term) =>
({ editor }) => {
replaceAll(term, editor)
return false
},
clearSearch: () => () => {
clear()
return false
},
}
},
addProseMirrorPlugins() {
return [
new Plugin({
key: new PluginKey('searchNReplace'),
state: {
init() {
return DecorationSet.empty
},
apply(tr, value, oldState) {
if (_updating || searching || (tr.docChanged && alwaysSearch)) {
return createDeco(tr.doc)
}
if (tr.docChanged) {
// todo: take care of this thing, I don't understand what this is
// return oldState.map(tr.mapping, tr.doc);
}
return oldState
},
},
props: {
decorations(state) {
return this.getState(state)
},
},
}),
]
},
}) |
Upvote |
Required too! |
Upvote |
@sereneinserenade I also thought of migrating it myself, but didn't find the time. Based on your mentioned lines, you have troubles to set some options (default options), correct? You might have a look at https://github.com/ueberdosis/tiptap/blob/main/packages/extension-text-align/src/text-align.ts And there you find a good example how you can set up (default) options, like: defaultOptions: {
types: [],
alignments: ['left', 'center', 'right', 'justify'],
defaultAlignment: 'left',
}, So in the case of your mentioned lines, it would be then easy as: defaultOptions: {
results : [],
searchTerm : null,
_updating: false,
}, I didn't test it myself, just saw your comment and that might help you. If that does the job ;-) and the extension work, do not forget the pull request. |
I'm also interested by a v2 version of the "search" extension. I tried to migrate the v1 version to v2 using the I guess there is more to change than just migrating things to the v2 syntax. |
@sereneinserenade Is there any news on this? A much needed feature! |
no news, but I am planning to work on it this weekend, see what comes up. Might try @rezaffm 's suggestion and see what emerges. Will update here definitely. |
So... I've been trying and it seems like it works. Right now it's a mess that no one would be able to use with lowest effort. But will publish after clearing up. Bildschirmaufnahme.2021-10-22.um.21.03.42.mov |
Wohow! Great news, not sure if my comment helped at all, but it is great to see you got something working. I am quite sure the tiptap guys will appreciate it when you push it. |
Hey @rezaffm yess, I implemented it based on your comment. Here's the PR #2075 . and here's my repo of Running version of search and replace in vue 3 https://github.com/sereneinserenade/tiptap-search-n-replace-demo which you can test at https://tiptap-search-n-replace-demo.vercel.app/ |
Tried the example and seams that looks great! |
@sereneinserenade , found a bug, could not match first world of the start line. maybe the regex expression has sth wrong. |
@fyeeme I'll take a look but I am not a regex master, I copied the regex from https://github.dev/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/Search.js If you or anyone looking this have an idea how to fix it, let me know. |
@sereneinserenade , Yeah, thanks a lot. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Hi guys,
I noticed you have the search and replace feature for tiptap v1.
Actually, I have the impression it is a must-have for every editor, so was wondering when you plan to bring it to v2?
Thank You.
The text was updated successfully, but these errors were encountered: