@standardnotes/markdown-visual 1.0.7 → 1.3.1

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 +114 -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 +223 -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,209 @@
1
+ .container {
2
+ .milkdown-container {
3
+ margin: 0 auto;
4
+ width: 100%;
5
+ max-width: 100%;
6
+ height: 100%;
7
+ max-height: 100%;
8
+ flex-grow: 1;
9
+ justify-content: flex-start;
10
+ display: flex;
11
+ flex-direction: column;
12
+ position: relative;
13
+ padding: 0;
14
+ box-sizing: border-box;
15
+
16
+ > div {
17
+ height: 100%;
18
+ }
19
+
20
+ .milkdown-menu-wrapper {
21
+ position: relative;
22
+ overflow: auto;
23
+ height: 100%;
24
+
25
+ .milkdown-menu {
26
+ top: 0;
27
+ z-index: 1;
28
+ left: 0;
29
+ right: 0;
30
+ box-sizing: border-box;
31
+ width: 100%;
32
+ display: flex;
33
+ flex-wrap: nowrap;
34
+ overflow-x: auto;
35
+ border: none;
36
+ background: rgba(var(--surface), 1);
37
+ background-color: var(--sn-stylekit-contrast-background-color);
38
+ border-color: var(--sn-stylekit-border-color);
39
+ position: absolute;
40
+
41
+ .button {
42
+ background-color: var(--sn-stylekit-secondary-background-color);
43
+ color: var(--sn-stylekit-neutral-color);
44
+ -webkit-transition: none;
45
+ transition: none;
46
+ }
47
+
48
+ .button.active {
49
+ background-color: var(--sn-stylekit-neutral-color) !important;
50
+ color: var(--sn-stylekit-neutral-contrast-color) !important;
51
+ }
52
+
53
+ .divider {
54
+ background-color: var(--sn-stylekit-border-color) !important;
55
+ }
56
+ }
57
+
58
+ .milkdown {
59
+ max-width: 100%;
60
+ height: 100%;
61
+ box-shadow: none !important;
62
+ background-color: var(--sn-stylekit-background-color) !important;
63
+ color: var(--sn-stylekit-editor-foreground-color) !important;
64
+ overflow: auto;
65
+
66
+ &::-webkit-scrollbar-thumb {
67
+ background-color: var(--sn-stylekit-scrollbar-thumb-color);
68
+ border: 2px solid transparent;
69
+ }
70
+
71
+ &::-webkit-scrollbar-thumb:hover {
72
+ background-color: var(--sn-stylekit-scrollbar-thumb-color);
73
+ }
74
+
75
+ .emoji {
76
+ height: 1.3rem !important;
77
+ width: 1.3rem !important;
78
+ }
79
+
80
+ .editor {
81
+ padding-top: 4.125rem !important;
82
+ padding-left: 1.25rem !important;
83
+ padding-right: 1.25rem !important;
84
+ padding-bottom: 0 !important;
85
+ max-width: 100% !important;
86
+
87
+ > * {
88
+ margin-top: 0 !important;
89
+ margin-bottom: 0.875rem !important;
90
+ }
91
+
92
+ h1.heading.h1 {
93
+ font-size: 2.8rem !important;
94
+ }
95
+
96
+ h2.heading.h2 {
97
+ font-size: 2.3rem !important;
98
+ }
99
+
100
+ h3.heading.h3 {
101
+ font-size: 1.8rem !important;
102
+ }
103
+
104
+ h4.heading.h4 {
105
+ font-size: 1.5rem !important;
106
+ }
107
+
108
+ h5.heading.h5 {
109
+ font-size: 1rem !important;
110
+ }
111
+
112
+ p.paragraph {
113
+ font-size: var(--sn-stylekit-font-size-editor) !important;
114
+ }
115
+
116
+ .strike-through {
117
+ text-decoration-color: rgba(var(--sn-stylekit-editor-foreground-color), 0.5);
118
+ }
119
+
120
+ .ProseMirror-gapcursor {
121
+ caret-color: transparent;
122
+ }
123
+
124
+ .tableWrapper table {
125
+ border-color: var(--sn-stylekit-border-color);
126
+
127
+ th {
128
+ color: var(--sn-stylekit-neutral-contrast-color);
129
+ border: var(--lineWidth) solid var(--sn-stylekit-border-color);
130
+ background-color: var(--sn-stylekit-neutral-color) !important;
131
+ background-clip: padding-box;
132
+ }
133
+
134
+ td {
135
+ color: var(--sn-stylekit-paragraph-text-color);
136
+ border: var(--lineWidth) solid var(--sn-stylekit-border-color);
137
+ background: inherit;
138
+ }
139
+ }
140
+
141
+ .image,
142
+ .system,
143
+ .empty {
144
+ background-color: var(--sn-stylekit-secondary-contrast-background-color);
145
+ }
146
+
147
+ .empty .placeholder::before {
148
+ color: var(--sn-stylekit-foreground-color);
149
+ }
150
+
151
+ .code-inline {
152
+ color: var(--sn-stylekit-background-color);
153
+ background-color: var(--sn-stylekit-foreground-color);
154
+ }
155
+ }
156
+
157
+ .slash-dropdown {
158
+ background-color: var(--sn-stylekit-contrast-background-color) !important;
159
+ border-color: var(--sn-stylekit-border-color) !important;
160
+
161
+ .slash-dropdown-item {
162
+ color: var(--sn-stylekit-paragraph-text-color) !important;
163
+
164
+ .icon {
165
+ color: var(--sn-stylekit-paragraph-text-color) !important;
166
+ }
167
+ }
168
+ }
169
+
170
+ .milkdown-emoji-filter {
171
+ background-color: var(--sn-stylekit-contrast-background-color) !important;
172
+ border-color: var(--sn-stylekit-border-color) !important;
173
+ color: var(--sn-stylekit-paragraph-text-color) !important;
174
+ }
175
+
176
+ .tooltip {
177
+ background-color: var(--sn-stylekit-contrast-background-color) !important;
178
+ border-color: var(--sn-stylekit-border-color) !important;
179
+
180
+ .icon {
181
+ color: var(--sn-stylekit-paragraph-text-color) !important;
182
+ }
183
+
184
+ .icon:not(:last-child)::after {
185
+ width: 0 !important;
186
+ right: 0 !important;
187
+ }
188
+ }
189
+
190
+ .tooltip-input {
191
+ background-color: var(--sn-stylekit-contrast-background-color) !important;
192
+ border-color: var(--sn-stylekit-border-color) !important;
193
+
194
+ button {
195
+ color: var(--sn-stylekit-success-color) !important;
196
+ }
197
+
198
+ input {
199
+ color: var(--sn-stylekit-paragraph-text-color) !important;
200
+ }
201
+
202
+ input::placeholder {
203
+ color: var(--sn-stylekit-neutral-color) !important;
204
+ }
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
@@ -0,0 +1,42 @@
1
+ import './styles.scss'
2
+
3
+ import PropTypes, { InferProps } from 'prop-types'
4
+ import { memo } from 'react'
5
+
6
+ export const enum SplitViewDirection {
7
+ Horizontal = 'horizontal',
8
+ Vertical = 'vertical',
9
+ }
10
+
11
+ const enum SplitViewType {
12
+ Row = 'row',
13
+ Column = 'column',
14
+ }
15
+
16
+ const propTypes = {
17
+ split: PropTypes.bool.isRequired,
18
+ direction: PropTypes.oneOf([SplitViewDirection.Horizontal, SplitViewDirection.Vertical]).isRequired,
19
+ children: PropTypes.arrayOf(PropTypes.element).isRequired,
20
+ }
21
+
22
+ type SplitViewProps = InferProps<typeof propTypes>
23
+
24
+ const SplitView: React.FC<SplitViewProps> = ({ children, split, direction }) => {
25
+ const childClassName = direction === SplitViewDirection.Horizontal ? SplitViewType.Column : SplitViewType.Row
26
+
27
+ return (
28
+ <div className={`container ${direction}`}>
29
+ <div className={`${childClassName} ${split ? 'half' : 'full'}`}>{children[0]}</div>
30
+ {split && (
31
+ <>
32
+ <div className="separator" />
33
+ <div className={`${childClassName} half`}>{children[1]}</div>
34
+ </>
35
+ )}
36
+ </div>
37
+ )
38
+ }
39
+
40
+ SplitView.propTypes = propTypes
41
+
42
+ export default memo(SplitView)
@@ -0,0 +1,51 @@
1
+ .container > .separator {
2
+ background-color: var(--sn-stylekit-border-color);
3
+ }
4
+
5
+ .container.horizontal {
6
+ flex-direction: row;
7
+ overflow-x: clip;
8
+
9
+ .column {
10
+ flex: 1;
11
+ }
12
+
13
+ .column.full {
14
+ max-width: 100%;
15
+ }
16
+
17
+ .column.half {
18
+ max-width: 50%;
19
+ }
20
+
21
+ .separator {
22
+ width: 10px;
23
+ }
24
+
25
+ .column.half:last-child {
26
+ max-width: 50%;
27
+ }
28
+ }
29
+
30
+ .container.vertical {
31
+ flex-direction: column;
32
+ overflow-y: clip;
33
+
34
+ $separator-height: 1%;
35
+
36
+ .row.full {
37
+ height: 100%;
38
+ }
39
+
40
+ .row.half {
41
+ height: 40%;
42
+ }
43
+
44
+ .separator {
45
+ height: $separator-height;
46
+ }
47
+
48
+ .row.half:last-child {
49
+ height: 60% - $separator-height;
50
+ }
51
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,223 @@
1
+ import './stylesheets/main.scss'
2
+
3
+ import { Component, createRef, StrictMode } from 'react'
4
+ import ReactDOM from 'react-dom'
5
+
6
+ import CodeMirrorEditor, { CodeMirrorRef } from './components/CodeMirror'
7
+ import MilkdownEditor, { MilkdownRef } from './components/Milkdown'
8
+ import SplitView, { SplitViewDirection } from './components/SplitView'
9
+
10
+ import EditorKit, { EditorKitDelegate } from '@standardnotes/editor-kit'
11
+ import { marked } from 'marked'
12
+
13
+ import { MenuConfig, menuConfig } from './components/Milkdown/plugins/advanced-menu/config'
14
+
15
+ enum TextChangeSource {
16
+ Milkdown = 'milkdown',
17
+ CodeMirror = 'codemirror',
18
+ }
19
+
20
+ type AppProps = {}
21
+ type AppState = {
22
+ splitView: boolean
23
+ splitViewDirection: SplitViewDirection
24
+ editable: boolean
25
+ spellcheck: boolean
26
+ isLoading: boolean
27
+ }
28
+
29
+ class AppWrapper extends Component<AppProps, AppState> {
30
+ private editorKit?: EditorKit
31
+ private prevText: string = ''
32
+
33
+ private milkdownRef = createRef<MilkdownRef>()
34
+ private codeMirrorRef = createRef<CodeMirrorRef>()
35
+
36
+ constructor(props: AppProps) {
37
+ super(props)
38
+
39
+ this.state = {
40
+ splitView: false,
41
+ splitViewDirection: this.getSplitViewDirection(),
42
+ editable: true,
43
+ spellcheck: true,
44
+ isLoading: true,
45
+ }
46
+
47
+ this.handleSplitViewDirectionChange = this.handleSplitViewDirectionChange.bind(this)
48
+ }
49
+
50
+ componentDidMount() {
51
+ this.configureEditorKit()
52
+ window.matchMedia('(max-width: 768px)').addEventListener('change', this.handleSplitViewDirectionChange)
53
+ }
54
+
55
+ componentWillUnmount(): void {
56
+ window.matchMedia('(max-width: 768px)').removeEventListener('change', this.handleSplitViewDirectionChange)
57
+ }
58
+
59
+ private configureEditorKit() {
60
+ const editorKitDelegate: EditorKitDelegate = {
61
+ setEditorRawText: (text: string) => {
62
+ this.prevText = text.trim()
63
+
64
+ this.updateMilkdownText(this.prevText)
65
+ this.updateCodeMirrorText(this.prevText)
66
+
67
+ this.setState({
68
+ isLoading: false,
69
+ })
70
+ },
71
+ generateCustomPreview: (text: string) => {
72
+ const htmlPreview = marked.parse(text)
73
+ const tmpElement = document.createElement('div')
74
+ tmpElement.innerHTML = htmlPreview
75
+
76
+ const preview = tmpElement.textContent || tmpElement.innerText || ''
77
+
78
+ return {
79
+ plain: this.truncateString(preview).trim(),
80
+ }
81
+ },
82
+ onNoteValueChange: async (note: any) => {
83
+ const editable = !note.content.appData?.['org.standardnotes.sn']?.locked ?? true
84
+ const spellcheck = note.content.spellcheck
85
+
86
+ this.setState({
87
+ editable,
88
+ spellcheck,
89
+ })
90
+ },
91
+ onNoteLockToggle: (locked: boolean) => {
92
+ const editable = !locked
93
+
94
+ this.setState({
95
+ editable,
96
+ })
97
+ },
98
+ handleRequestForContentHeight: () => {
99
+ let height = 0
100
+ height += document.querySelector('.ProseMirror')?.clientHeight ?? 0
101
+ height += document.querySelector('.milkdown-menu')?.clientHeight ?? 0
102
+ return height
103
+ },
104
+ }
105
+
106
+ this.editorKit = new EditorKit(editorKitDelegate, {
107
+ mode: 'markdown',
108
+ })
109
+ }
110
+
111
+ private updateMilkdownText(text: string) {
112
+ const { current } = this.milkdownRef
113
+ if (current) {
114
+ current.update(text)
115
+ }
116
+ }
117
+
118
+ private updateCodeMirrorText(text: string) {
119
+ const { current } = this.codeMirrorRef
120
+ if (current) {
121
+ current.update(text)
122
+ }
123
+ }
124
+
125
+ private onTextChange = (text: string, source = TextChangeSource.Milkdown) => {
126
+ if (this.prevText === text.trim()) {
127
+ return
128
+ }
129
+
130
+ this.prevText = text.trim()
131
+ this.editorKit!.onEditorValueChanged(text)
132
+
133
+ /**
134
+ * Bi-directional text update:
135
+ *
136
+ * - Codemirror <- Milkdown
137
+ * - Milkdown -> Codemirror
138
+ */
139
+ switch (source) {
140
+ case TextChangeSource.CodeMirror:
141
+ this.updateMilkdownText(this.prevText)
142
+ break
143
+ case TextChangeSource.Milkdown:
144
+ default:
145
+ this.updateCodeMirrorText(this.prevText)
146
+ break
147
+ }
148
+ }
149
+
150
+ private toggleSplitView = () => {
151
+ const { splitView } = this.state
152
+
153
+ this.setState({
154
+ splitView: !splitView,
155
+ })
156
+ }
157
+
158
+ private getSplitViewDirection = (): SplitViewDirection => {
159
+ const isMobile = window.innerWidth < 768
160
+ return isMobile ? SplitViewDirection.Vertical : SplitViewDirection.Horizontal
161
+ }
162
+
163
+ private handleSplitViewDirectionChange = (event: MediaQueryListEvent) => {
164
+ this.setState({
165
+ splitViewDirection: this.getSplitViewDirection(),
166
+ })
167
+ }
168
+
169
+ private truncateString(text: string, limit = 90) {
170
+ if (text.length <= limit) {
171
+ return text
172
+ }
173
+ return text.substring(0, limit) + '...'
174
+ }
175
+
176
+ render() {
177
+ const { splitView, splitViewDirection, editable, spellcheck, isLoading } = this.state
178
+
179
+ if (isLoading) {
180
+ return null
181
+ }
182
+
183
+ const customMenu: MenuConfig = [
184
+ ...menuConfig,
185
+ [
186
+ {
187
+ type: 'button',
188
+ icon: 'code',
189
+ active: () => splitView,
190
+ callback: this.toggleSplitView,
191
+ alwaysVisible: true,
192
+ },
193
+ ],
194
+ ]
195
+
196
+ return (
197
+ <SplitView split={splitView} direction={splitViewDirection}>
198
+ <MilkdownEditor
199
+ ref={this.milkdownRef}
200
+ onChange={this.onTextChange}
201
+ value={this.prevText}
202
+ menuConfig={customMenu}
203
+ editable={editable}
204
+ spellcheck={spellcheck}
205
+ />
206
+ <CodeMirrorEditor
207
+ ref={this.codeMirrorRef}
208
+ onChange={(text) => this.onTextChange(text, TextChangeSource.CodeMirror)}
209
+ value={this.prevText}
210
+ editable={editable}
211
+ spellcheck={spellcheck}
212
+ />
213
+ </SplitView>
214
+ )
215
+ }
216
+ }
217
+
218
+ ReactDOM.render(
219
+ <StrictMode>
220
+ <AppWrapper />
221
+ </StrictMode>,
222
+ document.getElementById('root'),
223
+ )
@@ -0,0 +1 @@
1
+ /// <reference types="react-scripts" />
@@ -0,0 +1,5 @@
1
+ // jest-dom adds custom jest matchers for asserting on DOM nodes.
2
+ // allows you to do things like:
3
+ // expect(element).toHaveTextContent(/react/i)
4
+ // learn more: https://github.com/testing-library/jest-dom
5
+ import '@testing-library/jest-dom'
@@ -0,0 +1,45 @@
1
+ @import '../../node_modules/@standardnotes/styles/src/Styles/main.scss';
2
+ @import 'material-icons/iconfont/material-icons.css';
3
+ @import 'katex/dist/katex.min.css';
4
+ @import 'prism/material-light.css';
5
+
6
+ :root {
7
+ --sn-stylekit-monospace-font: SFMono-Regular, Consolas, Liberation Mono, Menlo, 'Ubuntu Mono', courier, monospace;
8
+ }
9
+
10
+ body,
11
+ html {
12
+ background-color: var(--sn-stylekit-secondary-background-color);
13
+ height: 100%;
14
+ margin: 0;
15
+ padding: 0;
16
+ width: 100%;
17
+ }
18
+
19
+ * {
20
+ // To prevent gray flash when focusing input on mobile Safari
21
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
22
+ font-family: var(--sn-stylekit-sans-serif-font);
23
+ }
24
+
25
+ #root {
26
+ height: 100%;
27
+ }
28
+
29
+ .sn-component {
30
+ display: flex;
31
+ flex-direction: column;
32
+ font-size: var(--sn-stylekit-font-size-editor);
33
+ min-height: 100vh;
34
+ @media screen and (max-width: 420px) {
35
+ min-height: -webkit-fill-available;
36
+ }
37
+ }
38
+
39
+ .container {
40
+ flex: 1;
41
+ flex-grow: 1;
42
+ display: flex;
43
+ width: 100%;
44
+ height: 100%;
45
+ }