Skip to content

Commit e278263

Browse files
authored
Merge pull request #7173 from nextcloud-libraries/chore/refactor-nc-richtext
refactor(NcRichText): migrate helpers to Typescript
2 parents f4f2a7f + 0198ce5 commit e278263

File tree

9 files changed

+150
-116
lines changed

9 files changed

+150
-116
lines changed

src/components/NcRichText/NcReferencePicker/utils.js

Lines changed: 0 additions & 32 deletions
This file was deleted.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
let timerId: number | undefined
7+
8+
/**
9+
* @param callback - The callback to call after the delay
10+
* @param ms - The delay in milliseconds
11+
*/
12+
export function delay<P extends Array<unknown>, R>(callback: (...args: P) => R, ms: number): (...args: P) => void {
13+
return (...args) => {
14+
window.clearTimeout(timerId)
15+
timerId = window.setTimeout(() => {
16+
callback(...args)
17+
}, ms)
18+
}
19+
}
20+
21+
/**
22+
* Check wether a given string is a valid URL.
23+
*
24+
* @param str - The potential URL
25+
*/
26+
export function isUrl(str: string): boolean {
27+
try {
28+
return Boolean(new URL(str))
29+
} catch (error) {
30+
return false
31+
}
32+
}

src/components/NcRichText/NcRichText.vue

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,10 +316,10 @@ import rehypeExternalLinks from 'rehype-external-links'
316316
317317
import NcCheckboxRadioSwitch from '../NcCheckboxRadioSwitch/NcCheckboxRadioSwitch.vue'
318318
import NcReferenceList from './NcReferenceList.vue'
319-
import { getRoute, remarkAutolink } from './autolink.ts'
320-
import { remarkPlaceholder, prepareTextNode } from './placeholder.js'
319+
import { getRoute, parseUrl, remarkAutolink } from './autolink.ts'
321320
import { remarkUnescape } from './remarkUnescape.js'
322321
import { createElementId } from '../../utils/createElementId.ts'
322+
import { remarkPlaceholder } from './remarkPlaceholder.ts'
323323
324324
/**
325325
* Protocols allowed in links.
@@ -407,7 +407,7 @@ export default {
407407
const matches = entry.match(/^\{([a-z\-_.0-9]+)\}$/i)
408408
// just return plain string nodes as text
409409
if (!matches) {
410-
return prepareTextNode({ h, context: this }, entry)
410+
return this.prepareTextNode(entry)
411411
}
412412
// return component instance if argument is an object
413413
const argumentId = matches[1]
@@ -492,6 +492,33 @@ export default {
492492
: null,
493493
])
494494
},
495+
496+
/**
497+
* Render plain text nodes
498+
*
499+
* @param {string} text - Content of the node
500+
*/
501+
prepareTextNode(text) {
502+
if (this.autolink) {
503+
text = parseUrl(text)
504+
}
505+
506+
if (Array.isArray(text)) {
507+
return text.map((entry) => {
508+
if (typeof entry === 'string') {
509+
return entry
510+
}
511+
const { component, props } = entry
512+
// do not override class of NcLink
513+
const componentClass = component.name === 'NcLink' ? undefined : 'rich-text--component'
514+
return h(component, {
515+
...props,
516+
class: componentClass,
517+
})
518+
})
519+
}
520+
return text
521+
},
495522
createElement(type, props, key) {
496523
// Modified code from vue/jsx-runtime
497524
if (key) {
Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1-
/**
1+
/*!
22
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6+
import type { Literal } from 'unist'
7+
8+
/**
9+
* Unist literal node with string value like code blocks or text nodes
10+
*/
11+
export interface TextNode extends Literal {
12+
value: string
13+
}
14+
615
/**
716
* Regex pattern to match links to be resolved by the link-reference provider
8-
*
9-
* @type {RegExp}
1017
*/
1118
export const URL_PATTERN = /(\s|^)(https?:\/\/)([-A-Z0-9+_.]+(?::[0-9]+)?(?:\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*)(\s|$)/ig
1219

1320
/**
1421
* Regex pattern to identify strings as links and then making them clickable
15-
*
16-
* @type {RegExp}
1722
*/
1823
export const URL_PATTERN_AUTOLINK = /(\s|\(|^)((https?:\/\/)([-A-Z0-9+_.]+[-A-Z0-9]+(?::[0-9]+)?(?:\/[-A-Z0-9+&@#%?=~_|!:,.;()]*)*))(?=\s|\)|$)/ig
File renamed without changes.

src/components/NcRichText/placeholder.js

Lines changed: 0 additions & 57 deletions
This file was deleted.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type { Plugin, Transformer } from 'unified'
7+
import type { Node, Parent } from 'unist'
8+
import type { TextNode } from './helpers.ts'
9+
10+
import { u } from 'unist-builder'
11+
import { visit } from 'unist-util-visit'
12+
13+
/**
14+
* Check if the given node is a literal and specifically a text node
15+
* @param node - A Unist node
16+
*/
17+
function isTextNode(node: Node): node is TextNode {
18+
return node.type === 'text'
19+
}
20+
21+
const transformPlaceholders: Transformer = function(ast: Node) {
22+
// Apply the visitor to all text nodes of the AST
23+
visit(ast, isTextNode, visitor)
24+
25+
/**
26+
* @param node - The text node
27+
* @param index - The index of the node
28+
* @param parent - The parent node
29+
*/
30+
function visitor(node: TextNode, index?: number, parent?: Parent) {
31+
const placeholders = node.value.split(/(\{[a-z\-_.0-9]+\})/ig)
32+
.map((entry: string) => {
33+
const matches = entry.match(/^\{([a-z\-_.0-9]+)\}$/i)
34+
if (!matches) {
35+
return u('text', entry)
36+
}
37+
const [, component] = matches
38+
return u('element', {
39+
tagName: `#${component}`,
40+
children: [],
41+
})
42+
})
43+
44+
parent!.children.splice(index!, 1, ...placeholders)
45+
}
46+
}
47+
48+
export const remarkPlaceholder: Plugin = () => transformPlaceholders

src/components/NcRichText/remarkUnescape.js

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import type { Plugin } from 'unified'
7+
import type { Node, Parent } from 'unist'
8+
import type { TextNode } from './helpers.ts'
9+
10+
import { visit, SKIP } from 'unist-util-visit'
11+
12+
/**
13+
* Check if the given node is a literal and specifically a text node
14+
* @param node - A Unist node
15+
*/
16+
function isTextNode(node: Node): node is TextNode {
17+
return ['text', 'code', 'inlineCode'].includes(node.type)
18+
}
19+
20+
export const remarkUnescape: Plugin = function() {
21+
return function(tree: Node) {
22+
visit(tree, isTextNode, (node: TextNode, index?: number, parent?: Parent) => {
23+
parent!.children.splice(index!, 1, {
24+
...node,
25+
value: node.value.replace(/&lt;/gmi, '<').replace(/&gt;/gmi, '>'),
26+
} as TextNode)
27+
return [SKIP, index! + 1]
28+
})
29+
}
30+
}

0 commit comments

Comments
 (0)