Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search and Replace #2075

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 15 additions & 0 deletions demos/src/Extensions/SearchNReplace/React/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<div id="app"></div>
<script type="module">
import setup from '../../../../setup/react.ts'
import source from '@source'
setup('Extensions/SearchNReplace', source)
</script>
</body>
</html>
64 changes: 64 additions & 0 deletions demos/src/Extensions/SearchNReplace/React/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import React from 'react'
import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import SearchNReplace from '@tiptap/extension-search-n-replace'
import './styles.scss'

export default () => {
const editor = useEditor({
extensions: [StarterKit, SearchNReplace],
content: "<p>est for search and replace. search for 'amazing' and replace it with 'awe-inspiring'.</p><p></p><p><strong>tiptap is awesome</strong></p><p><strong>prosemirror is awesome</strong></p><p><strong>vue is awesome</strong></p><p><strong>react is awesome</strong></p>",
})

let searchTerm = ''

let replaceTerm = ''

const updateSearchTerm = (e = '') => {
const val = e ? e.target.value : e
searchTerm = val
editor.commands.setSearchTerm(val)
}

const updateReplaceTerm = (e = '') => {
const val = e ? e.target.value : e
replaceTerm = val
editor.commands.setReplaceTerm(val)
}

const clear = () => {
searchTerm = ''
replaceTerm = ''

updateSearchTerm()
updateReplaceTerm()
}

const replace = () => editor.commands.replace()

const replaceAll = () => editor.commands.replaceAll()

return (
<>
{editor && <section className="menubar">
<input type="text" placeholder="Search term..." onInput={updateSearchTerm} />

<input type="text" placeholder="Replace term..." onInput={updateReplaceTerm} onKeyPress={e => e.key === 'Enter' && replace()}/>

<button onClick={() => clear()} >
Clear
</button>

<button onClick={() => replace()} >
Replace
</button>

<button onClick={() => replaceAll()}>
Replace All
</button>
</section>}
<EditorContent editor={editor} />
</>
)
}
38 changes: 38 additions & 0 deletions demos/src/Extensions/SearchNReplace/React/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
.ProseMirror {
> * + * {
margin-top: 0.75em;
}

ul,
ol {
padding: 0 1rem;
}
}

.search-result {
background: rgb(255, 217, 0);
}

.menubar {
display: flex;
gap: 1em;
background: rgba(128, 128, 128, 0.25);
padding: 0.5em;
border-radius: 6px;
justify-content: center;

input {
height: 2em;
border-radius: 6px;
border: none;
outline: none;
padding: 0.4em;
}

button {
border-radius: 6px;
border: none;
cursor: pointer;
background: white;
}
}
15 changes: 15 additions & 0 deletions demos/src/Extensions/SearchNReplace/Vue/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<div id="app"></div>
<script type="module">
import setup from '../../../../setup/vue.ts'
import source from '@source'
setup('Extensions/SearchNReplace', source)
</script>
</body>
</html>
141 changes: 141 additions & 0 deletions demos/src/Extensions/SearchNReplace/Vue/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<template>
<div class="search-n-replace">
<section class="menubar">
<input type="text" v-model="searchTerm" placeholder="Search term...">

<input
@keypress.enter="replace"
type="text"
v-model="replaceTerm"
placeholder="Replace term..."
>

<button @click="clear">
Clear
</button>
<button @click="replace">
Replace
</button>
<button @click="replaceAll">
Replace All
</button>
</section>
<editor-content :editor="editor" />
</div>
</template>

<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import SearchNReplace from '@tiptap/extension-search-n-replace'

export default {
components: {
EditorContent,
},

data() {
return {
editor: null,
searchTerm: '',
replaceTerm: '',
}
},

mounted() {
this.editor = new Editor({
extensions: [StarterKit, SearchNReplace],
content: "<p>est for search and replace. search for 'amazing' and replace it with 'awe-inspiring'.</p><p></p><p><strong>tiptap is awesome</strong></p><p><strong>prosemirror is awesome</strong></p><p><strong>vue is awesome</strong></p><p><strong>react is awesome</strong></p>",
})

this.updateSearchAndReplaceTerms()
},

methods: {
updateSearchAndReplaceTerms() {
this.editor.commands.setSearchTerm(this.searchTerm.trim())
this.editor.commands.setReplaceTerm(this.replaceTerm.trim())
},
replace() {
this.editor.commands.replace()
},
clear() {
this.searchTerm = ''
this.replaceTerm = ''
},
replaceAll() {
this.editor.commands.replaceAll()
},
},

watch: {
searchTerm(val, oldVal) {
if (val === oldVal) return
this.updateSearchAndReplaceTerms()
},
replaceTerm(val, oldVal) {
if (val === oldVal) return
this.updateSearchAndReplaceTerms()
},
},

beforeUnmount() {
this.editor.destroy()
},
}
</script>

