@podlite/editor-react 0.0.20 → 0.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.cjs +7 -6
- package/lib/index.cjs.map +1 -1
- package/lib/index.esm.js +7 -6
- package/lib/index.esm.js.map +1 -1
- package/package.json +5 -3
- package/src/index.tsx +0 -372
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@podlite/editor-react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.21",
|
|
4
4
|
"description": "Podlite React component",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "lib/index.cjs",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
7
7
|
"typings": "./lib/index.d.ts",
|
|
8
8
|
"license": "MIT",
|
|
@@ -32,7 +32,9 @@
|
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public",
|
|
35
|
-
"module": "lib/index.esm.js"
|
|
35
|
+
"module": "lib/index.esm.js",
|
|
36
|
+
"main": "lib/index.cjs",
|
|
37
|
+
"types": "lib/index.d.ts"
|
|
36
38
|
},
|
|
37
39
|
"scripts": {
|
|
38
40
|
"clean": "rm -rf dist lib tsconfig.tsbuildinfo esm",
|
package/src/index.tsx
DELETED
|
@@ -1,372 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Controlled as CodeMirrorControlled, UnControlled as CodeMirror } from 'react-codemirror2'
|
|
3
|
-
import CMirror from 'codemirror'
|
|
4
|
-
import dictionary from './dict'
|
|
5
|
-
import { useState, useEffect, useRef } from 'react'
|
|
6
|
-
import { isElement } from 'react-is'
|
|
7
|
-
|
|
8
|
-
// TODO: use bundler to add into package
|
|
9
|
-
// import '../../../node_modules/codemirror/lib/codemirror.css';
|
|
10
|
-
import 'codemirror/mode/gfm/gfm'
|
|
11
|
-
import 'codemirror/addon/hint/show-hint'
|
|
12
|
-
import 'codemirror/addon/hint/show-hint.css'
|
|
13
|
-
import './Editor.css'
|
|
14
|
-
import { addVMargin, getSuggestionContextForLine, templateGetSelectionPos } from './helpers'
|
|
15
|
-
|
|
16
|
-
//@ts-ignore
|
|
17
|
-
function useDebouncedEffect(fn, deps, time) {
|
|
18
|
-
const dependencies = [...deps, time]
|
|
19
|
-
useEffect(() => {
|
|
20
|
-
const timeout = setTimeout(fn, time)
|
|
21
|
-
return () => {
|
|
22
|
-
clearTimeout(timeout)
|
|
23
|
-
}
|
|
24
|
-
}, dependencies)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/* set window title */
|
|
28
|
-
// @ts-ignore
|
|
29
|
-
// const setWindowTitle = (title: string) => { vmd.setWindowTitle(title) }
|
|
30
|
-
export interface ConverterResult {
|
|
31
|
-
errors?: any
|
|
32
|
-
result: any
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
let instanceCM = null
|
|
36
|
-
type Props = {
|
|
37
|
-
content: string
|
|
38
|
-
onChangeSource: Function
|
|
39
|
-
sourceType?: 'pod6' | 'md'
|
|
40
|
-
onConvertSource: (source: string) => ConverterResult
|
|
41
|
-
onSavePressed?: Function
|
|
42
|
-
isDarkTheme?: boolean
|
|
43
|
-
isLineNumbers?: boolean
|
|
44
|
-
isAutoComplete?: boolean
|
|
45
|
-
isPreviewModeEnabled?: boolean
|
|
46
|
-
isControlled?: boolean
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const Editor = ({
|
|
50
|
-
onChangeSource = () => {},
|
|
51
|
-
content,
|
|
52
|
-
isDarkTheme = false,
|
|
53
|
-
isLineNumbers = false,
|
|
54
|
-
isPreviewModeEnabled = false,
|
|
55
|
-
onConvertSource,
|
|
56
|
-
onSavePressed = () => {},
|
|
57
|
-
sourceType = 'pod6',
|
|
58
|
-
isControlled = false,
|
|
59
|
-
isAutoComplete = true,
|
|
60
|
-
}: Props) => {
|
|
61
|
-
const [text, updateText] = useState(content)
|
|
62
|
-
|
|
63
|
-
const [marks, updateMarks] = useState([])
|
|
64
|
-
const [, updateScrollMap] = useState([])
|
|
65
|
-
|
|
66
|
-
const [isPreviewMode, setPreviewMode] = useState(isPreviewModeEnabled)
|
|
67
|
-
|
|
68
|
-
const [isPreviewScroll, setPreviewScrolling] = useState(false)
|
|
69
|
-
const refValue = useRef(isPreviewScroll)
|
|
70
|
-
const [showTree, setShowTree] = useState(false)
|
|
71
|
-
|
|
72
|
-
const [filePath, setFilePath] = useState('')
|
|
73
|
-
const [fileName, setFileName] = useState('')
|
|
74
|
-
const [fileExt, setFileExt] = useState('')
|
|
75
|
-
const [isChanged, setChanged] = useState(false)
|
|
76
|
-
|
|
77
|
-
const [fileLoading, setFileLoading] = useState(true)
|
|
78
|
-
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
updateText(content)
|
|
81
|
-
}, [content])
|
|
82
|
-
|
|
83
|
-
const [result, updateResult] = useState<ConverterResult>()
|
|
84
|
-
useDebouncedEffect(
|
|
85
|
-
() => {
|
|
86
|
-
updateResult(onConvertSource(text))
|
|
87
|
-
},
|
|
88
|
-
[text],
|
|
89
|
-
50,
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
const inputEl = useRef(null)
|
|
93
|
-
|
|
94
|
-
// hot keys
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
const saveFileAction = () => {
|
|
97
|
-
if (isChanged) {
|
|
98
|
-
console.warn('Save File')
|
|
99
|
-
onSavePressed(text)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
useEffect(() => {
|
|
105
|
-
refValue.current = isPreviewScroll
|
|
106
|
-
})
|
|
107
|
-
var options: CMirror.EditorConfiguration = {
|
|
108
|
-
lineNumbers: isLineNumbers,
|
|
109
|
-
inputStyle: 'contenteditable',
|
|
110
|
-
//@ts-ignore
|
|
111
|
-
spellcheck: true,
|
|
112
|
-
autofocus: true,
|
|
113
|
-
lineWrapping: true,
|
|
114
|
-
viewportMargin: Infinity,
|
|
115
|
-
mode:
|
|
116
|
-
sourceType !== 'md'
|
|
117
|
-
? null
|
|
118
|
-
: {
|
|
119
|
-
name: 'gfm',
|
|
120
|
-
tokenTypeOverrides: {
|
|
121
|
-
emoji: 'emoji',
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
theme: isDarkTheme ? 'duotone-dark' : 'default',
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const previewEl = useRef(null)
|
|
128
|
-
|
|
129
|
-
useEffect(() => {
|
|
130
|
-
//@ts-ignore
|
|
131
|
-
const newScrollMap = [...document.querySelectorAll('.line-src')].map(n => {
|
|
132
|
-
const line = parseInt(n.getAttribute('data-line'), 10)
|
|
133
|
-
//@ts-ignore
|
|
134
|
-
const offsetTop = n.offsetTop
|
|
135
|
-
return { line, offsetTop }
|
|
136
|
-
})
|
|
137
|
-
//@ts-ignore
|
|
138
|
-
updateScrollMap(newScrollMap)
|
|
139
|
-
//@ts-ignore
|
|
140
|
-
const listener = e => {
|
|
141
|
-
if (!isPreviewScroll) {
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
let element = e.target
|
|
145
|
-
//@ts-ignore
|
|
146
|
-
const getLine = offset => {
|
|
147
|
-
const c = newScrollMap.filter(i => i.offsetTop > offset)
|
|
148
|
-
const lineElement = c.shift() || newScrollMap[newScrollMap.length - 1]
|
|
149
|
-
if (!lineElement) {
|
|
150
|
-
console.warn(`[podlite-editor] can't get line for offset. Forget add .line-src ?`)
|
|
151
|
-
}
|
|
152
|
-
return lineElement.line
|
|
153
|
-
}
|
|
154
|
-
const line = getLine(element.scrollTop)
|
|
155
|
-
if (instanceCM) {
|
|
156
|
-
const t = element.scrollTop === 0 ? 0 : instanceCM.charCoords({ line: line, ch: 0 }, 'local').top
|
|
157
|
-
instanceCM.scrollTo(null, t)
|
|
158
|
-
}
|
|
159
|
-
return true
|
|
160
|
-
}
|
|
161
|
-
if (previewEl && previewEl.current) {
|
|
162
|
-
//@ts-ignore
|
|
163
|
-
previewEl.current.addEventListener('scroll', listener)
|
|
164
|
-
}
|
|
165
|
-
return () => {
|
|
166
|
-
// @ts-ignore
|
|
167
|
-
previewEl && previewEl.current && previewEl && previewEl.current.removeEventListener('scroll', listener)
|
|
168
|
-
}
|
|
169
|
-
}, [text, isPreviewScroll])
|
|
170
|
-
|
|
171
|
-
useEffect(() => {
|
|
172
|
-
//@ts-ignore
|
|
173
|
-
let cm = instanceCM
|
|
174
|
-
if (!cm) {
|
|
175
|
-
return
|
|
176
|
-
}
|
|
177
|
-
//@ts-ignore
|
|
178
|
-
marks.forEach(marker => marker.clear())
|
|
179
|
-
//@ts-ignore
|
|
180
|
-
let cmMrks: Array<never> = []
|
|
181
|
-
//@ts-ignore
|
|
182
|
-
if (result && result.errors) {
|
|
183
|
-
//@ts-ignore
|
|
184
|
-
result.errors.map((loc: any) => {
|
|
185
|
-
// @ts-ignore
|
|
186
|
-
let from = { line: loc.start.line - 1, ch: loc.start.column - 1 - (loc.start.offset === loc.end.offset) }
|
|
187
|
-
let to = { line: loc.end.line - 1, ch: loc.end.column - 1 }
|
|
188
|
-
|
|
189
|
-
cmMrks.push(
|
|
190
|
-
//@ts-ignore
|
|
191
|
-
cm.markText(from, to, {
|
|
192
|
-
className: 'syntax-error',
|
|
193
|
-
title: ';data.error.message',
|
|
194
|
-
css: 'color : red',
|
|
195
|
-
}),
|
|
196
|
-
)
|
|
197
|
-
})
|
|
198
|
-
}
|
|
199
|
-
updateMarks(cmMrks)
|
|
200
|
-
}, [text, result])
|
|
201
|
-
|
|
202
|
-
const previewHtml = (
|
|
203
|
-
<div
|
|
204
|
-
className={'Editorright ' + (isDarkTheme ? 'dark' : '')}
|
|
205
|
-
onMouseEnter={() => setPreviewScrolling(true)}
|
|
206
|
-
onMouseMove={() => setPreviewScrolling(true)}
|
|
207
|
-
ref={previewEl}
|
|
208
|
-
>
|
|
209
|
-
{result ? (
|
|
210
|
-
isElement(result.result) ? (
|
|
211
|
-
<div className="content">{result.result}</div>
|
|
212
|
-
) : (
|
|
213
|
-
<div dangerouslySetInnerHTML={{ __html: result.result }} className="content"></div>
|
|
214
|
-
)
|
|
215
|
-
) : (
|
|
216
|
-
''
|
|
217
|
-
)}
|
|
218
|
-
</div>
|
|
219
|
-
)
|
|
220
|
-
//@ts-ignore
|
|
221
|
-
const scrollEditorHandler = editor => {
|
|
222
|
-
if (refValue.current) {
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
|
-
let scrollInfo = editor.getScrollInfo()
|
|
226
|
-
// get line number of the top line in the page
|
|
227
|
-
let lineNumber = editor.lineAtHeight(scrollInfo.top, 'local') + 1
|
|
228
|
-
if (previewEl) {
|
|
229
|
-
const el = previewEl.current
|
|
230
|
-
const elementId = `#line-${lineNumber}`
|
|
231
|
-
const scrollToElement = document.querySelector(elementId)
|
|
232
|
-
if (scrollToElement) {
|
|
233
|
-
//@ts-ignore
|
|
234
|
-
const scrollTo = scrollToElement.offsetTop
|
|
235
|
-
//@ts-ignore
|
|
236
|
-
el.scrollTo({
|
|
237
|
-
top: scrollTo,
|
|
238
|
-
left: 0,
|
|
239
|
-
behavior: 'smooth',
|
|
240
|
-
})
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
const [instanceCMLocal, updateInstanceCM] = useState<any>()
|
|
245
|
-
|
|
246
|
-
useEffect(() => {
|
|
247
|
-
if (!instanceCMLocal) return
|
|
248
|
-
if (!isAutoComplete) return
|
|
249
|
-
var onChange = function (instance, object) {
|
|
250
|
-
// Check if the last inserted character is `=`.
|
|
251
|
-
if (
|
|
252
|
-
object.text[0] === '=' &&
|
|
253
|
-
// start directive
|
|
254
|
-
instance.getRange({ ch: 0, line: object.to.line }, object.to).match(/^\s*$/)
|
|
255
|
-
) {
|
|
256
|
-
CMirror.showHint(instanceCMLocal, CMirror.hint.dictionaryHint)
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
instanceCMLocal.on('change', onChange)
|
|
260
|
-
|
|
261
|
-
CMirror.registerHelper('hint', 'dictionaryHint', function (editor) {
|
|
262
|
-
var cur = editor.getCursor()
|
|
263
|
-
var curLine = editor.getLine(cur.line)
|
|
264
|
-
var start = cur.ch
|
|
265
|
-
var end = start
|
|
266
|
-
while (end < curLine.length && /[\w$]/.test(curLine.charAt(end))) ++end
|
|
267
|
-
while (start && /[^=]/.test(curLine.charAt(start - 1))) --start
|
|
268
|
-
var curWord = start !== end && curLine.slice(start, end)
|
|
269
|
-
var regex = new RegExp('' + curWord, 'i')
|
|
270
|
-
// filter dict by regex and sort by mostly nearness
|
|
271
|
-
const filterDictByRegex = (arr, regex) => {
|
|
272
|
-
const dict = arr.reduce((acc, item) => {
|
|
273
|
-
if (item === null) {
|
|
274
|
-
return acc
|
|
275
|
-
}
|
|
276
|
-
const result =
|
|
277
|
-
typeof item === 'object' && !Array.isArray(item) ? item.displayText.match(regex) : item.match(regex)
|
|
278
|
-
if (result) {
|
|
279
|
-
acc.push({ item, index: result.index })
|
|
280
|
-
}
|
|
281
|
-
return acc
|
|
282
|
-
}, [])
|
|
283
|
-
return dict.sort((a, b) => a.index - b.index).map(i => i.item)
|
|
284
|
-
}
|
|
285
|
-
const langMode = sourceType === 'md' ? 'md' : getSuggestionContextForLine(editor.getValue(), cur.line + 1)
|
|
286
|
-
const langDict = dictionary.filter(({ lang = 'pod6' }) => lang === langMode)
|
|
287
|
-
// apply hint
|
|
288
|
-
const resultDict = (!curWord ? langDict : filterDictByRegex(langDict, regex)).map(item => {
|
|
289
|
-
return {
|
|
290
|
-
...item,
|
|
291
|
-
hint: function (cm, data, completion) {
|
|
292
|
-
const from = completion.from || data.from
|
|
293
|
-
const to = completion.to || data.to
|
|
294
|
-
// add vMargin
|
|
295
|
-
const text = addVMargin(from.ch, typeof completion == 'string' ? completion : completion.text)
|
|
296
|
-
const selFromTemplate = templateGetSelectionPos(text)
|
|
297
|
-
console.log({ from, to, text })
|
|
298
|
-
if (selFromTemplate) {
|
|
299
|
-
const { text, start, end } = selFromTemplate
|
|
300
|
-
cm.replaceRange(text, from, to, 'complete')
|
|
301
|
-
cm.setSelection(
|
|
302
|
-
{ line: start.line + from.line, ch: start.offset + from.ch },
|
|
303
|
-
{ line: end.line + to.line, ch: end.offset + to.ch - 1 },
|
|
304
|
-
)
|
|
305
|
-
} else {
|
|
306
|
-
cm.replaceRange(text, from, to, 'complete')
|
|
307
|
-
}
|
|
308
|
-
},
|
|
309
|
-
}
|
|
310
|
-
})
|
|
311
|
-
return {
|
|
312
|
-
list: resultDict,
|
|
313
|
-
from: CMirror.Pos(cur.line, start - 1),
|
|
314
|
-
to: CMirror.Pos(cur.line, end),
|
|
315
|
-
}
|
|
316
|
-
})
|
|
317
|
-
instanceCMLocal.refresh()
|
|
318
|
-
return () => {
|
|
319
|
-
//@ts-ignore
|
|
320
|
-
instanceCMLocal.off('change', onChange)
|
|
321
|
-
}
|
|
322
|
-
}, [instanceCMLocal, isAutoComplete])
|
|
323
|
-
|
|
324
|
-
return (
|
|
325
|
-
<div className="EditorApp">
|
|
326
|
-
<div className={isPreviewModeEnabled ? 'layoutPreview' : 'layout'}>
|
|
327
|
-
<div
|
|
328
|
-
className="Editorleft"
|
|
329
|
-
onMouseEnter={() => setPreviewScrolling(false)}
|
|
330
|
-
onMouseMove={() => setPreviewScrolling(false)}
|
|
331
|
-
>
|
|
332
|
-
{isControlled ? (
|
|
333
|
-
<CodeMirrorControlled
|
|
334
|
-
value={content}
|
|
335
|
-
editorDidMount={editor => {
|
|
336
|
-
instanceCM = editor
|
|
337
|
-
updateInstanceCM(editor)
|
|
338
|
-
}}
|
|
339
|
-
onBeforeChange={(editor, data, value) => {
|
|
340
|
-
setChanged(true)
|
|
341
|
-
// updateText(value);
|
|
342
|
-
onChangeSource(value)
|
|
343
|
-
}}
|
|
344
|
-
onScroll={scrollEditorHandler}
|
|
345
|
-
options={options}
|
|
346
|
-
className="editorApp"
|
|
347
|
-
/>
|
|
348
|
-
) : (
|
|
349
|
-
<CodeMirror
|
|
350
|
-
value={content}
|
|
351
|
-
editorDidMount={editor => {
|
|
352
|
-
instanceCM = editor
|
|
353
|
-
updateInstanceCM(editor)
|
|
354
|
-
}}
|
|
355
|
-
onChange={(editor, data, value) => {
|
|
356
|
-
setChanged(true)
|
|
357
|
-
updateText(value)
|
|
358
|
-
onChangeSource(value)
|
|
359
|
-
}}
|
|
360
|
-
onScroll={scrollEditorHandler}
|
|
361
|
-
options={options}
|
|
362
|
-
className="editorApp"
|
|
363
|
-
/>
|
|
364
|
-
)}
|
|
365
|
-
</div>
|
|
366
|
-
{previewHtml}
|
|
367
|
-
</div>
|
|
368
|
-
</div>
|
|
369
|
-
)
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
export default Editor
|