@standardnotes/markdown-visual 1.0.7 → 1.3.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.
Files changed (115) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/config-overrides.js +2 -0
  3. package/package.json +43 -49
  4. package/{build → public}/favicon.ico +0 -0
  5. package/public/index.html +31 -0
  6. package/{build → public}/logo192.png +0 -0
  7. package/{build → public}/logo512.png +0 -0
  8. package/{build → public}/manifest.json +0 -0
  9. package/{build → public}/robots.txt +0 -0
  10. package/public/sample.ext.json +15 -0
  11. package/src/components/CodeMirror/index.tsx +94 -0
  12. package/src/components/CodeMirror/styles.scss +55 -0
  13. package/src/components/Milkdown/editor.ts +62 -0
  14. package/src/components/Milkdown/index.tsx +77 -0
  15. package/src/components/Milkdown/plugins/advanced-menu/README.md +3 -0
  16. package/src/components/Milkdown/plugins/advanced-menu/button.ts +102 -0
  17. package/src/components/Milkdown/plugins/advanced-menu/config.ts +94 -0
  18. package/src/components/Milkdown/plugins/advanced-menu/divider.ts +37 -0
  19. package/src/components/Milkdown/plugins/advanced-menu/index.ts +64 -0
  20. package/src/components/Milkdown/plugins/advanced-menu/manager.ts +122 -0
  21. package/src/components/Milkdown/plugins/advanced-menu/menuBar.ts +108 -0
  22. package/src/components/Milkdown/plugins/advanced-menu/select.ts +163 -0
  23. package/src/components/Milkdown/styles.scss +209 -0
  24. package/src/components/SplitView/index.tsx +42 -0
  25. package/src/components/SplitView/styles.scss +51 -0
  26. package/src/index.tsx +215 -0
  27. package/src/react-app-env.d.ts +1 -0
  28. package/src/setupTests.ts +5 -0
  29. package/src/stylesheets/main.scss +45 -0
  30. package/src/stylesheets/prism/material-light.css +207 -0
  31. package/tsconfig.json +21 -0
  32. package/.env +0 -1
  33. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  34. package/.github/ISSUE_TEMPLATE/config.yml +0 -4
  35. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -22
  36. package/.github/ISSUE_TEMPLATE/help-request.md +0 -38
  37. package/.husky/pre-commit +0 -4
  38. package/build/asset-manifest.json +0 -83
  39. package/build/index.html +0 -1
  40. package/build/package.json +0 -22
  41. package/build/static/css/main.904cd38b.css +0 -2
  42. package/build/static/css/main.904cd38b.css.map +0 -1
  43. package/build/static/js/main.ad13c3af.js +0 -3
  44. package/build/static/js/main.ad13c3af.js.LICENSE.txt +0 -65
  45. package/build/static/js/main.ad13c3af.js.map +0 -1
  46. package/build/static/media/KaTeX_AMS-Regular.73ea273a72f4aca30ca5.woff2 +0 -0
  47. package/build/static/media/KaTeX_AMS-Regular.853be92419a6c3766b9a.ttf +0 -0
  48. package/build/static/media/KaTeX_AMS-Regular.d562e886c52f12660a41.woff +0 -0
  49. package/build/static/media/KaTeX_Caligraphic-Bold.7489a2fbfb9bfe704420.ttf +0 -0
  50. package/build/static/media/KaTeX_Caligraphic-Bold.a1abf90dfd72792a577a.woff2 +0 -0
  51. package/build/static/media/KaTeX_Caligraphic-Bold.d757c535a2e5902f1325.woff +0 -0
  52. package/build/static/media/KaTeX_Caligraphic-Regular.7e873d3833eb108a0758.ttf +0 -0
  53. package/build/static/media/KaTeX_Caligraphic-Regular.d6484fce1ef428d5bd94.woff2 +0 -0
  54. package/build/static/media/KaTeX_Caligraphic-Regular.db074fa22cf224af93d7.woff +0 -0
  55. package/build/static/media/KaTeX_Fraktur-Bold.354501bac435c3264834.woff +0 -0
  56. package/build/static/media/KaTeX_Fraktur-Bold.4c761b3711973ab04edf.ttf +0 -0
  57. package/build/static/media/KaTeX_Fraktur-Bold.931d67ea207ab37ee693.woff2 +0 -0
  58. package/build/static/media/KaTeX_Fraktur-Regular.172d3529b26f8cedef6b.woff2 +0 -0
  59. package/build/static/media/KaTeX_Fraktur-Regular.6fdf0ac577be0ba82a4c.woff +0 -0
  60. package/build/static/media/KaTeX_Fraktur-Regular.ed305b5434865e06ffde.ttf +0 -0
  61. package/build/static/media/KaTeX_Main-Bold.0c3b8929d377c0e9b2f3.woff +0 -0
  62. package/build/static/media/KaTeX_Main-Bold.39890742bc957b368704.woff2 +0 -0
  63. package/build/static/media/KaTeX_Main-Bold.8169508bf58f8bd92ad8.ttf +0 -0
  64. package/build/static/media/KaTeX_Main-BoldItalic.20f389c4120be058d80a.woff2 +0 -0
  65. package/build/static/media/KaTeX_Main-BoldItalic.428978dc7837d46de091.woff +0 -0
  66. package/build/static/media/KaTeX_Main-BoldItalic.828abcb200061cffbaae.ttf +0 -0
  67. package/build/static/media/KaTeX_Main-Italic.fa675e5e4bec9eb250b6.ttf +0 -0
  68. package/build/static/media/KaTeX_Main-Italic.fd947498bc16392e76c2.woff +0 -0
  69. package/build/static/media/KaTeX_Main-Italic.fe2176f79edaa716e621.woff2 +0 -0
  70. package/build/static/media/KaTeX_Main-Regular.4f35fbcc9ee8614c2bcc.woff +0 -0
  71. package/build/static/media/KaTeX_Main-Regular.9eba1d77abcf2aa6e94e.ttf +0 -0
  72. package/build/static/media/KaTeX_Main-Regular.f650f111a3b890d116f1.woff2 +0 -0
  73. package/build/static/media/KaTeX_Math-BoldItalic.3f07ed67f06c720120ce.woff +0 -0
  74. package/build/static/media/KaTeX_Math-BoldItalic.bf2d440b3a42ea78a998.ttf +0 -0
  75. package/build/static/media/KaTeX_Math-BoldItalic.dcbcbd93bac0470b462d.woff2 +0 -0
  76. package/build/static/media/KaTeX_Math-Italic.6d3d25f4820d0da8f01f.woff2 +0 -0
  77. package/build/static/media/KaTeX_Math-Italic.8a5f936332e8028c7278.ttf +0 -0
  78. package/build/static/media/KaTeX_Math-Italic.96759856b4e70f3a8338.woff +0 -0
  79. package/build/static/media/KaTeX_SansSerif-Bold.5b49f4993ae22d7975b4.ttf +0 -0
  80. package/build/static/media/KaTeX_SansSerif-Bold.95591a929f0d32aa282a.woff2 +0 -0
  81. package/build/static/media/KaTeX_SansSerif-Bold.b9cd458ac6d5889ff9c3.woff +0 -0
  82. package/build/static/media/KaTeX_SansSerif-Italic.7d393d382f3e7fb1c637.woff2 +0 -0
  83. package/build/static/media/KaTeX_SansSerif-Italic.8d593cfaa96238d5e2f8.woff +0 -0
  84. package/build/static/media/KaTeX_SansSerif-Italic.b257a18c016f37ee4543.ttf +0 -0
  85. package/build/static/media/KaTeX_SansSerif-Regular.02271ec5cb9f5b4588ac.woff +0 -0
  86. package/build/static/media/KaTeX_SansSerif-Regular.2f7bc363fc5424ebda59.ttf +0 -0
  87. package/build/static/media/KaTeX_SansSerif-Regular.cd5e231e0cc53b2cb2c0.woff2 +0 -0
  88. package/build/static/media/KaTeX_Script-Regular.073b3402d036714b4370.woff +0 -0
  89. package/build/static/media/KaTeX_Script-Regular.c81d1b2a4b75d3eded60.woff2 +0 -0
  90. package/build/static/media/KaTeX_Script-Regular.fc9ba5249878cd8f8d88.ttf +0 -0
  91. package/build/static/media/KaTeX_Size1-Regular.0108e89c9003e8c14ea3.woff +0 -0
  92. package/build/static/media/KaTeX_Size1-Regular.6de7d4b539221a49e9e2.ttf +0 -0
  93. package/build/static/media/KaTeX_Size1-Regular.6eec866c69313624be60.woff2 +0 -0
  94. package/build/static/media/KaTeX_Size2-Regular.2960900c4f271311eb36.woff2 +0 -0
  95. package/build/static/media/KaTeX_Size2-Regular.3a99e70aee4076660d38.woff +0 -0
  96. package/build/static/media/KaTeX_Size2-Regular.57f5c1837853986ea1db.ttf +0 -0
  97. package/build/static/media/KaTeX_Size3-Regular.7947224e8a9914fa332b.woff +0 -0
  98. package/build/static/media/KaTeX_Size3-Regular.8d6b6822586eea3d3b20.ttf +0 -0
  99. package/build/static/media/KaTeX_Size3-Regular.e1951519f6f0596f7356.woff2 +0 -0
  100. package/build/static/media/KaTeX_Size4-Regular.4ad7c7e8bb8d10a34bb7.ttf +0 -0
  101. package/build/static/media/KaTeX_Size4-Regular.aeffd8025cba3647f1a6.woff +0 -0
  102. package/build/static/media/KaTeX_Size4-Regular.e418bf257af1052628d8.woff2 +0 -0
  103. package/build/static/media/KaTeX_Typewriter-Regular.4c6b94fd1d07f8beff7c.woff +0 -0
  104. package/build/static/media/KaTeX_Typewriter-Regular.c295e7f71970f03c0549.woff2 +0 -0
  105. package/build/static/media/KaTeX_Typewriter-Regular.c5c02d763c89380dcb4e.ttf +0 -0
  106. package/build/static/media/material-icons-outlined.123a7ad6784163c39aaa.woff +0 -0
  107. package/build/static/media/material-icons-outlined.5d7deb03b9cecba7d247.woff2 +0 -0
  108. package/build/static/media/material-icons-round.159dc8004e17a33f287f.woff +0 -0
  109. package/build/static/media/material-icons-round.7c985a8aea387341edf9.woff2 +0 -0
  110. package/build/static/media/material-icons-sharp.25f4d306806e85bc60f6.woff2 +0 -0
  111. package/build/static/media/material-icons-sharp.77bc1b022b575be35fa7.woff +0 -0
  112. package/build/static/media/material-icons-two-tone.7e94e72135150c6b387c.woff2 +0 -0
  113. package/build/static/media/material-icons-two-tone.ee71463e28071436d096.woff +0 -0
  114. package/build/static/media/material-icons.824b570fb059ee0a44a9.woff +0 -0
  115. package/build/static/media/material-icons.aba5a2a316a1d312db26.woff2 +0 -0