<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}

/* Placeholder (at the top) */
.ProseMirror p.is-editor-empty:first-child::before {
content: attr(data-placeholder);
float: left;
color: #adb5bd;
pointer-events: none;
height: 0;
}

/* Placeholder (on every new line) */
/*.ProseMirror p.is-empty::before {
content: attr(data-placeholder);
float: left;
color: #adb5bd;
pointer-events: none;
height: 0;
}*/

.search-result {
background: rgb(255, 217, 0);
}

.menubar {
display: flex;
gap: 1em;
background: rgba(128, 128, 128, 0.25);
padding: 0.5em;
border-radius: 6px;
justify-content: center;

input {
height: 2em;
border-radius: 6px;
border: none;
outline: none;
padding: 0.4em;
}

button {
border-radius: 6px;
border: none;
cursor: pointer;
background: white;
}
}
</style>
68 changes: 68 additions & 0 deletions docs/api/extensions/search-n-replace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
description: Search and Replace
icon: stack-line
---

# SearchNReplace

The `SearchNReplace` is extension for search and replace for tiptap v2.

## Installation

Until the package is published on NPM, you have to implement it from search-n-replace.ts

## Commands

### setSearchTerm()
Set search term.

```js
editor.commands.setSearchTerm(searchTerm)
```

### setReplaceTerm()
Set replace term.

```js
editor.commands.setReplaceTerm(replaceTerm)
```

### replace()
Replace first instance of search results with `replaceTerm`.

```js
editor.commands.replace()
```

### replaceAll()
Replace all instance of search results with `replaceTerm`.

```js
editor.commands.replaceAll()
```

## Source code

[packages/extension-search-n-replace/](https://github.com/ueberdosis/tiptap/blob/main/packages/extension-search-n-replace/)

## Usage

Pass 'SearchNReplace' to the editor extensions.
You can configure which class should be added to the search results that are found and whether the search should be case-sensitive
and whether regex search should be enabled or not.

```js
import { Editor } from "@tiptap/core";
import SearchNReplace from "./path/to/search-n-replace.ts/";

const editor = new Editor({
content: "<p>Example Text</p>",
extensions: [
SearchNReplace.configure({
searchResultClass: "search-result", // class to give to found items. default 'search-result'
caseSensitive: false, // no need to explain
disableRegex: false, // also no need to explain
}),
],
});
```
36 changes: 36 additions & 0 deletions packages/extension-search-n-replace/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "@tiptap/extension-search-n-replace",
"description": "search and replace extension for tiptap",
"version": "2.0.0-beta.35",
"homepage": "https://tiptap.dev",
"keywords": [
"tiptap",
"tiptap extension"
],
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/ueberdosis"
},
"main": "dist/tiptap-extension-search-n-replace.cjs.js",
"umd": "dist/tiptap-extension-search-n-replace.umd.js",
"module": "dist/tiptap-extension-search-n-replace.esm.js",
"types": "dist/packages/extension-search-n-replace/src/index.d.ts",
"files": [
"src",
"dist"
],
"peerDependencies": {
"@tiptap/core": "^2.0.0-beta.1"
},
"dependencies": {
"prosemirror-model": "^1.14.3",
"prosemirror-state": "^1.3.4",
"prosemirror-view": "^1.20.3"
},
"repository": {
"type": "git",
"url": "https://github.com/ueberdosis/tiptap",
"directory": "packages/extension-search-n-replace"
}
}
5 changes: 5 additions & 0 deletions packages/extension-search-n-replace/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SearchNReplace } from './search-n-replace'

export * from './search-n-replace'

export default SearchNReplace