@esportsplus/ui 0.57.0 → 0.58.0
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/README.md +123 -39
- package/build/components/accordion/group.d.ts +14 -0
- package/build/components/accordion/group.js +21 -0
- package/build/components/accordion/index.d.ts +9 -0
- package/build/components/accordion/index.js +20 -3
- package/build/components/accordion/scss/index.scss +1 -1
- package/build/components/announcement/scss/index.scss +1 -1
- package/build/components/breadcrumb/scss/index.scss +1 -1
- package/build/components/button/scss/index.scss +1 -1
- package/build/components/button/sidebar.d.ts +3 -0
- package/build/components/button/sidebar.js +40 -10
- package/build/components/capslock/index.js +1 -1
- package/build/components/capslock/scss/index.scss +1 -1
- package/build/components/card/scss/index.scss +1 -1
- package/build/components/cc/scss/index.scss +1 -1
- package/build/components/chat-minimap/scss/index.scss +1 -1
- package/build/components/checkbox/scss/index.scss +1 -1
- package/build/components/color-picker/index.d.ts +1 -0
- package/build/components/color-picker/index.js +166 -136
- package/build/components/color-picker/scss/index.scss +1 -1
- package/build/components/command/index.d.ts +0 -1
- package/build/components/command/index.js +3 -2
- package/build/components/command/scss/index.scss +1 -1
- package/build/components/copy/scss/index.scss +1 -1
- package/build/components/counter/scss/index.scss +1 -1
- package/build/components/datalist/index.js +3 -3
- package/build/components/datalist/scss/index.scss +1 -1
- package/build/components/dock/scss/index.scss +1 -1
- package/build/components/editor/code/index.d.ts +0 -1
- package/build/components/editor/code/index.js +0 -1
- package/build/components/editor/code/minimap.d.ts +2 -1
- package/build/components/editor/code/minimap.js +5 -2
- package/build/components/editor/code/view.d.ts +3 -1
- package/build/components/editor/code/view.js +37 -26
- package/build/components/editor/diffs/index.d.ts +0 -1
- package/build/components/editor/diffs/index.js +0 -1
- package/build/components/editor/diffs/merge.js +7 -3
- package/build/components/editor/diffs/view.js +3 -1
- package/build/components/editor/index.d.ts +4 -1
- package/build/components/editor/index.js +2 -1
- package/build/components/editor/markdown/editing.js +22 -3
- package/build/components/editor/markdown/erase.d.ts +9 -0
- package/build/components/editor/markdown/erase.js +78 -0
- package/build/components/editor/markdown/index.d.ts +0 -1
- package/build/components/editor/markdown/index.js +0 -1
- package/build/components/editor/markdown/layout.d.ts +2 -9
- package/build/components/editor/markdown/layout.js +2 -7
- package/build/components/editor/markdown/render.d.ts +0 -1
- package/build/components/editor/markdown/render.js +287 -235
- package/build/components/editor/markdown/view.js +566 -667
- package/build/components/editor/viewer/index.d.ts +27 -0
- package/build/components/editor/viewer/index.js +250 -0
- package/build/components/editor/viewer/model.d.ts +15 -0
- package/build/components/editor/viewer/model.js +73 -0
- package/build/components/editor/workspace/index.d.ts +6 -2
- package/build/components/editor/workspace/index.js +243 -137
- package/build/components/editor/workspace/keybindings.d.ts +37 -0
- package/build/components/editor/workspace/keybindings.js +288 -0
- package/build/components/editor/workspace/model.d.ts +55 -2
- package/build/components/editor/workspace/model.js +141 -3
- package/build/components/editor/workspace/navigate.d.ts +19 -0
- package/build/components/editor/workspace/navigate.js +118 -0
- package/build/components/editor/workspace/session.d.ts +29 -0
- package/build/components/editor/workspace/session.js +373 -0
- package/build/components/editor/workspace/shortcuts.d.ts +19 -0
- package/build/components/editor/workspace/shortcuts.js +185 -0
- package/build/components/editor/workspace/status.d.ts +34 -0
- package/build/components/editor/workspace/status.js +193 -0
- package/build/components/editor/workspace/statusbar.d.ts +15 -0
- package/build/components/editor/workspace/statusbar.js +190 -0
- package/build/components/editor/workspace/strip.d.ts +27 -0
- package/build/components/editor/workspace/strip.js +239 -0
- package/build/components/editor/workspace/tabs.d.ts +15 -0
- package/build/components/editor/workspace/tabs.js +63 -0
- package/build/components/error/index.js +1 -1
- package/build/components/error/scss/index.scss +1 -1
- package/build/components/filter/index.js +5 -3
- package/build/components/filter/scss/index.scss +1 -1
- package/build/components/frame/scss/index.scss +1 -1
- package/build/components/heatmap/index.js +3 -3
- package/build/components/heatmap/scss/index.scss +1 -1
- package/build/components/highlight/scss/index.scss +1 -1
- package/build/components/icon/scss/index.scss +1 -1
- package/build/components/image/scss/index.scss +1 -1
- package/build/components/index.d.ts +2 -0
- package/build/components/index.js +2 -0
- package/build/components/inline-edit/scss/index.scss +1 -1
- package/build/components/input/scss/index.scss +1 -1
- package/build/components/link/scss/index.scss +1 -1
- package/build/components/loading/scss/index.scss +1 -1
- package/build/components/marquee/scss/index.scss +1 -1
- package/build/components/metric/scss/index.scss +1 -1
- package/build/components/nav/index.d.ts +1 -1
- package/build/components/nav/scss/index.scss +1 -1
- package/build/components/nav/tree.d.ts +3 -1
- package/build/components/nav/tree.js +30 -60
- package/build/components/notification/scss/index.scss +1 -1
- package/build/components/overlay/scss/index.scss +1 -1
- package/build/components/page/scss/index.scss +1 -1
- package/build/components/pagination/arrows.d.ts +15 -0
- package/build/components/pagination/arrows.js +65 -0
- package/build/components/pagination/cards.d.ts +23 -0
- package/build/components/pagination/cards.js +66 -0
- package/build/components/pagination/index.d.ts +5 -0
- package/build/components/pagination/index.js +3 -1
- package/build/components/pagination/scss/index.scss +1 -1
- package/build/components/progress/scss/index.scss +1 -1
- package/build/components/pull-to-refresh/scss/index.scss +1 -1
- package/build/components/range/index.js +1 -1
- package/build/components/range/scss/index.scss +1 -1
- package/build/components/relative-time/scss/index.scss +1 -1
- package/build/components/root/scss/index.scss +1 -1
- package/build/components/section/scss/index.scss +2 -0
- package/build/components/select/index.d.ts +1 -0
- package/build/components/select/index.js +16 -11
- package/build/components/select/scss/index.scss +1 -1
- package/build/components/shortcut-recorder/scss/index.scss +1 -1
- package/build/components/sortable/scss/index.scss +1 -1
- package/build/components/sticky-header/index.js +3 -3
- package/build/components/sticky-header/scss/index.scss +1 -1
- package/build/components/story/scss/index.scss +1 -1
- package/build/components/surface-field/scss/index.scss +1 -1
- package/build/components/tasklist/scss/index.scss +1 -1
- package/build/components/text/scss/index.scss +1 -1
- package/build/components/textarea/scss/index.scss +1 -1
- package/build/{css-utilities → components}/theme/index.d.ts +1 -1
- package/build/{css-utilities → components}/theme/index.js +15 -5
- package/build/components/theme/scss/index.scss +2 -0
- package/build/components/theme-picker/index.d.ts +52 -0
- package/build/components/theme-picker/index.js +167 -0
- package/build/components/theme-picker/scss/index.scss +2 -0
- package/build/components/toast/index.js +3 -5
- package/build/components/toast/scss/index.scss +1 -1
- package/build/components/tooltip/menu.js +3 -1
- package/build/components/tooltip/scss/index.scss +1 -1
- package/build/components/typewriter/scss/index.scss +1 -1
- package/build/components/uptime/scss/index.scss +1 -1
- package/build/components/usage-meter/scss/index.scss +1 -1
- package/build/{css-utilities → modifiers}/glass/index.d.ts +2 -2
- package/build/modifiers/glass/index.js +28 -0
- package/build/modifiers/glass/scss/index.scss +2 -0
- package/build/{css-utilities → modifiers}/index.d.ts +0 -1
- package/build/{css-utilities → modifiers}/index.js +0 -1
- package/build/modifiers/scrollbar/index.d.ts +72 -0
- package/build/{css-utilities → modifiers}/scrollbar/index.js +52 -19
- package/build/modifiers/scrollbar/scss/index.scss +2 -0
- package/build/normalize/scss/index.scss +1 -2
- package/layer.scss +1 -1
- package/package.json +11 -18
- package/scripts/code-editor-markdown.test.mjs +45 -14
- package/scripts/code-viewer.test.mjs +69 -0
- package/scripts/workspace-keybindings.test.mjs +273 -0
- package/scripts/workspace-session.test.mjs +349 -0
- package/scripts/workspace-status.test.mjs +229 -0
- package/scripts/workspace-tabs.test.mjs +142 -0
- package/src/components/accordion/group.ts +39 -0
- package/src/components/accordion/index.ts +32 -6
- package/src/components/accordion/scss/index.scss +12 -21
- package/src/components/accordion/scss/variables.scss +18 -3
- package/src/components/announcement/scss/index.scss +4 -4
- package/src/components/announcement/scss/variables.scss +6 -6
- package/src/components/banner/scss/index.scss +1 -1
- package/src/components/breadcrumb/scss/index.scss +35 -50
- package/src/components/breadcrumb/scss/variables.scss +23 -31
- package/src/components/button/scss/_fan.scss +39 -43
- package/src/components/button/scss/_morph.scss +66 -78
- package/src/components/button/scss/_sidebar.scss +41 -30
- package/src/components/button/scss/_split.scss +9 -9
- package/src/components/button/scss/index.scss +23 -19
- package/src/components/button/scss/variables.scss +94 -90
- package/src/components/button/sidebar.ts +36 -24
- package/src/components/capslock/index.ts +2 -2
- package/src/components/capslock/scss/index.scss +11 -6
- package/src/components/capslock/scss/variables.scss +3 -5
- package/src/components/card/scss/_expand.scss +58 -64
- package/src/components/card/scss/index.scss +40 -21
- package/src/components/card/scss/variables.scss +40 -31
- package/src/components/cc/scss/index.scss +68 -68
- package/src/components/cc/scss/variables.scss +13 -14
- package/src/components/chat-minimap/scss/index.scss +24 -16
- package/src/components/chat-minimap/scss/variables.scss +37 -37
- package/src/components/checkbox/scss/index.scss +54 -52
- package/src/components/checkbox/scss/variables.scss +37 -39
- package/src/components/color-picker/index.ts +157 -120
- package/src/components/color-picker/scss/index.scss +159 -96
- package/src/components/color-picker/scss/variables.scss +33 -15
- package/src/components/command/index.ts +3 -2
- package/src/components/command/scss/index.scss +10 -14
- package/src/components/command/scss/variables.scss +12 -35
- package/src/components/copy/scss/index.scss +6 -6
- package/src/components/copy/scss/variables.scss +12 -15
- package/src/components/counter/scss/index.scss +14 -14
- package/src/components/counter/scss/variables.scss +1 -1
- package/src/components/datalist/index.ts +3 -3
- package/src/components/datalist/scss/index.scss +7 -6
- package/src/components/datalist/scss/variables.scss +7 -3
- package/src/components/dock/scss/index.scss +10 -15
- package/src/components/dock/scss/variables.scss +29 -25
- package/src/components/editor/code/index.ts +0 -1
- package/src/components/editor/code/minimap.ts +13 -5
- package/src/components/editor/code/scss/_editor.scss +83 -81
- package/src/components/editor/code/scss/_features.scss +106 -97
- package/src/components/editor/code/scss/_services.scss +27 -25
- package/src/components/editor/code/scss/index.scss +0 -1
- package/src/components/editor/code/scss/variables.scss +30 -26
- package/src/components/editor/code/view.ts +14 -5
- package/src/components/editor/diffs/index.ts +0 -1
- package/src/components/editor/diffs/merge.ts +2 -1
- package/src/components/editor/diffs/scss/_diffs.scss +67 -57
- package/src/components/editor/diffs/scss/_merge.scss +17 -14
- package/src/components/editor/diffs/scss/index.scss +0 -1
- package/src/components/editor/diffs/scss/variables.scss +25 -18
- package/src/components/editor/diffs/view.ts +2 -1
- package/src/components/editor/index.ts +7 -1
- package/src/components/editor/markdown/editing.ts +38 -3
- package/src/components/editor/markdown/erase.ts +124 -0
- package/src/components/editor/markdown/index.ts +0 -1
- package/src/components/editor/markdown/layout.ts +3 -12
- package/src/components/editor/markdown/render.ts +65 -37
- package/src/components/editor/markdown/scss/index.scss +92 -124
- package/src/components/editor/markdown/scss/variables.scss +39 -19
- package/src/components/editor/markdown/view.ts +737 -786
- package/src/components/editor/tree/scss/index.scss +121 -110
- package/src/components/editor/tree/scss/variables.scss +48 -54
- package/src/components/editor/viewer/index.ts +321 -0
- package/src/components/editor/viewer/model.ts +130 -0
- package/src/components/editor/viewer/scss/_viewer.scss +302 -0
- package/src/components/editor/viewer/scss/index.scss +2 -0
- package/src/components/editor/viewer/scss/variables.scss +69 -0
- package/src/components/editor/workspace/index.ts +159 -31
- package/src/components/editor/workspace/keybindings.ts +447 -0
- package/src/components/editor/workspace/model.ts +233 -3
- package/src/components/editor/workspace/navigate.ts +194 -0
- package/src/components/editor/workspace/scss/index.scss +72 -59
- package/src/components/editor/workspace/scss/keybindings.scss +190 -0
- package/src/components/editor/workspace/scss/status.scss +122 -0
- package/src/components/editor/workspace/scss/tabs.scss +56 -0
- package/src/components/editor/workspace/session.ts +551 -0
- package/src/components/editor/workspace/shortcuts.ts +255 -0
- package/src/components/editor/workspace/status.ts +259 -0
- package/src/components/editor/workspace/statusbar.ts +255 -0
- package/src/components/editor/workspace/strip.ts +273 -0
- package/src/components/editor/workspace/tabs.ts +106 -0
- package/src/components/error/index.ts +1 -1
- package/src/components/error/scss/index.scss +19 -19
- package/src/components/error/scss/variables.scss +2 -2
- package/src/components/filter/index.ts +7 -2
- package/src/components/filter/scss/index.scss +1 -1
- package/src/components/filter/scss/variables.scss +11 -10
- package/src/components/frame/scss/index.scss +12 -12
- package/src/components/frame/scss/variables.scss +8 -8
- package/src/components/heatmap/index.ts +3 -3
- package/src/components/heatmap/scss/index.scss +28 -26
- package/src/components/heatmap/scss/variables.scss +18 -17
- package/src/components/highlight/scss/index.scss +17 -13
- package/src/components/highlight/scss/variables.scss +8 -10
- package/src/components/icon/scss/index.scss +10 -0
- package/src/components/icon/scss/variables.scss +5 -0
- package/src/components/image/scss/index.scss +9 -9
- package/src/components/image/scss/variables.scss +7 -2
- package/src/components/index.ts +2 -0
- package/src/components/inline-edit/scss/index.scss +99 -104
- package/src/components/inline-edit/scss/variables.scss +40 -39
- package/src/components/input/scss/_field.scss +6 -20
- package/src/components/input/scss/_scrub.scss +10 -8
- package/src/components/input/scss/_tag.scss +35 -37
- package/src/components/input/scss/variables.scss +100 -99
- package/src/components/link/scss/index.scss +4 -1
- package/src/components/loading/scss/index.scss +1 -1
- package/src/components/loading/scss/variables.scss +1 -1
- package/src/components/marquee/scss/index.scss +9 -5
- package/src/components/marquee/scss/variables.scss +3 -1
- package/src/components/metric/scss/_flash.scss +3 -1
- package/src/components/metric/scss/index.scss +8 -6
- package/src/components/metric/scss/variables.scss +26 -20
- package/src/components/nav/index.ts +1 -1
- package/src/components/nav/scss/index.scss +12 -40
- package/src/components/nav/scss/variables.scss +30 -11
- package/src/components/nav/tree.ts +19 -62
- package/src/components/notification/scss/index.scss +7 -7
- package/src/components/notification/scss/variables.scss +7 -9
- package/src/components/overlay/scss/index.scss +181 -170
- package/src/components/overlay/scss/variables.scss +26 -57
- package/src/components/page/scss/index.scss +12 -11
- package/src/components/page/scss/variables.scss +5 -0
- package/src/components/pagination/arrows.ts +58 -0
- package/src/components/pagination/cards.ts +57 -0
- package/src/components/pagination/index.ts +4 -1
- package/src/components/pagination/scss/_arrows.scss +69 -0
- package/src/components/pagination/scss/_cards.scss +121 -0
- package/src/components/pagination/scss/_dots.scss +2 -5
- package/src/components/pagination/scss/index.scss +8 -6
- package/src/components/pagination/scss/variables.scss +87 -37
- package/src/components/progress/scss/_text.scss +21 -18
- package/src/components/progress/scss/index.scss +1 -1
- package/src/components/progress/scss/variables.scss +12 -12
- package/src/components/pull-to-refresh/scss/index.scss +2 -1
- package/src/components/pull-to-refresh/scss/variables.scss +11 -6
- package/src/components/range/index.ts +1 -1
- package/src/components/range/scss/_filter.scss +62 -51
- package/src/components/range/scss/_slider.scss +78 -75
- package/src/components/range/scss/_vertical.scss +6 -6
- package/src/components/range/scss/index.scss +3 -2
- package/src/components/range/scss/variables.scss +26 -23
- package/src/components/relative-time/scss/index.scss +27 -27
- package/src/components/relative-time/scss/variables.scss +10 -12
- package/src/components/root/scss/index.scss +71 -43
- package/src/components/root/scss/variables.scss +35 -31
- package/src/components/section/scss/index.scss +31 -0
- package/src/components/section/scss/variables.scss +21 -0
- package/src/components/select/index.ts +11 -6
- package/src/components/select/scss/index.scss +21 -32
- package/src/components/select/scss/variables.scss +25 -18
- package/src/components/shortcut-recorder/scss/index.scss +13 -12
- package/src/components/shortcut-recorder/scss/variables.scss +12 -13
- package/src/components/sortable/scss/index.scss +47 -44
- package/src/components/sortable/scss/variables.scss +4 -5
- package/src/components/sticky-header/index.ts +4 -4
- package/src/components/sticky-header/scss/index.scss +14 -13
- package/src/components/sticky-header/scss/variables.scss +9 -9
- package/src/components/story/scss/index.scss +26 -30
- package/src/components/story/scss/variables.scss +16 -9
- package/src/components/surface-field/scss/index.scss +45 -40
- package/src/components/surface-field/scss/variables.scss +5 -6
- package/src/components/tasklist/scss/index.scss +19 -19
- package/src/components/tasklist/scss/variables.scss +26 -29
- package/src/components/text/scss/index.scss +2 -1
- package/src/components/textarea/scss/variables.scss +1 -1
- package/src/{css-utilities → components}/theme/index.ts +17 -3
- package/src/components/theme/scss/index.scss +1 -0
- package/src/components/theme/scss/variables.scss +78 -0
- package/src/components/theme-picker/index.ts +231 -0
- package/src/components/theme-picker/scss/index.scss +264 -0
- package/src/components/toast/index.ts +28 -30
- package/src/components/toast/scss/index.scss +87 -87
- package/src/components/toast/scss/variables.scss +39 -39
- package/src/components/tooltip/menu.ts +4 -1
- package/src/components/tooltip/scss/_east.scss +10 -10
- package/src/components/tooltip/scss/_menu.scss +284 -269
- package/src/components/tooltip/scss/_north.scss +10 -10
- package/src/components/tooltip/scss/_shared.scss +7 -7
- package/src/components/tooltip/scss/_south.scss +10 -10
- package/src/components/tooltip/scss/_west.scss +10 -10
- package/src/components/tooltip/scss/index.scss +21 -20
- package/src/components/tooltip/scss/variables.scss +104 -96
- package/src/components/typewriter/scss/_retype.scss +12 -11
- package/src/components/typewriter/scss/_rotate.scss +2 -1
- package/src/components/typewriter/scss/variables.scss +41 -39
- package/src/components/uptime/scss/index.scss +15 -15
- package/src/components/uptime/scss/variables.scss +8 -8
- package/src/components/usage-meter/scss/index.scss +3 -1
- package/src/components/usage-meter/scss/variables.scss +5 -5
- package/src/css-utilities/absolute.scss +64 -0
- package/src/css-utilities/background.scss +43 -0
- package/src/css-utilities/border.scss +45 -0
- package/src/css-utilities/box-shadow.scss +29 -0
- package/src/css-utilities/color.scss +41 -0
- package/src/css-utilities/flex.scss +64 -0
- package/src/css-utilities/font/geist/{scss/index.scss → index.scss} +12 -4
- package/src/css-utilities/font/montserrat/Montserrat-Italic-Variable.woff2 +0 -0
- package/src/css-utilities/font/montserrat/Montserrat-Variable.woff2 +0 -0
- package/src/css-utilities/font/montserrat/OFL.txt +93 -0
- package/src/css-utilities/font/montserrat/index.scss +38 -0
- package/src/{shared/scss → css-utilities}/icon.scss +1 -1
- package/src/css-utilities/{inline/scss/index.scss → inline.scss} +1 -1
- package/src/css-utilities/{skeleton/scss/index.scss → skeleton.scss} +11 -11
- package/src/css-utilities/surface.scss +266 -0
- package/src/css-utilities/text.scss +191 -0
- package/src/modifiers/glass/index.ts +44 -0
- package/src/{css-utilities → modifiers}/glass/scss/index.scss +6 -6
- package/src/{css-utilities → modifiers}/glass/scss/variables.scss +1 -1
- package/src/{css-utilities → modifiers}/index.ts +0 -1
- package/src/{css-utilities → modifiers}/scrollbar/index.ts +92 -17
- package/src/{css-utilities → modifiers}/scrollbar/scss/index.scss +178 -177
- package/src/{css-utilities → modifiers}/scrollbar/scss/variables.scss +28 -21
- package/src/normalize/scss/index.scss +4 -2
- package/src/shared/scss/color-weights.scss +19 -0
- package/src/shared/scss/index.scss +1 -12
- package/src/tokens/scss/blur.scss +5 -0
- package/src/tokens/scss/color.scss +74 -50
- package/src/tokens/scss/index.scss +1 -2
- package/src/tokens/scss/size.scss +0 -2
- package/src/tokens/scss/state.scss +3 -2
- package/src/tokens/scss/transition.scss +19 -2
- package/storage/svg/monitor.svg +1 -0
- package/storage/svg/moon.svg +1 -0
- package/storage/svg/pin.svg +1 -0
- package/storage/svg/sun.svg +1 -0
- package/vite.config.ts +33 -8
- package/build/css-utilities/background/scss/index.scss +0 -2
- package/build/css-utilities/border/scss/index.scss +0 -2
- package/build/css-utilities/color/scss/index.scss +0 -2
- package/build/css-utilities/flex/scss/index.scss +0 -2
- package/build/css-utilities/font/geist/index.d.ts +0 -1
- package/build/css-utilities/font/geist/index.js +0 -1
- package/build/css-utilities/font/geist/scss/index.scss +0 -2
- package/build/css-utilities/font/montserrat/index.d.ts +0 -1
- package/build/css-utilities/font/montserrat/index.js +0 -1
- package/build/css-utilities/font/montserrat/scss/Montserrat-Bold.woff +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-Bold.woff2 +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-BoldItalic.woff +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-BoldItalic.woff2 +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-Italic.woff +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-Italic.woff2 +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-Medium.woff +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-Medium.woff2 +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-MediumItalic.woff +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-MediumItalic.woff2 +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-Regular.woff +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-Regular.woff2 +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-SemiBold.woff +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-SemiBold.woff2 +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-SemiBoldItalic.woff +0 -0
- package/build/css-utilities/font/montserrat/scss/Montserrat-SemiBoldItalic.woff2 +0 -0
- package/build/css-utilities/font/montserrat/scss/index.scss +0 -2
- package/build/css-utilities/glass/index.js +0 -26
- package/build/css-utilities/glass/scss/index.scss +0 -2
- package/build/css-utilities/index.scss +0 -2
- package/build/css-utilities/inline/scss/index.scss +0 -2
- package/build/css-utilities/scrollbar/index.d.ts +0 -42
- package/build/css-utilities/scrollbar/scss/index.scss +0 -2
- package/build/css-utilities/skeleton/scss/index.scss +0 -2
- package/build/css-utilities/text/scss/index.scss +0 -2
- package/build/themes/dark/index.d.ts +0 -3
- package/build/themes/dark/index.js +0 -3
- package/build/themes/dark/input.d.ts +0 -3
- package/build/themes/dark/input.js +0 -12
- package/build/themes/dark/select.d.ts +0 -3
- package/build/themes/dark/select.js +0 -23
- package/build/themes/dark/textarea.d.ts +0 -2186
- package/build/themes/dark/textarea.js +0 -11
- package/src/css-utilities/background/scss/index.scss +0 -1
- package/src/css-utilities/background/scss/variables.scss +0 -18
- package/src/css-utilities/border/scss/index.scss +0 -1
- package/src/css-utilities/border/scss/variables.scss +0 -23
- package/src/css-utilities/color/scss/index.scss +0 -1
- package/src/css-utilities/color/scss/variables.scss +0 -17
- package/src/css-utilities/flex/scss/index.scss +0 -57
- package/src/css-utilities/flex/scss/variables.scss +0 -16
- package/src/css-utilities/font/geist/index.ts +0 -1
- package/src/css-utilities/font/montserrat/index.ts +0 -1
- package/src/css-utilities/font/montserrat/scss/Montserrat-Bold.woff +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-Bold.woff2 +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-BoldItalic.woff +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-BoldItalic.woff2 +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-Italic.woff +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-Italic.woff2 +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-Medium.woff +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-Medium.woff2 +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-MediumItalic.woff +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-MediumItalic.woff2 +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-Regular.woff +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-Regular.woff2 +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-SemiBold.woff +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-SemiBold.woff2 +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-SemiBoldItalic.woff +0 -0
- package/src/css-utilities/font/montserrat/scss/Montserrat-SemiBoldItalic.woff2 +0 -0
- package/src/css-utilities/font/montserrat/scss/index.scss +0 -94
- package/src/css-utilities/glass/index.ts +0 -42
- package/src/css-utilities/index.scss +0 -13
- package/src/css-utilities/skeleton/scss/variables.scss +0 -3
- package/src/css-utilities/text/scss/index.scss +0 -152
- package/src/css-utilities/text/scss/variables.scss +0 -22
- package/src/shared/scss/absolute.scss +0 -28
- package/src/shared/scss/background.scss +0 -21
- package/src/shared/scss/border.scss +0 -21
- package/src/shared/scss/color.scss +0 -21
- package/src/shared/scss/position.scss +0 -55
- package/src/shared/scss/text.scss +0 -31
- package/src/themes/dark/index.ts +0 -3
- package/src/themes/dark/input.ts +0 -16
- package/src/themes/dark/select.ts +0 -28
- package/src/themes/dark/textarea.ts +0 -13
- package/src/tokens/scss/box-shadow.scss +0 -23
- package/src/tokens/scss/font-family.scss +0 -5
- package/storage/code.md +0 -180
- package/storage/editor-features.md +0 -227
- package/build/css-utilities/font/geist/{scss/Geist-Italic-Variable.woff2 → Geist-Italic-Variable.woff2} +0 -0
- package/build/css-utilities/font/geist/{scss/Geist-Variable.woff2 → Geist-Variable.woff2} +0 -0
- package/build/css-utilities/font/geist/{scss/GeistMono-Italic-Variable.woff2 → GeistMono-Italic-Variable.woff2} +0 -0
- package/build/css-utilities/font/geist/{scss/GeistMono-Variable.woff2 → GeistMono-Variable.woff2} +0 -0
- package/src/{shared/scss → css-utilities}/anchor.scss +6 -6
- package/src/{shared/scss → css-utilities}/drag.scss +0 -0
- package/src/css-utilities/font/geist/{scss/Geist-Italic-Variable.woff2 → Geist-Italic-Variable.woff2} +0 -0
- package/src/css-utilities/font/geist/{scss/Geist-Variable.woff2 → Geist-Variable.woff2} +0 -0
- package/src/css-utilities/font/geist/{scss/GeistMono-Italic-Variable.woff2 → GeistMono-Italic-Variable.woff2} +0 -0
- package/src/css-utilities/font/geist/{scss/GeistMono-Variable.woff2 → GeistMono-Variable.woff2} +0 -0
- package/src/{shared/scss → css-utilities}/hidden.scss +11 -11
- /package/src/{shared/scss → css-utilities}/layer.scss +0 -0
- /package/src/{shared/scss → css-utilities}/reset.scss +0 -0
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { flush, reactive } from '@esportsplus/reactivity';
|
|
2
2
|
import { html, type Attributes } from '@esportsplus/template';
|
|
3
|
-
import
|
|
3
|
+
import scrollbar from '~/modifiers/scrollbar';
|
|
4
4
|
import { clamp } from '~/shared/clamp';
|
|
5
5
|
import { mac } from '~/shared/platform';
|
|
6
6
|
import { observer, observeSize, type Observer } from '~/shared/resize';
|
|
7
7
|
import { copied, read, write } from '../code/clipboard';
|
|
8
8
|
import { addNextOccurrence, deleteCharacter, indent, insertText, lineCommand, toggleComment } from '../code/commands';
|
|
9
9
|
import { composition } from '../code/composition';
|
|
10
|
-
import { write as writeField } from '../code/field';
|
|
11
10
|
import { find } from '../code/find';
|
|
12
11
|
import { mapFolds, outerFolds, type FoldRange } from '../code/folding';
|
|
13
12
|
import { goto } from '../code/goto';
|
|
@@ -16,17 +15,19 @@ import { EditorLayout, type Rect } from '../code/layout';
|
|
|
16
15
|
import { minimap, type Source } from '../code/minimap';
|
|
17
16
|
import { move } from '../code/navigation';
|
|
18
17
|
import { pointer } from '../code/pointer';
|
|
19
|
-
import {
|
|
18
|
+
import { NativeText } from '../code/projection';
|
|
20
19
|
import { syntaxCache, type Token } from '../code/syntax';
|
|
21
20
|
import { markdownCommand } from './editing';
|
|
21
|
+
import { reach, widen, type Span } from './erase';
|
|
22
22
|
import { parseInline, type Inline, type References } from './inline';
|
|
23
|
-
import { arrange, cache,
|
|
23
|
+
import { arrange, cache, MarkdownLayout, unitStart, type Unit } from './layout';
|
|
24
24
|
import {
|
|
25
25
|
blockAt,
|
|
26
26
|
contentEnd,
|
|
27
27
|
markdownBackspace,
|
|
28
28
|
markdownReferences,
|
|
29
29
|
parseMarkdown,
|
|
30
|
+
quotePrefix,
|
|
30
31
|
reparse,
|
|
31
32
|
toggleTask,
|
|
32
33
|
type Kind,
|
|
@@ -34,15 +35,18 @@ import {
|
|
|
34
35
|
} from './model';
|
|
35
36
|
import { renderer } from './render';
|
|
36
37
|
import { foldable, foldAt, markdownFolds } from './structure';
|
|
37
|
-
import type { Change,
|
|
38
|
+
import type { Change, EditorDocument, Selection, Snapshot } from '../code/document';
|
|
38
39
|
import type { Callbacks, Controller, Options } from '../code/view';
|
|
39
|
-
import
|
|
40
|
+
import tooltip from '~/components/tooltip';
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
type Active = { first: number; from: number; last: number; to: number };
|
|
43
|
-
|
|
44
43
|
type Decoration = { kind: string; style: string };
|
|
45
44
|
|
|
45
|
+
// Space standing in for the units left undrawn between drawn ones.
|
|
46
|
+
type Gap = { height: number };
|
|
47
|
+
|
|
48
|
+
type Item = Gap | Unit;
|
|
49
|
+
|
|
46
50
|
type MarkdownController = Omit<Controller, 'setDocument' | 'setOptions'> & {
|
|
47
51
|
bold(): boolean;
|
|
48
52
|
italic(): boolean;
|
|
@@ -69,15 +73,34 @@ type Metrics = {
|
|
|
69
73
|
width: number;
|
|
70
74
|
};
|
|
71
75
|
|
|
76
|
+
type Point = { node: Node; offset: number };
|
|
77
|
+
|
|
72
78
|
|
|
73
79
|
const DECORATIONS = 1000;
|
|
74
80
|
|
|
81
|
+
// A list item whose text so far is '[]' or '[ ]': a space turns it into a task.
|
|
82
|
+
const ITEM_TASK = /^[ \t]*(?:[-+*]|\d+[.)])[ \t]+\[ ?\]$/;
|
|
83
|
+
|
|
84
|
+
// Kinds whose text joins a neighbour's when the break between them is deleted.
|
|
85
|
+
const JOINABLE = new Set<Kind>(['heading', 'list', 'paragraph', 'quote']);
|
|
86
|
+
|
|
87
|
+
const LINE_BREAK = /[\r\n]/;
|
|
88
|
+
|
|
89
|
+
// Long enough that the hint only shows once the pointer rests on a link, not as it passes over text.
|
|
90
|
+
const LINK_DELAY = 700;
|
|
91
|
+
|
|
92
|
+
// Source the editor keeps literal, so typing there never turns into markup.
|
|
93
|
+
const LITERAL = new Set<Kind>(['code', 'fence', 'frontmatter', 'html']);
|
|
94
|
+
|
|
75
95
|
const NAVIGATION = /^(ArrowDown|ArrowLeft|ArrowRight|ArrowUp|End|Home|PageDown|PageUp)$/;
|
|
76
96
|
|
|
77
97
|
const NONE: readonly number[] = [];
|
|
78
98
|
|
|
79
99
|
const NO_TOKENS: readonly Token[] = [];
|
|
80
100
|
|
|
101
|
+
// A line whose text so far is '[]' or '[ ]': a space turns it into a task item.
|
|
102
|
+
const TASK = /^\[ ?\]$/;
|
|
103
|
+
|
|
81
104
|
const TOKEN_COLORS = ['comment', 'keyword', 'operator', 'property', 'string', 'type', 'variable'] as const;
|
|
82
105
|
|
|
83
106
|
const VERTICAL = /^(ArrowDown|ArrowUp|PageDown|PageUp)$/;
|
|
@@ -86,9 +109,9 @@ const VERTICAL = /^(ArrowDown|ArrowUp|PageDown|PageUp)$/;
|
|
|
86
109
|
let uid = 0;
|
|
87
110
|
|
|
88
111
|
|
|
89
|
-
// Brings a reactive list to 'next' with the fewest splices, so
|
|
90
|
-
//
|
|
91
|
-
function assign(list:
|
|
112
|
+
// Brings a reactive list to 'next' with the fewest splices, so items that stay keep their DOM. Both are ordered runs
|
|
113
|
+
// of the same items.
|
|
114
|
+
function assign(list: Item[], next: readonly Item[]) {
|
|
92
115
|
if (list.length === next.length) {
|
|
93
116
|
let same = true;
|
|
94
117
|
|
|
@@ -151,46 +174,37 @@ function pixels(value: string) {
|
|
|
151
174
|
return parseFloat(value) || 0;
|
|
152
175
|
}
|
|
153
176
|
|
|
154
|
-
function prevent(e: Event) {
|
|
155
|
-
e.preventDefault();
|
|
156
|
-
}
|
|
157
|
-
|
|
158
177
|
|
|
159
|
-
// The markdown editor's view: blocks
|
|
160
|
-
//
|
|
161
|
-
//
|
|
178
|
+
// The markdown editor's view: blocks stay formatted while they edit. The rendered text itself is editable; every input
|
|
179
|
+
// is turned into an edit of the markdown source, and the blocks it touched draw again with the caret put back. Markup
|
|
180
|
+
// never shows: deletions skip over it, and a delimiter left with nothing to wrap goes with what it wrapped. Only units
|
|
181
|
+
// near the viewport are drawn, plus the ones holding the selection's ends. Returns its template and controller.
|
|
162
182
|
const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller: MarkdownController) => void) => {
|
|
163
|
-
let
|
|
164
|
-
activeIndex = -1,
|
|
165
|
-
apple = mac(),
|
|
166
|
-
before: Before | null = null,
|
|
167
|
-
beforeRanges: readonly Selection[] = [],
|
|
168
|
-
beforeSelection: Selection | undefined,
|
|
183
|
+
let apple = mac(),
|
|
169
184
|
blocks = parseMarkdown(model),
|
|
185
|
+
// The final text of the composition ending, and the selection it replaces.
|
|
186
|
+
composed = '',
|
|
187
|
+
composing: Selection | null = null,
|
|
188
|
+
content: HTMLElement | undefined,
|
|
170
189
|
decorations = reactive([] as Decoration[]),
|
|
171
190
|
definitions: References = new Map(),
|
|
172
191
|
disposed = false,
|
|
173
|
-
drag: { anchor: number; head: number } | null = null,
|
|
174
192
|
// Content height the minimap last drew.
|
|
175
193
|
drawn = 0,
|
|
176
194
|
dropAt: number | null = null,
|
|
177
|
-
|
|
195
|
+
elements = new Map<Unit, HTMLElement>(),
|
|
178
196
|
escapeTab = false,
|
|
179
|
-
estimated = false,
|
|
180
|
-
fieldSource = '',
|
|
181
197
|
folds: FoldRange[] = [],
|
|
182
198
|
frame = 0,
|
|
183
|
-
|
|
199
|
+
gaps: Gap[] = [],
|
|
184
200
|
goals: number[] = [],
|
|
185
201
|
help = `markdown-editor-help-${++uid}`,
|
|
186
202
|
host: HTMLElement | undefined,
|
|
187
203
|
inlines = new WeakMap<MarkdownBlock, { definitions: References; tokens: readonly Inline[] }>(),
|
|
188
|
-
|
|
189
|
-
inputType = '',
|
|
204
|
+
items = reactive([] as Item[]),
|
|
190
205
|
keys = keymap(apple),
|
|
191
206
|
layout = new MarkdownLayout(),
|
|
192
207
|
length = model.value.length,
|
|
193
|
-
listed = { after: reactive([] as Unit[]), before: reactive([] as Unit[]) },
|
|
194
208
|
metrics: Metrics = {
|
|
195
209
|
charWidth: 7.8,
|
|
196
210
|
clientHeight: 320,
|
|
@@ -208,9 +222,9 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
208
222
|
palette: Record<string, string> = {},
|
|
209
223
|
parsed = model.revision,
|
|
210
224
|
pendingValue: string | undefined,
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
225
|
+
// The native selection this view last set, which maps to the document's but needn't map back to it exactly.
|
|
226
|
+
placed: { anchor: Point; focus: Point } | null = null,
|
|
227
|
+
rendered: Unit[] = [],
|
|
214
228
|
resize: Observer | undefined = observer(resized),
|
|
215
229
|
revealing = false,
|
|
216
230
|
ruler: HTMLElement | undefined,
|
|
@@ -222,46 +236,27 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
222
236
|
selections: model.selections.map((range) => ({ ...range }))
|
|
223
237
|
}),
|
|
224
238
|
surface: HTMLElement | undefined,
|
|
225
|
-
textarea: HTMLTextAreaElement | undefined,
|
|
226
239
|
ui = reactive({
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
editing: false,
|
|
230
|
-
foldable: false,
|
|
231
|
-
height: 20,
|
|
232
|
-
input: '',
|
|
240
|
+
empty: !model.value,
|
|
241
|
+
fold: false,
|
|
233
242
|
label: 'Markdown editor',
|
|
243
|
+
// The platform's modifier is held, so links show they open on a click.
|
|
244
|
+
linking: false,
|
|
234
245
|
minimap: false,
|
|
235
|
-
name: '',
|
|
236
|
-
next: 0,
|
|
237
246
|
placeholder: '',
|
|
238
247
|
readonly: false,
|
|
239
248
|
spellcheck: true,
|
|
240
|
-
style: '',
|
|
241
249
|
tabSize: 4,
|
|
242
|
-
top: 0,
|
|
243
250
|
wrap: true
|
|
244
251
|
}),
|
|
245
252
|
units = cache(),
|
|
246
253
|
unitsDirty = true,
|
|
254
|
+
// The document's selection hasn't reached the native one yet, so the native one is stale.
|
|
255
|
+
unplaced = false,
|
|
247
256
|
unsubscribe: VoidFunction | undefined,
|
|
248
257
|
// Where each document this editor showed was left.
|
|
249
258
|
visited = new WeakMap<EditorDocument, Memo>(),
|
|
250
|
-
whole: { layout: EditorLayout; projection: NativeText; revision: number } | null = null
|
|
251
|
-
writing = false,
|
|
252
|
-
written = '';
|
|
253
|
-
|
|
254
|
-
let activeUnit: Unit = {
|
|
255
|
-
active: true,
|
|
256
|
-
block: blocks[0],
|
|
257
|
-
continuation: false,
|
|
258
|
-
continues: false,
|
|
259
|
-
estimate: 0,
|
|
260
|
-
estimated: -1,
|
|
261
|
-
height: 0,
|
|
262
|
-
measured: -1,
|
|
263
|
-
raw: false
|
|
264
|
-
};
|
|
259
|
+
whole: { layout: EditorLayout; projection: NativeText; revision: number } | null = null;
|
|
265
260
|
|
|
266
261
|
let draw = renderer({
|
|
267
262
|
fold: (unit) => {
|
|
@@ -269,18 +264,23 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
269
264
|
},
|
|
270
265
|
foldable: opens,
|
|
271
266
|
inline: inlineOf,
|
|
272
|
-
press,
|
|
273
267
|
readonly: () => ui.readonly,
|
|
274
268
|
shown: (element, unit) => {
|
|
275
269
|
if (unit) {
|
|
270
|
+
elements.set(unit, element);
|
|
276
271
|
shown.set(element, unit);
|
|
277
|
-
|
|
278
272
|
resize?.observe(element);
|
|
273
|
+
return;
|
|
279
274
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
275
|
+
|
|
276
|
+
let previous = shown.get(element);
|
|
277
|
+
|
|
278
|
+
if (previous && elements.get(previous) === element) {
|
|
279
|
+
elements.delete(previous);
|
|
283
280
|
}
|
|
281
|
+
|
|
282
|
+
shown.delete(element);
|
|
283
|
+
resize?.unobserve(element);
|
|
284
284
|
},
|
|
285
285
|
task: (block) => {
|
|
286
286
|
if (!disposed && !options.readonly && !ime.busy() && block.task) {
|
|
@@ -293,22 +293,30 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
293
293
|
}
|
|
294
294
|
});
|
|
295
295
|
|
|
296
|
+
// The input method writes into the drawn text itself; once it ends, its text replaces what it was typed over and
|
|
297
|
+
// the block draws again, so none of the nodes it made stay behind.
|
|
296
298
|
let ime = composition({
|
|
297
299
|
begin: () => {
|
|
298
300
|
capture();
|
|
299
|
-
|
|
300
|
-
|
|
301
|
+
composed = '';
|
|
302
|
+
composing = { ...model.selection };
|
|
301
303
|
model.breakHistory();
|
|
302
304
|
},
|
|
303
305
|
commit: (conflict) => {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
306
|
+
let range = composing;
|
|
307
|
+
|
|
308
|
+
composing = null;
|
|
309
|
+
|
|
310
|
+
if (range && !conflict && composed && !options.readonly) {
|
|
311
|
+
model.replace(range.start, range.end, composed, {
|
|
312
|
+
selection: { start: range.start + composed.length },
|
|
313
|
+
source: 'composition'
|
|
314
|
+
});
|
|
310
315
|
}
|
|
311
316
|
|
|
317
|
+
composed = '';
|
|
318
|
+
invalidate(range?.start ?? model.selection.start);
|
|
319
|
+
|
|
312
320
|
if (pendingValue !== undefined) {
|
|
313
321
|
let value = pendingValue;
|
|
314
322
|
|
|
@@ -351,11 +359,7 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
351
359
|
});
|
|
352
360
|
|
|
353
361
|
let map = minimap({
|
|
354
|
-
focus: () => {
|
|
355
|
-
if (editing) {
|
|
356
|
-
textarea?.focus({ preventScroll: true });
|
|
357
|
-
}
|
|
358
|
-
},
|
|
362
|
+
focus: () => content?.focus({ preventScroll: true }),
|
|
359
363
|
scroll: (top, relative) => {
|
|
360
364
|
scrollTo(relative ? metrics.scrollTop + top : top);
|
|
361
365
|
}
|
|
@@ -381,6 +385,9 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
381
385
|
|
|
382
386
|
let size = observeSize(() => measure());
|
|
383
387
|
|
|
388
|
+
// Links place the caret on a plain click, so resting on one says how to open it.
|
|
389
|
+
let tip = tooltip.shared({ delay: { open: LINK_DELAY } });
|
|
390
|
+
|
|
384
391
|
let source: Source = {
|
|
385
392
|
breaks: () => NONE,
|
|
386
393
|
get count() {
|
|
@@ -426,115 +433,29 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
426
433
|
}
|
|
427
434
|
};
|
|
428
435
|
|
|
429
|
-
// Applies a native input to the document. 'beforeinput' recorded the field's selection and length, so most inputs
|
|
430
|
-
// resolve without a diff; the rest diff the field's value.
|
|
431
|
-
function accept(composed = false) {
|
|
432
|
-
if (disposed || !textarea || !active) {
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (options.readonly) {
|
|
437
|
-
reset();
|
|
438
|
-
resync();
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
let after = { end: textarea.selectionEnd, length: textarea.textLength, start: textarea.selectionStart },
|
|
443
|
-
base = active.from,
|
|
444
|
-
direction = textarea.selectionDirection,
|
|
445
|
-
native = before && !composed ? inputEdit(before, after, inputType, inputData) : null,
|
|
446
|
-
ranges = beforeRanges.length ? beforeRanges : model.selections,
|
|
447
|
-
value = textarea.value,
|
|
448
|
-
edit: Edit;
|
|
449
|
-
|
|
450
|
-
if (native) {
|
|
451
|
-
edit = projection.toSourceEdit(model.value, native.from, native.to, native.insert);
|
|
452
|
-
}
|
|
453
|
-
else if (value === projection.value) {
|
|
454
|
-
reset();
|
|
455
|
-
capture();
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
else {
|
|
459
|
-
let selection = beforeSelection
|
|
460
|
-
? { end: clamp(beforeSelection.end - base, 0, fieldSource.length), start: clamp(beforeSelection.start - base, 0, fieldSource.length) }
|
|
461
|
-
: undefined;
|
|
462
|
-
|
|
463
|
-
edit = projection.edit(model.value, value, selection, inputType);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// A native edit the projection can't map would remove text the field doesn't show; the field goes back.
|
|
467
|
-
if (edit.from === edit.to && !edit.insert) {
|
|
468
|
-
reset();
|
|
469
|
-
resync();
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
let group = !composed && /^(deleteContentBackward|deleteContentForward|insertText)$/.test(inputType) ? inputType : undefined,
|
|
474
|
-
origin = composed ? 'composition' : 'input';
|
|
475
|
-
|
|
476
|
-
written = value;
|
|
477
|
-
|
|
478
|
-
if (ranges.length > 1) {
|
|
479
|
-
reset();
|
|
480
|
-
insertText(model, edit.insert, origin);
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
let from = base + edit.from,
|
|
485
|
-
caret = native
|
|
486
|
-
? { direction, end: from + edit.insert.length, start: from + edit.insert.length }
|
|
487
|
-
: caretAfter(edit, after, direction);
|
|
488
|
-
|
|
489
|
-
reset();
|
|
490
|
-
model.replace(from, base + edit.to, edit.insert, { group, selection: caret, source: origin });
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Shows the field for the selection.
|
|
494
|
-
function activate(focus: boolean) {
|
|
495
|
-
if (disposed) {
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
editing = true;
|
|
500
|
-
ui.editing = true;
|
|
501
|
-
refresh();
|
|
502
|
-
schedule();
|
|
503
|
-
|
|
504
|
-
if (focus && textarea && host?.ownerDocument.activeElement !== textarea) {
|
|
505
|
-
// The field must be displayed before it can take focus.
|
|
506
|
-
flush();
|
|
507
|
-
textarea.focus({ preventScroll: true });
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
436
|
// Takes over from the previous document: whatever was derived from it goes, and this one's view comes back as
|
|
512
437
|
// this editor left it, folds only while its text is unchanged since.
|
|
513
438
|
function adopt() {
|
|
514
439
|
let memo = visited.get(model),
|
|
515
440
|
{ selections, ...single } = model.state;
|
|
516
441
|
|
|
517
|
-
reset();
|
|
518
|
-
active = null;
|
|
519
|
-
activeIndex = -1;
|
|
520
442
|
blocks = parseMarkdown(model);
|
|
521
|
-
|
|
522
|
-
|
|
443
|
+
composed = '';
|
|
444
|
+
composing = null;
|
|
523
445
|
dropAt = null;
|
|
524
446
|
escapeTab = false;
|
|
525
|
-
fieldSource = '';
|
|
526
447
|
folds = memo?.revision === model.revision ? memo.folds : [];
|
|
527
|
-
geometry = null;
|
|
528
448
|
goals = [];
|
|
529
449
|
length = model.value.length;
|
|
530
450
|
metrics.scrollLeft = memo?.left ?? 0;
|
|
531
451
|
metrics.scrollTop = memo?.top ?? 0;
|
|
532
452
|
parsed = model.revision;
|
|
533
453
|
pendingValue = undefined;
|
|
534
|
-
|
|
454
|
+
placed = null;
|
|
535
455
|
secondary = selections.length > 1;
|
|
456
|
+
ui.empty = !model.value;
|
|
457
|
+
unitsDirty = true;
|
|
536
458
|
whole = null;
|
|
537
|
-
written = '';
|
|
538
459
|
state.selections.splice(0, state.selections.length, ...selections.map((range) => ({ ...range })));
|
|
539
460
|
Object.assign(state, single, { selection: { ...single.selection } });
|
|
540
461
|
gestures.reset();
|
|
@@ -549,21 +470,45 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
549
470
|
search.result();
|
|
550
471
|
}
|
|
551
472
|
|
|
473
|
+
// Reads the native selection back into the document, unless it is the one this view placed or the document's is
|
|
474
|
+
// still waiting to be placed.
|
|
552
475
|
function capture() {
|
|
553
|
-
if (disposed || !
|
|
476
|
+
if (disposed || !content || ime.busy() || unplaced) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
let selection = content.ownerDocument.getSelection(),
|
|
481
|
+
anchorNode = selection?.anchorNode,
|
|
482
|
+
focusNode = selection?.focusNode;
|
|
483
|
+
|
|
484
|
+
if (!selection || !anchorNode || !focusNode || !content.contains(anchorNode) || !content.contains(focusNode)) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (
|
|
489
|
+
placed &&
|
|
490
|
+
placed.anchor.node === anchorNode &&
|
|
491
|
+
placed.anchor.offset === selection.anchorOffset &&
|
|
492
|
+
placed.focus.node === focusNode &&
|
|
493
|
+
placed.focus.offset === selection.focusOffset
|
|
494
|
+
) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
let anchor = sourceAt(anchorNode, selection.anchorOffset),
|
|
499
|
+
head = sourceAt(focusNode, selection.focusOffset);
|
|
500
|
+
|
|
501
|
+
if (anchor === null || head === null) {
|
|
554
502
|
return;
|
|
555
503
|
}
|
|
556
504
|
|
|
557
|
-
let
|
|
505
|
+
let primary = model.selection,
|
|
558
506
|
next: Selection = {
|
|
559
|
-
direction:
|
|
560
|
-
end:
|
|
561
|
-
start:
|
|
507
|
+
direction: head < anchor ? 'backward' : 'forward',
|
|
508
|
+
end: Math.max(anchor, head),
|
|
509
|
+
start: Math.min(anchor, head)
|
|
562
510
|
};
|
|
563
511
|
|
|
564
|
-
let primary = model.selection;
|
|
565
|
-
|
|
566
|
-
// A field reports a direction even for a caret, and 'forward' where the document has none.
|
|
567
512
|
if (
|
|
568
513
|
next.start === primary.start &&
|
|
569
514
|
next.end === primary.end &&
|
|
@@ -575,15 +520,7 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
575
520
|
model.selectMany([next, ...model.selections.slice(1)]);
|
|
576
521
|
}
|
|
577
522
|
|
|
578
|
-
|
|
579
|
-
let base = active!.from,
|
|
580
|
-
next = new NativeText(fieldSource.slice(0, edit.from) + edit.insert + fieldSource.slice(edit.to));
|
|
581
|
-
|
|
582
|
-
return { direction, end: base + next.toSource(after.end), start: base + next.toSource(after.start) };
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// Client rectangle of a source offset: the field's geometry inside it, the drawn text elsewhere, or else the
|
|
586
|
-
// unit's top.
|
|
523
|
+
// Client rectangle of a source offset: the drawn text there, or else the unit's top.
|
|
587
524
|
function clientRect(offset: number): Rect | null {
|
|
588
525
|
if (!surface) {
|
|
589
526
|
return null;
|
|
@@ -591,21 +528,6 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
591
528
|
|
|
592
529
|
current();
|
|
593
530
|
|
|
594
|
-
let box = surface.getBoundingClientRect(),
|
|
595
|
-
originX = box.left + surface.clientLeft - surface.scrollLeft,
|
|
596
|
-
originY = box.top + surface.clientTop - surface.scrollTop;
|
|
597
|
-
|
|
598
|
-
if (active && editing && geometry && offset >= active.from && offset <= active.to) {
|
|
599
|
-
let rect = geometry.rect(projection.toNative(offset - active.from));
|
|
600
|
-
|
|
601
|
-
return {
|
|
602
|
-
height: rect.height,
|
|
603
|
-
left: originX + fieldLeft() + rect.left,
|
|
604
|
-
top: originY + metrics.padTop + layout.top(activeIndex) + rect.top,
|
|
605
|
-
width: rect.width
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
|
|
609
531
|
let index = layout.indexOf(offset),
|
|
610
532
|
unit = layout.units[index];
|
|
611
533
|
|
|
@@ -619,7 +541,14 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
619
541
|
return rects[0];
|
|
620
542
|
}
|
|
621
543
|
|
|
622
|
-
|
|
544
|
+
let box = surface.getBoundingClientRect();
|
|
545
|
+
|
|
546
|
+
return {
|
|
547
|
+
height: metrics.lineHeight,
|
|
548
|
+
left: box.left + surface.clientLeft - surface.scrollLeft + metrics.padLeft,
|
|
549
|
+
top: box.top + surface.clientTop - surface.scrollTop + metrics.padTop + layout.top(index),
|
|
550
|
+
width: 1
|
|
551
|
+
};
|
|
623
552
|
}
|
|
624
553
|
|
|
625
554
|
function clip(e: ClipboardEvent, cut: boolean) {
|
|
@@ -641,13 +570,12 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
641
570
|
}
|
|
642
571
|
|
|
643
572
|
function connect() {
|
|
644
|
-
if (disposed || !
|
|
573
|
+
if (disposed || !content || !host || !ruler || !surface || unsubscribe) {
|
|
645
574
|
return;
|
|
646
575
|
}
|
|
647
576
|
|
|
648
577
|
measure();
|
|
649
578
|
subscribe();
|
|
650
|
-
refresh();
|
|
651
579
|
unitsDirty = true;
|
|
652
580
|
schedule();
|
|
653
581
|
void host.ownerDocument.fonts?.ready.then(() => {
|
|
@@ -670,27 +598,12 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
670
598
|
}
|
|
671
599
|
}
|
|
672
600
|
|
|
673
|
-
// Hides the field; every block shows formatted again.
|
|
674
|
-
function deactivate() {
|
|
675
|
-
if (!editing) {
|
|
676
|
-
return;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
ime.flush();
|
|
680
|
-
editing = false;
|
|
681
|
-
drag = null;
|
|
682
|
-
ui.editing = false;
|
|
683
|
-
gestures.reset();
|
|
684
|
-
refresh();
|
|
685
|
-
schedule();
|
|
686
|
-
}
|
|
687
|
-
|
|
688
601
|
function decorate() {
|
|
689
602
|
let items: Decoration[] = [],
|
|
690
603
|
ranges = model.selections,
|
|
691
604
|
matches = search.state.open || search.state.query ? search.result() : null;
|
|
692
605
|
|
|
693
|
-
if (ranges.length < 2 &&
|
|
606
|
+
if (ranges.length < 2 && dropAt === null && !matches?.matches.length) {
|
|
694
607
|
if (decorations.length) {
|
|
695
608
|
decorations.splice(0, decorations.length);
|
|
696
609
|
}
|
|
@@ -743,10 +656,6 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
743
656
|
mark(head, head, 'markdown-editor-mark markdown-editor-mark--caret');
|
|
744
657
|
}
|
|
745
658
|
|
|
746
|
-
if (drag) {
|
|
747
|
-
mark(Math.min(drag.anchor, drag.head), Math.max(drag.anchor, drag.head), 'markdown-editor-mark markdown-editor-mark--selection');
|
|
748
|
-
}
|
|
749
|
-
|
|
750
659
|
if (dropAt !== null) {
|
|
751
660
|
mark(dropAt, dropAt, 'markdown-editor-mark markdown-editor-mark--caret');
|
|
752
661
|
}
|
|
@@ -765,102 +674,152 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
765
674
|
}
|
|
766
675
|
}
|
|
767
676
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
677
|
+
// The native point standing for a source offset: in the drawn text holding it, or after the text before it when it
|
|
678
|
+
// falls on markup. Offsets in units that aren't drawn go to the nearest drawn edge. Null while the unit's element
|
|
679
|
+
// has yet to connect.
|
|
680
|
+
function domAt(offset: number): Point | null {
|
|
681
|
+
current();
|
|
682
|
+
|
|
683
|
+
let unit = layout.units[layout.indexOf(offset)];
|
|
684
|
+
|
|
685
|
+
if (!unit || !draw.segments(unit)) {
|
|
686
|
+
let first = rendered[0],
|
|
687
|
+
last = rendered[rendered.length - 1];
|
|
688
|
+
|
|
689
|
+
if (!first || !last) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if (offset < unitStart(first)) {
|
|
694
|
+
unit = first;
|
|
695
|
+
offset = contentStart(first);
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
unit = last;
|
|
699
|
+
offset = contentEnd(model.value, last.block);
|
|
700
|
+
}
|
|
771
701
|
}
|
|
772
702
|
|
|
773
|
-
|
|
774
|
-
|
|
703
|
+
let element = elements.get(unit),
|
|
704
|
+
segments = draw.segments(unit) ?? [],
|
|
705
|
+
after: Point | null = null,
|
|
706
|
+
before: Point | null = null;
|
|
775
707
|
|
|
776
|
-
let
|
|
708
|
+
for (let i = 0, n = segments.length; i < n; i++) {
|
|
709
|
+
let segment = segments[i],
|
|
710
|
+
at = segment.block.from + segment.from,
|
|
711
|
+
end = at + segment.node.length;
|
|
777
712
|
|
|
778
|
-
|
|
713
|
+
if (at <= offset && offset <= end) {
|
|
714
|
+
return { node: segment.node, offset: offset - at };
|
|
715
|
+
}
|
|
779
716
|
|
|
780
|
-
|
|
781
|
-
|
|
717
|
+
if (end < offset) {
|
|
718
|
+
before = { node: segment.node, offset: segment.node.length };
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
after = { node: segment.node, offset: 0 };
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
782
725
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
if (!active) {
|
|
786
|
-
geometry = null;
|
|
787
|
-
return;
|
|
726
|
+
if (before || after) {
|
|
727
|
+
return before ?? after;
|
|
788
728
|
}
|
|
789
729
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
lineHeight = block.level && block.level <= 2 ? fontSize * SCALED_LINE : metrics.lineHeight,
|
|
794
|
-
wrap = options.wrap !== false,
|
|
795
|
-
width = Math.max(1, metrics.width - block.quoteDepth * metrics.quoteIndent);
|
|
730
|
+
if (!element) {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
796
733
|
|
|
797
|
-
|
|
798
|
-
|
|
734
|
+
// No text: on the line break that gives the caret its line.
|
|
735
|
+
let children = element.childNodes;
|
|
799
736
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
737
|
+
for (let i = 0, n = children.length; i < n; i++) {
|
|
738
|
+
if (children[i].nodeName === 'BR') {
|
|
739
|
+
return { node: element, offset: i };
|
|
803
740
|
}
|
|
804
741
|
}
|
|
805
742
|
|
|
806
|
-
|
|
807
|
-
|
|
743
|
+
return { node: element, offset: children.length };
|
|
744
|
+
}
|
|
808
745
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
746
|
+
// Source offset at the drawn units' edge nearest a point outside them: on the content root or a spacer.
|
|
747
|
+
function edgeAt(node: Node, offset: number) {
|
|
748
|
+
if (!content) {
|
|
749
|
+
return null;
|
|
813
750
|
}
|
|
814
751
|
|
|
815
|
-
|
|
816
|
-
ui.input = `markdown-editor-input markdown-editor-input--${block.kind}${block.level ? ` markdown-editor-input--h${block.level}` : ''}${block.quoteDepth ? ' markdown-editor-input--quoted' : ''}`;
|
|
817
|
-
ui.style = `--depth: ${block.quoteDepth};${wrap ? '' : ` --field-width: ${Math.ceil((widest + 1) * metrics.charWidth * scale)}px;`}`;
|
|
752
|
+
let child: Node | null = node === content ? content.childNodes[offset] ?? null : node;
|
|
818
753
|
|
|
819
|
-
|
|
820
|
-
|
|
754
|
+
while (child && child.parentNode !== content) {
|
|
755
|
+
child = child.parentNode;
|
|
756
|
+
}
|
|
821
757
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
return metrics.padLeft + (active ? blocks[active.first].quoteDepth * metrics.quoteIndent : 0);
|
|
825
|
-
}
|
|
758
|
+
for (let at = child; at; at = at.nextSibling) {
|
|
759
|
+
let unit = shown.get(at as Element);
|
|
826
760
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
return;
|
|
761
|
+
if (unit) {
|
|
762
|
+
return contentStart(unit);
|
|
763
|
+
}
|
|
831
764
|
}
|
|
832
765
|
|
|
833
|
-
let
|
|
834
|
-
|
|
835
|
-
y = box.top + surface.clientTop - surface.scrollTop + metrics.padTop + layout.top(activeIndex),
|
|
836
|
-
end = geometry.rect(projection.toNative(clamp(to, active.from, active.to) - active.from)),
|
|
837
|
-
start = geometry.rect(projection.toNative(clamp(from, active.from, active.to) - active.from)),
|
|
838
|
-
lineHeight = geometry.lineHeight;
|
|
766
|
+
for (let at = child ? child.previousSibling : content.lastChild; at; at = at.previousSibling) {
|
|
767
|
+
let unit = shown.get(at as Element);
|
|
839
768
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
769
|
+
if (unit) {
|
|
770
|
+
return contentEnd(model.value, unit.block);
|
|
771
|
+
}
|
|
843
772
|
}
|
|
844
773
|
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
right = top === end.top ? end.left : geometry.width;
|
|
774
|
+
return null;
|
|
775
|
+
}
|
|
848
776
|
|
|
849
|
-
|
|
777
|
+
function editable(run: () => boolean) {
|
|
778
|
+
if (disposed || options.readonly || ime.busy()) {
|
|
779
|
+
return false;
|
|
850
780
|
}
|
|
781
|
+
|
|
782
|
+
capture();
|
|
783
|
+
revealFolds(true);
|
|
784
|
+
revealing = true;
|
|
785
|
+
|
|
786
|
+
let changed = run();
|
|
787
|
+
|
|
788
|
+
schedule();
|
|
789
|
+
|
|
790
|
+
return changed;
|
|
851
791
|
}
|
|
852
792
|
|
|
853
|
-
//
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
793
|
+
// Backspace or Delete at a single selection: a selection goes, a caret takes the visible character beside it,
|
|
794
|
+
// and at a block's edge the blocks join, or the empty line or rule beside it goes.
|
|
795
|
+
function erase(backward: boolean) {
|
|
796
|
+
if (model.selections.length > 1) {
|
|
797
|
+
return backward ? markdownCommand(model, 'backspace', kindAt) : deleteCharacter(model, false);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
current();
|
|
801
|
+
|
|
802
|
+
let { end, start } = model.selection;
|
|
803
|
+
|
|
804
|
+
if (start !== end) {
|
|
805
|
+
return remove({ from: start, to: end });
|
|
857
806
|
}
|
|
858
807
|
|
|
859
|
-
let
|
|
808
|
+
let index = blockAt(blocks, start),
|
|
809
|
+
block = blocks[index],
|
|
810
|
+
unit = layout.units[layout.indexOf(start)],
|
|
811
|
+
span = unit && draw.segments(unit) ? reach(model.value, spans(unit), start, backward) : null;
|
|
812
|
+
|
|
813
|
+
if (span) {
|
|
814
|
+
// The info string and the code under it never merge, nor does code run into a fence.
|
|
815
|
+
if ((block.kind === 'fence' || block.kind === 'frontmatter') && span.from < block.contentFrom && span.to >= block.contentFrom) {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return remove(span);
|
|
820
|
+
}
|
|
860
821
|
|
|
861
|
-
|
|
862
|
-
model.select({ direction: head < anchor ? 'backward' : 'forward', end: Math.max(anchor, head), start: Math.min(anchor, head) });
|
|
863
|
-
activate(true);
|
|
822
|
+
return backward ? joinBackward(index, start) : joinForward(index, start);
|
|
864
823
|
}
|
|
865
824
|
|
|
866
825
|
function inlineOf(block: MarkdownBlock) {
|
|
@@ -880,34 +839,79 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
880
839
|
return entry.tokens;
|
|
881
840
|
}
|
|
882
841
|
|
|
883
|
-
//
|
|
884
|
-
function
|
|
885
|
-
|
|
842
|
+
// Drops a block's drawing, for when something other than this view wrote into it.
|
|
843
|
+
function invalidate(offset: number) {
|
|
844
|
+
let block = blocks[blockAt(blocks, offset)];
|
|
845
|
+
|
|
846
|
+
if (block) {
|
|
847
|
+
units.folded.delete(block);
|
|
848
|
+
units.plain.delete(block);
|
|
849
|
+
units.raw.delete(block);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
unitsDirty = true;
|
|
853
|
+
schedule();
|
|
886
854
|
}
|
|
887
855
|
|
|
888
|
-
//
|
|
889
|
-
|
|
890
|
-
|
|
856
|
+
// Backspace at the start of a block's text: a list or quote marker goes first, then a heading's, then the break to
|
|
857
|
+
// the block before.
|
|
858
|
+
function joinBackward(index: number, offset: number) {
|
|
859
|
+
if (markdownBackspace(model)) {
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
let block = blocks[index],
|
|
864
|
+
previous = blocks[index - 1];
|
|
865
|
+
|
|
866
|
+
if (block.kind === 'heading' && block.contentFrom > block.from && offset <= block.contentFrom) {
|
|
867
|
+
let from = block.from + quotePrefix(model.lineText(model.lineAt(block.from))).length;
|
|
868
|
+
|
|
869
|
+
return model.replace(from, block.contentFrom, '', { selection: { start: from }, source: 'markdown-unwrap' });
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (!previous) {
|
|
891
873
|
return false;
|
|
892
874
|
}
|
|
893
875
|
|
|
894
|
-
|
|
876
|
+
if (previous.kind === 'blank' || previous.kind === 'rule') {
|
|
877
|
+
return model.replace(previous.from, previous.to, '', {
|
|
878
|
+
selection: { start: offset - (previous.to - previous.from) },
|
|
879
|
+
source: 'delete'
|
|
880
|
+
});
|
|
881
|
+
}
|
|
895
882
|
|
|
896
|
-
if (
|
|
897
|
-
return
|
|
883
|
+
if (JOINABLE.has(previous.kind) && (JOINABLE.has(block.kind) || block.kind === 'blank')) {
|
|
884
|
+
return model.replace(previous.contentTo, offset, '', { selection: { start: previous.contentTo }, source: 'delete' });
|
|
898
885
|
}
|
|
899
886
|
|
|
900
|
-
|
|
901
|
-
|
|
887
|
+
model.select({ start: previous.contentTo });
|
|
888
|
+
|
|
889
|
+
return false;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Delete at the end of a block's text: the empty line or rule after it goes, or the next block's text joins it.
|
|
893
|
+
function joinForward(index: number, offset: number) {
|
|
894
|
+
let block = blocks[index],
|
|
895
|
+
next = blocks[index + 1];
|
|
896
|
+
|
|
897
|
+
if (!next) {
|
|
898
|
+
return false;
|
|
902
899
|
}
|
|
903
900
|
|
|
904
|
-
|
|
901
|
+
if (next.kind === 'blank' || next.kind === 'rule') {
|
|
902
|
+
return model.replace(next.from, next.to, '', { selection: { start: offset }, source: 'delete' });
|
|
903
|
+
}
|
|
905
904
|
|
|
906
|
-
if (
|
|
907
|
-
return
|
|
905
|
+
if (JOINABLE.has(next.kind) && (JOINABLE.has(block.kind) || block.kind === 'blank')) {
|
|
906
|
+
return model.replace(offset, next.contentFrom, '', { selection: { start: offset }, source: 'delete' });
|
|
908
907
|
}
|
|
909
908
|
|
|
910
|
-
return
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// The block kind at a source offset, for commands that leave literal source alone.
|
|
913
|
+
function kindAt(offset: number): Kind | undefined {
|
|
914
|
+
return blocks[blockAt(blocks, offset)]?.kind;
|
|
911
915
|
}
|
|
912
916
|
|
|
913
917
|
// Source lines [first, last] unit 'index' covers.
|
|
@@ -919,12 +923,18 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
919
923
|
return { first: model.lineAt(from), last: model.lineAt(Math.max(from, to)) };
|
|
920
924
|
}
|
|
921
925
|
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
926
|
+
function linking(e: KeyboardEvent | PointerEvent) {
|
|
927
|
+
let next = apple ? e.metaKey : e.ctrlKey;
|
|
928
|
+
|
|
929
|
+
if (ui.linking !== next) {
|
|
930
|
+
ui.linking = next;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
925
933
|
|
|
926
|
-
|
|
927
|
-
|
|
934
|
+
// Marks which drawn units show a fold marker; only neighbouring blocks are looked at.
|
|
935
|
+
function markFoldable() {
|
|
936
|
+
for (let i = 0, n = rendered.length; i < n; i++) {
|
|
937
|
+
let unit = rendered[i],
|
|
928
938
|
state = draw.state(unit);
|
|
929
939
|
|
|
930
940
|
if (!state) {
|
|
@@ -941,7 +951,7 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
941
951
|
|
|
942
952
|
// Reads computed geometry and colors on connect, resize, font load or refresh; never per frame.
|
|
943
953
|
function measure() {
|
|
944
|
-
if (disposed || !host || !ruler || !surface
|
|
954
|
+
if (disposed || !host || !ruler || !surface) {
|
|
945
955
|
return;
|
|
946
956
|
}
|
|
947
957
|
|
|
@@ -980,7 +990,6 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
980
990
|
width: metrics.width
|
|
981
991
|
})) {
|
|
982
992
|
whole = null;
|
|
983
|
-
fieldGeometry();
|
|
984
993
|
map.invalidate();
|
|
985
994
|
unitsDirty = true;
|
|
986
995
|
}
|
|
@@ -988,17 +997,39 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
988
997
|
schedule();
|
|
989
998
|
}
|
|
990
999
|
|
|
1000
|
+
// A single caret moves through the drawn text the way the browser lays it out; several move by source.
|
|
991
1001
|
function moveSelection(key: string, extend: boolean, word = false, add = false, boundary = false) {
|
|
1002
|
+
let native = content?.ownerDocument.getSelection();
|
|
1003
|
+
|
|
1004
|
+
if (
|
|
1005
|
+
native &&
|
|
1006
|
+
model.selections.length === 1 &&
|
|
1007
|
+
!add &&
|
|
1008
|
+
!key.startsWith('Page') &&
|
|
1009
|
+
content?.ownerDocument.activeElement === content
|
|
1010
|
+
) {
|
|
1011
|
+
let vertical = VERTICAL.test(key);
|
|
1012
|
+
|
|
1013
|
+
native.modify(
|
|
1014
|
+
extend ? 'extend' : 'move',
|
|
1015
|
+
key === 'ArrowLeft' || key === 'ArrowUp' || key === 'Home' ? 'backward' : 'forward',
|
|
1016
|
+
vertical ? 'line' : key === 'Home' || key === 'End' ? (boundary ? 'documentboundary' : 'lineboundary') : word ? 'word' : 'character'
|
|
1017
|
+
);
|
|
1018
|
+
capture();
|
|
1019
|
+
start(true);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
992
1023
|
if (!VERTICAL.test(key) || boundary || add) {
|
|
993
1024
|
goals = [];
|
|
994
1025
|
}
|
|
995
1026
|
|
|
996
1027
|
if (!whole || whole.revision !== model.revision) {
|
|
997
|
-
let
|
|
1028
|
+
let text = new NativeText(model.value);
|
|
998
1029
|
|
|
999
1030
|
whole = {
|
|
1000
|
-
layout: new EditorLayout(
|
|
1001
|
-
projection:
|
|
1031
|
+
layout: new EditorLayout(text, model.value, Number.MAX_SAFE_INTEGER, metrics.lineHeight, metrics.charWidth, options.tabSize ?? 4, false),
|
|
1032
|
+
projection: text,
|
|
1002
1033
|
revision: model.revision
|
|
1003
1034
|
};
|
|
1004
1035
|
}
|
|
@@ -1018,7 +1049,7 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1018
1049
|
}
|
|
1019
1050
|
|
|
1020
1051
|
function offsetAt(clientX: number, clientY: number) {
|
|
1021
|
-
if (disposed || !surface) {
|
|
1052
|
+
if (disposed || !surface || !host) {
|
|
1022
1053
|
return null;
|
|
1023
1054
|
}
|
|
1024
1055
|
|
|
@@ -1030,22 +1061,22 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1030
1061
|
return null;
|
|
1031
1062
|
}
|
|
1032
1063
|
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
}
|
|
1064
|
+
let doc = host.ownerDocument as Document & {
|
|
1065
|
+
caretPositionFromPoint?: (x: number, y: number) => { offset: number; offsetNode: Node } | null;
|
|
1066
|
+
},
|
|
1067
|
+
point = doc.caretPositionFromPoint?.(clientX, clientY),
|
|
1068
|
+
range = point ? null : doc.caretRangeFromPoint?.(clientX, clientY),
|
|
1069
|
+
node = point?.offsetNode ?? range?.startContainer ?? null;
|
|
1040
1070
|
|
|
1041
|
-
|
|
1071
|
+
if (node && content?.contains(node)) {
|
|
1072
|
+
let offset = sourceAt(node, point?.offset ?? range?.startOffset ?? 0);
|
|
1042
1073
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1074
|
+
if (offset !== null) {
|
|
1075
|
+
return offset;
|
|
1076
|
+
}
|
|
1045
1077
|
}
|
|
1046
1078
|
|
|
1047
|
-
|
|
1048
|
-
let under = host!.ownerDocument.elementFromPoint(clientX, clientY)?.closest('.markdown-editor-block'),
|
|
1079
|
+
let under = doc.elementFromPoint(clientX, clientY)?.closest('.markdown-editor-block'),
|
|
1049
1080
|
unit = under && shown.get(under);
|
|
1050
1081
|
|
|
1051
1082
|
if (unit) {
|
|
@@ -1059,7 +1090,7 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1059
1090
|
|
|
1060
1091
|
// Whether a drawn unit shows a fold marker; only neighbouring blocks are looked at.
|
|
1061
1092
|
function opens(unit: Unit) {
|
|
1062
|
-
return options.fold
|
|
1093
|
+
return !!options.fold && !unit.continuation && !unit.raw && !unit.fold && foldable(blocks, blockAt(blocks, unit.block.from), model.value);
|
|
1063
1094
|
}
|
|
1064
1095
|
|
|
1065
1096
|
function paint() {
|
|
@@ -1071,52 +1102,17 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1071
1102
|
|
|
1072
1103
|
current();
|
|
1073
1104
|
|
|
1074
|
-
if (estimated && textarea && editing) {
|
|
1075
|
-
// The arithmetic only estimated some of the field's rows; it takes the height the DOM drew instead.
|
|
1076
|
-
flush();
|
|
1077
|
-
|
|
1078
|
-
let height = textarea.scrollHeight;
|
|
1079
|
-
|
|
1080
|
-
if (height > ui.height + 0.5) {
|
|
1081
|
-
ui.height = height;
|
|
1082
|
-
layout.measure(activeUnit, height);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
estimated = false;
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
1105
|
if (revealing) {
|
|
1089
1106
|
revealing = false;
|
|
1090
1107
|
reveal();
|
|
1091
1108
|
}
|
|
1092
1109
|
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
split = a < 0 ? n : a,
|
|
1097
|
-
s1 = Math.min(view.start, split),
|
|
1098
|
-
e1 = Math.min(view.end, split),
|
|
1099
|
-
s2 = Math.max(view.start, split + 1),
|
|
1100
|
-
e2 = Math.max(view.end, s2);
|
|
1101
|
-
|
|
1102
|
-
if (a < 0) {
|
|
1103
|
-
s2 = e2 = n;
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
if (view.start > split) {
|
|
1107
|
-
s1 = e1 = split;
|
|
1110
|
+
// An input method owns the drawn text until it commits.
|
|
1111
|
+
if (!ime.busy()) {
|
|
1112
|
+
present();
|
|
1108
1113
|
}
|
|
1109
1114
|
|
|
1110
|
-
|
|
1111
|
-
assign(listed.after, layout.units.slice(s2, e2));
|
|
1112
|
-
ui.top = layout.top(s1);
|
|
1113
|
-
ui.between = a < 0 ? 0 : layout.top(split) - layout.top(e1);
|
|
1114
|
-
ui.next = a < 0 ? 0 : layout.top(s2) - layout.top(a + 1);
|
|
1115
|
-
ui.bottom = layout.total - layout.top(a < 0 ? e1 : e2);
|
|
1116
|
-
ui.foldable = !!active && options.fold !== false && foldable(blocks, active.first, model.value);
|
|
1117
|
-
|
|
1118
|
-
markFoldable(s1, e1);
|
|
1119
|
-
markFoldable(s2, e2);
|
|
1115
|
+
markFoldable();
|
|
1120
1116
|
// Rectangles come from the drawn text; units this frame added draw in the list's own pass, queued before.
|
|
1121
1117
|
queueMicrotask(() => {
|
|
1122
1118
|
if (!disposed) {
|
|
@@ -1140,67 +1136,104 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1140
1136
|
}
|
|
1141
1137
|
}
|
|
1142
1138
|
|
|
1143
|
-
//
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
},
|
|
1148
|
-
point = doc.caretPositionFromPoint?.(clientX, clientY),
|
|
1149
|
-
range = point ? null : doc.caretRangeFromPoint?.(clientX, clientY);
|
|
1139
|
+
// Puts the native selection where the document's is, unless it already maps there: moving it anyway would lose
|
|
1140
|
+
// the column the browser keeps for vertical moves.
|
|
1141
|
+
function place() {
|
|
1142
|
+
unplaced = false;
|
|
1150
1143
|
|
|
1151
|
-
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
// Draws the window at the scroll position now rather than next frame, so the surface is as tall as its content
|
|
1155
|
-
// before the position applies.
|
|
1156
|
-
function present() {
|
|
1157
|
-
if (disposed || !surface) {
|
|
1144
|
+
if (disposed || !content || ime.busy() || content.ownerDocument.activeElement !== content) {
|
|
1158
1145
|
return;
|
|
1159
1146
|
}
|
|
1160
1147
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1148
|
+
let native = content.ownerDocument.getSelection();
|
|
1149
|
+
|
|
1150
|
+
if (!native) {
|
|
1151
|
+
return;
|
|
1163
1152
|
}
|
|
1164
1153
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
}
|
|
1154
|
+
let selection = model.selection,
|
|
1155
|
+
backward = selection.direction === 'backward',
|
|
1156
|
+
anchorAt = backward ? selection.end : selection.start,
|
|
1157
|
+
headAt = backward ? selection.start : selection.end;
|
|
1170
1158
|
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1159
|
+
if (
|
|
1160
|
+
native.anchorNode &&
|
|
1161
|
+
native.focusNode &&
|
|
1162
|
+
content.contains(native.anchorNode) &&
|
|
1163
|
+
content.contains(native.focusNode) &&
|
|
1164
|
+
sourceAt(native.anchorNode, native.anchorOffset) === anchorAt &&
|
|
1165
|
+
sourceAt(native.focusNode, native.focusOffset) === headAt
|
|
1166
|
+
) {
|
|
1175
1167
|
return;
|
|
1176
1168
|
}
|
|
1177
1169
|
|
|
1178
|
-
let
|
|
1170
|
+
let anchor = domAt(anchorAt),
|
|
1171
|
+
focus = anchorAt === headAt ? anchor : domAt(headAt);
|
|
1179
1172
|
|
|
1180
|
-
|
|
1173
|
+
// A unit drawn this frame connects after it; the selection goes there next frame.
|
|
1174
|
+
if (!anchor || !focus) {
|
|
1175
|
+
unplaced = true;
|
|
1176
|
+
schedule();
|
|
1181
1177
|
return;
|
|
1182
1178
|
}
|
|
1183
1179
|
|
|
1184
|
-
|
|
1180
|
+
native.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);
|
|
1181
|
+
placed = { anchor, focus };
|
|
1182
|
+
unplaced = false;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// Draws the units near the viewport and the ones holding the selection's ends, with spacers for the rest, then
|
|
1186
|
+
// puts the native selection back.
|
|
1187
|
+
function present() {
|
|
1188
|
+
let n = layout.units.length,
|
|
1189
|
+
view = layout.window(metrics.scrollTop - metrics.padTop, metrics.clientHeight),
|
|
1190
|
+
selection = model.selection,
|
|
1191
|
+
runs: Span[] = [];
|
|
1192
|
+
|
|
1193
|
+
if (n) {
|
|
1194
|
+
let anchor = layout.indexOf(selection.direction === 'backward' ? selection.end : selection.start),
|
|
1195
|
+
head = layout.indexOf(selection.direction === 'backward' ? selection.start : selection.end);
|
|
1196
|
+
|
|
1197
|
+
runs.push({ from: anchor, to: anchor + 1 }, { from: head, to: head + 1 }, { from: view.start, to: view.end });
|
|
1198
|
+
runs.sort((a, b) => a.from - b.from);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
let cursor = 0,
|
|
1202
|
+
gap = 0,
|
|
1203
|
+
next: Item[] = [];
|
|
1185
1204
|
|
|
1186
|
-
let
|
|
1187
|
-
|
|
1205
|
+
let space = (height: number) => {
|
|
1206
|
+
let item = gaps[gap++] ??= reactive({ height: 0 });
|
|
1188
1207
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
head: offset
|
|
1208
|
+
item.height = height;
|
|
1209
|
+
next.push(item);
|
|
1192
1210
|
};
|
|
1193
1211
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1212
|
+
for (let i = 0, m = runs.length; i < m; i++) {
|
|
1213
|
+
let { from, to } = runs[i];
|
|
1214
|
+
|
|
1215
|
+
from = Math.max(from, cursor);
|
|
1216
|
+
|
|
1217
|
+
if (to <= from) {
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
if (from > cursor || !next.length) {
|
|
1222
|
+
space(layout.top(from) - layout.top(cursor));
|
|
1197
1223
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1224
|
+
|
|
1225
|
+
for (let j = from; j < to; j++) {
|
|
1226
|
+
next.push(layout.units[j]);
|
|
1200
1227
|
}
|
|
1228
|
+
|
|
1229
|
+
cursor = to;
|
|
1201
1230
|
}
|
|
1202
1231
|
|
|
1203
|
-
|
|
1232
|
+
space(layout.total - layout.top(cursor));
|
|
1233
|
+
assign(items, next);
|
|
1234
|
+
rendered = next.filter((item): item is Unit => 'block' in item);
|
|
1235
|
+
flush();
|
|
1236
|
+
place();
|
|
1204
1237
|
}
|
|
1205
1238
|
|
|
1206
1239
|
function rangeRects(from: number, to: number) {
|
|
@@ -1210,17 +1243,10 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1210
1243
|
last = layout.indexOf(to);
|
|
1211
1244
|
|
|
1212
1245
|
for (let i = first; i <= last && out.length < DECORATIONS; i++) {
|
|
1213
|
-
let
|
|
1246
|
+
let rects = textRects(units[i], from, to);
|
|
1214
1247
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
}
|
|
1218
|
-
else {
|
|
1219
|
-
let rects = textRects(unit, from, to);
|
|
1220
|
-
|
|
1221
|
-
for (let j = 0, n = rects.length; j < n; j++) {
|
|
1222
|
-
out.push(rects[j]);
|
|
1223
|
-
}
|
|
1248
|
+
for (let j = 0, n = rects.length; j < n; j++) {
|
|
1249
|
+
out.push(rects[j]);
|
|
1224
1250
|
}
|
|
1225
1251
|
}
|
|
1226
1252
|
|
|
@@ -1241,28 +1267,7 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1241
1267
|
}
|
|
1242
1268
|
}
|
|
1243
1269
|
|
|
1244
|
-
|
|
1245
|
-
activeUnit.block = blocks[active.first];
|
|
1246
|
-
}
|
|
1247
|
-
|
|
1248
|
-
let list = arrange(
|
|
1249
|
-
blocks,
|
|
1250
|
-
model.value,
|
|
1251
|
-
options.fold === false ? [] : folds,
|
|
1252
|
-
units,
|
|
1253
|
-
active && editing ? { first: active.first, last: active.last, unit: activeUnit } : null,
|
|
1254
|
-
raw ? (index) => raw.has(index) : null
|
|
1255
|
-
);
|
|
1256
|
-
|
|
1257
|
-
layout.set(list, model.value);
|
|
1258
|
-
activeIndex = -1;
|
|
1259
|
-
|
|
1260
|
-
if (active && editing) {
|
|
1261
|
-
let index = layout.indexOf(active.from);
|
|
1262
|
-
|
|
1263
|
-
activeIndex = list[index] === activeUnit ? index : list.indexOf(activeUnit);
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1270
|
+
layout.set(arrange(blocks, model.value, options.fold ? folds : [], units, raw ? (index) => raw.has(index) : null), model.value);
|
|
1266
1271
|
unitsDirty = false;
|
|
1267
1272
|
}
|
|
1268
1273
|
|
|
@@ -1289,85 +1294,18 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1289
1294
|
unitsDirty = true;
|
|
1290
1295
|
}
|
|
1291
1296
|
|
|
1292
|
-
//
|
|
1293
|
-
function
|
|
1294
|
-
|
|
1295
|
-
return;
|
|
1296
|
-
}
|
|
1297
|
+
// Removes source, widened to take any delimiters it leaves empty.
|
|
1298
|
+
function remove(span: Span) {
|
|
1299
|
+
let block = blocks[blockAt(blocks, span.from)];
|
|
1297
1300
|
|
|
1298
|
-
if (
|
|
1299
|
-
|
|
1300
|
-
active = null;
|
|
1301
|
-
geometry = null;
|
|
1302
|
-
unitsDirty = true;
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
|
-
return;
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
let selection = model.selection,
|
|
1309
|
-
first = blockAt(blocks, selection.start),
|
|
1310
|
-
last = blockAt(blocks, selection.end) + 1;
|
|
1311
|
-
|
|
1312
|
-
// A drag inside the field never shrinks it under the pointer.
|
|
1313
|
-
if (pinned && active && active.last <= blocks.length) {
|
|
1314
|
-
first = Math.min(first, active.first);
|
|
1315
|
-
last = Math.max(last, active.last);
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
let from = blocks[first].from,
|
|
1319
|
-
to = Math.max(selection.end, contentEnd(model.value, blocks[last - 1]));
|
|
1320
|
-
|
|
1321
|
-
if (!active || active.first !== first || active.last !== last) {
|
|
1322
|
-
unitsDirty = true;
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
|
-
active = { first, from, last, to };
|
|
1326
|
-
|
|
1327
|
-
let text = model.value.slice(from, to),
|
|
1328
|
-
shaped = fieldSource !== text || !geometry;
|
|
1329
|
-
|
|
1330
|
-
if (fieldSource !== text) {
|
|
1331
|
-
fieldSource = text;
|
|
1332
|
-
projection = new NativeText(text);
|
|
1333
|
-
}
|
|
1334
|
-
|
|
1335
|
-
if (shaped || activeUnit.block !== blocks[first]) {
|
|
1336
|
-
activeUnit.block = blocks[first];
|
|
1337
|
-
fieldGeometry();
|
|
1301
|
+
if (block && JOINABLE.has(block.kind) && span.to <= block.to) {
|
|
1302
|
+
span = widen(model.value, inlineOf(block), block.from, span);
|
|
1338
1303
|
}
|
|
1339
1304
|
|
|
1340
|
-
|
|
1341
|
-
return;
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
if (written !== projection.value) {
|
|
1345
|
-
writing = true;
|
|
1346
|
-
|
|
1347
|
-
try {
|
|
1348
|
-
writeField(textarea, written, projection.value, null);
|
|
1349
|
-
}
|
|
1350
|
-
finally {
|
|
1351
|
-
writing = false;
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
|
-
written = projection.value;
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
let nativeEnd = projection.toNative(clamp(selection.end - from, 0, text.length)),
|
|
1358
|
-
nativeStart = projection.toNative(clamp(selection.start - from, 0, text.length));
|
|
1359
|
-
|
|
1360
|
-
if (
|
|
1361
|
-
textarea.selectionStart !== nativeStart ||
|
|
1362
|
-
textarea.selectionEnd !== nativeEnd ||
|
|
1363
|
-
(nativeStart !== nativeEnd && selection.direction !== 'none' && textarea.selectionDirection !== selection.direction)
|
|
1364
|
-
) {
|
|
1365
|
-
textarea.setSelectionRange(nativeStart, nativeEnd, selection.direction);
|
|
1366
|
-
}
|
|
1305
|
+
return model.replace(span.from, span.to, '', { group: 'delete', selection: { start: span.from }, source: 'delete' });
|
|
1367
1306
|
}
|
|
1368
1307
|
|
|
1369
|
-
// Brings the blocks up to the document: only from the edited block until block boundaries line up again.
|
|
1370
|
-
// when only blocks the field stands for changed, so the units stay as they are.
|
|
1308
|
+
// Brings the blocks up to the document: only from the edited block until block boundaries line up again.
|
|
1371
1309
|
function reparseBlocks() {
|
|
1372
1310
|
let deltas = model.deltas(parsed),
|
|
1373
1311
|
previous = length;
|
|
@@ -1379,11 +1317,11 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1379
1317
|
blocks = parseMarkdown(model);
|
|
1380
1318
|
map.invalidate();
|
|
1381
1319
|
references();
|
|
1382
|
-
return
|
|
1320
|
+
return;
|
|
1383
1321
|
}
|
|
1384
1322
|
|
|
1385
1323
|
if (!deltas.length) {
|
|
1386
|
-
return
|
|
1324
|
+
return;
|
|
1387
1325
|
}
|
|
1388
1326
|
|
|
1389
1327
|
let end = -1,
|
|
@@ -1429,7 +1367,7 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1429
1367
|
if (changed) {
|
|
1430
1368
|
map.invalidate();
|
|
1431
1369
|
references();
|
|
1432
|
-
return
|
|
1370
|
+
return;
|
|
1433
1371
|
}
|
|
1434
1372
|
|
|
1435
1373
|
// Edits that kept the line count only redraw their lines in the minimap.
|
|
@@ -1439,20 +1377,6 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1439
1377
|
else {
|
|
1440
1378
|
map.invalidate(start, end);
|
|
1441
1379
|
}
|
|
1442
|
-
|
|
1443
|
-
return !!active &&
|
|
1444
|
-
editing &&
|
|
1445
|
-
splice.dropped.length === splice.inserted &&
|
|
1446
|
-
splice.start >= active.first &&
|
|
1447
|
-
splice.start + splice.inserted <= active.last;
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
function reset() {
|
|
1451
|
-
before = null;
|
|
1452
|
-
beforeRanges = [];
|
|
1453
|
-
beforeSelection = undefined;
|
|
1454
|
-
inputData = null;
|
|
1455
|
-
inputType = '';
|
|
1456
1380
|
}
|
|
1457
1381
|
|
|
1458
1382
|
// Heights the DOM drew for units. Units above the viewport that changed move the view by as much, so it stays on
|
|
@@ -1497,28 +1421,29 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1497
1421
|
}
|
|
1498
1422
|
}
|
|
1499
1423
|
|
|
1500
|
-
// Writes the field again whatever it holds.
|
|
1501
|
-
function resync() {
|
|
1502
|
-
written = '';
|
|
1503
|
-
refresh();
|
|
1504
|
-
schedule();
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
1424
|
// Scrolls the primary selection's head into view.
|
|
1508
1425
|
function reveal() {
|
|
1509
1426
|
let selection = model.selection,
|
|
1510
1427
|
head = selection.direction === 'backward' ? selection.start : selection.end,
|
|
1511
|
-
|
|
1512
|
-
|
|
1428
|
+
index = layout.indexOf(head),
|
|
1429
|
+
unit = layout.units[index];
|
|
1430
|
+
|
|
1431
|
+
if (!unit || !surface) {
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1513
1434
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1435
|
+
let height = Math.min(layout.height(unit), metrics.clientHeight),
|
|
1436
|
+
y = metrics.padTop + layout.top(index),
|
|
1437
|
+
rect = draw.segments(unit) ? textRects(unit, head, head)[0] : undefined;
|
|
1438
|
+
|
|
1439
|
+
if (rect) {
|
|
1440
|
+
let box = surface.getBoundingClientRect();
|
|
1516
1441
|
|
|
1517
1442
|
height = rect.height;
|
|
1518
|
-
y =
|
|
1443
|
+
y = rect.top - box.top - surface.clientTop + surface.scrollTop;
|
|
1519
1444
|
|
|
1520
|
-
if (options.wrap === false
|
|
1521
|
-
let x =
|
|
1445
|
+
if (options.wrap === false) {
|
|
1446
|
+
let x = rect.left - box.left - surface.clientLeft + surface.scrollLeft;
|
|
1522
1447
|
|
|
1523
1448
|
if (x < metrics.scrollLeft + metrics.padLeft) {
|
|
1524
1449
|
surface.scrollLeft = metrics.scrollLeft = Math.max(0, x - metrics.padLeft);
|
|
@@ -1528,17 +1453,6 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1528
1453
|
}
|
|
1529
1454
|
}
|
|
1530
1455
|
}
|
|
1531
|
-
else {
|
|
1532
|
-
let index = layout.indexOf(head),
|
|
1533
|
-
unit = layout.units[index];
|
|
1534
|
-
|
|
1535
|
-
if (!unit) {
|
|
1536
|
-
return;
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
y = metrics.padTop + layout.top(index);
|
|
1540
|
-
height = Math.min(layout.height(unit), metrics.clientHeight);
|
|
1541
|
-
}
|
|
1542
1456
|
|
|
1543
1457
|
if (y < metrics.scrollTop) {
|
|
1544
1458
|
scrollTo(y - metrics.padTop);
|
|
@@ -1610,12 +1524,92 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1610
1524
|
schedule();
|
|
1611
1525
|
}
|
|
1612
1526
|
|
|
1613
|
-
//
|
|
1614
|
-
function
|
|
1615
|
-
|
|
1616
|
-
|
|
1527
|
+
// A space after '[]' at the start of a line or a list item's text makes it a task, as '- ' makes a list item.
|
|
1528
|
+
function shortcut(text: string) {
|
|
1529
|
+
let { end, start } = model.selection;
|
|
1530
|
+
|
|
1531
|
+
if (text !== ' ' || start !== end || model.selections.length > 1 || LITERAL.has(kindAt(start)!)) {
|
|
1532
|
+
return false;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
let from = model.lineStart(model.lineAt(start)),
|
|
1536
|
+
before = model.value.slice(from, start),
|
|
1537
|
+
at = from + quotePrefix(before).length,
|
|
1538
|
+
rest = model.value.slice(at, start);
|
|
1539
|
+
|
|
1540
|
+
if (TASK.test(rest)) {
|
|
1541
|
+
return model.replace(at, start, '- [ ] ', { selection: { start: at + 6 }, source: 'markdown-task' });
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (ITEM_TASK.test(rest)) {
|
|
1545
|
+
let bracket = at + rest.lastIndexOf('[');
|
|
1546
|
+
|
|
1547
|
+
return model.replace(bracket, start, '[ ] ', { selection: { start: bracket + 4 }, source: 'markdown-task' });
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
return false;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// Source offset of a native point: in a run of drawn text directly, otherwise from the runs of its unit around it.
|
|
1554
|
+
function sourceAt(node: Node, offset: number): number | null {
|
|
1555
|
+
let direct = draw.offset(node, offset);
|
|
1556
|
+
|
|
1557
|
+
if (direct !== null) {
|
|
1558
|
+
return direct;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
let element = (node.nodeType === 1 ? node as Element : node.parentElement)?.closest('.markdown-editor-block'),
|
|
1562
|
+
unit = element ? shown.get(element) : undefined;
|
|
1563
|
+
|
|
1564
|
+
if (!unit) {
|
|
1565
|
+
return edgeAt(node, offset);
|
|
1617
1566
|
}
|
|
1618
1567
|
|
|
1568
|
+
let range = node.ownerDocument!.createRange(),
|
|
1569
|
+
segments = draw.segments(unit) ?? [],
|
|
1570
|
+
after: number | null = null,
|
|
1571
|
+
before: number | null = null;
|
|
1572
|
+
|
|
1573
|
+
range.setStart(node, offset);
|
|
1574
|
+
|
|
1575
|
+
for (let i = 0, n = segments.length; i < n; i++) {
|
|
1576
|
+
let segment = segments[i];
|
|
1577
|
+
|
|
1578
|
+
if (range.comparePoint(segment.node, segment.node.length) <= 0) {
|
|
1579
|
+
before = segment.block.from + segment.from + segment.node.length;
|
|
1580
|
+
}
|
|
1581
|
+
else if (range.comparePoint(segment.node, 0) >= 0) {
|
|
1582
|
+
after = segment.block.from + segment.from;
|
|
1583
|
+
break;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// Just past a line break the caret is on the next row.
|
|
1588
|
+
let broken = node.nodeType === 3 && offset > 0 && LINE_BREAK.test(node.textContent![offset - 1]);
|
|
1589
|
+
|
|
1590
|
+
if (after !== null && (broken || before === null)) {
|
|
1591
|
+
return after;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
return before ?? contentStart(unit);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
// Absolute source runs of a drawn unit's text, in order.
|
|
1598
|
+
function spans(unit: Unit) {
|
|
1599
|
+
let out: Span[] = [],
|
|
1600
|
+
segments = draw.segments(unit) ?? [];
|
|
1601
|
+
|
|
1602
|
+
for (let i = 0, n = segments.length; i < n; i++) {
|
|
1603
|
+
let from = segments[i].block.from + segments[i].from;
|
|
1604
|
+
|
|
1605
|
+
out.push({ from, to: from + segments[i].node.length });
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
return out;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
// Shows the selection once a gesture or command set it, revealing it when asked.
|
|
1612
|
+
function start(reveal: boolean) {
|
|
1619
1613
|
revealing ||= reveal;
|
|
1620
1614
|
schedule();
|
|
1621
1615
|
}
|
|
@@ -1635,6 +1629,7 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1635
1629
|
if (change.textChanged) {
|
|
1636
1630
|
ime.conflict();
|
|
1637
1631
|
textChanged(change);
|
|
1632
|
+
ui.empty = !snapshot.value;
|
|
1638
1633
|
}
|
|
1639
1634
|
|
|
1640
1635
|
if (change.selectionChanged) {
|
|
@@ -1644,8 +1639,12 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1644
1639
|
secondary = selections.length > 1;
|
|
1645
1640
|
}
|
|
1646
1641
|
|
|
1647
|
-
|
|
1648
|
-
|
|
1642
|
+
// Drawn at once, so the text under the caret is current before the next input reads the selection.
|
|
1643
|
+
if (frame) {
|
|
1644
|
+
cancelAnimationFrame(frame);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
paint();
|
|
1649
1648
|
|
|
1650
1649
|
if (change.selectionChanged) {
|
|
1651
1650
|
callbacks.onSelection?.(snapshot.selection, model.position());
|
|
@@ -1658,15 +1657,14 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1658
1657
|
}
|
|
1659
1658
|
|
|
1660
1659
|
function textChanged(change: Change) {
|
|
1661
|
-
|
|
1660
|
+
reparseBlocks();
|
|
1662
1661
|
|
|
1663
1662
|
if (folds.length) {
|
|
1664
1663
|
folds = outerFolds(mapFolds(folds, change.editBatches ?? [], model));
|
|
1665
|
-
local = false;
|
|
1666
1664
|
}
|
|
1667
1665
|
|
|
1668
1666
|
whole = null;
|
|
1669
|
-
unitsDirty
|
|
1667
|
+
unitsDirty = true;
|
|
1670
1668
|
}
|
|
1671
1669
|
|
|
1672
1670
|
// Client rectangles of [from, to) in a drawn unit's text; a collapsed range gives its caret.
|
|
@@ -1706,6 +1704,11 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1706
1704
|
return out;
|
|
1707
1705
|
}
|
|
1708
1706
|
|
|
1707
|
+
// Typed text, unless it completes a shortcut.
|
|
1708
|
+
function typed(text: string) {
|
|
1709
|
+
return editable(() => shortcut(text) || insertText(model, text, 'input'));
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1709
1712
|
let controller: MarkdownController = {
|
|
1710
1713
|
addNextOccurrence: (all = false) => {
|
|
1711
1714
|
if (disposed || ime.busy()) {
|
|
@@ -1753,12 +1756,16 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1753
1756
|
findNext: () => search.next(),
|
|
1754
1757
|
findPrevious: () => search.previous(),
|
|
1755
1758
|
focus: () => {
|
|
1756
|
-
if (!
|
|
1757
|
-
|
|
1759
|
+
if (disposed || !content) {
|
|
1760
|
+
return;
|
|
1758
1761
|
}
|
|
1762
|
+
|
|
1763
|
+
content.focus({ preventScroll: true });
|
|
1764
|
+
place();
|
|
1765
|
+
schedule();
|
|
1759
1766
|
},
|
|
1760
1767
|
fold: (line) => {
|
|
1761
|
-
if (disposed || ime.busy() || options.fold
|
|
1768
|
+
if (disposed || ime.busy() || !options.fold) {
|
|
1762
1769
|
return false;
|
|
1763
1770
|
}
|
|
1764
1771
|
|
|
@@ -1776,26 +1783,18 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1776
1783
|
model.select({ start: range.open ?? range.from });
|
|
1777
1784
|
}
|
|
1778
1785
|
|
|
1779
|
-
// A field showing the fold's own lines would hide nothing; the block shows folded instead.
|
|
1780
|
-
if (active && range.from < blocks[active.last - 1].to && range.to > active.from) {
|
|
1781
|
-
deactivate();
|
|
1782
|
-
textarea?.blur();
|
|
1783
|
-
}
|
|
1784
|
-
|
|
1785
1786
|
unitsDirty = true;
|
|
1786
1787
|
schedule();
|
|
1787
1788
|
|
|
1788
1789
|
return true;
|
|
1789
1790
|
},
|
|
1790
1791
|
foldAll: () => {
|
|
1791
|
-
if (disposed || ime.busy() || options.fold
|
|
1792
|
+
if (disposed || ime.busy() || !options.fold) {
|
|
1792
1793
|
return;
|
|
1793
1794
|
}
|
|
1794
1795
|
|
|
1795
1796
|
model.select({ start: 0 });
|
|
1796
1797
|
folds = outerFolds(markdownFolds(model, blocks));
|
|
1797
|
-
deactivate();
|
|
1798
|
-
textarea?.blur();
|
|
1799
1798
|
unitsDirty = true;
|
|
1800
1799
|
schedule();
|
|
1801
1800
|
},
|
|
@@ -1858,7 +1857,6 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1858
1857
|
refresh: () => {
|
|
1859
1858
|
if (!disposed) {
|
|
1860
1859
|
measure();
|
|
1861
|
-
fieldGeometry();
|
|
1862
1860
|
unitsDirty = true;
|
|
1863
1861
|
schedule();
|
|
1864
1862
|
}
|
|
@@ -1896,10 +1894,10 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1896
1894
|
return;
|
|
1897
1895
|
}
|
|
1898
1896
|
|
|
1899
|
-
let focused =
|
|
1897
|
+
let focused = !!content && host?.ownerDocument.activeElement === content;
|
|
1900
1898
|
|
|
1901
|
-
//
|
|
1902
|
-
|
|
1899
|
+
// A composition commits to the document it started in.
|
|
1900
|
+
ime.flush();
|
|
1903
1901
|
|
|
1904
1902
|
if (surface) {
|
|
1905
1903
|
visited.set(model, { folds, left: metrics.scrollLeft, revision: model.revision, top: metrics.scrollTop });
|
|
@@ -1908,10 +1906,20 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1908
1906
|
model = next;
|
|
1909
1907
|
adopt();
|
|
1910
1908
|
controller.setOptions(settings ?? {}, !!settings);
|
|
1911
|
-
|
|
1909
|
+
|
|
1910
|
+
if (frame) {
|
|
1911
|
+
cancelAnimationFrame(frame);
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
paint();
|
|
1915
|
+
|
|
1916
|
+
if (surface) {
|
|
1917
|
+
surface.scrollLeft = metrics.scrollLeft;
|
|
1918
|
+
surface.scrollTop = metrics.scrollTop;
|
|
1919
|
+
}
|
|
1912
1920
|
|
|
1913
1921
|
if (focused) {
|
|
1914
|
-
|
|
1922
|
+
controller.focus();
|
|
1915
1923
|
}
|
|
1916
1924
|
},
|
|
1917
1925
|
setOptions: (next, replace = false) => {
|
|
@@ -1926,9 +1934,9 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1926
1934
|
options.indent = ' ';
|
|
1927
1935
|
}
|
|
1928
1936
|
|
|
1937
|
+
ui.fold = !!options.fold;
|
|
1929
1938
|
ui.label = options.label ?? 'Markdown editor';
|
|
1930
1939
|
ui.minimap = !!options.minimap;
|
|
1931
|
-
ui.name = options.name ?? '';
|
|
1932
1940
|
ui.placeholder = options.placeholder ?? '';
|
|
1933
1941
|
ui.readonly = !!options.readonly;
|
|
1934
1942
|
ui.spellcheck = options.spellcheck ?? true;
|
|
@@ -1936,12 +1944,11 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1936
1944
|
ui.wrap = options.wrap !== false;
|
|
1937
1945
|
search.status();
|
|
1938
1946
|
|
|
1939
|
-
if (options.fold
|
|
1947
|
+
if (!options.fold) {
|
|
1940
1948
|
folds = [];
|
|
1941
1949
|
}
|
|
1942
1950
|
|
|
1943
1951
|
whole = null;
|
|
1944
|
-
fieldGeometry();
|
|
1945
1952
|
map.invalidate();
|
|
1946
1953
|
unitsDirty = true;
|
|
1947
1954
|
schedule();
|
|
@@ -1964,9 +1971,6 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
1964
1971
|
get state() {
|
|
1965
1972
|
return state as Snapshot;
|
|
1966
1973
|
},
|
|
1967
|
-
get textarea() {
|
|
1968
|
-
return textarea!;
|
|
1969
|
-
},
|
|
1970
1974
|
toggleComment: () => editable(() => toggleComment(model, options.lineComment ?? false, options.blockComment ?? ['<!--', '-->'])),
|
|
1971
1975
|
undo: () => editable(() => model.undo()),
|
|
1972
1976
|
undoSelection: () => {
|
|
@@ -2012,7 +2016,7 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
2012
2016
|
cache: () => syntaxCache(model, 'markdown'),
|
|
2013
2017
|
capture,
|
|
2014
2018
|
controller: controller as Controller,
|
|
2015
|
-
deleteVisible: (backwards, word) => deleteCharacter(model, backwards,
|
|
2019
|
+
deleteVisible: (backwards, word) => word ? deleteCharacter(model, backwards, true) : erase(backwards),
|
|
2016
2020
|
get document() {
|
|
2017
2021
|
return model;
|
|
2018
2022
|
},
|
|
@@ -2026,97 +2030,123 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
2026
2030
|
}
|
|
2027
2031
|
});
|
|
2028
2032
|
|
|
2033
|
+
// The editable text. The browser never edits it: each input is read as an edit of the source and prevented, the
|
|
2034
|
+
// blocks it touched draw again, and the selection is put back. Only an input method writes into it, until it ends.
|
|
2029
2035
|
let field: Attributes = {
|
|
2030
2036
|
'aria-describedby': help,
|
|
2031
2037
|
'aria-label': () => ui.label,
|
|
2032
|
-
|
|
2033
|
-
|
|
2038
|
+
'aria-multiline': 'true',
|
|
2039
|
+
'aria-placeholder': () => ui.placeholder,
|
|
2040
|
+
'aria-readonly': () => ui.readonly ? 'true' : 'false',
|
|
2041
|
+
autocapitalize: 'off',
|
|
2042
|
+
class: 'markdown-editor-content',
|
|
2043
|
+
contenteditable: () => ui.readonly ? 'false' : 'true',
|
|
2044
|
+
'data-placeholder': () => ui.placeholder,
|
|
2034
2045
|
onbeforeinput: (e: InputEvent) => {
|
|
2035
|
-
|
|
2046
|
+
let type = e.inputType;
|
|
2047
|
+
|
|
2048
|
+
if (ime.busy() || e.isComposing || type === 'insertCompositionText' || type === 'deleteCompositionText' || type === 'insertFromComposition') {
|
|
2036
2049
|
return;
|
|
2037
2050
|
}
|
|
2038
2051
|
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
before = { end: textarea.selectionEnd, length: textarea.textLength, start: textarea.selectionStart };
|
|
2043
|
-
beforeRanges = model.selections;
|
|
2044
|
-
beforeSelection = model.selection;
|
|
2045
|
-
inputData = e.data;
|
|
2046
|
-
inputType = e.inputType;
|
|
2047
|
-
|
|
2048
|
-
if (e.inputType === 'historyUndo' || e.inputType === 'historyRedo') {
|
|
2049
|
-
if (e.cancelable) {
|
|
2050
|
-
e.preventDefault();
|
|
2051
|
-
e.inputType === 'historyUndo' ? controller.undo() : controller.redo();
|
|
2052
|
-
}
|
|
2053
|
-
|
|
2052
|
+
// One the browser applies regardless is drawn over from the document once it lands.
|
|
2053
|
+
if (!e.cancelable) {
|
|
2054
2054
|
return;
|
|
2055
2055
|
}
|
|
2056
2056
|
|
|
2057
|
-
|
|
2057
|
+
e.preventDefault();
|
|
2058
|
+
|
|
2059
|
+
if (options.readonly) {
|
|
2058
2060
|
return;
|
|
2059
2061
|
}
|
|
2060
2062
|
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
e.preventDefault();
|
|
2064
|
-
editable(() => insertText(model, e.data!, 'input'));
|
|
2065
|
-
return;
|
|
2066
|
-
}
|
|
2063
|
+
capture();
|
|
2064
|
+
revealFolds(!type.startsWith('history'));
|
|
2067
2065
|
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2066
|
+
switch (type) {
|
|
2067
|
+
case 'deleteContentBackward':
|
|
2068
|
+
case 'deleteContentForward':
|
|
2069
|
+
editable(() => erase(type === 'deleteContentBackward'));
|
|
2070
|
+
return;
|
|
2071
|
+
case 'deleteWordBackward':
|
|
2072
|
+
case 'deleteWordForward':
|
|
2073
|
+
editable(() => deleteCharacter(model, type === 'deleteWordBackward', true));
|
|
2074
|
+
return;
|
|
2075
|
+
case 'formatBold':
|
|
2076
|
+
controller.bold();
|
|
2077
|
+
return;
|
|
2078
|
+
case 'formatItalic':
|
|
2079
|
+
controller.italic();
|
|
2080
|
+
return;
|
|
2081
|
+
case 'historyRedo':
|
|
2082
|
+
controller.redo();
|
|
2083
|
+
return;
|
|
2084
|
+
case 'historyUndo':
|
|
2085
|
+
controller.undo();
|
|
2086
|
+
return;
|
|
2087
|
+
case 'insertLineBreak':
|
|
2088
|
+
editable(() => insertText(model, model.eol, 'input'));
|
|
2073
2089
|
return;
|
|
2090
|
+
case 'insertParagraph':
|
|
2091
|
+
controller.newline();
|
|
2092
|
+
return;
|
|
2093
|
+
case 'insertReplacementText': {
|
|
2094
|
+
let range = e.getTargetRanges()[0],
|
|
2095
|
+
from = range ? sourceAt(range.startContainer, range.startOffset) : null,
|
|
2096
|
+
to = range ? sourceAt(range.endContainer, range.endOffset) : null;
|
|
2097
|
+
|
|
2098
|
+
if (from !== null && to !== null) {
|
|
2099
|
+
model.select({ end: Math.max(from, to), start: Math.min(from, to) });
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
break;
|
|
2074
2103
|
}
|
|
2075
2104
|
}
|
|
2076
2105
|
|
|
2077
|
-
if (
|
|
2078
|
-
|
|
2079
|
-
|
|
2106
|
+
if (type.startsWith('delete')) {
|
|
2107
|
+
// Deletions to a line's edge cover what the browser says they do.
|
|
2108
|
+
let range = e.getTargetRanges()[0],
|
|
2109
|
+
from = range ? sourceAt(range.startContainer, range.startOffset) : null,
|
|
2110
|
+
to = range ? sourceAt(range.endContainer, range.endOffset) : null;
|
|
2111
|
+
|
|
2112
|
+
if (from !== null && to !== null && from !== to) {
|
|
2113
|
+
editable(() => remove({ from: Math.min(from, to), to: Math.max(from, to) }));
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
return;
|
|
2080
2117
|
}
|
|
2081
|
-
|
|
2082
|
-
|
|
2118
|
+
|
|
2119
|
+
let text = e.data ?? e.dataTransfer?.getData('text/plain') ?? '';
|
|
2120
|
+
|
|
2121
|
+
if (text) {
|
|
2122
|
+
typed(text);
|
|
2083
2123
|
}
|
|
2084
2124
|
},
|
|
2085
|
-
oncompositionend:
|
|
2125
|
+
oncompositionend: (e: CompositionEvent) => {
|
|
2126
|
+
composed = e.data ?? '';
|
|
2127
|
+
ime.attributes.oncompositionend();
|
|
2128
|
+
},
|
|
2086
2129
|
oncompositionstart: ime.attributes.oncompositionstart,
|
|
2087
2130
|
oncopy: (e: ClipboardEvent) => clip(e, false),
|
|
2088
2131
|
oncut: (e: ClipboardEvent) => clip(e, true),
|
|
2089
2132
|
ondragstart: gestures.ondragstart,
|
|
2090
2133
|
onfocus: () => {
|
|
2091
|
-
|
|
2092
|
-
activate(false);
|
|
2093
|
-
}
|
|
2134
|
+
let native = content?.ownerDocument.getSelection();
|
|
2094
2135
|
|
|
2095
|
-
|
|
2136
|
+
if (!native?.anchorNode || !content?.contains(native.anchorNode)) {
|
|
2137
|
+
place();
|
|
2138
|
+
}
|
|
2096
2139
|
},
|
|
2097
2140
|
oninput: (e: InputEvent) => {
|
|
2098
|
-
if (writing) {
|
|
2099
|
-
return;
|
|
2100
|
-
}
|
|
2101
|
-
|
|
2102
2141
|
if (ime.input(e)) {
|
|
2103
|
-
// The field grows with a composition the document doesn't hold yet.
|
|
2104
|
-
if (textarea && textarea.scrollHeight > ui.height) {
|
|
2105
|
-
ui.height = textarea.scrollHeight;
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
return;
|
|
2109
|
-
}
|
|
2110
|
-
|
|
2111
|
-
if (e.inputType === 'historyUndo' || e.inputType === 'historyRedo') {
|
|
2112
|
-
e.inputType === 'historyUndo' ? controller.undo() : controller.redo();
|
|
2113
2142
|
return;
|
|
2114
2143
|
}
|
|
2115
2144
|
|
|
2116
|
-
|
|
2145
|
+
invalidate(model.selection.start);
|
|
2117
2146
|
},
|
|
2118
2147
|
onkeydown: (e: KeyboardEvent) => {
|
|
2119
2148
|
gestures.alt(e);
|
|
2149
|
+
linking(e);
|
|
2120
2150
|
|
|
2121
2151
|
if (ime.busy() || e.isComposing || e.keyCode === 229) {
|
|
2122
2152
|
return;
|
|
@@ -2163,62 +2193,25 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
2163
2193
|
return;
|
|
2164
2194
|
}
|
|
2165
2195
|
|
|
2166
|
-
if (
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
}
|
|
2170
|
-
|
|
2171
|
-
if (/^(End|Home)$/.test(e.key) || (apple && /^Arrow(Down|Up)$/.test(e.key))) {
|
|
2172
|
-
e.preventDefault();
|
|
2173
|
-
moveSelection(e.key, e.shiftKey, false, false, true);
|
|
2174
|
-
}
|
|
2175
|
-
else if (model.selections.length > 1 || (/^Arrow(Left|Right)$/.test(e.key) && leaves(e.key))) {
|
|
2176
|
-
e.preventDefault();
|
|
2177
|
-
moveSelection(e.key, e.shiftKey, true);
|
|
2178
|
-
}
|
|
2179
|
-
|
|
2196
|
+
if (NAVIGATION.test(e.key) && !e.altKey && model.selections.length > 1) {
|
|
2197
|
+
e.preventDefault();
|
|
2198
|
+
moveSelection(e.key, e.shiftKey, mod);
|
|
2180
2199
|
return;
|
|
2181
2200
|
}
|
|
2182
2201
|
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
}
|
|
2187
|
-
else if (e.key === 'Enter' && !e.altKey && !options.readonly) {
|
|
2188
|
-
e.preventDefault();
|
|
2189
|
-
controller.newline();
|
|
2190
|
-
}
|
|
2191
|
-
else if ((e.key === 'Backspace' || e.key === 'Delete') && !options.readonly && model.selections.length > 1) {
|
|
2192
|
-
e.preventDefault();
|
|
2193
|
-
editable(() => e.key === 'Backspace' && !e.altKey
|
|
2194
|
-
? markdownCommand(model, 'backspace', kindAt)
|
|
2195
|
-
: deleteCharacter(model, e.key === 'Backspace', e.altKey));
|
|
2196
|
-
}
|
|
2197
|
-
else if ((e.key === 'Backspace' || e.key === 'Delete') && !options.readonly && active) {
|
|
2198
|
-
let selection = model.selection,
|
|
2199
|
-
collapsed = selection.start === selection.end;
|
|
2202
|
+
// Tab nests list items and indents code; in prose, where indentation would make code, it moves focus.
|
|
2203
|
+
if (e.key === 'Tab' && !e.altKey && !mod && options.captureTab !== false && !options.readonly) {
|
|
2204
|
+
let kind = kindAt(model.selection.start);
|
|
2200
2205
|
|
|
2201
|
-
if (
|
|
2206
|
+
if (kind === 'code' || kind === 'fence' || kind === 'list') {
|
|
2202
2207
|
e.preventDefault();
|
|
2203
|
-
|
|
2204
|
-
else if (e.key === 'Backspace' && collapsed && selection.start === active.from && active.from > 0) {
|
|
2205
|
-
// The line break before the field isn't in it; joining lines takes the document.
|
|
2206
|
-
e.preventDefault();
|
|
2207
|
-
editable(() => deleteCharacter(model, true, e.altKey));
|
|
2208
|
-
}
|
|
2209
|
-
else if (e.key === 'Delete' && collapsed && selection.end === active.to && active.to < model.value.length) {
|
|
2210
|
-
e.preventDefault();
|
|
2211
|
-
editable(() => deleteCharacter(model, false, e.altKey));
|
|
2208
|
+
e.shiftKey ? controller.outdent() : controller.indent();
|
|
2212
2209
|
}
|
|
2213
2210
|
}
|
|
2214
|
-
else if (NAVIGATION.test(e.key) && (model.selections.length > 1 || e.key.startsWith('Page') || leaves(e.key))) {
|
|
2215
|
-
e.preventDefault();
|
|
2216
|
-
moveSelection(e.key, e.shiftKey, e.altKey);
|
|
2217
|
-
}
|
|
2218
2211
|
},
|
|
2219
2212
|
onkeyup: (e: KeyboardEvent) => {
|
|
2220
2213
|
gestures.alt(e);
|
|
2221
|
-
|
|
2214
|
+
linking(e);
|
|
2222
2215
|
},
|
|
2223
2216
|
onpaste: (e: ClipboardEvent) => {
|
|
2224
2217
|
if (ime.busy() || options.readonly || !e.clipboardData) {
|
|
@@ -2231,30 +2224,24 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
2231
2224
|
|
|
2232
2225
|
editable(() => insertText(model, text, 'paste'));
|
|
2233
2226
|
},
|
|
2234
|
-
|
|
2235
|
-
goals = [];
|
|
2236
|
-
pinned = true;
|
|
2237
|
-
},
|
|
2238
|
-
onpointerup: () => {
|
|
2239
|
-
pinned = false;
|
|
2240
|
-
capture();
|
|
2241
|
-
},
|
|
2242
|
-
onselect: capture,
|
|
2243
|
-
onselectionchange: capture,
|
|
2244
|
-
placeholder: () => ui.placeholder,
|
|
2245
|
-
readOnly: () => ui.readonly,
|
|
2227
|
+
role: 'textbox',
|
|
2246
2228
|
spellcheck: () => ui.spellcheck,
|
|
2247
|
-
|
|
2248
|
-
wrap: () => ui.wrap ? 'soft' : 'off'
|
|
2229
|
+
tabindex: '0'
|
|
2249
2230
|
};
|
|
2250
2231
|
|
|
2251
|
-
|
|
2252
2232
|
let body: Attributes = {
|
|
2253
|
-
'aria-label': () => ui.label,
|
|
2254
2233
|
onclick: (e: MouseEvent) => {
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2234
|
+
let link = (e.target as Element).closest('a');
|
|
2235
|
+
|
|
2236
|
+
if (!link) {
|
|
2237
|
+
return;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
// Links place the caret on a plain click and open with the platform's modifier.
|
|
2241
|
+
e.preventDefault();
|
|
2242
|
+
|
|
2243
|
+
if ((e.ctrlKey || e.metaKey) && link.href) {
|
|
2244
|
+
window.open(link.href, '_blank', 'noopener');
|
|
2258
2245
|
}
|
|
2259
2246
|
},
|
|
2260
2247
|
onconnect: (element: HTMLElement) => {
|
|
@@ -2265,56 +2252,43 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
2265
2252
|
ondragleave: gestures.ondragleave,
|
|
2266
2253
|
ondragover: gestures.ondragover,
|
|
2267
2254
|
ondrop: gestures.ondrop,
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
controller.focus();
|
|
2271
|
-
}
|
|
2272
|
-
},
|
|
2273
|
-
onlostpointercapture: () => {
|
|
2274
|
-
gestures.end();
|
|
2275
|
-
finish();
|
|
2276
|
-
},
|
|
2255
|
+
onlostpointercapture: gestures.end,
|
|
2256
|
+
// A press beside the text, in the padding or past the end, still puts the caret on the nearest text.
|
|
2277
2257
|
onmousedown: (e: MouseEvent) => {
|
|
2278
|
-
if (e.
|
|
2258
|
+
if (e.button !== 0 || ime.busy() || (e.target !== surface && !(e.target as Element).classList.contains('markdown-editor-spacer'))) {
|
|
2279
2259
|
return;
|
|
2280
2260
|
}
|
|
2281
2261
|
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
gestures.end();
|
|
2286
|
-
finish();
|
|
2262
|
+
e.preventDefault();
|
|
2263
|
+
controller.focus();
|
|
2264
|
+
model.select({ start: offsetAt(e.clientX, e.clientY) ?? model.value.length });
|
|
2287
2265
|
},
|
|
2266
|
+
onpointercancel: gestures.end,
|
|
2288
2267
|
onpointerdown: (e: PointerEvent) => {
|
|
2289
|
-
|
|
2268
|
+
tip.release();
|
|
2290
2269
|
gestures.onpointerdown(e);
|
|
2291
2270
|
},
|
|
2271
|
+
onpointerleave: () => tip.release(),
|
|
2292
2272
|
onpointermove: (e: PointerEvent) => {
|
|
2293
2273
|
gestures.onpointermove(e);
|
|
2274
|
+
linking(e);
|
|
2275
|
+
},
|
|
2276
|
+
onpointerover: (e: PointerEvent) => {
|
|
2277
|
+
let link = (e.target as Element).closest<HTMLElement>('a.markdown-editor-link[href]');
|
|
2294
2278
|
|
|
2295
|
-
if (
|
|
2296
|
-
|
|
2279
|
+
if (link && e.pointerType !== 'touch') {
|
|
2280
|
+
tip.request(link, apple ? '⌘+Click to open' : 'Ctrl+Click to open');
|
|
2297
2281
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
if (offset !== null && offset !== drag.head) {
|
|
2302
|
-
drag.head = offset;
|
|
2303
|
-
schedule();
|
|
2282
|
+
else {
|
|
2283
|
+
tip.release();
|
|
2304
2284
|
}
|
|
2305
2285
|
},
|
|
2306
|
-
onpointerup:
|
|
2307
|
-
pinned = false;
|
|
2308
|
-
pointerId = -1;
|
|
2309
|
-
gestures.end();
|
|
2310
|
-
finish();
|
|
2311
|
-
},
|
|
2286
|
+
onpointerup: gestures.end,
|
|
2312
2287
|
onscroll: () => {
|
|
2313
2288
|
metrics.scrollLeft = surface!.scrollLeft;
|
|
2314
2289
|
metrics.scrollTop = surface!.scrollTop;
|
|
2315
2290
|
schedule();
|
|
2316
|
-
}
|
|
2317
|
-
tabindex: () => ui.editing ? -1 : 0
|
|
2291
|
+
}
|
|
2318
2292
|
};
|
|
2319
2293
|
|
|
2320
2294
|
let template = (attributes?: Attributes) => html`
|
|
@@ -2325,6 +2299,9 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
2325
2299
|
class: [
|
|
2326
2300
|
() => ime.state.composing && 'markdown-editor--composing',
|
|
2327
2301
|
() => gestures.state.crosshair && 'markdown-editor--crosshair',
|
|
2302
|
+
() => ui.empty && 'markdown-editor--empty',
|
|
2303
|
+
() => ui.linking && 'markdown-editor--linking',
|
|
2304
|
+
() => ui.fold && 'markdown-editor--fold',
|
|
2328
2305
|
() => ui.minimap && 'markdown-editor--minimap',
|
|
2329
2306
|
() => !ui.wrap && 'markdown-editor--nowrap',
|
|
2330
2307
|
() => ui.readonly && 'markdown-editor--readonly'
|
|
@@ -2334,13 +2311,10 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
2334
2311
|
connect();
|
|
2335
2312
|
},
|
|
2336
2313
|
ondisconnect: () => controller.dispose(),
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
capture();
|
|
2342
|
-
deactivate();
|
|
2343
|
-
}
|
|
2314
|
+
ondocumentselectionchange: capture,
|
|
2315
|
+
// A modifier released in another window never reaches this one.
|
|
2316
|
+
onwindowblur: () => {
|
|
2317
|
+
ui.linking = false;
|
|
2344
2318
|
},
|
|
2345
2319
|
style: () => `--tab-size: ${ui.tabSize};`
|
|
2346
2320
|
}}
|
|
@@ -2348,50 +2322,26 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
2348
2322
|
${search.template()}
|
|
2349
2323
|
${jump.template()}
|
|
2350
2324
|
<div class='markdown-editor-body' ${size}>
|
|
2351
|
-
<div class='markdown-editor-surface
|
|
2325
|
+
<div class='markdown-editor-surface' ${body} ${scrollbar()}>
|
|
2352
2326
|
<div aria-hidden='true' class='markdown-editor-overlay'>
|
|
2353
2327
|
${html.reactive(decorations, (decoration) => html`
|
|
2354
2328
|
<div ${{ class: () => decoration.kind, style: () => decoration.style }}></div>
|
|
2355
2329
|
`)}
|
|
2356
2330
|
</div>
|
|
2357
|
-
<div
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
controller.fold(model.lineAt(blocks[active.first].from) + 1);
|
|
2371
|
-
}
|
|
2372
|
-
},
|
|
2373
|
-
onmousedown: prevent
|
|
2374
|
-
}}
|
|
2375
|
-
>
|
|
2376
|
-
${icon({ 'aria-hidden': 'true', class: 'markdown-editor-fold-icon' }, chevron)}
|
|
2377
|
-
</button>
|
|
2378
|
-
<textarea
|
|
2379
|
-
autocapitalize='off'
|
|
2380
|
-
autocomplete='off'
|
|
2381
|
-
autocorrect='off'
|
|
2382
|
-
dir='ltr'
|
|
2383
|
-
${field}
|
|
2384
|
-
${{
|
|
2385
|
-
onconnect: (element: HTMLTextAreaElement) => {
|
|
2386
|
-
textarea = element;
|
|
2387
|
-
connect();
|
|
2388
|
-
}
|
|
2389
|
-
}}
|
|
2390
|
-
></textarea>
|
|
2331
|
+
<div
|
|
2332
|
+
${field}
|
|
2333
|
+
${{
|
|
2334
|
+
onconnect: (element: HTMLElement) => {
|
|
2335
|
+
content = element;
|
|
2336
|
+
connect();
|
|
2337
|
+
}
|
|
2338
|
+
}}
|
|
2339
|
+
>
|
|
2340
|
+
${html.reactive(items, (item) => 'block' in item
|
|
2341
|
+
? draw.template(item)
|
|
2342
|
+
: html`<div aria-hidden='true' class='markdown-editor-spacer' contenteditable='false' ${{ style: () => `height: ${item.height}px;` }}></div>`
|
|
2343
|
+
)}
|
|
2391
2344
|
</div>
|
|
2392
|
-
<div aria-hidden='true' class='markdown-editor-spacer' ${{ style: () => `height: ${ui.next}px;` }}></div>
|
|
2393
|
-
${html.reactive(listed.after, draw.template)}
|
|
2394
|
-
<div aria-hidden='true' class='markdown-editor-spacer' ${{ style: () => `height: ${ui.bottom}px;` }}></div>
|
|
2395
2345
|
</div>
|
|
2396
2346
|
${map.template()}
|
|
2397
2347
|
<span
|
|
@@ -2406,6 +2356,7 @@ const view = (model: EditorDocument, callbacks: Callbacks, receive?: (controller
|
|
|
2406
2356
|
>0000000000000000000000000000000000000000000000000000000000000000</span>
|
|
2407
2357
|
</div>
|
|
2408
2358
|
<span class='markdown-editor-help' id='${help}'>Press Escape then Tab to move focus out of the editor.</span>
|
|
2359
|
+
${tip.render()}
|
|
2409
2360
|
</div>
|
|
2410
2361
|
`;
|
|
2411
2362
|
|