You've already forked joplin
							
							
				mirror of
				https://github.com/laurent22/joplin.git
				synced 2025-10-31 00:07:48 +02:00 
			
		
		
		
	Chore: Set up repository for testing/preparation for mobile markdown toolbar PR (#6650)
This commit is contained in:
		| @@ -46,7 +46,7 @@ packages/app-desktop/packageInfo.js | ||||
| packages/app-desktop/services/electron-context-menu.js | ||||
| packages/app-desktop/vendor/lib/ | ||||
| packages/app-mobile/android | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror.bundle.js | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.bundle.js | ||||
| packages/app-mobile/ios | ||||
| packages/app-mobile/lib/rnInjectedJs/ | ||||
| packages/app-mobile/locales | ||||
| @@ -853,9 +853,18 @@ packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js.ma | ||||
| packages/app-mobile/components/NoteBodyViewer/hooks/useSource.d.ts | ||||
| packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js | ||||
| packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js.map | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror.d.ts | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror.js | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror.js.map | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.d.ts | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.js | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.js.map | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.d.ts | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.js | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.js.map | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.test.d.ts | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.test.js | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.test.js.map | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/theme.d.ts | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/theme.js | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/theme.js.map | ||||
| packages/app-mobile/components/NoteEditor/NoteEditor.d.ts | ||||
| packages/app-mobile/components/NoteEditor/NoteEditor.js | ||||
| packages/app-mobile/components/NoteEditor/NoteEditor.js.map | ||||
|   | ||||
							
								
								
									
										15
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -843,9 +843,18 @@ packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js.ma | ||||
| packages/app-mobile/components/NoteBodyViewer/hooks/useSource.d.ts | ||||
| packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js | ||||
| packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js.map | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror.d.ts | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror.js | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror.js.map | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.d.ts | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.js | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/CodeMirror.js.map | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.d.ts | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.js | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.js.map | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.test.d.ts | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.test.js | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/markdownMathParser.test.js.map | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/theme.d.ts | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/theme.js | ||||
| packages/app-mobile/components/NoteEditor/CodeMirror/theme.js.map | ||||
| packages/app-mobile/components/NoteEditor/NoteEditor.d.ts | ||||
| packages/app-mobile/components/NoteEditor/NoteEditor.js | ||||
| packages/app-mobile/components/NoteEditor/NoteEditor.js.map | ||||
|   | ||||
							
								
								
									
										19
									
								
								packages/app-clipper/popup/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										19
									
								
								packages/app-clipper/popup/package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -20253,6 +20253,19 @@ | ||||
|       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", | ||||
|       "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" | ||||
|     }, | ||||
|     "node_modules/typescript": { | ||||
|       "version": "3.9.10", | ||||
|       "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", | ||||
|       "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", | ||||
|       "peer": true, | ||||
|       "bin": { | ||||
|         "tsc": "bin/tsc", | ||||
|         "tsserver": "bin/tsserver" | ||||
|       }, | ||||
|       "engines": { | ||||
|         "node": ">=4.2.0" | ||||
|       } | ||||
|     }, | ||||
|     "node_modules/unicode-canonical-property-names-ecmascript": { | ||||
|       "version": "1.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", | ||||
| @@ -37995,6 +38008,12 @@ | ||||
|       "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", | ||||
|       "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" | ||||
|     }, | ||||
|     "typescript": { | ||||
|       "version": "3.9.10", | ||||
|       "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", | ||||
|       "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", | ||||
|       "peer": true | ||||
|     }, | ||||
|     "unicode-canonical-property-names-ecmascript": { | ||||
|       "version": "1.0.4", | ||||
|       "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", | ||||
|   | ||||
							
								
								
									
										4
									
								
								packages/app-mobile/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								packages/app-mobile/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -63,7 +63,7 @@ buck-out/ | ||||
| lib/csstojs/ | ||||
| lib/rnInjectedJs/ | ||||
| dist/ | ||||
| components/NoteEditor/CodeMirror.bundle.js | ||||
| components/NoteEditor/CodeMirror.bundle.min.js | ||||
| components/NoteEditor/CodeMirror/CodeMirror.bundle.js | ||||
| components/NoteEditor/CodeMirror/CodeMirror.bundle.min.js | ||||
|  | ||||
| utils/fs-driver-android.js | ||||
|   | ||||
| @@ -9,11 +9,11 @@ | ||||
| // wrapper to access CodeMirror functionalities. Anything else should be done
 | ||||
| // from NoteEditor.tsx.
 | ||||
| 
 | ||||
| import { EditorState, Extension } from '@codemirror/state'; | ||||
| import createTheme from './theme'; | ||||
| 
 | ||||
| import { EditorState } from '@codemirror/state'; | ||||
| import { markdown } from '@codemirror/lang-markdown'; | ||||
| import { highlightSelectionMatches, search } from '@codemirror/search'; | ||||
| import { defaultHighlightStyle, syntaxHighlighting, HighlightStyle } from '@codemirror/language'; | ||||
| import { tags } from '@lezer/highlight'; | ||||
| import { EditorView, drawSelection, highlightSpecialChars, ViewUpdate } from '@codemirror/view'; | ||||
| import { undo, redo, history, undoDepth, redoDepth } from '@codemirror/commands'; | ||||
| 
 | ||||
| @@ -21,6 +21,8 @@ import { keymap } from '@codemirror/view'; | ||||
| import { indentOnInput } from '@codemirror/language'; | ||||
| import { searchKeymap } from '@codemirror/search'; | ||||
| import { historyKeymap, defaultKeymap } from '@codemirror/commands'; | ||||
| import { MarkdownMathExtension } from './markdownMathParser'; | ||||
| import { GFM as GitHubFlavoredMarkdownExtension } from '@lezer/markdown'; | ||||
| 
 | ||||
| interface CodeMirrorResult { | ||||
| 	editor: EditorView; | ||||
| @@ -42,120 +44,6 @@ function logMessage(...msg: any[]) { | ||||
| 	postMessage('onLog', { value: msg }); | ||||
| } | ||||
| 
 | ||||
| // For an example on how to customize the theme, see:
 | ||||
| //
 | ||||
| // https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts
 | ||||
| //
 | ||||
| // For a tutorial, see:
 | ||||
| //
 | ||||
| // https://codemirror.net/6/examples/styling/#themes
 | ||||
| //
 | ||||
| // Use Safari developer tools to view the content of the CodeMirror iframe while
 | ||||
| // the app is running. It seems that what appears as ".ͼ1" in the CSS is the
 | ||||
| // equivalent of "&" in the theme object. So to target ".ͼ1.cm-focused", you'd
 | ||||
| // use '&.cm-focused' in the theme.
 | ||||
| const createTheme = (theme: any): Extension[] => { | ||||
| 	const isDarkTheme = theme.appearance === 'dark'; | ||||
| 
 | ||||
| 	const baseGlobalStyle: Record<string, string> = { | ||||
| 		color: theme.color, | ||||
| 		backgroundColor: theme.backgroundColor, | ||||
| 		fontFamily: theme.fontFamily, | ||||
| 		fontSize: `${theme.fontSize}px`, | ||||
| 	}; | ||||
| 	const baseCursorStyle: Record<string, string> = { }; | ||||
| 	const baseContentStyle: Record<string, string> = { }; | ||||
| 	const baseSelectionStyle: Record<string, string> = { }; | ||||
| 
 | ||||
| 	// If we're in dark mode, the caret and selection are difficult to see.
 | ||||
| 	// Adjust them appropriately
 | ||||
| 	if (isDarkTheme) { | ||||
| 		// Styling the caret requires styling both the caret itself
 | ||||
| 		// and the CodeMirror caret.
 | ||||
| 		// See https://codemirror.net/6/examples/styling/#themes
 | ||||
| 		baseContentStyle.caretColor = 'white'; | ||||
| 		baseCursorStyle.borderLeftColor = 'white'; | ||||
| 
 | ||||
| 		baseSelectionStyle.backgroundColor = '#6b6b6b'; | ||||
| 	} | ||||
| 
 | ||||
| 	const baseTheme = EditorView.baseTheme({ | ||||
| 		'&': baseGlobalStyle, | ||||
| 
 | ||||
| 		// These must be !important or more specific than CodeMirror's built-ins
 | ||||
| 		'.cm-content': baseContentStyle, | ||||
| 		'&.cm-focused .cm-cursor': baseCursorStyle, | ||||
| 		'&.cm-focused .cm-selectionBackground, ::selection': baseSelectionStyle, | ||||
| 
 | ||||
| 		'&.cm-focused': { | ||||
| 			outline: 'none', | ||||
| 		}, | ||||
| 	}); | ||||
| 
 | ||||
| 	const appearanceTheme = EditorView.theme({}, { dark: isDarkTheme }); | ||||
| 
 | ||||
| 	const baseHeadingStyle = { | ||||
| 		fontWeight: 'bold', | ||||
| 		fontFamily: theme.fontFamily, | ||||
| 	}; | ||||
| 
 | ||||
| 	const highlightingStyle = HighlightStyle.define([ | ||||
| 		{ | ||||
| 			tag: tags.strong, | ||||
| 			fontWeight: 'bold', | ||||
| 		}, | ||||
| 		{ | ||||
| 			tag: tags.emphasis, | ||||
| 			fontStyle: 'italic', | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading1, | ||||
| 			fontSize: '1.6em', | ||||
| 			borderBottom: `1px solid ${theme.dividerColor}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading2, | ||||
| 			fontSize: '1.4em', | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading3, | ||||
| 			fontSize: '1.3em', | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading4, | ||||
| 			fontSize: '1.2em', | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading5, | ||||
| 			fontSize: '1.1em', | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading6, | ||||
| 			fontSize: '1.0em', | ||||
| 		}, | ||||
| 		{ | ||||
| 			tag: tags.list, | ||||
| 			fontFamily: theme.fontFamily, | ||||
| 		}, | ||||
| 	]); | ||||
| 
 | ||||
| 	return [ | ||||
| 		baseTheme, | ||||
| 		appearanceTheme, | ||||
| 		syntaxHighlighting(highlightingStyle), | ||||
| 
 | ||||
| 		// If we haven't defined highlighting for tags, fall back
 | ||||
| 		// to the default.
 | ||||
| 		syntaxHighlighting(defaultHighlightStyle, { fallback: true }), | ||||
| 	]; | ||||
| }; | ||||
| 
 | ||||
| export function initCodeMirror(parentElement: any, initialText: string, theme: any): CodeMirrorResult { | ||||
| 	logMessage('Initializing CodeMirror...'); | ||||
| 
 | ||||
| @@ -183,7 +71,12 @@ export function initCodeMirror(parentElement: any, initialText: string, theme: a | ||||
| 			// See https://github.com/codemirror/basic-setup/blob/main/src/codemirror.ts
 | ||||
| 			// for a sample configuration.
 | ||||
| 			extensions: [ | ||||
| 				markdown(), | ||||
| 				markdown({ | ||||
| 					extensions: [ | ||||
| 						MarkdownMathExtension, | ||||
| 						GitHubFlavoredMarkdownExtension, | ||||
| 					], | ||||
| 				}), | ||||
| 				...createTheme(theme), | ||||
| 				history(), | ||||
| 				search(), | ||||
| @@ -0,0 +1,152 @@ | ||||
| import { markdown } from '@codemirror/lang-markdown'; | ||||
| import { ensureSyntaxTree } from '@codemirror/language'; | ||||
| import { SyntaxNode } from '@lezer/common'; | ||||
| import { EditorState } from '@codemirror/state'; | ||||
| import { blockMathTagName, inlineMathContentTagName, inlineMathTagName, MarkdownMathExtension } from './markdownMathParser'; | ||||
| import { GFM as GithubFlavoredMarkdownExt } from '@lezer/markdown'; | ||||
|  | ||||
| const syntaxTreeCreateTimeout = 100; // ms | ||||
|  | ||||
| /** Create an EditorState with markdown extensions */ | ||||
| const createEditorState = (initialText: string): EditorState => { | ||||
| 	return EditorState.create({ | ||||
| 		doc: initialText, | ||||
| 		extensions: [ | ||||
| 			markdown({ | ||||
| 				extensions: [MarkdownMathExtension, GithubFlavoredMarkdownExt], | ||||
| 			}), | ||||
| 		], | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Returns a list of all nodes with the given name in the given editor's syntax tree. | ||||
|  * Attempts to create the syntax tree if it doesn't exist. | ||||
|  */ | ||||
| const findNodesWithName = (editor: EditorState, nodeName: string) => { | ||||
| 	const result: SyntaxNode[] = []; | ||||
| 	ensureSyntaxTree(editor, syntaxTreeCreateTimeout)?.iterate({ | ||||
| 		enter: (node) => { | ||||
| 			if (node.name === nodeName) { | ||||
| 				result.push(node.node); | ||||
| 			} | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	return result; | ||||
| }; | ||||
|  | ||||
| describe('Inline parsing', () => { | ||||
| 	it('Document with just a math region', () => { | ||||
| 		const documentText = '$3 + 3$'; | ||||
| 		const editor = createEditorState(documentText); | ||||
| 		const inlineMathNodes = findNodesWithName(editor, inlineMathTagName); | ||||
| 		const inlineMathContentNodes = findNodesWithName(editor, inlineMathContentTagName); | ||||
|  | ||||
| 		// There should only be one inline node | ||||
| 		expect(inlineMathNodes.length).toBe(1); | ||||
|  | ||||
| 		expect(inlineMathNodes[0].from).toBe(0); | ||||
| 		expect(inlineMathNodes[0].to).toBe(documentText.length); | ||||
|  | ||||
| 		// The content tag should be replaced by the internal sTeX parser | ||||
| 		expect(inlineMathContentNodes.length).toBe(0); | ||||
| 	}); | ||||
|  | ||||
| 	it('Inline math mixed with text', () => { | ||||
| 		const beforeMath = '# Testing!\n\nThis is a test of '; | ||||
| 		const mathRegion = '$\\TeX % TeX Comment!$'; | ||||
| 		const afterMath = ' formatting.'; | ||||
| 		const documentText = `${beforeMath}${mathRegion}${afterMath}`; | ||||
|  | ||||
| 		const editor = createEditorState(documentText); | ||||
| 		const inlineMathNodes = findNodesWithName(editor, inlineMathTagName); | ||||
| 		const blockMathNodes = findNodesWithName(editor, blockMathTagName); | ||||
| 		const commentNodes = findNodesWithName(editor, 'comment'); | ||||
|  | ||||
| 		expect(inlineMathNodes.length).toBe(1); | ||||
| 		expect(blockMathNodes.length).toBe(0); | ||||
| 		expect(commentNodes.length).toBe(1); | ||||
|  | ||||
| 		expect(inlineMathNodes[0].from).toBe(beforeMath.length); | ||||
| 		expect(inlineMathNodes[0].to).toBe(beforeMath.length + mathRegion.length); | ||||
| 	}); | ||||
|  | ||||
| 	it('Inline math with no ending $ in a block', () => { | ||||
| 		const documentText = 'This is a $test\n\nof inline math$...'; | ||||
| 		const editor = createEditorState(documentText); | ||||
| 		const inlineMathNodes = findNodesWithName(editor, inlineMathTagName); | ||||
|  | ||||
| 		// Math should end if there is no matching '$'. | ||||
| 		expect(inlineMathNodes.length).toBe(0); | ||||
| 	}); | ||||
|  | ||||
| 	it('Shouldn\'t start if block would have spaces just inside', () => { | ||||
| 		const documentText = 'This is a $ test of inline math$...\n\n$Testing... $...'; | ||||
| 		const editor = createEditorState(documentText); | ||||
| 		expect(findNodesWithName(editor, inlineMathTagName).length).toBe(0); | ||||
| 	}); | ||||
|  | ||||
| 	it('Shouldn\'t start if $ is escaped', () => { | ||||
| 		const documentText = 'This is a \\$test of inline math$...'; | ||||
| 		const editor = createEditorState(documentText); | ||||
| 		expect(findNodesWithName(editor, inlineMathTagName).length).toBe(0); | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| describe('Block math tests', () => { | ||||
| 	it('Document with just block math', () => { | ||||
| 		const documentText = '$$\n\t\\{ 1, 1, 2, 3, 5, ... \\}\n$$'; | ||||
| 		const editor = createEditorState(documentText); | ||||
| 		const inlineMathNodes = findNodesWithName(editor, inlineMathTagName); | ||||
| 		const blockMathNodes = findNodesWithName(editor, blockMathTagName); | ||||
|  | ||||
| 		expect(inlineMathNodes.length).toBe(0); | ||||
| 		expect(blockMathNodes.length).toBe(1); | ||||
|  | ||||
| 		expect(blockMathNodes[0].from).toBe(0); | ||||
| 		expect(blockMathNodes[0].to).toBe(documentText.length); | ||||
| 	}); | ||||
|  | ||||
| 	it('Block math with comment', () => { | ||||
| 		const startingText = '$$ % Testing...\n\t\\text{Test.}\n$$'; | ||||
| 		const afterMath = '\nTest.'; | ||||
| 		const editor = createEditorState(startingText + afterMath); | ||||
| 		const inlineMathNodes = findNodesWithName(editor, inlineMathTagName); | ||||
| 		const blockMathNodes = findNodesWithName(editor, blockMathTagName); | ||||
| 		const texParserComments = findNodesWithName(editor, 'comment'); | ||||
|  | ||||
| 		expect(inlineMathNodes.length).toBe(0); | ||||
| 		expect(blockMathNodes.length).toBe(1); | ||||
| 		expect(texParserComments.length).toBe(1); | ||||
|  | ||||
| 		expect(blockMathNodes[0].from).toBe(0); | ||||
| 		expect(blockMathNodes[0].to).toBe(startingText.length); | ||||
|  | ||||
| 		expect(texParserComments[0]).toMatchObject({ | ||||
| 			from: '$$ '.length, | ||||
| 			to: '$$ % Testing...'.length, | ||||
| 		}); | ||||
| 	}); | ||||
|  | ||||
| 	it('Block math without an ending tag', () => { | ||||
| 		const beforeMath = '# Testing...\n\n'; | ||||
| 		const documentText = `${beforeMath}$$\n\t\\text{Testing...}\n\n\t3 + 3 = 6`; | ||||
| 		const editor = createEditorState(documentText); | ||||
| 		const blockMathNodes = findNodesWithName(editor, blockMathTagName); | ||||
|  | ||||
| 		expect(blockMathNodes.length).toBe(1); | ||||
| 		expect(blockMathNodes[0].from).toBe(beforeMath.length); | ||||
| 		expect(blockMathNodes[0].to).toBe(documentText.length); | ||||
| 	}); | ||||
|  | ||||
| 	it('Single-line declaration of block math', () => { | ||||
| 		const documentText = '$$ Test. $$'; | ||||
| 		const editor = createEditorState(documentText); | ||||
| 		const blockMathNodes = findNodesWithName(editor, blockMathTagName); | ||||
|  | ||||
| 		expect(blockMathNodes.length).toBe(1); | ||||
| 		expect(blockMathNodes[0].from).toBe(0); | ||||
| 		expect(blockMathNodes[0].to).toBe(documentText.length); | ||||
| 	}); | ||||
| }); | ||||
| @@ -0,0 +1,216 @@ | ||||
| /** | ||||
|  * Search for $s and $$s in markdown and mark the regions between them as math. | ||||
|  * | ||||
|  * Text between single $s is marked as InlineMath and text between $$s is marked | ||||
|  * as BlockMath. | ||||
|  */ | ||||
|  | ||||
| import { tags, Tag } from '@lezer/highlight'; | ||||
| import { parseMixed, SyntaxNodeRef, Input, NestedParse, ParseWrapper } from '@lezer/common'; | ||||
|  | ||||
| // Extend the existing markdown parser | ||||
| import { | ||||
| 	MarkdownConfig, InlineContext, | ||||
| 	BlockContext, Line, LeafBlock, | ||||
| } from '@lezer/markdown'; | ||||
|  | ||||
| // The existing stexMath parser is used to parse the text between the $s | ||||
| import { stexMath } from '@codemirror/legacy-modes/mode/stex'; | ||||
| import { StreamLanguage } from '@codemirror/language'; | ||||
|  | ||||
| const dollarSignCharcode = 36; | ||||
| const backslashCharcode = 92; | ||||
|  | ||||
| // (?:[>]\s*)?: Optionally allow block math lines to start with '> ' | ||||
| const mathBlockStartRegex = /^(?:\s*[>]\s*)?\$\$/; | ||||
| const mathBlockEndRegex = /\$\$\s*$/; | ||||
|  | ||||
| const texLanguage = StreamLanguage.define(stexMath); | ||||
| export const blockMathTagName = 'BlockMath'; | ||||
| export const blockMathContentTagName = 'BlockMathContent'; | ||||
| export const inlineMathTagName = 'InlineMath'; | ||||
| export const inlineMathContentTagName = 'InlineMathContent'; | ||||
|  | ||||
| export const mathTag = Tag.define(tags.monospace); | ||||
| export const inlineMathTag = Tag.define(mathTag); | ||||
|  | ||||
| /** | ||||
|  * Wraps a TeX math-mode parser. This removes [nodeTag] from the syntax tree | ||||
|  * and replaces it with a region handled by the sTeXMath parser. | ||||
|  * | ||||
|  * @param nodeTag Name of the nodes to replace with regions parsed by the sTeX parser. | ||||
|  * @returns a wrapped sTeX parser. | ||||
|  */ | ||||
| const wrappedTeXParser = (nodeTag: string): ParseWrapper => { | ||||
| 	return parseMixed((node: SyntaxNodeRef, _input: Input): NestedParse => { | ||||
| 		if (node.name !== nodeTag) { | ||||
| 			return null; | ||||
| 		} | ||||
|  | ||||
| 		return { | ||||
| 			parser: texLanguage.parser, | ||||
| 		}; | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| // Markdown extension for recognizing inline code | ||||
| const InlineMathConfig: MarkdownConfig = { | ||||
| 	defineNodes: [ | ||||
| 		{ | ||||
| 			name: inlineMathTagName, | ||||
| 			style: inlineMathTag, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: inlineMathContentTagName, | ||||
| 		}, | ||||
| 	], | ||||
| 	parseInline: [{ | ||||
| 		name: inlineMathTagName, | ||||
| 		after: 'InlineCode', | ||||
|  | ||||
| 		parse(cx: InlineContext, current: number, pos: number): number { | ||||
| 			const prevCharCode = pos - 1 >= 0 ? cx.char(pos - 1) : -1; | ||||
| 			const nextCharCode = cx.char(pos + 1); | ||||
| 			if (current !== dollarSignCharcode | ||||
| 					|| prevCharCode === dollarSignCharcode | ||||
| 					|| nextCharCode === dollarSignCharcode) { | ||||
| 				return -1; | ||||
| 			} | ||||
|  | ||||
| 			// Don't match if there's a space directly after the '$' | ||||
| 			if (/\s/.exec(String.fromCharCode(nextCharCode))) { | ||||
| 				return -1; | ||||
| 			} | ||||
|  | ||||
| 			const start = pos; | ||||
| 			const end = cx.end; | ||||
| 			let escaped = false; | ||||
|  | ||||
| 			pos ++; | ||||
|  | ||||
| 			// Scan ahead for the next '$' symbol | ||||
| 			for (; pos < end && (escaped || cx.char(pos) !== dollarSignCharcode); pos++) { | ||||
| 				if (!escaped && cx.char(pos) === backslashCharcode) { | ||||
| 					escaped = true; | ||||
| 				} else { | ||||
| 					escaped = false; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			// Don't match if the ending '$' is preceded by a space. | ||||
| 			const prevChar = String.fromCharCode(cx.char(pos - 1)); | ||||
| 			if (/\s/.exec(prevChar)) { | ||||
| 				return -1; | ||||
| 			} | ||||
|  | ||||
| 			// It isn't a math region if there is no ending '$' | ||||
| 			if (pos === end) { | ||||
| 				return -1; | ||||
| 			} | ||||
|  | ||||
| 			// Advance to just after the ending '$' | ||||
| 			pos ++; | ||||
|  | ||||
| 			// Add a wraping inlineMathTagName node that contains an inlineMathContentTagName. | ||||
| 			// The inlineMathContentTagName node can thus be safely removed and the region | ||||
| 			// will still be marked as a math region. | ||||
| 			const contentElem = cx.elt(inlineMathContentTagName, start + 1, pos - 1); | ||||
| 			cx.addElement(cx.elt(inlineMathTagName, start, pos, [contentElem])); | ||||
|  | ||||
| 			return pos + 1; | ||||
| 		}, | ||||
| 	}], | ||||
| 	wrap: wrappedTeXParser(inlineMathContentTagName), | ||||
| }; | ||||
|  | ||||
| // Extension for recognising block code | ||||
| const BlockMathConfig: MarkdownConfig = { | ||||
| 	defineNodes: [ | ||||
| 		{ | ||||
| 			name: blockMathTagName, | ||||
| 			style: mathTag, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: blockMathContentTagName, | ||||
| 		}, | ||||
| 	], | ||||
| 	parseBlock: [{ | ||||
| 		name: blockMathTagName, | ||||
| 		before: 'Blockquote', | ||||
| 		parse(cx: BlockContext, line: Line): boolean { | ||||
| 			const delimLen = 2; | ||||
|  | ||||
| 			// $$ delimiter? Start math! | ||||
| 			const mathStartMatch = mathBlockStartRegex.exec(line.text); | ||||
| 			if (mathStartMatch) { | ||||
| 				const start = cx.lineStart + mathStartMatch[0].length; | ||||
| 				let stop; | ||||
|  | ||||
| 				let endMatch = mathBlockEndRegex.exec( | ||||
| 					line.text.substring(mathStartMatch[0].length) | ||||
| 				); | ||||
|  | ||||
| 				// If the math region ends immediately (on the same line), | ||||
| 				if (endMatch) { | ||||
| 					const lineLength = line.text.length; | ||||
| 					stop = cx.lineStart + lineLength - endMatch[0].length; | ||||
| 				} else { | ||||
| 					let hadNextLine = false; | ||||
|  | ||||
| 					// Otherwise, it's a multi-line block display. | ||||
| 					// Consume lines until we reach the end. | ||||
| 					do { | ||||
| 						hadNextLine = cx.nextLine(); | ||||
| 						endMatch = hadNextLine ? mathBlockEndRegex.exec(line.text) : null; | ||||
| 					} | ||||
| 					while (hadNextLine && endMatch === null); | ||||
|  | ||||
| 					if (hadNextLine && endMatch) { | ||||
| 						const lineLength = line.text.length; | ||||
|  | ||||
| 						// Remove the ending delimiter | ||||
| 						stop = cx.lineStart + lineLength - endMatch[0].length; | ||||
| 					} else { | ||||
| 						stop = cx.lineStart; | ||||
| 					} | ||||
| 				} | ||||
| 				const lineEnd = cx.lineStart + line.text.length; | ||||
|  | ||||
| 				// Label the region. Add two labels so that one can be removed. | ||||
| 				const contentElem = cx.elt(blockMathContentTagName, start, stop); | ||||
| 				const containerElement = cx.elt( | ||||
| 					blockMathTagName, | ||||
| 					start - delimLen, | ||||
|  | ||||
| 					// Math blocks don't need ending delimiters, so ensure we don't | ||||
| 					// include text that doesn't exist. | ||||
| 					Math.min(lineEnd, stop + delimLen), | ||||
|  | ||||
| 					// The child of the container element should be the content element | ||||
| 					[contentElem] | ||||
| 				); | ||||
| 				cx.addElement(containerElement); | ||||
|  | ||||
| 				// Don't re-process the ending delimiter (it may look the same | ||||
| 				// as the starting delimiter). | ||||
| 				cx.nextLine(); | ||||
|  | ||||
| 				return true; | ||||
| 			} | ||||
|  | ||||
| 			return false; | ||||
| 		}, | ||||
| 		// End paragraph-like blocks | ||||
| 		endLeaf(_cx: BlockContext, line: Line, _leaf: LeafBlock): boolean { | ||||
| 			// Leaf blocks (e.g. block quotes) end early if math starts. | ||||
| 			return mathBlockStartRegex.exec(line.text) !== null; | ||||
| 		}, | ||||
| 	}], | ||||
| 	wrap: wrappedTeXParser(blockMathContentTagName), | ||||
| }; | ||||
|  | ||||
| /** Markdown configuration for block and inline math support. */ | ||||
| export const MarkdownMathExtension: MarkdownConfig[] = [ | ||||
| 	InlineMathConfig, | ||||
| 	BlockMathConfig, | ||||
| ]; | ||||
							
								
								
									
										126
									
								
								packages/app-mobile/components/NoteEditor/CodeMirror/theme.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								packages/app-mobile/components/NoteEditor/CodeMirror/theme.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,126 @@ | ||||
| /** | ||||
|  * Create a set of Extensions that provide syntax highlighting. | ||||
|  */ | ||||
|  | ||||
|  | ||||
| import { defaultHighlightStyle, syntaxHighlighting, HighlightStyle } from '@codemirror/language'; | ||||
| import { tags } from '@lezer/highlight'; | ||||
| import { EditorView } from '@codemirror/view'; | ||||
| import { Extension } from '@codemirror/state'; | ||||
|  | ||||
| // For an example on how to customize the theme, see: | ||||
| // | ||||
| // https://github.com/codemirror/theme-one-dark/blob/main/src/one-dark.ts | ||||
| // | ||||
| // For a tutorial, see: | ||||
| // | ||||
| // https://codemirror.net/6/examples/styling/#themes | ||||
| // | ||||
| // Use Safari developer tools to view the content of the CodeMirror iframe while | ||||
| // the app is running. It seems that what appears as ".ͼ1" in the CSS is the | ||||
| // equivalent of "&" in the theme object. So to target ".ͼ1.cm-focused", you'd | ||||
| // use '&.cm-focused' in the theme. | ||||
| const createTheme = (theme: any): Extension[] => { | ||||
| 	const isDarkTheme = theme.appearance === 'dark'; | ||||
|  | ||||
| 	const baseGlobalStyle: Record<string, string> = { | ||||
| 		color: theme.color, | ||||
| 		backgroundColor: theme.backgroundColor, | ||||
| 		fontFamily: theme.fontFamily, | ||||
| 		fontSize: `${theme.fontSize}px`, | ||||
| 	}; | ||||
| 	const baseCursorStyle: Record<string, string> = { }; | ||||
| 	const baseContentStyle: Record<string, string> = { }; | ||||
| 	const baseSelectionStyle: Record<string, string> = { }; | ||||
|  | ||||
| 	// If we're in dark mode, the caret and selection are difficult to see. | ||||
| 	// Adjust them appropriately | ||||
| 	if (isDarkTheme) { | ||||
| 		// Styling the caret requires styling both the caret itself | ||||
| 		// and the CodeMirror caret. | ||||
| 		// See https://codemirror.net/6/examples/styling/#themes | ||||
| 		baseContentStyle.caretColor = 'white'; | ||||
| 		baseCursorStyle.borderLeftColor = 'white'; | ||||
|  | ||||
| 		baseSelectionStyle.backgroundColor = '#6b6b6b'; | ||||
| 	} | ||||
|  | ||||
| 	const baseTheme = EditorView.baseTheme({ | ||||
| 		'&': baseGlobalStyle, | ||||
|  | ||||
| 		// These must be !important or more specific than CodeMirror's built-ins | ||||
| 		'.cm-content': baseContentStyle, | ||||
| 		'&.cm-focused .cm-cursor': baseCursorStyle, | ||||
| 		'&.cm-focused .cm-selectionBackground, ::selection': baseSelectionStyle, | ||||
|  | ||||
| 		'&.cm-focused': { | ||||
| 			outline: 'none', | ||||
| 		}, | ||||
| 	}); | ||||
|  | ||||
| 	const appearanceTheme = EditorView.theme({}, { dark: isDarkTheme }); | ||||
|  | ||||
| 	const baseHeadingStyle = { | ||||
| 		fontWeight: 'bold', | ||||
| 		fontFamily: theme.fontFamily, | ||||
| 	}; | ||||
|  | ||||
| 	const highlightingStyle = HighlightStyle.define([ | ||||
| 		{ | ||||
| 			tag: tags.strong, | ||||
| 			fontWeight: 'bold', | ||||
| 		}, | ||||
| 		{ | ||||
| 			tag: tags.emphasis, | ||||
| 			fontStyle: 'italic', | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading1, | ||||
| 			fontSize: '1.6em', | ||||
| 			borderBottom: `1px solid ${theme.dividerColor}`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading2, | ||||
| 			fontSize: '1.4em', | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading3, | ||||
| 			fontSize: '1.3em', | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading4, | ||||
| 			fontSize: '1.2em', | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading5, | ||||
| 			fontSize: '1.1em', | ||||
| 		}, | ||||
| 		{ | ||||
| 			...baseHeadingStyle, | ||||
| 			tag: tags.heading6, | ||||
| 			fontSize: '1.0em', | ||||
| 		}, | ||||
| 		{ | ||||
| 			tag: tags.list, | ||||
| 			fontFamily: theme.fontFamily, | ||||
| 		}, | ||||
| 	]); | ||||
|  | ||||
| 	return [ | ||||
| 		baseTheme, | ||||
| 		appearanceTheme, | ||||
| 		syntaxHighlighting(highlightingStyle), | ||||
|  | ||||
| 		// If we haven't defined highlighting for tags, fall back | ||||
| 		// to the default. | ||||
| 		syntaxHighlighting(defaultHighlightStyle, { fallback: true }), | ||||
| 	]; | ||||
| }; | ||||
|  | ||||
|  | ||||
| export default createTheme; | ||||
| @@ -44,145 +44,6 @@ function fontFamilyFromSettings() { | ||||
| 	return [f, 'sans-serif'].join(', '); | ||||
| } | ||||
|  | ||||
| // Obsolete with CodeMirror 6. See ./CodeMirror.ts for styling. | ||||
| // function useCss(themeId:number):string { | ||||
| // 	const [css, setCss] = useState(''); | ||||
|  | ||||
| // 	// useEffect(() => { | ||||
| // 	// 	const theme = themeStyle(themeId); | ||||
|  | ||||
| // 	// 	// Selection in dark mode is hard to see so make it brighter. | ||||
| // 	// 	// https://discourse.joplinapp.org/t/dragging-in-dark-theme/12433/4?u=laurent | ||||
| // 	// 	const selectionColorCss = theme.appearance === ThemeAppearance.Dark ? | ||||
| // 	// 		`.CodeMirror-selected { | ||||
| // 	// 			background: #6b6b6b !important; | ||||
| // 	// 		}` : ''; | ||||
| // 	// 	const monospaceFonts = []; | ||||
| // 	// 	// if (Setting.value('style.editor.monospaceFontFamily')) monospaceFonts.push(`"${Setting.value('style.editor.monospaceFontFamily')}"`); | ||||
| // 	// 	monospaceFonts.push('monospace'); | ||||
|  | ||||
| // 	// 	const fontSize = 15; | ||||
| // 	// 	const fontFamily = fontFamilyFromSettings(); | ||||
|  | ||||
| // 	// 	// BUG: caret-color seems to be ignored for some reason | ||||
| // 	// 	const caretColor = theme.appearance === ThemeAppearance.Dark ? "white" : 'black'; | ||||
|  | ||||
| // 	// 	setCss(` | ||||
| // 	// 		/* These must be important to prevent the codemirror defaults from taking over*/ | ||||
| // 	// 		.CodeMirror { | ||||
| // 	// 			font-family: ${fontFamily}; | ||||
| // 	// 			font-size: ${fontSize}px; | ||||
| // 	// 			height: 100% !important; | ||||
| // 	// 			width: 100% !important; | ||||
| // 	// 			color: ${theme.color}; | ||||
| // 	// 			background-color: ${theme.backgroundColor}; | ||||
| // 	// 			position: absolute !important; | ||||
| // 	// 			-webkit-box-shadow: none !important; // Some themes add a box shadow for some reason | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.CodeMirror-lines { | ||||
| // 	// 			/* This is used to enable the scroll-past end behaviour. The same height should */ | ||||
| // 	// 			/* be applied to the viewer. */ | ||||
| // 	// 			padding-bottom: 400px !important; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		/* Left padding is applied at the editor component level, so we should remove it from the lines */ | ||||
| // 	// 		.CodeMirror pre.CodeMirror-line, | ||||
| // 	// 		.CodeMirror pre.CodeMirror-line-like { | ||||
| // 	// 			padding-left: 0; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.CodeMirror-sizer { | ||||
| // 	// 			/* Add a fixed right padding to account for the appearance (and disappearance) */ | ||||
| // 	// 			/* of the sidebar */ | ||||
| // 	// 			padding-right: 10px !important; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		/* This enforces monospace for certain elements (code, tables, etc.) */ | ||||
| // 	// 		.cm-jn-monospace { | ||||
| // 	// 			font-family: ${monospaceFonts.join(', ')} !important; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-header-1 { | ||||
| // 	// 			font-size: 1.5em; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-header-2 { | ||||
| // 	// 			font-size: 1.3em; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-header-3 { | ||||
| // 	// 			font-size: 1.1em; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-header-4, .cm-header-5, .cm-header-6 { | ||||
| // 	// 			font-size: 1em; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-header-1, .cm-header-2, .cm-header-3, .cm-header-4, .cm-header-5, .cm-header-6 { | ||||
| // 	// 			line-height: 1.5em; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-search-marker { | ||||
| // 	// 			background: ${theme.searchMarkerBackgroundColor}; | ||||
| // 	// 			color: ${theme.searchMarkerColor} !important; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-search-marker-selected { | ||||
| // 	// 			background: ${theme.selectedColor2}; | ||||
| // 	// 			color: ${theme.color2} !important; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-search-marker-scrollbar { | ||||
| // 	// 			background: ${theme.searchMarkerBackgroundColor}; | ||||
| // 	// 			-moz-box-sizing: border-box; | ||||
| // 	// 			box-sizing: border-box; | ||||
| // 	// 			opacity: .5; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		/* We need to use important to override theme specific values */ | ||||
| // 	// 		.cm-error { | ||||
| // 	// 			color: inherit !important; | ||||
| // 	// 			background-color: inherit !important; | ||||
| // 	// 			border-bottom: 1px dotted #dc322f; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		/* The default dark theme colors don't have enough contrast with the background */ | ||||
| // 	// 		.cm-s-nord span.cm-comment { | ||||
| // 	// 			color: #9aa4b6 !important; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-s-dracula span.cm-comment { | ||||
| // 	// 			color: #a1abc9 !important; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-s-monokai span.cm-comment { | ||||
| // 	// 			color: #908b74 !important; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-s-material-darker span.cm-comment { | ||||
| // 	// 			color: #878787 !important; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		.cm-s-solarized.cm-s-dark span.cm-comment { | ||||
| // 	// 			color: #8ba1a7 !important; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		/* MOBILE SPECIFIC */ | ||||
|  | ||||
| // 	// 		.CodeMirror .cm-scroller, | ||||
| // 	// 		.CodeMirror .cm-line { | ||||
| // 	// 			font-family: ${fontFamily}; | ||||
| // 	// 			caret-color: ${caretColor}; | ||||
| // 	// 		} | ||||
|  | ||||
| // 	// 		${selectionColorCss} | ||||
| // 	// 	`); | ||||
| // 	// }, [themeId]); | ||||
|  | ||||
| // 	return css; | ||||
| // } | ||||
|  | ||||
| function useCss(themeId: number): string { | ||||
| 	return useMemo(() => { | ||||
| 		const theme = themeStyle(themeId); | ||||
| @@ -274,7 +135,6 @@ function NoteEditor(props: Props, ref: any) { | ||||
| 		} catch (e) { | ||||
| 			window.ReactNativeWebView.postMessage("error:" + e.message + ": " + JSON.stringify(e)) | ||||
| 		} | ||||
|  | ||||
| 		true; | ||||
| 	`; | ||||
|  | ||||
|   | ||||
							
								
								
									
										25
									
								
								packages/app-mobile/injectedJS.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								packages/app-mobile/injectedJS.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| // Configuration file for rollup | ||||
|  | ||||
| const { dirname } = require('path'); | ||||
| import typescript from '@rollup/plugin-typescript'; | ||||
| import { nodeResolve } from '@rollup/plugin-node-resolve'; | ||||
|  | ||||
|  | ||||
| const rootDir = dirname(dirname(dirname(__dirname))); | ||||
| const mobileDir = `${rootDir}/packages/app-mobile`; | ||||
| const codeMirrorDir = `${mobileDir}/components/NoteEditor/CodeMirror`; | ||||
| const outputFile = `${codeMirrorDir}/CodeMirror.bundle.js`; | ||||
|  | ||||
| export default { | ||||
| 	output: outputFile, | ||||
| 	plugins: [ | ||||
| 		typescript({ | ||||
| 			// Exclude all .js files. Rollup will attempt to import a .js | ||||
| 			// file if both a .ts and .js file are present, conflicting | ||||
| 			// with our build setup. See | ||||
| 			// https://discourse.joplinapp.org/t/importing-a-ts-file-from-a-rollup-bundled-ts-file/ | ||||
| 			exclude: `${codeMirrorDir}/*.js`, | ||||
| 		}), | ||||
| 		nodeResolve(), | ||||
| 	], | ||||
| }; | ||||
							
								
								
									
										17
									
								
								packages/app-mobile/jest.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								packages/app-mobile/jest.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| // Test configuration | ||||
| // See https://jestjs.io/docs/configuration#testenvironment-string | ||||
|  | ||||
| const config = { | ||||
| 	preset: 'ts-jest', | ||||
|  | ||||
| 	// File extensions for imports, in order of precedence: | ||||
| 	// prefer importing from .ts or .tsx to importing from .js | ||||
| 	// files. | ||||
| 	moduleFileExtensions: [ | ||||
| 		'ts', | ||||
| 		'tsx', | ||||
| 		'js', | ||||
| 	], | ||||
| }; | ||||
|  | ||||
| module.exports = config; | ||||
| @@ -9,6 +9,8 @@ | ||||
|     "android": "react-native run-android", | ||||
|     "build": "gulp build", | ||||
|     "tsc": "tsc --project tsconfig.json", | ||||
|     "test": "jest", | ||||
|     "test-ci": "yarn test", | ||||
|     "watch": "tsc --watch --preserveWatchOutput --project tsconfig.json", | ||||
|     "clean": "node tools/clean.js", | ||||
|     "buildInjectedJs": "gulp buildInjectedJs", | ||||
| @@ -75,6 +77,7 @@ | ||||
|     "@codemirror/commands": "^6.0.0", | ||||
|     "@codemirror/lang-markdown": "^6.0.0", | ||||
|     "@codemirror/language": "^6.0.0", | ||||
|     "@codemirror/legacy-modes": "^6.1.0", | ||||
|     "@codemirror/search": "^6.0.0", | ||||
|     "@codemirror/state": "^6.0.0", | ||||
|     "@codemirror/view": "^6.0.0", | ||||
| @@ -82,6 +85,7 @@ | ||||
|     "@lezer/highlight": "^1.0.0", | ||||
|     "@rollup/plugin-node-resolve": "^13.0.0", | ||||
|     "@rollup/plugin-typescript": "^8.2.1", | ||||
|     "@types/jest": "^28.1.3", | ||||
|     "@types/node": "^14.14.6", | ||||
|     "@types/react": "^16.9.55", | ||||
|     "@types/react-native": "^0.64.4", | ||||
| @@ -89,10 +93,13 @@ | ||||
|     "execa": "^4.0.0", | ||||
|     "fs-extra": "^8.1.0", | ||||
|     "gulp": "^4.0.2", | ||||
|     "jest": "^28.1.1", | ||||
|     "jest-environment-jsdom": "^28.1.1", | ||||
|     "jetifier": "^1.6.5", | ||||
|     "metro-react-native-babel-preset": "^0.66.2", | ||||
|     "nodemon": "^2.0.12", | ||||
|     "rollup": "^2.53.1", | ||||
|     "ts-jest": "^28.0.5", | ||||
|     "typescript": "^4.0.5", | ||||
|     "uglify-js": "^3.13.10" | ||||
|   } | ||||
|   | ||||
| @@ -10,7 +10,8 @@ const execa = require('execa'); | ||||
| const rootDir = path.dirname(path.dirname(path.dirname(__dirname))); | ||||
| const mobileDir = `${rootDir}/packages/app-mobile`; | ||||
| const outputDir = `${mobileDir}/lib/rnInjectedJs`; | ||||
| const codeMirrorBundleFile = `${mobileDir}/components/NoteEditor/CodeMirror.bundle.min.js`; | ||||
| const codeMirrorDir = `${mobileDir}/components/NoteEditor/CodeMirror`; | ||||
| const codeMirrorBundleFile = `${codeMirrorDir}/CodeMirror.bundle.min.js`; | ||||
|  | ||||
| async function copyJs(name, filePath) { | ||||
| 	const outputPath = `${outputDir}/${name}.js`; | ||||
| @@ -23,17 +24,16 @@ async function copyJs(name, filePath) { | ||||
| async function buildCodeMirrorBundle() { | ||||
| 	console.info('Building CodeMirror bundle...'); | ||||
|  | ||||
| 	const sourceFile = `${mobileDir}/components/NoteEditor/CodeMirror.ts`; | ||||
| 	const fullBundleFile = `${mobileDir}/components/NoteEditor/CodeMirror.bundle.js`; | ||||
| 	const sourceFile = `${codeMirrorDir}/CodeMirror.ts`; | ||||
| 	const fullBundleFile = `${codeMirrorDir}/CodeMirror.bundle.js`; | ||||
|  | ||||
| 	await execa('yarn', [ | ||||
| 		'run', 'rollup', | ||||
| 		sourceFile, | ||||
| 		'--name', 'codeMirrorBundle', | ||||
| 		'--config', `${mobileDir}/injectedJS.config.js`, | ||||
| 		'-f', 'iife', | ||||
| 		'-o', fullBundleFile, | ||||
| 		'-p', '@rollup/plugin-node-resolve', | ||||
| 		'-p', '@rollup/plugin-typescript', | ||||
| 	]); | ||||
|  | ||||
| 	// await execa('./node_modules/uglify-js/bin/uglifyjs', [ | ||||
| @@ -49,7 +49,7 @@ async function main() { | ||||
| 	await fs.mkdirp(outputDir); | ||||
| 	await buildCodeMirrorBundle(); | ||||
| 	await copyJs('webviewLib', `${mobileDir}/../lib/renderers/webviewLib.js`); | ||||
| 	await copyJs('CodeMirror.bundle', `${mobileDir}/components/NoteEditor/CodeMirror.bundle.min.js`); | ||||
| 	await copyJs('CodeMirror.bundle', `${codeMirrorDir}/CodeMirror.bundle.min.js`); | ||||
| } | ||||
|  | ||||
| module.exports = main; | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| { | ||||
|     "extends": "../../tsconfig.json", | ||||
|     "include": [ | ||||
|         "**/*.ts", | ||||
|         "**/*.tsx", | ||||
| 	"extends": "../../tsconfig.json", | ||||
| 	"include": [ | ||||
| 		"**/*.ts", | ||||
| 		"**/*.tsx", | ||||
| 	], | ||||
| 	"exclude": [ | ||||
| 		"**/node_modules", | ||||
| 		"**/*.test.ts", | ||||
| 		"**/*.test.tsx", | ||||
| 	], | ||||
| } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user