Skip to content

Commit 4f646c7

Browse files
committed
[wip] fix(NcRichText): escape 'ordered list'-like syntax where not intended
Signed-off-by: Maksim Sukharev <antreesy.web@gmail.com>
1 parent cd84ecd commit 4f646c7

File tree

1 file changed

+37
-246
lines changed

1 file changed

+37
-246
lines changed

src/components/NcRichText/NcRichText.vue

Lines changed: 37 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,24 @@ This component displays rich text with optional autolink or [Markdown support](h
2525
export default {
2626
data() {
2727
return {
28-
text: `## Hello everyone 🎉
29-
The file {file} was added by {username}. Visit https://nextcloud.com to check it!
28+
text: `3. one
29+
4. three
30+
5. five
31+
6. eleven
3032

31-
Some examples for markdown syntax:
32-
1. **bold text**
33-
2. _italic text_
34-
3. example of \`inline code\`
33+
fsdf
3534

36-
> blockquote example
37-
`,
35+
2. one
36+
4. three
37+
7. five
38+
11. eleven
39+
40+
afsfd
41+
42+
3. one
43+
4. three
44+
5. five
45+
6. eleven`,
3846
autolink: true,
3947
useMarkdown: true,
4048
args: {
@@ -62,243 +70,6 @@ textarea {
6270
</style>
6371
```
6472

65-
### Flavored Markdown
66-
67-
This component can support [Github Flavored Markdown](https://github.github.com/gfm/).
68-
It adds such elements, as tables, task lists, strikethrough, and supports code syntax highlighting and autolinks by default
69-
70-
It is also possible to make a rendered content interactive and listen for events
71-
72-
```vue
73-
<template>
74-
<div>
75-
<textarea v-model="text" />
76-
77-
<NcRichText :text="text"
78-
:use-extended-markdown="true"
79-
:interactive="true"
80-
@interact-todo="handleInteraction"/>
81-
</div>
82-
</template>
83-
<script>
84-
export default {
85-
data() {
86-
return {
87-
text: `## Try flavored markdown right now!
88-
89-
~~strikethrough~~
90-
91-
- [ ] task to be done
92-
- [x] task completed
93-
94-
Table header | Column A | Column B
95-
-- | -- | --
96-
Table row | value A | value B
97-
98-
---
99-
100-
\`\`\`js
101-
const createElementId = (length) => {
102-
\treturn Math.random()
103-
\t\t.toString(36)
104-
\t\t.replace(/[^a-z]+/g, '')
105-
\t\t.slice(0, length || 5)
106-
}
107-
\`\`\`
108-
`,
109-
}
110-
},
111-
methods: {
112-
handleInteraction(id) {
113-
const parentId = id.split('-markdown-input-')[0]
114-
const index = Array.from(document.querySelectorAll(`span[id^="${parentId}-markdown-input-"]`)).findIndex((el) => el.id.includes(id))
115-
if (index === -1 ) {
116-
return
117-
}
118-
let checkBoxIndex = 0
119-
let lines = this.text.split('\n')
120-
for (let i = 0; i < lines.length; i++) {
121-
if (lines[i].includes('[ ]') || lines[i].includes('[x]')) {
122-
if (checkBoxIndex === index) {
123-
const isChecked = lines[i].includes('[x]')
124-
if (isChecked) {
125-
lines[i] = lines[i].replace('[x]', '[ ]')
126-
} else {
127-
lines[i] = lines[i].replace('[ ]', '[x]')
128-
}
129-
break
130-
}
131-
checkBoxIndex++
132-
}
133-
}
134-
this.text = lines.join('\n')
135-
},
136-
},
137-
}
138-
</script>
139-
<style lang="scss">
140-
textarea {
141-
width: 100%;
142-
height: 200px;
143-
}
144-
</style>
145-
```
146-
147-
### Usage with NcRichContenteditable
148-
149-
See [NcRichContenteditable](#/Components/NcRichContenteditable) documentation for more information
150-
151-
```vue
152-
<template>
153-
<div>
154-
<NcRichContenteditable v-model="message"
155-
:auto-complete="autoComplete"
156-
:maxlength="100"
157-
:user-data="userData"
158-
placeholder="Try mentioning user @Test01 or inserting emoji :smile"
159-
@submit="onSubmit" />
160-
161-
<NcCheckboxRadioSwitch v-model="autolink" type="checkbox">Autolink</NcCheckboxRadioSwitch>
162-
<NcCheckboxRadioSwitch v-model="useMarkdown" type="checkbox">Use Markdown</NcCheckboxRadioSwitch>
163-
<NcCheckboxRadioSwitch v-model="useExtendedMarkdown" type="checkbox">Use extended Markdown</NcCheckboxRadioSwitch>
164-
165-
<NcRichText :text="text"
166-
:autolink="autolink"
167-
:arguments="userMentions"
168-
:use-markdown="useMarkdown"
169-
:use-extended-markdown="useExtendedMarkdown" />
170-
</div>
171-
</template>
172-
<script>
173-
export default {
174-
data() {
175-
return {
176-
message: '',
177-
autolink: true,
178-
useMarkdown: true,
179-
useExtendedMarkdown: true,
180-
userData: {
181-
Test01: {
182-
icon: 'icon-user',
183-
id: 'Test01',
184-
label: 'Test01',
185-
source: 'users',
186-
primary: true,
187-
},
188-
Test02: {
189-
icon: 'icon-user',
190-
id: 'Test02',
191-
label: 'Test02',
192-
source: 'users',
193-
status: {
194-
clearAt: null,
195-
icon: '🎡',
196-
message: 'Visiting London',
197-
status: 'away',
198-
},
199-
subline: 'Visiting London',
200-
},
201-
'Test@User': {
202-
icon: 'icon-user',
203-
id: 'Test@User',
204-
label: 'Test 03',
205-
source: 'users',
206-
status: {
207-
clearAt: null,
208-
icon: '🎡',
209-
message: 'Having space in my name',
210-
status: 'online',
211-
},
212-
subline: 'Visiting London',
213-
},
214-
'Test Offline': {
215-
icon: 'icon-user',
216-
id: 'Test Offline',
217-
label: 'Test Offline',
218-
source: 'users',
219-
status: {
220-
clearAt: null,
221-
icon: null,
222-
message: null,
223-
status: 'offline',
224-
},
225-
subline: null,
226-
},
227-
'Test DND': {
228-
icon: 'icon-user',
229-
id: 'Test DND',
230-
label: 'Test DND',
231-
source: 'users',
232-
status: {
233-
clearAt: null,
234-
icon: null,
235-
message: 'Out sick',
236-
status: 'dnd',
237-
},
238-
subline: 'Out sick',
239-
},
240-
},
241-
userMentions: {
242-
'user-1': {
243-
component: 'NcUserBubble',
244-
props: {
245-
displayName: 'Test01',
246-
user: 'Test01',
247-
primary: true,
248-
},
249-
},
250-
'user-2': {
251-
component: 'NcUserBubble',
252-
props: {
253-
displayName: 'Test02',
254-
user: 'Test02',
255-
},
256-
},
257-
'user-3': {
258-
component: 'NcUserBubble',
259-
props: {
260-
displayName: 'Test 03',
261-
user: 'Test@User',
262-
},
263-
},
264-
'user-4': {
265-
component: 'NcUserBubble',
266-
props: {
267-
displayName: 'Test Offline',
268-
user: 'Test Offline',
269-
},
270-
},
271-
'user-5': {
272-
component: 'NcUserBubble',
273-
props: {
274-
displayName: 'Test DND',
275-
user: 'Test DND',
276-
},
277-
},
278-
},
279-
}
280-
},
281-
computed: {
282-
text() {
283-
return this.message
284-
.replace('@Test01', '{user-1}')
285-
.replace('@Test02', '{user-2}')
286-
.replace('@Test@User', '{user-3}')
287-
.replace('@"Test Offline"', '{user-4}')
288-
.replace('@"Test DND"', '{user-5}')
289-
},
290-
},
291-
methods: {
292-
autoComplete(search, callback) {
293-
callback(Object.values(this.userData))
294-
},
295-
onSubmit() {
296-
alert(this.message)
297-
}
298-
}
299-
}
300-
</script>
301-
```
30273
</docs>
30374

30475
<script>
@@ -491,7 +262,25 @@ export default {
491262
// escape special symbol "<" to not treat text as HTML
492263
.replace(/<[^>]+>/g, (match) => match.replace(/</g, '&lt;'))
493264
// unescape special symbol ">" to parse blockquotes
494-
.replace(/&gt;/gmi, '>'))
265+
.replace(/&gt;/gmi, '>')
266+
// ordered list
267+
.replace(/\b(\d+)\.\s/g, (match, p1, offset, string) => {
268+
const following = string.slice(offset + match.length)
269+
const nextMatch = following.match(/\b(\d+)\.\s/)?.[1]
270+
271+
if (!nextMatch) {
272+
return match
273+
}
274+
// If the next number is not +1, we fix the current number to be +1 of the previous one
275+
// This ensures that lists are always rendered correctly, even if the input is wrong
276+
console.log(parseInt(p1, 10) + 1, parseInt(nextMatch, 10), parseInt(p1, 10) + 1 !== parseInt(nextMatch, 10))
277+
if (parseInt(p1, 10) + 1 !== parseInt(nextMatch, 10)) {
278+
console.log('fixing |', p1 + '\\. ')
279+
return p1 + '\\. '
280+
}
281+
return match
282+
})
283+
)
495284
.result
496285
497286
return h('div', { class: 'rich-text--wrapper rich-text--wrapper-markdown' }, [
@@ -537,6 +326,8 @@ export default {
537326
},
538327
539328
createElement(type, props, key) {
329+
console.log(type, props, key)
330+
540331
// Modified code from vue/jsx-runtime
541332
if (key) {
542333
props.key = key

0 commit comments

Comments
 (0)