mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-24 23:49:33 +01:00
298 lines
7.3 KiB
TypeScript
298 lines
7.3 KiB
TypeScript
/*
|
|
This file is part of the Notesnook project (https://notesnook.com/)
|
|
|
|
Copyright (C) 2023 Streetwriters (Private) Limited
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
import { Note, NoteContent } from "@notesnook/core";
|
|
import { useThemeColors } from "@notesnook/theme";
|
|
import React, {
|
|
RefObject,
|
|
useCallback,
|
|
useEffect,
|
|
useLayoutEffect,
|
|
useRef,
|
|
useState
|
|
} from "react";
|
|
import { Linking, Platform, TextInput, View, ViewStyle } from "react-native";
|
|
import { WebView } from "react-native-webview";
|
|
import {
|
|
ShouldStartLoadRequest,
|
|
WebViewMessageEvent
|
|
} from "react-native-webview/lib/WebViewTypes";
|
|
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
|
|
import { eOnLoadNote } from "../utils/events";
|
|
import { defaultBorderRadius } from "../utils/size";
|
|
|
|
const EditorMobileSourceUrl =
|
|
Platform.OS === "android"
|
|
? "file:///android_asset/plaineditor.html"
|
|
: "extension.bundle/plaineditor.html";
|
|
/**
|
|
* Replace this with dev url when debugging or working on the editor mobile repo.
|
|
* The url should be something like this: http://192.168.100.126:3000/index.html
|
|
*/
|
|
export const EDITOR_URI = __DEV__
|
|
? EditorMobileSourceUrl
|
|
: EditorMobileSourceUrl;
|
|
|
|
export async function post(
|
|
ref: RefObject<any>,
|
|
type: string,
|
|
value: any = null
|
|
) {
|
|
const message = {
|
|
type,
|
|
value
|
|
};
|
|
setImmediate(() => ref.current?.postMessage(JSON.stringify(message)));
|
|
}
|
|
|
|
const useEditor = () => {
|
|
const ref = useRef<WebView>(null);
|
|
const { colors } = useThemeColors("editor");
|
|
const currentNote = useRef<
|
|
Note & {
|
|
content: NoteContent<false>;
|
|
}
|
|
>(undefined);
|
|
|
|
const postMessage = useCallback(
|
|
async (type: string, data: any) => post(ref, type, data),
|
|
[]
|
|
);
|
|
|
|
const loadNote = useCallback(
|
|
(
|
|
note: Note & {
|
|
content: NoteContent<false>;
|
|
}
|
|
) => {
|
|
postMessage("html", note.content.data);
|
|
currentNote.current = note;
|
|
},
|
|
[postMessage]
|
|
);
|
|
|
|
useEffect(() => {
|
|
eSubscribeEvent(eOnLoadNote + "shareEditor", loadNote);
|
|
return () => {
|
|
eUnSubscribeEvent(eOnLoadNote + "shareEditor", loadNote);
|
|
};
|
|
}, [loadNote]);
|
|
|
|
const onLoad = () => {
|
|
setTimeout(() => {
|
|
postMessage(
|
|
"theme",
|
|
`
|
|
body * {
|
|
color: ${colors.primary.paragraph};
|
|
}
|
|
|
|
h1,
|
|
h2,
|
|
h3,
|
|
h4,
|
|
h5,
|
|
h6 {
|
|
color: ${colors.primary.heading};
|
|
}
|
|
|
|
a {
|
|
color: ${colors.primary.accent};
|
|
}
|
|
`
|
|
);
|
|
}, 1);
|
|
};
|
|
|
|
return { ref, onLoad, currentNote };
|
|
};
|
|
|
|
const useEditorEvents = (
|
|
editor: ReturnType<typeof useEditor>,
|
|
onChange: (data: string) => void
|
|
) => {
|
|
const onMessage = (event: WebViewMessageEvent) => {
|
|
const data = event.nativeEvent.data;
|
|
const editorMessage = JSON.parse(data);
|
|
|
|
switch (editorMessage.type) {
|
|
case "content":
|
|
onChange(editorMessage.value);
|
|
break;
|
|
}
|
|
};
|
|
return onMessage;
|
|
};
|
|
|
|
const onShouldStartLoadWithRequest = (request: ShouldStartLoadRequest) => {
|
|
if (request.url.includes("https")) {
|
|
if (Platform.OS === "ios" && !request.isTopFrame) return true;
|
|
Linking.openURL(request.url);
|
|
return false;
|
|
} else {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
const style: ViewStyle = {
|
|
height: "100%",
|
|
maxHeight: "100%",
|
|
width: "100%",
|
|
alignSelf: "center",
|
|
backgroundColor: "transparent"
|
|
};
|
|
|
|
export type EditorRef = {
|
|
focus: () => void;
|
|
};
|
|
|
|
export const Editor = ({
|
|
onChange,
|
|
onLoad,
|
|
editorRef
|
|
}: {
|
|
onChange: (data: string) => void;
|
|
onLoad: () => void;
|
|
editorRef: RefObject<EditorRef | null>;
|
|
}) => {
|
|
const { colors } = useThemeColors();
|
|
const editor = useEditor();
|
|
const inputRef = useRef<TextInput>(null);
|
|
const onMessage = useEditorEvents(editor, onChange);
|
|
const [loading, setLoading] = useState(true);
|
|
useLayoutEffect(() => {
|
|
onLoad?.();
|
|
}, [onLoad]);
|
|
|
|
useEffect(() => {
|
|
if (editorRef) {
|
|
editorRef.current = {
|
|
focus: () => {
|
|
setTimeout(() => {
|
|
inputRef.current?.focus();
|
|
editor.ref.current?.injectJavaScript(`(() => {
|
|
const editor = document.getElementById('editor');
|
|
if (editor) {
|
|
editor.focus();
|
|
}
|
|
})();`);
|
|
editor.ref?.current?.requestFocus();
|
|
});
|
|
}
|
|
};
|
|
}
|
|
}, []);
|
|
|
|
return (
|
|
<View
|
|
style={{
|
|
flex: 1
|
|
}}
|
|
>
|
|
<TextInput
|
|
ref={inputRef}
|
|
style={{
|
|
width: 1,
|
|
height: 1,
|
|
position: "absolute",
|
|
zIndex: -1
|
|
}}
|
|
/>
|
|
<WebView
|
|
ref={editor.ref}
|
|
onLoad={() => {
|
|
editor.onLoad();
|
|
setTimeout(() => {
|
|
setLoading(false);
|
|
onLoad?.();
|
|
}, 1);
|
|
}}
|
|
nestedScrollEnabled
|
|
javaScriptEnabled={true}
|
|
setSupportMultipleWindows={false}
|
|
overScrollMode="never"
|
|
scrollEnabled={Platform.OS === "ios"}
|
|
keyboardDisplayRequiresUserAction={false}
|
|
cacheMode="LOAD_DEFAULT"
|
|
cacheEnabled={true}
|
|
domStorageEnabled={true}
|
|
bounces={false}
|
|
setBuiltInZoomControls={false}
|
|
setDisplayZoomControls={false}
|
|
allowFileAccess={true}
|
|
scalesPageToFit={true}
|
|
hideKeyboardAccessoryView={false}
|
|
allowsFullscreenVideo={true}
|
|
allowFileAccessFromFileURLs={true}
|
|
allowUniversalAccessFromFileURLs={true}
|
|
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
|
|
originWhitelist={["*"]}
|
|
source={{
|
|
uri: EDITOR_URI
|
|
}}
|
|
style={style}
|
|
autoManageStatusBarEnabled={false}
|
|
onMessage={onMessage || undefined}
|
|
/>
|
|
{loading ? (
|
|
<View
|
|
style={{
|
|
position: "absolute",
|
|
width: "100%",
|
|
height: "100%",
|
|
backgroundColor: colors.primary.background,
|
|
alignItems: "flex-start",
|
|
zIndex: 999,
|
|
paddingHorizontal: 12
|
|
}}
|
|
>
|
|
<View
|
|
style={{
|
|
height: 16,
|
|
width: "100%",
|
|
backgroundColor: colors.secondary.background,
|
|
borderRadius: defaultBorderRadius,
|
|
marginTop: 10
|
|
}}
|
|
/>
|
|
|
|
<View
|
|
style={{
|
|
height: 16,
|
|
width: "100%",
|
|
backgroundColor: colors.secondary.background,
|
|
borderRadius: defaultBorderRadius,
|
|
marginTop: 10
|
|
}}
|
|
/>
|
|
|
|
<View
|
|
style={{
|
|
height: 16,
|
|
width: "60%",
|
|
backgroundColor: colors.secondary.background,
|
|
borderRadius: defaultBorderRadius,
|
|
marginTop: 10
|
|
}}
|
|
/>
|
|
</View>
|
|
) : null}
|
|
</View>
|
|
);
|
|
};
|