1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-21 09:38:01 +02:00

Mobile: Added new beta editor based on CodeMirror 6

This commit is contained in:
Laurent Cozic 2021-07-13 19:13:13 +01:00
parent e01a17528a
commit 8395d5daa9
15 changed files with 1808 additions and 68 deletions

View File

@ -53,6 +53,7 @@ packages/app-mobile/locales
packages/app-mobile/node_modules
packages/app-mobile/pluginAssets/
packages/app-mobile/lib/rnInjectedJs/
packages/app-mobile/components/NoteEditor/CodeMirror.bundle.js
packages/lib/assets/
packages/lib/rnInjectedJs/
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.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/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.js
packages/app-mobile/components/SelectDateTimeDialog.js.map

6
.gitignore vendored
View File

@ -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.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/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.js
packages/app-mobile/components/SelectDateTimeDialog.js.map

View File

@ -61,4 +61,6 @@ buck-out/
# Custom
lib/csstojs/
lib/rnInjectedJs/
dist/
dist/
components/NoteEditor/CodeMirror.bundle.js
components/NoteEditor/CodeMirror.bundle.min.js

View File

@ -141,8 +141,8 @@ android {
applicationId "net.cozic.joplin"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 2097639
versionName "2.2.0"
versionCode 2097640
versionName "2.2.1"
ndk {
abiFilters "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}

View 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);
},
};
}

View 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);

View File