@@ -0,0 +1,102 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+
3
+ import { css } from '@emotion/css'
4
+ import { CmdKey, commandsCtx, Ctx } from '@milkdown/core'
5
+ import type { Icon } from '@milkdown/design-system'
6
+ import type { EditorView } from '@milkdown/prose'
7
+ import type { Utils } from '@milkdown/utils'
8
+
9
+ import type { MenuCommonConfig } from './config'
10
+
11
+ type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> &
12
+ {
13
+ [K in Keys]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<Keys, K>>>
14
+ }[Keys]
15
+
16
+ type Config<T = any> = {
17
+ type: 'button'
18
+ icon: Icon
19
+ key?: CmdKey<T>
20
+ callback?: () => void
21
+ options?: T
22
+ active?: (view?: EditorView) => boolean
23
+ alwaysVisible: boolean
24
+ } & MenuCommonConfig
25
+
26
+ export type ButtonConfig = RequireAtLeastOne<Config, 'key' | 'callback'>
27
+
28
+ export const button = (utils: Utils, config: Config, ctx: Ctx, view: EditorView) => {
29
+ const buttonStyle = utils.getStyle((themeTool) => {
30
+ return css`
31
+ border: 0;
32
+ box-sizing: unset;
33
+ width: 1.5rem;
34
+ height: 1.5rem;
35
+ padding: 0.25rem;
36
+ margin: 0.5rem;
37
+ flex-shrink: 0;
38
+ display: flex;
39
+ justify-content: center;
40
+ align-items: center;
41
+ background-color: ${themeTool.palette('surface')};
42
+ color: ${themeTool.palette('solid')};
43
+ transition: all 0.4s ease-in-out;
44
+ cursor: pointer;
45
+ &.active,
46
+ &:hover {
47
+ background-color: ${themeTool.palette('secondary', 0.12)};
48
+ color: ${themeTool.palette('primary')};
49
+ }
50
+ &:disabled {
51
+ display: none;
52
+ }
53
+ `
54
+ })
55
+
56
+ const $button = document.createElement('button')
57
+ $button.setAttribute('type', 'button')
58
+ $button.classList.add('button')
59
+
60
+ if (buttonStyle) {
61
+ $button.classList.add(buttonStyle)
62
+ }
63
+
64
+ const $label = utils.themeTool.slots.label(config.icon)
65
+ if ($label) {
66
+ $button.setAttribute('aria-label', $label)
67
+ $button.setAttribute('title', $label)
68
+ }
69
+
70
+ const $icon = utils.themeTool.slots.icon(config.icon)
71
+ $button.appendChild($icon)
72
+ $button.addEventListener('click', (e) => {
73
+ e.preventDefault()
74
+ e.stopPropagation()
75
+
76
+ config.callback && config.callback()
77
+ config.key && ctx.get(commandsCtx).call(config.key, config.options)
78
+ })
79
+
80
+ if (config.active) {
81
+ const active = config.active()
82
+ if (active) {
83
+ $button.classList.add('active')
84
+ } else {
85
+ $button.classList.remove('active')
86
+ }
87
+ }
88
+
89
+ if (config.alwaysVisible) {
90
+ $button.removeAttribute('disabled')
91
+ return $button
92
+ }
93
+
94
+ const disabled = !view.editable || (config.disabled && config.disabled(view))
95
+ if (disabled) {
96
+ $button.setAttribute('disabled', 'true')
97
+ } else {
98
+ $button.removeAttribute('disabled')
99
+ }
100
+
101
+ return $button
102
+ }
@@ -0,0 +1,94 @@
1
+ import {
2
+ InsertHr,
3
+ InsertImage,
4
+ LiftListItem,
5
+ SinkListItem,
6
+ WrapInBlockquote,
7
+ WrapInBulletList,
8
+ WrapInOrderedList,
9
+ } from '@milkdown/preset-commonmark'
10
+ import { InsertTable, TurnIntoTaskList } from '@milkdown/preset-gfm'
11
+ import { EditorView, liftListItem, sinkListItem, wrapIn } from '@milkdown/prose'
12
+ import { ButtonConfig } from './button'
13
+ import { SelectConfig } from './select'
14
+
15
+ export type MenuCommonConfig = {
16
+ disabled?: (view: EditorView) => boolean
17
+ }
18
+
19
+ export type MenuConfigItem = SelectConfig | ButtonConfig
20
+ export type MenuConfig = Array<Array<MenuConfigItem>>
21
+
22
+ export const menuConfig: any = [
23
+ [
24
+ {
25
+ type: 'button',
26
+ icon: 'bulletList',
27
+ key: WrapInBulletList,
28
+ disabled: (view: EditorView) => {
29
+ const { state } = view
30
+ return !wrapIn(state.schema.nodes.bullet_list)(state)
31
+ },
32
+ },
33
+ {
34
+ type: 'button',
35
+ icon: 'orderedList',
36
+ key: WrapInOrderedList,
37
+ disabled: (view: EditorView) => {
38
+ const { state } = view
39
+ return !wrapIn(state.schema.nodes.ordered_list)(state)
40
+ },
41
+ },
42
+ {
43
+ type: 'button',
44
+ icon: 'taskList',
45
+ key: TurnIntoTaskList,
46
+ disabled: (view: EditorView) => {
47
+ const { state } = view
48
+ return !wrapIn(state.schema.nodes.task_list_item)(state)
49
+ },
50
+ },
51
+ {
52
+ type: 'button',
53
+ icon: 'liftList',
54
+ key: LiftListItem,
55
+ disabled: (view: EditorView) => {
56
+ const { state } = view
57
+ return !liftListItem(state.schema.nodes.list_item)(state)
58
+ },
59
+ },
60
+ {
61
+ type: 'button',
62
+ icon: 'sinkList',
63
+ key: SinkListItem,
64
+ disabled: (view: EditorView) => {
65
+ const { state } = view
66
+ return !sinkListItem(state.schema.nodes.list_item)(state)
67
+ },
68
+ },
69
+ ],
70
+ [
71
+ {
72
+ type: 'button',
73
+ icon: 'image',
74
+ key: InsertImage,
75
+ },
76
+ {
77
+ type: 'button',
78
+ icon: 'table',
79
+ key: InsertTable,
80
+ },
81
+ ],
82
+ [
83
+ {
84
+ type: 'button',
85
+ icon: 'quote',
86
+ key: WrapInBlockquote,
87
+ },
88
+ {
89
+ type: 'button',
90
+ icon: 'divider',
91
+ key: InsertHr,
92
+ },
93
+ ],
94
+ ]
@@ -0,0 +1,37 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+
3
+ import { css } from '@emotion/css'
4
+ import { Utils } from '@milkdown/utils'
5
+
6
+ export type DividerConfig = {
7
+ type: 'divider'
8
+ group: HTMLElement[]
9
+ }
10
+
11
+ export const divider = (utils: Utils, config: DividerConfig) => {
12
+ const dividerStyle = utils.getStyle((themeTool) => {
13
+ return css`
14
+ flex-shrink: 0;
15
+ width: ${themeTool.size.lineWidth};
16
+ background-color: ${themeTool.palette('line')};
17
+ margin: 0.75rem 1rem;
18
+ `
19
+ })
20
+
21
+ const $divider = document.createElement('div')
22
+ $divider.classList.add('divider')
23
+
24
+ if (dividerStyle) {
25
+ $divider.classList.add(dividerStyle)
26
+ }
27
+
28
+ const disabled = config.group.every((x) => x.getAttribute('disabled') || x.classList.contains('disabled'))
29
+
30
+ if (disabled) {
31
+ $divider.classList.add('disabled')
32
+ } else {
33
+ $divider.classList.remove('disabled')
34
+ }
35
+
36
+ return $divider
37
+ }
@@ -0,0 +1,64 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+
3
+ import { createCmd, createCmdKey, Ctx } from '@milkdown/core'
4
+ import { EditorView, Plugin, PluginKey, selectParentNode } from '@milkdown/prose'
5
+ import { createPlugin } from '@milkdown/utils'
6
+
7
+ import { MenuConfig, menuConfig } from './config'
8
+ import { Manager } from './manager'
9
+ import { HandleDOM, MenuBar } from './menuBar'
10
+
11
+ export type Options = {
12
+ config: MenuConfig
13
+ domHandler: HandleDOM
14
+ }
15
+
16
+ export { menuConfig } from './config'
17
+
18
+ export const menu = createPlugin<string, Options>((utils, options) => {
19
+ const config = options?.config ?? menuConfig
20
+ const domHandler = options?.domHandler
21
+
22
+ let restoreDOM: (() => void) | null = null
23
+ let menu: HTMLDivElement | null = null
24
+ let manager: Manager | null = null
25
+
26
+ const SelectParent = createCmdKey()
27
+
28
+ const initIfNecessary = (ctx: Ctx, editorView: EditorView) => {
29
+ if (!menu) {
30
+ const [_menu, _restoreDOM] = MenuBar(utils, editorView, ctx, domHandler)
31
+ menu = _menu
32
+ restoreDOM = () => {
33
+ _restoreDOM()
34
+ menu = null
35
+ manager = null
36
+ }
37
+ }
38
+
39
+ if (!manager) {
40
+ manager = new Manager(config, utils, ctx, menu, editorView)
41
+ }
42
+ }
43
+
44
+ return {
45
+ commands: () => [createCmd(SelectParent, () => selectParentNode)],
46
+ prosePlugins: (_, ctx) => {
47
+ const plugin = new Plugin({
48
+ key: new PluginKey('milkdown-advanced-menu'),
49
+ view: (editorView) => {
50
+ initIfNecessary(ctx, editorView)
51
+ if (editorView.editable) {
52
+ manager?.update(editorView)
53
+ }
54
+ return {
55
+ update: (view) => manager?.update(view),
56
+ destroy: () => restoreDOM?.(),
57
+ }
58
+ },
59
+ })
60
+
61
+ return [plugin]
62
+ },
63
+ }
64
+ })
@@ -0,0 +1,122 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+
3
+ import { Ctx } from '@milkdown/core'
4
+ import { EditorView } from '@milkdown/prose'
5
+ import { Utils } from '@milkdown/utils'
6
+
7
+ import { button, ButtonConfig } from './button'
8
+ import { MenuConfig, MenuConfigItem } from './config'
9
+ import { divider, DividerConfig } from './divider'
10
+ import { select, SelectConfig } from './select'
11
+
12
+ type InnerConfig = (MenuConfigItem | DividerConfig) & { $: HTMLElement }
13
+
14
+ export class Manager {
15
+ private config: InnerConfig[]
16
+
17
+ constructor(originalConfig: MenuConfig, private utils: Utils, private ctx: Ctx, menu: HTMLElement, view: EditorView) {
18
+ this.config = originalConfig
19
+ .map((xs) =>
20
+ xs.map((x) => ({
21
+ ...x,
22
+ $: this.$create(x, view),
23
+ })),
24
+ )
25
+ .map((xs, i): Array<InnerConfig> => {
26
+ if (i === originalConfig.length - 1) {
27
+ return xs
28
+ }
29
+ const dividerConfig: DividerConfig = {
30
+ type: 'divider',
31
+ group: xs.map((x) => x.$),
32
+ }
33
+ return [
34
+ ...xs,
35
+ {
36
+ ...dividerConfig,
37
+ $: this.$create(dividerConfig, view),
38
+ },
39
+ ]
40
+ })
41
+ .flat()
42
+
43
+ this.config.forEach((x) => menu.appendChild(x.$))
44
+ }
45
+
46
+ public update(view: EditorView) {
47
+ const enabled = view.editable
48
+
49
+ this.config.forEach((config) => {
50
+ switch (config.type) {
51
+ case 'button': {
52
+ if (config.active) {
53
+ const active = config.active(view)
54
+ if (active) {
55
+ config.$.classList.add('active')
56
+ } else {
57
+ config.$.classList.remove('active')
58
+ }
59
+ }
60
+
61
+ if (config.alwaysVisible) {
62
+ config.$.removeAttribute('disabled')
63
+ return
64
+ }
65
+
66
+ const disabled = !enabled || (config.disabled && config.disabled(view))
67
+ if (disabled) {
68
+ config.$.setAttribute('disabled', 'true')
69
+ } else {
70
+ config.$.removeAttribute('disabled')
71
+ }
72
+ break
73
+ }
74
+
75
+ case 'select': {
76
+ if (config.alwaysVisible) {
77
+ config.$.removeAttribute('disabled')
78
+ return
79
+ }
80
+
81
+ const disabled = !enabled || (config.disabled && config.disabled(view))
82
+ if (disabled) {
83
+ config.$.classList.add('disabled')
84
+ config.$.children[0].setAttribute('disabled', 'true')
85
+ } else {
86
+ config.$.classList.remove('disabled')
87
+ config.$.children[0].removeAttribute('disabled')
88
+ }
89
+ break
90
+ }
91
+
92
+ case 'divider': {
93
+ const disabled = config.group.every((x) => x.getAttribute('disabled') || x.classList.contains('disabled'))
94
+ if (disabled) {
95
+ config.$.classList.add('disabled')
96
+ } else {
97
+ config.$.classList.remove('disabled')
98
+ }
99
+ break
100
+ }
101
+ }
102
+ })
103
+ }
104
+
105
+ private $create(item: ButtonConfig | DividerConfig | SelectConfig, view: EditorView): HTMLElement {
106
+ const { utils, ctx } = this
107
+
108
+ switch (item.type) {
109
+ case 'button': {
110
+ return button(utils, item, ctx, view)
111
+ }
112
+ case 'select': {
113
+ return select(utils, item, ctx, view)
114
+ }
115
+ case 'divider': {
116
+ return divider(utils, item)
117
+ }
118
+ default:
119
+ throw new Error()
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,108 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+
3
+ import { css } from '@emotion/css'
4
+ import { Ctx, rootCtx } from '@milkdown/core'
5
+ import { EditorView } from '@milkdown/prose'
6
+ import { Utils } from '@milkdown/utils'
7
+
8
+ export const MenuBar = (utils: Utils, view: EditorView, ctx: Ctx, domHandler: HandleDOM = defaultDOMHandler) => {
9
+ const menuWrapper = document.createElement('div')
10
+ menuWrapper.classList.add('milkdown-menu-wrapper')
11
+ const menu = document.createElement('div')
12
+ menu.classList.add('milkdown-menu')
13
+
14
+ const editorDOM = view.dom as HTMLDivElement
15
+
16
+ const editorWrapperStyle = utils.getStyle((themeTool) => {
17
+ return themeTool.mixin.scrollbar('y')
18
+ })
19
+
20
+ if (editorWrapperStyle) {
21
+ editorDOM.classList.add(editorWrapperStyle)
22
+ }
23
+
24
+ const menuStyle = utils.getStyle((themeTool) => {
25
+ const border = themeTool.mixin.border()
26
+ const scrollbar = themeTool.mixin.scrollbar('x')
27
+ const style = css`
28
+ box-sizing: border-box;
29
+ width: 100%;
30
+ display: flex;
31
+ flex-wrap: nowrap;
32
+ overflow-x: auto;
33
+ ${border};
34
+ ${scrollbar};
35
+ background: ${themeTool.palette('surface')};
36
+ -webkit-overflow-scrolling: auto;
37
+ .disabled {
38
+ display: none;
39
+ }
40
+ `
41
+ return style
42
+ })
43
+
44
+ if (menuStyle) {
45
+ menuStyle.split(' ').forEach((x) => menu.classList.add(x))
46
+ }
47
+
48
+ const root = ctx.get(rootCtx)
49
+
50
+ const editorRoot = getRoot(root) as HTMLElement
51
+ const milkdownDOM = editorDOM.parentElement
52
+
53
+ if (!milkdownDOM) {
54
+ throw new Error('No parent node found')
55
+ }
56
+
57
+ domHandler({
58
+ menu,
59
+ menuWrapper,
60
+ editorDOM,
61
+ editorRoot,
62
+ milkdownDOM,
63
+ })
64
+
65
+ const restoreDOM = () => {
66
+ restore({
67
+ menu,
68
+ menuWrapper,
69
+ editorDOM,
70
+ editorRoot,
71
+ milkdownDOM,
72
+ })
73
+ }
74
+
75
+ return [menu, restoreDOM] as const
76
+ }
77
+
78
+ export type HandleDOMParams = {
79
+ menu: HTMLDivElement
80
+ menuWrapper: HTMLDivElement
81
+ editorRoot: HTMLElement
82
+ milkdownDOM: HTMLElement
83
+ editorDOM: HTMLDivElement
84
+ }
85
+
86
+ export type HandleDOM = (params: HandleDOMParams) => void
87
+
88
+ const restore: HandleDOM = ({ milkdownDOM, editorRoot, menu, menuWrapper }) => {
89
+ editorRoot.appendChild(milkdownDOM)
90
+ menuWrapper.remove()
91
+ menu.remove()
92
+ }
93
+
94
+ const defaultDOMHandler: HandleDOM = ({ menu, menuWrapper, editorRoot, milkdownDOM }) => {
95
+ menuWrapper.appendChild(menu)
96
+ editorRoot.replaceChild(menuWrapper, milkdownDOM)
97
+ menuWrapper.appendChild(milkdownDOM)
98
+ }
99
+
100
+ const getRoot = (root: string | Node | null | undefined) => {
101
+ if (!root) return document.body
102
+ if (typeof root === 'string') {
103
+ const el = document.querySelector(root)
104
+ if (el) return el
105
+ return document.body
106
+ }
107
+ return root
108
+ }
@@ -0,0 +1,163 @@
1
+ /* Copyright 2021, Milkdown by Mirone. */
2
+
3
+ import { css } from '@emotion/css'
4
+ import { CmdKey, commandsCtx, Ctx } from '@milkdown/core'
5
+ import { EditorView } from '@milkdown/prose'
6
+ import { Utils } from '@milkdown/utils'
7
+
8
+ import type { MenuCommonConfig } from './config'
9
+
10
+ type SelectOptions = {
11
+ id: string
12
+ text: string
13
+ }
14
+
15
+ export type SelectConfig<T = any> = {
16
+ type: 'select'
17
+ text: string
18
+ options: SelectOptions[]
19
+ active?: (view: EditorView) => string
20
+ onSelect: (id: string, view: EditorView) => [key: CmdKey<T>, info?: T]
21
+ alwaysVisible: boolean
22
+ } & MenuCommonConfig
23
+
24
+ export const select = (utils: Utils, config: SelectConfig, ctx: Ctx, view: EditorView) => {
25
+ const selectStyle = utils.getStyle((themeTool) => {
26
+ return css`
27
+ flex-shrink: 0;
28
+ font-weight: 500;
29
+ font-size: 0.875rem;
30
+ ${themeTool.mixin.border('right')};
31
+ ${themeTool.mixin.border('left')};
32
+ .menu-selector {
33
+ border: 0;
34
+ box-sizing: unset;
35
+ cursor: pointer;
36
+ font: inherit;
37
+ text-align: left;
38
+ justify-content: space-between;
39
+ align-items: center;
40
+ color: ${themeTool.palette('neutral', 0.87)};
41
+ display: flex;
42
+ padding: 0.25rem 0.5rem;
43
+ margin: 0.5rem;
44
+ background: ${themeTool.palette('secondary', 0.12)};
45
+ width: 10.375rem;
46
+ &:disabled {
47
+ display: none;
48
+ }
49
+ }
50
+ .menu-selector-value {
51
+ flex: 1;
52
+ white-space: nowrap;
53
+ text-overflow: ellipsis;
54
+ }
55
+ .menu-selector-list {
56
+ width: calc(12.375rem);
57
+ position: absolute;
58
+ top: 3rem;
59
+ background: ${themeTool.palette('surface')};
60
+ ${themeTool.mixin.border()};
61
+ ${themeTool.mixin.shadow()};
62
+ border-bottom-left-radius: ${themeTool.size.radius};
63
+ border-bottom-right-radius: ${themeTool.size.radius};
64
+ z-index: 3;
65
+ }
66
+ .menu-selector-list-item {
67
+ background-color: transparent;
68
+ border: 0;
69
+ cursor: pointer;
70
+ display: block;
71
+ font: inherit;
72
+ text-align: left;
73
+ padding: 0.75rem 1rem;
74
+ line-height: 1.5rem;
75
+ width: 100%;
76
+ color: ${themeTool.palette('neutral', 0.87)};
77
+ &:hover {
78
+ background: ${themeTool.palette('secondary', 0.12)};
79
+ color: ${themeTool.palette('primary')};
80
+ }
81
+ }
82
+ &.fold {
83
+ border-color: transparent;
84
+ .menu-selector {
85
+ background: unset;
86
+ }
87
+ .menu-selector-list {
88
+ display: none;
89
+ }
90
+ }
91
+ `
92
+ })
93
+
94
+ const selectorWrapper = document.createElement('div')
95
+ selectorWrapper.classList.add('menu-selector-wrapper', 'fold')
96
+
97
+ const selector = document.createElement('button')
98
+ selector.setAttribute('type', 'button')
99
+ selector.classList.add('menu-selector', 'fold')
100
+ selector.addEventListener('mousedown', (e) => {
101
+ e.preventDefault()
102
+ e.stopPropagation()
103
+ selectorWrapper.classList.toggle('fold')
104
+ selectorList.style.left = `${
105
+ selectorWrapper.getBoundingClientRect().left - view.dom.getBoundingClientRect().left
106
+ }px`
107
+ })
108
+ view.dom.addEventListener('click', () => {
109
+ selectorWrapper.classList.add('fold')
110
+ })
111
+
112
+ const selectorValue = document.createElement('span')
113
+ selectorValue.classList.add('menu-selector-value')
114
+ selectorValue.textContent = config.text
115
+
116
+ const selectorButton = utils.themeTool.slots.icon('downArrow')
117
+ selectorButton.setAttribute('aria-hidden', 'true')
118
+
119
+ selectorWrapper.appendChild(selector)
120
+ selector.appendChild(selectorValue)
121
+ selector.appendChild(selectorButton)
122
+
123
+ const selectorList = document.createElement('div')
124
+ selectorList.classList.add('menu-selector-list')
125
+ config.options.forEach((option) => {
126
+ const selectorListItem = document.createElement('button')
127
+ selectorListItem.setAttribute('type', 'button')
128
+ selectorListItem.dataset.id = option.id
129
+ selectorListItem.textContent = option.text
130
+ selectorListItem.classList.add('menu-selector-list-item')
131
+ selectorList.appendChild(selectorListItem)
132
+ })
133
+
134
+ selectorList.addEventListener('mousedown', (e) => {
135
+ const { target } = e
136
+ if (target instanceof HTMLButtonElement && target.dataset.id) {
137
+ ctx.get(commandsCtx).call(...config.onSelect(target.dataset.id, view))
138
+ selectorWrapper.classList.add('fold')
139
+ }
140
+ })
141
+
142
+ selectorWrapper.appendChild(selectorList)
143
+
144
+ if (selectStyle) {
145
+ selectorWrapper.classList.add(selectStyle)
146
+ }
147
+
148
+ if (config.alwaysVisible) {
149
+ selector.removeAttribute('disabled')
150
+ return selectorWrapper
151
+ }
152
+
153
+ const disabled = !view.editable || (config.disabled && config.disabled(view))
154
+ if (disabled) {
155
+ selector.classList.add('disabled')
156
+ selector.children[0].setAttribute('disabled', 'true')
157
+ } else {
158
+ selector.classList.remove('disabled')
159
+ selector.children[0].removeAttribute('disabled')
160
+ }
161
+
162
+ return selectorWrapper
163
+ }