You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-08-27 20:29:45 +02:00
Compare commits
9 Commits
ios-v12.13
...
mobile_cod
Author | SHA1 | Date | |
---|---|---|---|
|
cc3c6596e7 | ||
|
2758f7ebb8 | ||
|
5a734318a3 | ||
|
e33fd0dc11 | ||
|
4733f3cd76 | ||
|
a41b5be4dc | ||
|
3660825a8f | ||
|
5f8f3165ec | ||
|
19bb889197 |
@@ -53,6 +53,7 @@ packages/app-mobile/locales
|
|||||||
packages/app-mobile/node_modules
|
packages/app-mobile/node_modules
|
||||||
packages/app-mobile/pluginAssets/
|
packages/app-mobile/pluginAssets/
|
||||||
packages/app-mobile/lib/rnInjectedJs/
|
packages/app-mobile/lib/rnInjectedJs/
|
||||||
|
packages/app-mobile/components/NoteEditor/CodeMirror.bundle.js
|
||||||
packages/lib/assets/
|
packages/lib/assets/
|
||||||
packages/lib/rnInjectedJs/
|
packages/lib/rnInjectedJs/
|
||||||
packages/lib/vendor/
|
packages/lib/vendor/
|
||||||
@@ -701,6 +702,12 @@ 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.d.ts
|
||||||
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js
|
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js
|
||||||
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js.map
|
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/NoteEditor.d.ts
|
||||||
|
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
||||||
|
packages/app-mobile/components/NoteEditor/NoteEditor.js.map
|
||||||
packages/app-mobile/components/SelectDateTimeDialog.d.ts
|
packages/app-mobile/components/SelectDateTimeDialog.d.ts
|
||||||
packages/app-mobile/components/SelectDateTimeDialog.js
|
packages/app-mobile/components/SelectDateTimeDialog.js
|
||||||
packages/app-mobile/components/SelectDateTimeDialog.js.map
|
packages/app-mobile/components/SelectDateTimeDialog.js.map
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@@ -687,6 +687,12 @@ 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.d.ts
|
||||||
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js
|
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js
|
||||||
packages/app-mobile/components/NoteBodyViewer/hooks/useSource.js.map
|
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/NoteEditor.d.ts
|
||||||
|
packages/app-mobile/components/NoteEditor/NoteEditor.js
|
||||||
|
packages/app-mobile/components/NoteEditor/NoteEditor.js.map
|
||||||
packages/app-mobile/components/SelectDateTimeDialog.d.ts
|
packages/app-mobile/components/SelectDateTimeDialog.d.ts
|
||||||
packages/app-mobile/components/SelectDateTimeDialog.js
|
packages/app-mobile/components/SelectDateTimeDialog.js
|
||||||
packages/app-mobile/components/SelectDateTimeDialog.js.map
|
packages/app-mobile/components/SelectDateTimeDialog.js.map
|
||||||
|
4
packages/app-mobile/.gitignore
vendored
4
packages/app-mobile/.gitignore
vendored
@@ -61,4 +61,6 @@ buck-out/
|
|||||||
# Custom
|
# Custom
|
||||||
lib/csstojs/
|
lib/csstojs/
|
||||||
lib/rnInjectedJs/
|
lib/rnInjectedJs/
|
||||||
dist/
|
dist/
|
||||||
|
components/NoteEditor/CodeMirror.bundle.js
|
||||||
|
components/NoteEditor/CodeMirror.bundle.min.js
|
||||||
|
@@ -141,8 +141,8 @@ android {
|
|||||||
applicationId "net.cozic.joplin"
|
applicationId "net.cozic.joplin"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 2097639
|
versionCode 2097640
|
||||||
versionName "2.2.0"
|
versionName "2.2.1"
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||||
}
|
}
|
||||||
|
160
packages/app-mobile/components/NoteEditor/CodeMirror.ts
Normal file
160
packages/app-mobile/components/NoteEditor/CodeMirror.ts
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
|
||||||
|
// This contains the CodeMirror instance, which needs to be built into a bundle
|
||||||
|
// using `npm run buildInjectedJs`. This bundle is then loaded from
|
||||||
|
// NoteEditor.tsx into the webview.
|
||||||
|
//
|
||||||
|
// In general, since this file is harder to debug due to the intermediate built
|
||||||
|
// step, it's better to keep it as light as possible - it shoud just be a light
|
||||||
|
// wrapper to access CodeMirror functionalities. Anything else should be done
|
||||||
|
// from NoteEditor.tsx.
|
||||||
|
|
||||||
|
import { EditorState, Extension } from '@codemirror/state';
|
||||||
|
import { EditorView, drawSelection, highlightSpecialChars, ViewUpdate } from '@codemirror/view';
|
||||||
|
import { markdown } from '@codemirror/lang-markdown';
|
||||||
|
import { defaultHighlightStyle, HighlightStyle, tags } from '@codemirror/highlight';
|
||||||
|
import { undo, redo, history, undoDepth, redoDepth } from '@codemirror/history';
|
||||||
|
|
||||||
|
interface CodeMirrorResult {
|
||||||
|
editor: EditorView;
|
||||||
|
undo: Function;
|
||||||
|
redo: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
function postMessage(name: string, data: any) {
|
||||||
|
(window as any).ReactNativeWebView.postMessage(JSON.stringify({
|
||||||
|
data,
|
||||||
|
name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function logMessage(...msg: any[]) {
|
||||||
|
postMessage('onLog', { value: msg });
|
||||||
|
}
|
||||||
|
|
||||||
|
const createTheme = (theme: any): Extension => {
|
||||||
|
const baseTheme = EditorView.baseTheme({
|
||||||
|
'&': {
|
||||||
|
color: theme.color,
|
||||||
|
backgroundColor: theme.backgroundColor,
|
||||||
|
fontFamily: theme.fontFamily,
|
||||||
|
fontSize: `${theme.fontSize}px`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const appearanceTheme = EditorView.theme({}, { dark: theme.appearance === 'dark' });
|
||||||
|
|
||||||
|
const baseHeadingStyle = {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
fontFamily: theme.fontFamily,
|
||||||
|
};
|
||||||
|
|
||||||
|
const syntaxHighlighting = 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,
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function initCodeMirror(parentElement: any, initialText: string, theme: any): CodeMirrorResult {
|
||||||
|
logMessage('Initializing CodeMirror...');
|
||||||
|
|
||||||
|
let schedulePostUndoRedoDepthChangeId_: any = 0;
|
||||||
|
function schedulePostUndoRedoDepthChange(editor: EditorView, doItNow: boolean = false) {
|
||||||
|
if (schedulePostUndoRedoDepthChangeId_) {
|
||||||
|
if (doItNow) {
|
||||||
|
clearTimeout(schedulePostUndoRedoDepthChangeId_);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
schedulePostUndoRedoDepthChangeId_ = setTimeout(() => {
|
||||||
|
schedulePostUndoRedoDepthChangeId_ = null;
|
||||||
|
postMessage('onUndoRedoDepthChange', {
|
||||||
|
undoDepth: undoDepth(editor.state),
|
||||||
|
redoDepth: redoDepth(editor.state),
|
||||||
|
});
|
||||||
|
}, doItNow ? 0 : 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = new EditorView({
|
||||||
|
state: EditorState.create({
|
||||||
|
extensions: [
|
||||||
|
markdown(),
|
||||||
|
createTheme(theme),
|
||||||
|
history(),
|
||||||
|
drawSelection(),
|
||||||
|
highlightSpecialChars(),
|
||||||
|
EditorView.lineWrapping,
|
||||||
|
defaultHighlightStyle.fallback,
|
||||||
|
EditorView.updateListener.of((viewUpdate: ViewUpdate) => {
|
||||||
|
if (viewUpdate.docChanged) {
|
||||||
|
postMessage('onChange', { value: editor.state.doc.toString() });
|
||||||
|
schedulePostUndoRedoDepthChange(editor);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
doc: initialText,
|
||||||
|
}),
|
||||||
|
parent: parentElement,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
editor,
|
||||||
|
undo: () => {
|
||||||
|
undo(editor);
|
||||||
|
schedulePostUndoRedoDepthChange(editor, true);
|
||||||
|
},
|
||||||
|
redo: () => {
|
||||||
|
redo(editor);
|
||||||
|
schedulePostUndoRedoDepthChange(editor, true);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
333
packages/app-mobile/components/NoteEditor/NoteEditor.tsx
Normal file
333
packages/app-mobile/components/NoteEditor/NoteEditor.tsx
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
|
import shim from '@joplin/lib/shim';
|
||||||
|
import { themeStyle } from '@joplin/lib/theme';
|
||||||
|
const React = require('react');
|
||||||
|
const { forwardRef, useImperativeHandle, useEffect, useState, useCallback, useRef } = require('react');
|
||||||
|
const { WebView } = require('react-native-webview');
|
||||||
|
const { editorFont } = require('../global-style');
|
||||||
|
|
||||||
|
export interface ChangeEvent {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UndoRedoDepthChangeEvent {
|
||||||
|
undoDepth: number;
|
||||||
|
redoDepth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChangeEventHandler = (event: ChangeEvent)=> void;
|
||||||
|
type UndoRedoDepthChangeHandler = (event: UndoRedoDepthChangeEvent)=> void;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
themeId: number;
|
||||||
|
initialText: string;
|
||||||
|
style: any;
|
||||||
|
onChange: ChangeEventHandler;
|
||||||
|
onUndoRedoDepthChange: UndoRedoDepthChangeHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fontFamilyFromSettings() {
|
||||||
|
const f = editorFont(Setting.value('style.editor.fontFamily'));
|
||||||
|
return [f, 'sans-serif'].join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 useHtml(css: string): string {
|
||||||
|
const [html, setHtml] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setHtml(
|
||||||
|
`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
|
<style>
|
||||||
|
.cm-editor {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
${css}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body style="margin:0; height:100vh; width:100vh; width:100vw; min-width:100vw;">
|
||||||
|
<div class="CodeMirror" style="height:100%;"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
);
|
||||||
|
}, [css]);
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function editorTheme(themeId: number) {
|
||||||
|
return {
|
||||||
|
...themeStyle(themeId),
|
||||||
|
fontSize: 15,
|
||||||
|
fontFamily: fontFamilyFromSettings(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function NoteEditor(props: Props, ref: any) {
|
||||||
|
const [source, setSource] = useState(undefined);
|
||||||
|
const webviewRef = useRef(null);
|
||||||
|
|
||||||
|
const injectedJavaScript = `
|
||||||
|
function postMessage(name, data) {
|
||||||
|
window.ReactNativeWebView.postMessage(JSON.stringify({
|
||||||
|
data,
|
||||||
|
name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function logMessage(...msg) {
|
||||||
|
postMessage('onLog', { value: msg });
|
||||||
|
}
|
||||||
|
|
||||||
|
// This variable is not used within this script
|
||||||
|
// but is called using "injectJavaScript" from
|
||||||
|
// the wrapper component.
|
||||||
|
let cm = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
${shim.injectedJs('codeMirrorBundle')};
|
||||||
|
|
||||||
|
const parentElement = document.getElementsByClassName('CodeMirror')[0];
|
||||||
|
const theme = ${JSON.stringify(editorTheme(props.themeId))};
|
||||||
|
const initialText = ${JSON.stringify(props.initialText)};
|
||||||
|
|
||||||
|
cm = codeMirrorBundle.initCodeMirror(parentElement, initialText, theme);
|
||||||
|
} catch (e) {
|
||||||
|
window.ReactNativeWebView.postMessage("error:" + e.message + ": " + JSON.stringify(e))
|
||||||
|
} finally {
|
||||||
|
true;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// const css = useCss(props.themeId);
|
||||||
|
const html = useHtml('');
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => {
|
||||||
|
return {
|
||||||
|
undo: function() {
|
||||||
|
webviewRef.current.injectJavaScript('cm.undo(); true;');
|
||||||
|
},
|
||||||
|
redo: function() {
|
||||||
|
webviewRef.current.injectJavaScript('cm.redo(); true;');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
async function createHtmlFile() {
|
||||||
|
const tempFile = `${Setting.value('resourceDir')}/NoteEditor.html`;
|
||||||
|
await shim.fsDriver().writeFile(tempFile, html, 'utf8');
|
||||||
|
if (cancelled) return;
|
||||||
|
|
||||||
|
setSource({
|
||||||
|
uri: `file://${tempFile}?r=${Math.round(Math.random() * 100000000)}`,
|
||||||
|
baseUrl: `file://${Setting.value('resourceDir')}/`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void createHtmlFile();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [html]);
|
||||||
|
|
||||||
|
const onMessage = useCallback((event: any) => {
|
||||||
|
const data = event.nativeEvent.data;
|
||||||
|
|
||||||
|
if (data.indexOf('error:') === 0) {
|
||||||
|
console.error('CodeMirror:', data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msg = JSON.parse(data);
|
||||||
|
|
||||||
|
const handlers: Record<string, Function> = {
|
||||||
|
onLog: (event: any) => {
|
||||||
|
console.info('CodeMirror:', ...event.value);
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange: (event: ChangeEvent) => {
|
||||||
|
props.onChange(event);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUndoRedoDepthChange: (event: UndoRedoDepthChangeEvent) => {
|
||||||
|
console.info('onUndoRedoDepthChange', event);
|
||||||
|
props.onUndoRedoDepthChange(event);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (handlers[msg.name]) {
|
||||||
|
handlers[msg.name](msg.data);
|
||||||
|
} else {
|
||||||
|
console.info('Unsupported CodeMirror message:', msg);
|
||||||
|
}
|
||||||
|
}, [props.onChange]);
|
||||||
|
|
||||||
|
const onError = useCallback(() => {
|
||||||
|
console.error('NoteEditor: webview error');
|
||||||
|
});
|
||||||
|
|
||||||
|
// - `setSupportMultipleWindows` must be `true` for security reasons:
|
||||||
|
// https://github.com/react-native-webview/react-native-webview/releases/tag/v11.0.0
|
||||||
|
return <WebView
|
||||||
|
style={props.style}
|
||||||
|
ref={webviewRef}
|
||||||
|
useWebKit={true}
|
||||||
|
source={source}
|
||||||
|
setSupportMultipleWindows={true}
|
||||||
|
allowingReadAccessToURL={`file://${Setting.value('resourceDir')}`}
|
||||||
|
originWhitelist={['file://*', './*', 'http://*', 'https://*']}
|
||||||
|
allowFileAccess={true}
|
||||||
|
injectedJavaScript={injectedJavaScript}
|
||||||
|
onMessage={onMessage}
|
||||||
|
onError={onError}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default forwardRef(NoteEditor);
|
@@ -1,10 +1,11 @@
|
|||||||
import AsyncActionQueue from '@joplin/lib/AsyncActionQueue';
|
import AsyncActionQueue from '@joplin/lib/AsyncActionQueue';
|
||||||
import UndoRedoService from '@joplin/lib/services/UndoRedoService';
|
|
||||||
import uuid from '@joplin/lib/uuid';
|
import uuid from '@joplin/lib/uuid';
|
||||||
import Setting from '@joplin/lib/models/Setting';
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
import shim from '@joplin/lib/shim';
|
import shim from '@joplin/lib/shim';
|
||||||
|
import UndoRedoService from '@joplin/lib/services/UndoRedoService';
|
||||||
import NoteBodyViewer from '../NoteBodyViewer/NoteBodyViewer';
|
import NoteBodyViewer from '../NoteBodyViewer/NoteBodyViewer';
|
||||||
import checkPermissions from '../../utils/checkPermissions';
|
import checkPermissions from '../../utils/checkPermissions';
|
||||||
|
import NoteEditor, { ChangeEvent, UndoRedoDepthChangeEvent } from '../NoteEditor/NoteEditor';
|
||||||
|
|
||||||
const FileViewer = require('react-native-file-viewer').default;
|
const FileViewer = require('react-native-file-viewer').default;
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
@@ -42,6 +43,7 @@ const ImagePicker = require('react-native-image-picker').default;
|
|||||||
import SelectDateTimeDialog from '../SelectDateTimeDialog';
|
import SelectDateTimeDialog from '../SelectDateTimeDialog';
|
||||||
import ShareExtension from '../../utils/ShareExtension.js';
|
import ShareExtension from '../../utils/ShareExtension.js';
|
||||||
import CameraView from '../CameraView';
|
import CameraView from '../CameraView';
|
||||||
|
import { NoteEntity } from '@joplin/lib/services/database/types';
|
||||||
const urlUtils = require('@joplin/lib/urlUtils');
|
const urlUtils = require('@joplin/lib/urlUtils');
|
||||||
|
|
||||||
const emptyArray: any[] = [];
|
const emptyArray: any[] = [];
|
||||||
@@ -95,6 +97,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
|
|
||||||
this.styles_ = {};
|
this.styles_ = {};
|
||||||
|
|
||||||
|
this.editorRef = React.createRef();
|
||||||
|
|
||||||
const saveDialog = async () => {
|
const saveDialog = async () => {
|
||||||
if (this.isModified()) {
|
if (this.isModified()) {
|
||||||
const buttonId = await dialogs.pop(this, _('This note has been modified:'), [{ text: _('Save changes'), id: 'save' }, { text: _('Discard changes'), id: 'discard' }, { text: _('Cancel'), id: 'cancel' }]);
|
const buttonId = await dialogs.pop(this, _('This note has been modified:'), [{ text: _('Save changes'), id: 'save' }, { text: _('Discard changes'), id: 'discard' }, { text: _('Cancel'), id: 'cancel' }]);
|
||||||
@@ -230,16 +234,38 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
this.body_selectionChange = this.body_selectionChange.bind(this);
|
this.body_selectionChange = this.body_selectionChange.bind(this);
|
||||||
this.onBodyViewerLoadEnd = this.onBodyViewerLoadEnd.bind(this);
|
this.onBodyViewerLoadEnd = this.onBodyViewerLoadEnd.bind(this);
|
||||||
this.onBodyViewerCheckboxChange = this.onBodyViewerCheckboxChange.bind(this);
|
this.onBodyViewerCheckboxChange = this.onBodyViewerCheckboxChange.bind(this);
|
||||||
|
this.onBodyChange = this.onBodyChange.bind(this);
|
||||||
|
this.onUndoRedoDepthChange = this.onUndoRedoDepthChange.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
undoRedoService_stackChange() {
|
private useEditorBeta(): boolean {
|
||||||
this.setState({ undoRedoButtonState: {
|
return this.props.useEditorBeta;
|
||||||
canUndo: this.undoRedoService_.canUndo,
|
|
||||||
canRedo: this.undoRedoService_.canRedo,
|
|
||||||
} });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async undoRedo(type: string) {
|
private onBodyChange(event: ChangeEvent) {
|
||||||
|
shared.noteComponent_change(this, 'body', event.value);
|
||||||
|
this.scheduleSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onUndoRedoDepthChange(event: UndoRedoDepthChangeEvent) {
|
||||||
|
if (this.useEditorBeta()) {
|
||||||
|
this.setState({ undoRedoButtonState: {
|
||||||
|
canUndo: !!event.undoDepth,
|
||||||
|
canRedo: !!event.redoDepth,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private undoRedoService_stackChange() {
|
||||||
|
if (!this.useEditorBeta()) {
|
||||||
|
this.setState({ undoRedoButtonState: {
|
||||||
|
canUndo: this.undoRedoService_.canUndo,
|
||||||
|
canRedo: this.undoRedoService_.canRedo,
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async undoRedo(type: string) {
|
||||||
const undoState = await this.undoRedoService_[type](this.undoState());
|
const undoState = await this.undoRedoService_[type](this.undoState());
|
||||||
if (!undoState) return;
|
if (!undoState) return;
|
||||||
|
|
||||||
@@ -253,11 +279,25 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
screenHeader_undoButtonPress() {
|
screenHeader_undoButtonPress() {
|
||||||
void this.undoRedo('undo');
|
if (this.useEditorBeta()) {
|
||||||
|
this.editorRef.current.undo();
|
||||||
|
} else {
|
||||||
|
void this.undoRedo('undo');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screenHeader_redoButtonPress() {
|
screenHeader_redoButtonPress() {
|
||||||
void this.undoRedo('redo');
|
if (this.useEditorBeta()) {
|
||||||
|
this.editorRef.current.redo();
|
||||||
|
} else {
|
||||||
|
void this.undoRedo('redo');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
undoState(noteBody: string = null) {
|
||||||
|
return {
|
||||||
|
body: noteBody === null ? this.state.note.body : noteBody,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
styles() {
|
styles() {
|
||||||
@@ -355,12 +395,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
return shared.isModified(this);
|
return shared.isModified(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
undoState(noteBody: string = null) {
|
|
||||||
return {
|
|
||||||
body: noteBody === null ? this.state.note.body : noteBody,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async requestGeoLocationPermissions() {
|
async requestGeoLocationPermissions() {
|
||||||
if (!Setting.value('trackLocation')) return;
|
if (!Setting.value('trackLocation')) return;
|
||||||
|
|
||||||
@@ -448,6 +482,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
} else {
|
} else {
|
||||||
this.undoRedoService_.schedulePush(this.undoState());
|
this.undoRedoService_.schedulePush(this.undoState());
|
||||||
}
|
}
|
||||||
|
|
||||||
shared.noteComponent_change(this, 'body', text);
|
shared.noteComponent_change(this, 'body', text);
|
||||||
this.scheduleSave();
|
this.scheduleSave();
|
||||||
}
|
}
|
||||||
@@ -844,6 +879,11 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
output.push({
|
output.push({
|
||||||
title: _('Attach...'),
|
title: _('Attach...'),
|
||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
|
if (this.state.mode === 'edit' && this.useEditorBeta()) {
|
||||||
|
alert('Attaching files from the beta editor is not yet supported. You may do so from the viewer mode instead.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const buttons = [];
|
const buttons = [];
|
||||||
|
|
||||||
// On iOS, it will show "local files", which means certain files saved from the browser
|
// On iOS, it will show "local files", which means certain files saved from the browser
|
||||||
@@ -1017,7 +1057,7 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme = themeStyle(this.props.themeId);
|
||||||
const note = this.state.note;
|
const note: NoteEntity = this.state.note;
|
||||||
const isTodo = !!Number(note.is_todo);
|
const isTodo = !!Number(note.is_todo);
|
||||||
|
|
||||||
if (this.state.showCamera) {
|
if (this.state.showCamera) {
|
||||||
@@ -1066,25 +1106,37 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
// abcd|
|
// abcd|
|
||||||
// abcde|
|
// abcde|
|
||||||
// abcde|f
|
// abcde|f
|
||||||
bodyComponent = (
|
|
||||||
<TextInput
|
if (!this.useEditorBeta()) {
|
||||||
autoCapitalize="sentences"
|
bodyComponent = (
|
||||||
|
<TextInput
|
||||||
|
autoCapitalize="sentences"
|
||||||
|
style={this.styles().bodyTextInput}
|
||||||
|
ref="noteBodyTextField"
|
||||||
|
multiline={true}
|
||||||
|
value={note.body}
|
||||||
|
onChangeText={(text: string) => this.body_changeText(text)}
|
||||||
|
onSelectionChange={this.body_selectionChange}
|
||||||
|
blurOnSubmit={false}
|
||||||
|
selectionColor={theme.textSelectionColor}
|
||||||
|
keyboardAppearance={theme.keyboardAppearance}
|
||||||
|
placeholder={_('Add body')}
|
||||||
|
placeholderTextColor={theme.colorFaded}
|
||||||
|
// need some extra padding for iOS so that the keyboard won't cover last line of the note
|
||||||
|
// see https://github.com/laurent22/joplin/issues/3607
|
||||||
|
paddingBottom={ Platform.OS === 'ios' ? 40 : 0}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
bodyComponent = <NoteEditor
|
||||||
|
ref={this.editorRef}
|
||||||
|
themeId={this.props.themeId}
|
||||||
|
initialText={note.body}
|
||||||
|
onChange={this.onBodyChange}
|
||||||
|
onUndoRedoDepthChange={this.onUndoRedoDepthChange}
|
||||||
style={this.styles().bodyTextInput}
|
style={this.styles().bodyTextInput}
|
||||||
ref="noteBodyTextField"
|
/>;
|
||||||
multiline={true}
|
}
|
||||||
value={note.body}
|
|
||||||
onChangeText={(text: string) => this.body_changeText(text)}
|
|
||||||
onSelectionChange={this.body_selectionChange}
|
|
||||||
blurOnSubmit={false}
|
|
||||||
selectionColor={theme.textSelectionColor}
|
|
||||||
keyboardAppearance={theme.keyboardAppearance}
|
|
||||||
placeholder={_('Add body')}
|
|
||||||
placeholderTextColor={theme.colorFaded}
|
|
||||||
// need some extra padding for iOS so that the keyboard won't cover last line of the note
|
|
||||||
// see https://github.com/laurent22/joplin/issues/3607
|
|
||||||
paddingBottom={ Platform.OS === 'ios' ? 40 : 0}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderActionButton = () => {
|
const renderActionButton = () => {
|
||||||
@@ -1187,6 +1239,7 @@ const NoteScreen = connect((state: any) => {
|
|||||||
showSideMenu: state.showSideMenu,
|
showSideMenu: state.showSideMenu,
|
||||||
provisionalNoteIds: state.provisionalNoteIds,
|
provisionalNoteIds: state.provisionalNoteIds,
|
||||||
highlightedWords: state.highlightedWords,
|
highlightedWords: state.highlightedWords,
|
||||||
|
useEditorBeta: state.settings['editor.beta'],
|
||||||
};
|
};
|
||||||
})(NoteScreenComponent);
|
})(NoteScreenComponent);
|
||||||
|
|
||||||
|
@@ -5,8 +5,8 @@ const tasks = {
|
|||||||
encodeAssets: {
|
encodeAssets: {
|
||||||
fn: require('./tools/encodeAssets'),
|
fn: require('./tools/encodeAssets'),
|
||||||
},
|
},
|
||||||
buildReactNativeInjectedJs: {
|
buildInjectedJs: {
|
||||||
fn: require('./tools/buildReactNativeInjectedJs'),
|
fn: require('./tools/buildInjectedJs'),
|
||||||
},
|
},
|
||||||
podInstall: {
|
podInstall: {
|
||||||
fn: require('./tools/podInstall'),
|
fn: require('./tools/podInstall'),
|
||||||
@@ -16,7 +16,7 @@ const tasks = {
|
|||||||
utils.registerGulpTasks(gulp, tasks);
|
utils.registerGulpTasks(gulp, tasks);
|
||||||
|
|
||||||
gulp.task('build', gulp.series(
|
gulp.task('build', gulp.series(
|
||||||
'buildReactNativeInjectedJs',
|
'buildInjectedJs',
|
||||||
'encodeAssets',
|
'encodeAssets',
|
||||||
'podInstall'
|
'podInstall'
|
||||||
));
|
));
|
||||||
|
1148
packages/app-mobile/package-lock.json
generated
1148
packages/app-mobile/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -10,11 +10,14 @@
|
|||||||
"tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json",
|
"tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json",
|
||||||
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json",
|
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json",
|
||||||
"clean": "node tools/clean.js",
|
"clean": "node tools/clean.js",
|
||||||
|
"buildInjectedJs": "gulp buildInjectedJs",
|
||||||
|
"watchInjectedJs": "nodemon --verbose --watch components/NoteEditor/CodeMirror.ts --exec \"npm run buildInjectedJs\"",
|
||||||
"postinstall": "jetify && npm run build"
|
"postinstall": "jetify && npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@joplin/lib": "^1.0.9",
|
"@joplin/lib": "^2.2.0",
|
||||||
"@joplin/renderer": "^1.0.17",
|
"@joplin/renderer": "^2.2.0",
|
||||||
|
"@joplin/tools": "^2.2.0",
|
||||||
"@react-native-community/clipboard": "^1.5.0",
|
"@react-native-community/clipboard": "^1.5.0",
|
||||||
"@react-native-community/datetimepicker": "^3.0.3",
|
"@react-native-community/datetimepicker": "^3.0.3",
|
||||||
"@react-native-community/geolocation": "^2.0.2",
|
"@react-native-community/geolocation": "^2.0.2",
|
||||||
@@ -62,7 +65,14 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.11.6",
|
"@babel/core": "^7.11.6",
|
||||||
"@babel/runtime": "^7.11.2",
|
"@babel/runtime": "^7.11.2",
|
||||||
|
"@codemirror/highlight": "^0.18.4",
|
||||||
|
"@codemirror/history": "^0.18.1",
|
||||||
|
"@codemirror/lang-markdown": "^0.18.4",
|
||||||
|
"@codemirror/state": "^0.18.7",
|
||||||
|
"@codemirror/view": "^0.18.19",
|
||||||
"@joplin/tools": "^1.0.9",
|
"@joplin/tools": "^1.0.9",
|
||||||
|
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||||
|
"@rollup/plugin-typescript": "^8.2.1",
|
||||||
"@types/node": "^14.14.6",
|
"@types/node": "^14.14.6",
|
||||||
"@types/react": "^16.9.55",
|
"@types/react": "^16.9.55",
|
||||||
"@types/react-native": "^0.64.4",
|
"@types/react-native": "^0.64.4",
|
||||||
@@ -71,6 +81,9 @@
|
|||||||
"gulp": "^4.0.2",
|
"gulp": "^4.0.2",
|
||||||
"jetifier": "^1.6.5",
|
"jetifier": "^1.6.5",
|
||||||
"metro-react-native-babel-preset": "^0.63.0",
|
"metro-react-native-babel-preset": "^0.63.0",
|
||||||
"typescript": "^4.0.5"
|
"nodemon": "^2.0.12",
|
||||||
|
"rollup": "^2.53.1",
|
||||||
|
"typescript": "^4.0.5",
|
||||||
|
"uglify-js": "^3.13.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
packages/app-mobile/tools/buildInjectedJs.js
Normal file
33
packages/app-mobile/tools/buildInjectedJs.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// React Native WebView cannot load external JS files, however it can load
|
||||||
|
// arbitrary JS via the injectedJavaScript property. So we use this to load external
|
||||||
|
// files: First here we convert the JS file to a plain string, and that string
|
||||||
|
// is then loaded by eg. the Mermaid plugin, and finally injected in the WebView.
|
||||||
|
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const { execCommand2, rootDir } = require('@joplin/tools/tool-utils');
|
||||||
|
const mobileDir = `${rootDir}/packages/app-mobile`;
|
||||||
|
const outputDir = `${mobileDir}/lib/rnInjectedJs`;
|
||||||
|
const codeMirrorBundleFile = `${mobileDir}/components/NoteEditor/CodeMirror.bundle.min.js`;
|
||||||
|
|
||||||
|
async function copyJs(name, filePath) {
|
||||||
|
const js = await fs.readFile(filePath, 'utf-8');
|
||||||
|
const json = `module.exports = ${JSON.stringify(js)};`;
|
||||||
|
const outputPath = `${outputDir}/${name}.js`;
|
||||||
|
await fs.writeFile(outputPath, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildCodeMirrorBundle() {
|
||||||
|
const sourceFile = `${mobileDir}/components/NoteEditor/CodeMirror.ts`;
|
||||||
|
const fullBundleFile = `${mobileDir}/components/NoteEditor/CodeMirror.bundle.js`;
|
||||||
|
await execCommand2(`./node_modules/rollup/dist/bin/rollup "${sourceFile}" --name codeMirrorBundle -f iife -o "${fullBundleFile}" -p @rollup/plugin-node-resolve -p @rollup/plugin-typescript`);
|
||||||
|
await execCommand2(`./node_modules/uglify-js/bin/uglifyjs --compress -o "${codeMirrorBundleFile}" -- "${fullBundleFile}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await fs.mkdirp(outputDir);
|
||||||
|
await buildCodeMirrorBundle();
|
||||||
|
await copyJs('webviewLib', `${mobileDir}/node_modules/@joplin/lib/renderers/webviewLib.js`);
|
||||||
|
await copyJs('CodeMirror.bundle', `${mobileDir}/components/NoteEditor/CodeMirror.bundle.min.js`);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = main;
|
@@ -1,23 +0,0 @@
|
|||||||
// React Native WebView cannot load external JS files, however it can load
|
|
||||||
// arbitrary JS via the injectedJavaScript property. So we use this to load external
|
|
||||||
// files: First here we convert the JS file to a plain string, and that string
|
|
||||||
// is then loaded by eg. the Mermaid plugin, and finally injected in the WebView.
|
|
||||||
|
|
||||||
const fs = require('fs-extra');
|
|
||||||
|
|
||||||
const rnDir = `${__dirname}/..`;
|
|
||||||
const outputDir = `${rnDir}/lib/rnInjectedJs`;
|
|
||||||
|
|
||||||
async function copyJs(name, filePath) {
|
|
||||||
const js = await fs.readFile(filePath, 'utf-8');
|
|
||||||
const json = `module.exports = ${JSON.stringify(js)};`;
|
|
||||||
const outputPath = `${outputDir}/${name}.js`;
|
|
||||||
await fs.writeFile(outputPath, json);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
await fs.mkdirp(outputDir);
|
|
||||||
await copyJs('webviewLib', `${rnDir}/node_modules/@joplin/lib/renderers/webviewLib.js`);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = main;
|
|
@@ -14,6 +14,7 @@ const Resource = require('@joplin/lib/models/Resource').default;
|
|||||||
|
|
||||||
const injectedJs = {
|
const injectedJs = {
|
||||||
webviewLib: require('@joplin/lib/rnInjectedJs/webviewLib'),
|
webviewLib: require('@joplin/lib/rnInjectedJs/webviewLib'),
|
||||||
|
codeMirrorBundle: require('../lib/rnInjectedJs/CodeMirror.bundle'),
|
||||||
};
|
};
|
||||||
|
|
||||||
function shimInit() {
|
function shimInit() {
|
||||||
|
@@ -785,10 +785,10 @@ class Setting extends BaseModel {
|
|||||||
value: false,
|
value: false,
|
||||||
type: SettingItemType.Bool,
|
type: SettingItemType.Bool,
|
||||||
section: 'note',
|
section: 'note',
|
||||||
public: false, // mobilePlatform === 'ios',
|
public: true,
|
||||||
appTypes: [AppType.Mobile],
|
appTypes: [AppType.Mobile],
|
||||||
label: () => 'Opt-in to the editor beta',
|
label: () => 'Opt-in to the editor beta',
|
||||||
description: () => 'This beta adds list continuation, Markdown preview, and Markdown shortcuts. If you find bugs, please report them in the Discourse forum.',
|
description: () => 'This beta adds list continuation and syntax highlighting. If you find bugs, please report them in the Discourse forum.',
|
||||||
},
|
},
|
||||||
|
|
||||||
newTodoFocus: {
|
newTodoFocus: {
|
||||||
|
@@ -1,5 +1,12 @@
|
|||||||
# Joplin Android app changelog
|
# Joplin Android app changelog
|
||||||
|
|
||||||
|
## [android-v2.2.1](https://github.com/laurent22/joplin/releases/tag/android-v2.2.1) (Pre-release) - 2021-07-13T17:37:38Z
|
||||||
|
|
||||||
|
- New: Added improved editor (beta)
|
||||||
|
- Improved: Disable backup to Google Drive (#5114 by Roman Musin)
|
||||||
|
- Improved: Interpret only valid search filters (#5103) (#3871 by [@JackGruber](https://github.com/JackGruber))
|
||||||
|
- Improved: Removed old editor code (e01a175)
|
||||||
|
|
||||||
## [android-v2.1.4](https://github.com/laurent22/joplin/releases/tag/android-v2.1.4) - 2021-07-03T08:31:36Z
|
## [android-v2.1.4](https://github.com/laurent22/joplin/releases/tag/android-v2.1.4) - 2021-07-03T08:31:36Z
|
||||||
|
|
||||||
- Fixed: Fixes #5133: Items keep being uploaded to Joplin Server after a note has been shared.
|
- Fixed: Fixes #5133: Items keep being uploaded to Joplin Server after a note has been shared.
|
||||||
|
Reference in New Issue
Block a user