@ -1,10 +1,11 @@
import AsyncActionQueue from '@joplin/lib/AsyncActionQueue';
import UndoRedoService from '@joplin/lib/services/UndoRedoService';
import uuid from '@joplin/lib/uuid';
import Setting from '@joplin/lib/models/Setting';
import shim from '@joplin/lib/shim';
import UndoRedoService from '@joplin/lib/services/UndoRedoService';
import NoteBodyViewer from '../NoteBodyViewer/NoteBodyViewer';
import checkPermissions from '../../utils/checkPermissions';
import NoteEditor, { ChangeEvent, UndoRedoDepthChangeEvent } from '../NoteEditor/NoteEditor';
const FileViewer = require('react-native-file-viewer').default;
const React = require('react');
@ -42,6 +43,7 @@ const ImagePicker = require('react-native-image-picker').default;
import SelectDateTimeDialog from '../SelectDateTimeDialog';
import ShareExtension from '../../utils/ShareExtension.js';
import CameraView from '../CameraView';
import { NoteEntity } from '@joplin/lib/services/database/types';
const urlUtils = require('@joplin/lib/urlUtils');
const emptyArray: any[] = [];
@ -95,6 +97,8 @@ class NoteScreenComponent extends BaseScreenComponent {
this.styles_ = {};
this.editorRef = React.createRef();
const saveDialog = async () => {
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' }]);
@ -230,16 +234,38 @@ class NoteScreenComponent extends BaseScreenComponent {
this.body_selectionChange = this.body_selectionChange.bind(this);
this.onBodyViewerLoadEnd = this.onBodyViewerLoadEnd.bind(this);
this.onBodyViewerCheckboxChange = this.onBodyViewerCheckboxChange.bind(this);
this.onBodyChange = this.onBodyChange.bind(this);
this.onUndoRedoDepthChange = this.onUndoRedoDepthChange.bind(this);
}
undoRedoService_stackChange() {
this.setState({ undoRedoButtonState: {
canUndo: this.undoRedoService_.canUndo,
canRedo: this.undoRedoService_.canRedo,
} });
private useEditorBeta(): boolean {
return this.props.useEditorBeta;
}
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());
if (!undoState) return;
@ -253,11 +279,25 @@ class NoteScreenComponent extends BaseScreenComponent {
}
screenHeader_undoButtonPress() {
void this.undoRedo('undo');
if (this.useEditorBeta()) {
this.editorRef.current.undo();
} else {
void this.undoRedo('undo');
}
}
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() {
@ -355,12 +395,6 @@ class NoteScreenComponent extends BaseScreenComponent {
return shared.isModified(this);
}
undoState(noteBody: string = null) {
return {
body: noteBody === null ? this.state.note.body : noteBody,
};
}
async requestGeoLocationPermissions() {
if (!Setting.value('trackLocation')) return;
@ -448,6 +482,7 @@ class NoteScreenComponent extends BaseScreenComponent {
} else {
this.undoRedoService_.schedulePush(this.undoState());
}
shared.noteComponent_change(this, 'body', text);
this.scheduleSave();
}
@ -844,6 +879,11 @@ class NoteScreenComponent extends BaseScreenComponent {
output.push({
title: _('Attach...'),
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 = [];
// 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 note = this.state.note;
const note: NoteEntity = this.state.note;
const isTodo = !!Number(note.is_todo);
if (this.state.showCamera) {
@ -1066,25 +1106,37 @@ class NoteScreenComponent extends BaseScreenComponent {
// abcd|
// abcde|
// abcde|f
bodyComponent = (
<TextInput
autoCapitalize="sentences"
if (!this.useEditorBeta()) {
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}
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 = () => {
@ -1187,6 +1239,7 @@ const NoteScreen = connect((state: any) => {
showSideMenu: state.showSideMenu,
provisionalNoteIds: state.provisionalNoteIds,
highlightedWords: state.highlightedWords,
useEditorBeta: state.settings['editor.beta'],
};
})(NoteScreenComponent);

View File

@ -5,8 +5,8 @@ const tasks = {
encodeAssets: {
fn: require('./tools/encodeAssets'),
},
buildReactNativeInjectedJs: {
fn: require('./tools/buildReactNativeInjectedJs'),
buildInjectedJs: {
fn: require('./tools/buildInjectedJs'),
},
podInstall: {
fn: require('./tools/podInstall'),
@ -16,7 +16,7 @@ const tasks = {
utils.registerGulpTasks(gulp, tasks);
gulp.task('build', gulp.series(
'buildReactNativeInjectedJs',
'buildInjectedJs',
'encodeAssets',
'podInstall'
));

File diff suppressed because it is too large Load Diff

View File

@ -10,11 +10,14 @@
"tsc": "node node_modules/typescript/bin/tsc --project tsconfig.json",
"watch": "node node_modules/typescript/bin/tsc --watch --project tsconfig.json",
"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"
},
"dependencies": {
"@joplin/lib": "^1.0.9",
"@joplin/renderer": "^1.0.17",
"@joplin/lib": "^2.2.0",
"@joplin/renderer": "^2.2.0",
"@joplin/tools": "^2.2.0",
"@react-native-community/clipboard": "^1.5.0",
"@react-native-community/datetimepicker": "^3.0.3",
"@react-native-community/geolocation": "^2.0.2",
@ -62,7 +65,14 @@
"devDependencies": {
"@babel/core": "^7.11.6",
"@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",
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-typescript": "^8.2.1",
"@types/node": "^14.14.6",
"@types/react": "^16.9.55",
"@types/react-native": "^0.64.4",
@ -71,6 +81,9 @@
"gulp": "^4.0.2",
"jetifier": "^1.6.5",
"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"
}
}

View 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;

View File

@ -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;

View File

@ -14,6 +14,7 @@ const Resource = require('@joplin/lib/models/Resource').default;
const injectedJs = {
webviewLib: require('@joplin/lib/rnInjectedJs/webviewLib'),
codeMirrorBundle: require('../lib/rnInjectedJs/CodeMirror.bundle'),
};
function shimInit() {

View File

@ -785,10 +785,10 @@ class Setting extends BaseModel {
value: false,
type: SettingItemType.Bool,
section: 'note',
public: false, // mobilePlatform === 'ios',
public: true,
appTypes: [AppType.Mobile],
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: {

View File

@ -1,5 +1,12 @@
# 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
- Fixed: Fixes #5133: Items keep being uploaded to Joplin Server after a note has been shared.