mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 06:59:31 +01:00
migrate basic editor to tiptap
This commit is contained in:
@@ -4,6 +4,7 @@ import Animated, { Easing } from 'react-native-reanimated';
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||
import { notesnook } from '../../../e2e/test.ids';
|
||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
||||
import { useSelectionStore, useSettingStore } from '../../stores/stores';
|
||||
import { editing, getElevation, showTooltip, TOOLTIP_POSITIONS } from '../../utils';
|
||||
import { normalize, SIZE } from '../../utils/size';
|
||||
@@ -28,13 +29,13 @@ export const FloatingButton = ({ title, onPress, color = 'accent', shouldShow =
|
||||
}
|
||||
|
||||
const onKeyboardHide = async () => {
|
||||
editing.keyboardState = false;
|
||||
editorState().keyboardState = false;
|
||||
if (deviceMode !== 'mobile') return;
|
||||
animate(0);
|
||||
};
|
||||
|
||||
const onKeyboardShow = async () => {
|
||||
editing.keyboardState = true;
|
||||
editorState().keyboardState = true;
|
||||
if (deviceMode !== 'mobile') return;
|
||||
animate(150);
|
||||
};
|
||||
|
||||
@@ -37,6 +37,7 @@ import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { Walkthrough } from '../walkthroughs';
|
||||
import NewFeature from '../sheets/new-feature/index';
|
||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
||||
|
||||
const Launcher = React.memo(
|
||||
() => {
|
||||
@@ -166,9 +167,9 @@ const Launcher = React.memo(
|
||||
!appState.movedAway &&
|
||||
Date.now() < appState.timestamp + 3600000
|
||||
) {
|
||||
editing.isRestoringState = true;
|
||||
editing.currentlyEditing = true;
|
||||
editing.movedAway = false;
|
||||
editorState().currentlyEditing = true;
|
||||
editorState().isRestoringState = true;
|
||||
editorState().movedAway = false;
|
||||
if (!DDS.isTab) {
|
||||
tabBarRef.current?.goToPage(1);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import tiny from '../../screens/editor/tiny/tiny';
|
||||
import { Button } from '../ui/button';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
||||
|
||||
export const translatePrem = new Animated.Value(-dWidth);
|
||||
export const opacityPrem = new Animated.Value(0);
|
||||
@@ -94,7 +95,7 @@ export const PremiumToast = ({ close, context = 'global', offset = 0 }) => {
|
||||
const onPress = async () => {
|
||||
open(null);
|
||||
eSendEvent(eCloseActionSheet);
|
||||
if (editing.isFocused) {
|
||||
if (editorState().isFocused) {
|
||||
tiny.call(EditorWebView, tiny.blur);
|
||||
}
|
||||
await sleep(300);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Button } from '../ui/button';
|
||||
import SheetWrapper from '../ui/sheet';
|
||||
import Heading from '../ui/typography/heading';
|
||||
import Paragraph from '../ui/typography/paragraph';
|
||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
||||
|
||||
const SheetProvider = ({ context = 'global' }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
@@ -45,7 +46,7 @@ const SheetProvider = ({ context = 'global' }) => {
|
||||
setVisible(true);
|
||||
if (data.editor) {
|
||||
editor.current.refocus = false;
|
||||
if (editing.keyboardState) {
|
||||
if (editorState().keyboardState) {
|
||||
tiny.call(EditorWebView, tiny.cacheRange + tiny.blur);
|
||||
editor.current.refocus = true;
|
||||
}
|
||||
@@ -59,8 +60,8 @@ const SheetProvider = ({ context = 'global' }) => {
|
||||
actionSheetRef.current?.setModalVisible(true);
|
||||
return;
|
||||
} else {
|
||||
if (editor.current.refocus) {
|
||||
editing.isFocused = true;
|
||||
if (editor.current?.refocus) {
|
||||
editorState().isFocused = true;
|
||||
await reFocusEditor();
|
||||
tiny.call(EditorWebView, tiny.restoreRange + tiny.clearRange);
|
||||
editor.current.refocus = false;
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { Component, createRef } from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { Keyboard } from 'react-native';
|
||||
import { FlatList, TextInput, View } from 'react-native';
|
||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { editing } from '../../utils';
|
||||
|
||||
@@ -34,7 +35,7 @@ export default class Tabs extends Component {
|
||||
this.hideKeyboardIfVisible();
|
||||
let cOffset = this.scrollOffset.toFixed(0);
|
||||
let pOffset = this.props.offsets.b.toFixed(0);
|
||||
// let heightCheck = !editing.tooltip
|
||||
// let heightCheck = !editorState().tooltip
|
||||
// ? this.props.dimensions.height - 70
|
||||
// : this.props.dimensions.height - 140;
|
||||
|
||||
@@ -86,13 +87,13 @@ export default class Tabs extends Component {
|
||||
hideKeyboardIfVisible(close) {
|
||||
if (!close && this.nextPage === 1) return;
|
||||
if (Platform.OS === 'ios') return;
|
||||
if (editing.movedAway) return;
|
||||
if (editorState().movedAway) return;
|
||||
|
||||
if (
|
||||
(editing.keyboardState || editing.isFocused) &&
|
||||
(editorState().keyboardState || editorState().isFocused) &&
|
||||
this.scrollOffset < this.props.offsets.b - 50
|
||||
) {
|
||||
editing.keyboardState = false;
|
||||
editorState().keyboardState = false;
|
||||
Keyboard.dismiss();
|
||||
}
|
||||
}
|
||||
@@ -270,7 +271,7 @@ export default class Tabs extends Component {
|
||||
snapToAlignment="start"
|
||||
snapToOffsets={[this.props.offsets.a, this.props.offsets.b, this.props.offsets.c]}
|
||||
contentOffset={{
|
||||
x: editing.movedAway ? this.props.offsets.a : this.props.offsets.b
|
||||
x: editorState().movedAway ? this.props.offsets.a : this.props.offsets.b
|
||||
}}
|
||||
data={['drawer', 'navigation', 'editor']}
|
||||
renderItem={this.renderItem}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { notesnook } from '../../e2e/test.ids';
|
||||
import { SideMenu } from '../components/side-menu';
|
||||
import Tabs from '../components/tabs';
|
||||
import { EditorWrapper } from '../screens/editor/EditorWrapper';
|
||||
import { editorState } from '../screens/editor/tiptap/utils';
|
||||
import { DDS } from '../services/device-detection';
|
||||
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../services/event-manager';
|
||||
import { useEditorStore, useSettingStore } from '../stores/stores';
|
||||
@@ -151,10 +152,10 @@ export const TabsHolder = React.memo(
|
||||
if (current === 'tablet') {
|
||||
tabBarRef.current?.goToIndex(0);
|
||||
} else {
|
||||
if (!editing.movedAway) {
|
||||
if (!editorState().movedAway) {
|
||||
tabBarRef.current?.goToIndex(2);
|
||||
} else {
|
||||
console.log('index one', editing.movedAway);
|
||||
console.log('index one', editorState().movedAway);
|
||||
tabBarRef.current?.goToIndex(1);
|
||||
}
|
||||
}
|
||||
@@ -316,17 +317,17 @@ let layoutTimer = null;
|
||||
|
||||
const onChangeTab = async obj => {
|
||||
if (obj.i === 1) {
|
||||
editing.movedAway = false;
|
||||
editing.isFocused = true;
|
||||
editorState().movedAway = false;
|
||||
editorState().isFocused = true;
|
||||
activateKeepAwake();
|
||||
if (!editing.currentlyEditing) {
|
||||
if (!editorState().currentlyEditing) {
|
||||
eSendEvent(eOnLoadNote, { type: 'new' });
|
||||
}
|
||||
} else {
|
||||
if (obj.from === 1) {
|
||||
deactivateKeepAwake();
|
||||
editing.movedAway = true;
|
||||
editing.isFocused = false;
|
||||
editorState().movedAway = true;
|
||||
editorState().isFocused = false;
|
||||
eSendEvent(eClearEditor, 'removeHandler');
|
||||
setTimeout(() => useEditorStore.getState().setSearchReplace(false), 1);
|
||||
let id = useEditorStore.getState().currentEditingNote;
|
||||
|
||||
@@ -36,6 +36,7 @@ import tiny, { safeKeyboardDismiss } from './tiny/tiny';
|
||||
import { endSearch } from './tiny/toolbar/commands';
|
||||
import { toolbarRef } from './tiny/toolbar/constants';
|
||||
import picker from './tiny/toolbar/picker';
|
||||
import { editorState } from './tiptap/utils';
|
||||
|
||||
const EditorHeader = ({ editor }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
@@ -59,7 +60,7 @@ const EditorHeader = ({ editor }) => {
|
||||
return;
|
||||
}
|
||||
if (deviceMode === 'mobile') {
|
||||
editing.movedAway = true;
|
||||
editorState().movedAway = true;
|
||||
}
|
||||
eSendEvent('showTooltip');
|
||||
toolbarRef.current?.scrollTo({
|
||||
@@ -83,7 +84,7 @@ const EditorHeader = ({ editor }) => {
|
||||
setTimeout(() => {
|
||||
useEditorStore.getState().setCurrentlyEditingNote(null);
|
||||
}, 1);
|
||||
editing.currentlyEditing = false;
|
||||
editorState().currentlyEditing = false;
|
||||
keyboardListener.current?.remove();
|
||||
editor?.reset();
|
||||
}
|
||||
@@ -122,9 +123,9 @@ const EditorHeader = ({ editor }) => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (editing.isFocused) {
|
||||
if (editorState().isFocused) {
|
||||
safeKeyboardDismiss();
|
||||
editing.isFocused = true;
|
||||
editorState().isFocused = true;
|
||||
}
|
||||
eSendEvent(eOpenPublishNoteDialog, note);
|
||||
};
|
||||
@@ -142,9 +143,9 @@ const EditorHeader = ({ editor }) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (editing.isFocused || editing.keyboardState) {
|
||||
if (editorState().isFocused || editorState().keyboardState) {
|
||||
safeKeyboardDismiss();
|
||||
editing.isFocused = true;
|
||||
editorState().isFocused = true;
|
||||
}
|
||||
|
||||
Properties.present(note, ['Dark Mode']);
|
||||
@@ -208,13 +209,13 @@ const EditorHeader = ({ editor }) => {
|
||||
handleBack.current = BackHandler.addEventListener('hardwareBackPress', _onHardwareBackPress);
|
||||
return;
|
||||
}
|
||||
if (editing.currentlyEditing) {
|
||||
if (editorState().currentlyEditing) {
|
||||
await _onBackPress();
|
||||
}
|
||||
};
|
||||
|
||||
const _onHardwareBackPress = async () => {
|
||||
if (editing.currentlyEditing) {
|
||||
if (editorState().currentlyEditing) {
|
||||
await _onBackPress();
|
||||
return true;
|
||||
}
|
||||
@@ -280,9 +281,9 @@ const EditorHeader = ({ editor }) => {
|
||||
}}
|
||||
top={50}
|
||||
onPress={async () => {
|
||||
if (editing.isFocused) {
|
||||
if (editorState().isFocused) {
|
||||
safeKeyboardDismiss();
|
||||
editing.isFocused = true;
|
||||
editorState().isFocused = true;
|
||||
}
|
||||
umami.pageView('/pro-screen', '/editor');
|
||||
eSendEvent(eOpenPremiumDialog);
|
||||
@@ -343,7 +344,7 @@ const EditorHeader = ({ editor }) => {
|
||||
top={50}
|
||||
onPress={() => {
|
||||
eSendEvent(eOpenFullscreenEditor);
|
||||
editing.isFullscreen = true;
|
||||
editorState().isFullscreen = true;
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { editing } from '../../utils';
|
||||
import { eOnLoadNote } from '../../utils/events';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { sleep, timeConverter } from '../../utils/time';
|
||||
import { editorState } from './tiptap/utils';
|
||||
|
||||
let timer = null;
|
||||
let timerError = null;
|
||||
@@ -21,7 +22,7 @@ const EditorOverlay = () => {
|
||||
const opacity = useValue(1);
|
||||
|
||||
const load = async _loading => {
|
||||
editing.overlay = true;
|
||||
editorState().overlay = true;
|
||||
clearTimeout(timer);
|
||||
clearTimeout(timerError);
|
||||
clearTimeout(timerClosing);
|
||||
@@ -42,7 +43,7 @@ const EditorOverlay = () => {
|
||||
clearTimeout(timerError);
|
||||
clearTimeout(timerClosing);
|
||||
setError(false);
|
||||
editing.overlay = false;
|
||||
editorState().overlay = false;
|
||||
timing(opacity, {
|
||||
toValue: 0,
|
||||
duration: 150,
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useThemeStore } from '../../stores/theme';
|
||||
import { editorRef } from '../../utils/global-refs';
|
||||
import useIsFloatingKeyboard from '../../utils/hooks/use-is-floating-keyboard';
|
||||
import EditorOverlay from './EditorOverlay';
|
||||
import { textInput } from './tiptap/utils';
|
||||
import { editorController, editorState, textInput } from './tiptap/utils';
|
||||
|
||||
export const EditorWrapper = ({ width }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
@@ -27,9 +27,9 @@ export const EditorWrapper = ({ width }) => {
|
||||
|
||||
const onAppStateChanged = async state => {
|
||||
if (state === 'active') {
|
||||
// if (!editing.movedAway) {
|
||||
// await checkStatus(false);
|
||||
// }
|
||||
if (!editorState().movedAway) {
|
||||
editorController.current.onReady();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Platform, View } from 'react-native';
|
||||
import { IconButton } from '../../components/ui/icon-button';
|
||||
import { useThemeStore } from '../../stores/theme';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import { editing } from '../../utils';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { useThemeStore } from '../../stores/theme';
|
||||
import useKeyboard from '../../utils/hooks/use-keyboard';
|
||||
import { SIZE } from '../../utils/size';
|
||||
import { EditorWebView } from './Functions';
|
||||
import tiny, { safeKeyboardDismiss } from './tiny/tiny';
|
||||
import { editorState } from './tiptap/utils';
|
||||
|
||||
const HistoryComponent = () => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
@@ -16,7 +16,7 @@ const HistoryComponent = () => {
|
||||
redo: false
|
||||
});
|
||||
const keyboard = useKeyboard();
|
||||
editing.keyboardState = keyboard.keyboardShown;
|
||||
editorState().keyboardState = keyboard.keyboardShown;
|
||||
|
||||
const onHistoryChange = data => {
|
||||
setHistoryState(data);
|
||||
@@ -47,7 +47,7 @@ const HistoryComponent = () => {
|
||||
height: 35
|
||||
}}
|
||||
onPress={() => {
|
||||
editing.keyboardState = true;
|
||||
editorState().keyboardState = true;
|
||||
safeKeyboardDismiss();
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -2,10 +2,11 @@ import React from 'react';
|
||||
import { Platform, View } from 'react-native';
|
||||
import WebView from 'react-native-webview';
|
||||
import { notesnook } from '../../../e2e/test.ids';
|
||||
import { useEditorStore, useUserStore } from '../../stores/stores';
|
||||
import { useUserStore } from '../../stores/stores';
|
||||
import EditorHeader from './EditorHeader';
|
||||
import { sourceUri, _onShouldStartLoadWithRequest } from './Functions';
|
||||
import { useEditor } from './tiptap/use-editor';
|
||||
import { editorController } from './tiptap/utils';
|
||||
|
||||
const source = { uri: sourceUri + 'index.html' };
|
||||
|
||||
@@ -20,8 +21,8 @@ const style = {
|
||||
const Editor = React.memo(
|
||||
() => {
|
||||
const premiumUser = useUserStore(state => state.premium);
|
||||
const sessionId = useEditorStore(state => state.sessionId);
|
||||
const editor = useEditor();
|
||||
editorController.current = editor;
|
||||
|
||||
return editor.loading ? null : (
|
||||
<>
|
||||
@@ -43,7 +44,7 @@ const Editor = React.memo(
|
||||
// onError={() => {
|
||||
// onResetRequested();
|
||||
// }}
|
||||
injectedJavaScript={`globalThis.sessionId="${sessionId}";`}
|
||||
injectedJavaScript={`globalThis.sessionId="${editor.sessionId}";`}
|
||||
javaScriptEnabled={true}
|
||||
focusable={true}
|
||||
keyboardDisplayRequiresUserAction={false}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Platform } from 'react-native';
|
||||
import { editing } from '../../../utils';
|
||||
import { EditorWebView, getWebviewInit, post } from '../Functions';
|
||||
import { textInput } from '../tiptap/utils';
|
||||
import { editorState, textInput } from '../tiptap/utils';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -206,8 +206,8 @@ function call(webview, func, noqueue) {
|
||||
}
|
||||
|
||||
export function safeKeyboardDismiss() {
|
||||
console.log('keyboard state', editing.keyboardState);
|
||||
if (!editing.keyboardState) return;
|
||||
console.log('keyboard state', editorState().keyboardState);
|
||||
if (!editorState().keyboardState) return;
|
||||
if (Platform.OS === 'android') {
|
||||
textInput.current?.focus();
|
||||
textInput.current?.blur();
|
||||
@@ -224,10 +224,10 @@ const redo = `tinymce.activeEditor.undoManager.redo();`;
|
||||
const clearHistory = `tinymce.activeEditor.undoManager.clear();`;
|
||||
|
||||
const onKeyboardShow = () => {
|
||||
if (!editing.movedAway) {
|
||||
editing.isFocused = true;
|
||||
if (!editorState().movedAway) {
|
||||
editorState().isFocused = true;
|
||||
if (Platform.OS === 'ios') {
|
||||
if (editing.focusType === 'title') return;
|
||||
if (editorState().focusType === 'title') return;
|
||||
call(EditorWebView, keyboardStateChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { eSendEvent } from '../../../../services/event-manager';
|
||||
import { editing } from '../../../../utils';
|
||||
import { sleep } from '../../../../utils/time';
|
||||
import { EditorWebView, textInput } from '../../Functions';
|
||||
import { editorState } from '../../tiptap/utils';
|
||||
import tiny from '../tiny';
|
||||
|
||||
export const properties = {
|
||||
@@ -34,13 +35,13 @@ export async function focusEditor(format, kill = true) {
|
||||
}
|
||||
|
||||
export async function reFocusEditor() {
|
||||
if (editing.isFocused === true) {
|
||||
if (editorState().isFocused === true) {
|
||||
if (Platform.OS === 'android') {
|
||||
await sleep(300);
|
||||
textInput.current?.focus();
|
||||
}
|
||||
await sleep(300);
|
||||
if (editing.focusType == 'editor') {
|
||||
if (editorState().focusType == 'editor') {
|
||||
focusEditor(null, false);
|
||||
} else {
|
||||
Platform.OS === 'android' && EditorWebView.current?.requestFocus();
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
import ToolbarItemPin from './itempin';
|
||||
import ToolbarListFormat from './listformat';
|
||||
import { Table } from './table';
|
||||
import { editorState } from '../../tiptap/utils';
|
||||
|
||||
const ToolbarItem = ({
|
||||
format,
|
||||
@@ -90,7 +91,7 @@ const ToolbarItem = ({
|
||||
properties.selection = data;
|
||||
let formats = Object.keys(data);
|
||||
if (!data['link'] && type === 'tooltip') {
|
||||
if (editing.tooltip) {
|
||||
if (editorState().tooltip) {
|
||||
eSendEvent('showTooltip');
|
||||
}
|
||||
}
|
||||
@@ -203,9 +204,9 @@ const ToolbarItem = ({
|
||||
const onPress = async event => {
|
||||
if (premium && !isPro) {
|
||||
let user = await db.user.getUser();
|
||||
if (editing.isFocused) {
|
||||
if (editorState().isFocused) {
|
||||
safeKeyboardDismiss();
|
||||
editing.isFocused = true;
|
||||
editorState().isFocused = true;
|
||||
}
|
||||
if (user && !isPro && !user.isEmailConfirmed) {
|
||||
PremiumService.showVerifyEmailDialog();
|
||||
@@ -223,15 +224,15 @@ const ToolbarItem = ({
|
||||
}
|
||||
|
||||
if (type === 'settings') {
|
||||
if (editing.isFocused) {
|
||||
if (editorState().isFocused) {
|
||||
safeKeyboardDismiss();
|
||||
editing.isFocused = true;
|
||||
editorState().isFocused = true;
|
||||
}
|
||||
eSendEvent('openEditorSettings');
|
||||
return;
|
||||
}
|
||||
|
||||
if (editing.tooltip === format && !formatValue) {
|
||||
if (editorState().tooltip === format && !formatValue) {
|
||||
focusEditor(format);
|
||||
eSendEvent('showTooltip');
|
||||
|
||||
@@ -316,7 +317,7 @@ const ToolbarItem = ({
|
||||
}
|
||||
|
||||
focusEditor(format);
|
||||
editing.tooltip = null;
|
||||
editorState().tooltip = null;
|
||||
};
|
||||
|
||||
const isdefaultColorFormat = /^(dhilitecolor|dforecolor)$/.test(format);
|
||||
|
||||
@@ -14,6 +14,7 @@ import tiny from '../tiny';
|
||||
import { execCommands } from './commands';
|
||||
import { focusEditor, formatSelection, INPUT_MODE, properties } from './constants';
|
||||
import LinkPreview from './linkpreview';
|
||||
import { editorState } from '../../tiptap/utils';
|
||||
|
||||
let inputValue = null;
|
||||
|
||||
@@ -30,11 +31,11 @@ const ToolbarLinkInput = ({ format, value, setVisible }) => {
|
||||
}
|
||||
inputValue = value;
|
||||
properties.inputMode = value ? INPUT_MODE.NO_EDIT : INPUT_MODE.EDITING;
|
||||
editing.tooltip = format;
|
||||
editorState().tooltip = format;
|
||||
properties.userBlur = false;
|
||||
return () => {
|
||||
properties.inputMode = null;
|
||||
editing.tooltip = null;
|
||||
editorState().tooltip = null;
|
||||
inputValue = null;
|
||||
};
|
||||
}, [format]);
|
||||
@@ -87,7 +88,7 @@ const ToolbarLinkInput = ({ format, value, setVisible }) => {
|
||||
tiny.call(EditorWebView, tiny.clearRange);
|
||||
}
|
||||
|
||||
editing.tooltip = null;
|
||||
editorState().tooltip = null;
|
||||
|
||||
if (inputValue) {
|
||||
properties.pauseSelectionChange = true;
|
||||
|
||||
@@ -12,6 +12,7 @@ import { db } from '../../../../utils/database';
|
||||
import { eCloseProgressDialog } from '../../../../utils/events';
|
||||
import { sleep } from '../../../../utils/time';
|
||||
import { EditorWebView, getNote } from '../../Functions';
|
||||
import { editorState } from '../../tiptap/utils';
|
||||
import tiny, { safeKeyboardDismiss } from '../tiny';
|
||||
|
||||
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
|
||||
@@ -199,9 +200,9 @@ const gallery = async options => {
|
||||
const pick = async options => {
|
||||
if (!PremiumService.get()) {
|
||||
let user = await db.user.getUser();
|
||||
if (editing.isFocused) {
|
||||
if (editorState().isFocused) {
|
||||
safeKeyboardDismiss();
|
||||
editing.isFocused = true;
|
||||
editorState().isFocused = true;
|
||||
}
|
||||
if (user && !PremiumService.get() && !user.isEmailConfirmed) {
|
||||
PremiumService.showVerifyEmailDialog();
|
||||
@@ -220,9 +221,9 @@ const pick = async options => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (editing.isFocused) {
|
||||
if (editorState().isFocused) {
|
||||
safeKeyboardDismiss();
|
||||
editing.isFocused = true;
|
||||
editorState().isFocused = true;
|
||||
}
|
||||
|
||||
presentSheet({
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ScrollView, View } from 'react-native';
|
||||
import { useThemeStore } from '../../../../stores/theme';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../../services/event-manager';
|
||||
import { editing } from '../../../../utils';
|
||||
import { useThemeStore } from '../../../../stores/theme';
|
||||
import layoutmanager from '../../../../utils/layout-manager';
|
||||
import { normalize } from '../../../../utils/size';
|
||||
import { EditorWebView } from '../../Functions';
|
||||
import { editorState } from '../../tiptap/utils';
|
||||
import tiny from '../tiny';
|
||||
import ColorGroup from './colorgroup';
|
||||
import { properties } from './constants';
|
||||
@@ -28,7 +28,7 @@ const Tooltip = () => {
|
||||
const show = async data => {
|
||||
properties.userBlur = true;
|
||||
if (!data) {
|
||||
editing.tooltip = null;
|
||||
editorState().tooltip = null;
|
||||
if (group) {
|
||||
layoutmanager.withAnimation(150);
|
||||
setGroup(null);
|
||||
@@ -36,14 +36,14 @@ const Tooltip = () => {
|
||||
return;
|
||||
}
|
||||
if (!data) return;
|
||||
editing.tooltip = data.title;
|
||||
editorState().tooltip = data.title;
|
||||
if (!data.type) {
|
||||
data.type = data.title;
|
||||
}
|
||||
layoutmanager.withSpringAnimation(200);
|
||||
setGroup(data);
|
||||
setTimeout(() => {
|
||||
if (editing.tooltip !== 'link') {
|
||||
if (editorState().tooltip !== 'link') {
|
||||
properties.pauseSelectionChange = false;
|
||||
}
|
||||
tiny.call(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { RefObject } from 'react';
|
||||
import { createRef, MutableRefObject, RefObject } from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import WebView from 'react-native-webview';
|
||||
import { sleep } from '../../../utils/time';
|
||||
import { textInput } from './utils';
|
||||
|
||||
function call(webview: RefObject<WebView> | null, func?: string) {
|
||||
function call(webview: RefObject<WebView | undefined>, func?: string) {
|
||||
if (!webview || !func) return;
|
||||
webview.current?.injectJavaScript(func);
|
||||
}
|
||||
@@ -14,8 +14,8 @@ const fn = (fn: string) => `(() => {
|
||||
})();`;
|
||||
|
||||
class Commands {
|
||||
ref: RefObject<WebView> | null = null;
|
||||
constructor(ref: RefObject<WebView>) {
|
||||
ref = createRef<WebView | undefined>();
|
||||
constructor(ref: MutableRefObject<WebView | undefined>) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
@@ -50,9 +50,9 @@ statusBar.current.set({date:"",saved:""});
|
||||
);
|
||||
};
|
||||
|
||||
setSessionId = (id: string) => call(this.ref, fn(`globalThis.sessionId = "${id}"`));
|
||||
setSessionId = (id: string | null) => call(this.ref, fn(`globalThis.sessionId = "${id}"`));
|
||||
|
||||
setStatus = (date: string, saved: string) =>
|
||||
setStatus = (date: string | undefined, saved: string) =>
|
||||
call(this.ref, fn(`statusBar.current.set({date:"${date}",saved:"${saved}"})`));
|
||||
}
|
||||
|
||||
|
||||
16
apps/mobile/src/screens/editor/tiptap/types.ts
Normal file
16
apps/mobile/src/screens/editor/tiptap/types.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useEditor } from './use-editor';
|
||||
|
||||
export type useEditorType = ReturnType<typeof useEditor>;
|
||||
|
||||
export type EditorState = {
|
||||
currentlyEditing: boolean;
|
||||
isFullscreen: boolean;
|
||||
onNoteCreated: (id: string) => void;
|
||||
isFocused: boolean;
|
||||
focusType: 'title' | 'editor' | null;
|
||||
movedAway: boolean;
|
||||
tooltip: boolean;
|
||||
isRestoringState: boolean;
|
||||
keyboardState: boolean;
|
||||
ready: boolean;
|
||||
};
|
||||
@@ -1,216 +0,0 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
|
||||
import { useEditorStore } from '../../../stores/stores';
|
||||
import { editing } from '../../../utils';
|
||||
import { db } from '../../../utils/database';
|
||||
import { eOnLoadNote } from '../../../utils/events';
|
||||
import { timeConverter } from '../../../utils/time';
|
||||
import Commands from './commands';
|
||||
import { EditorEvents, isEditorLoaded, makeSessionId, post } from './utils';
|
||||
|
||||
export const useEditor = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const sessionId = useEditorStore(state => state.sessionId);
|
||||
const editorRef = useRef();
|
||||
const currentNote = useRef();
|
||||
const currentContent = useRef();
|
||||
const postMessage = async (type, data) => await post(editorRef, type, data);
|
||||
const timers = useRef({});
|
||||
const commands = new Commands(editorRef);
|
||||
const sessionHistoryId = useRef();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('sessionId:', sessionId);
|
||||
commands.setSessionId(sessionId);
|
||||
if (sessionId) {
|
||||
(async () => {
|
||||
if (!(await isEditorLoaded(editorRef))) {
|
||||
console.log('should reload');
|
||||
setLoading(true);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}, [sessionId, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
const withTimer = (id, fn, duration) => {
|
||||
clearTimeout(id);
|
||||
timers.current[id] = setTimeout(fn, duration);
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
currentNote.current = null;
|
||||
currentContent.current = null;
|
||||
useEditorStore.getState().setCurrentlyEditingNote(null);
|
||||
commands.clearContent();
|
||||
};
|
||||
|
||||
async function saveNote(title, id, data, type, sessionId) {
|
||||
console.log('saving note', id);
|
||||
try {
|
||||
if (id && !db.notes.note(id)) {
|
||||
useEditorStore.getState().setCurrentlyEditingNote(null);
|
||||
return;
|
||||
}
|
||||
let note = id && db.notes.note(id)?.data;
|
||||
let locked = note?.locked;
|
||||
if (note?.conflicted) return;
|
||||
|
||||
if (!sessionHistoryId.current) {
|
||||
if (note) {
|
||||
sessionHistoryId.current = note.dateEdited;
|
||||
} else {
|
||||
sessionHistoryId.current = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
let noteData = {
|
||||
id,
|
||||
sessionId: sessionHistoryId.current
|
||||
};
|
||||
|
||||
if (title) {
|
||||
noteData.title = title;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
noteData.content = {
|
||||
data: data,
|
||||
type: type
|
||||
};
|
||||
}
|
||||
|
||||
if (!locked) {
|
||||
id = await db.notes.add(noteData);
|
||||
if (!note) currentNote.current = db.notes.note(id).data;
|
||||
|
||||
if (useEditorStore.getState().currentEditingNote !== id) {
|
||||
setTimeout(() => {
|
||||
useEditorStore.getState().setCurrentlyEditingNote(id);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
noteData.contentId = note.contentId;
|
||||
await db.vault.save(noteData);
|
||||
}
|
||||
|
||||
commands.setStatus(timeConverter(db.notes.note(id)?.data?.dateEdited), 'Saved');
|
||||
|
||||
return id;
|
||||
|
||||
//let n = db.notes.note(id)?.data?.dateEdited;
|
||||
// tiny.call(EditorWebView, tiny.updateDateEdited(n ? timeConverter(n) : ''));
|
||||
// tiny.call(EditorWebView, tiny.updateSavingState(!n ? '' : 'Saved'));
|
||||
} catch (e) {
|
||||
console.log('error saving: ', e);
|
||||
}
|
||||
}
|
||||
|
||||
const loadContent = async note => {
|
||||
currentNote.current = note;
|
||||
if (note.locked) {
|
||||
currentContent.current = {
|
||||
data: note.content.data,
|
||||
type: note.content.type
|
||||
};
|
||||
} else {
|
||||
let data = await db.content.raw(note.contentId);
|
||||
if (data) {
|
||||
data = await db.content.insertPlaceholders(data, 'placeholder.svg');
|
||||
currentContent.current = {
|
||||
data: data.data,
|
||||
type: data.type
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadNote = async item => {
|
||||
console.log('loading note', item.type);
|
||||
editing.currentlyEditing = true;
|
||||
const editorState = useEditorStore.getState();
|
||||
|
||||
if (item && item.type === 'new') {
|
||||
currentNote.current && reset();
|
||||
editorState.setSessionId(makeSessionId());
|
||||
commands.focus();
|
||||
} else {
|
||||
if (!item.forced && currentNote.current?.id === item.id) return;
|
||||
currentNote.current && reset();
|
||||
await loadContent(item);
|
||||
editorState.setSessionId(makeSessionId(item));
|
||||
editorState.setCurrentlyEditingNote(item.id);
|
||||
currentNote.current = item;
|
||||
commands.setStatus(timeConverter(item.dateEdited), 'Saved');
|
||||
await postMessage(EditorEvents.title, item.title);
|
||||
await postMessage(EditorEvents.html, currentContent.current?.data);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOnLoadNote, loadNote);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eOnLoadNote, loadNote);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onMessage = useCallback(
|
||||
event => {
|
||||
let message = event.nativeEvent.data;
|
||||
message = JSON.parse(message);
|
||||
|
||||
console.log(message.type);
|
||||
|
||||
if (message.sessionId !== sessionId && message.type !== EditorEvents.status) {
|
||||
console.log(
|
||||
'useEditor: message recieved from invalid session',
|
||||
message.type,
|
||||
sessionId,
|
||||
message.sessionId
|
||||
);
|
||||
return;
|
||||
}
|
||||
switch (message.type) {
|
||||
case EditorEvents.logger:
|
||||
console.log(message.type, message.value);
|
||||
break;
|
||||
case 'editor-event:content':
|
||||
saveContent(null, message.value);
|
||||
break;
|
||||
case 'editor-event:selection':
|
||||
break;
|
||||
case 'editor-event:title':
|
||||
saveContent(message.value);
|
||||
break;
|
||||
}
|
||||
if (message.type.startsWith('native:')) {
|
||||
eSendEvent(message.type, message);
|
||||
}
|
||||
},
|
||||
[sessionId]
|
||||
);
|
||||
|
||||
const saveContent = (title, content) => {
|
||||
currentContent.current = {
|
||||
data: content,
|
||||
type: 'tiny'
|
||||
};
|
||||
let params = [title, currentNote.current?.id, content, 'tiny', sessionId];
|
||||
withTimer(currentNote.current?.id, () => saveNote(...params));
|
||||
};
|
||||
|
||||
const onLoad = () => {
|
||||
console.log('on editor load');
|
||||
if (currentNote.current) {
|
||||
console.log('force reload note');
|
||||
loadNote({ ...currentNote.current, forced: true });
|
||||
}
|
||||
};
|
||||
|
||||
return { onMessage, ref: editorRef, onLoad, commands, reset, loading, setLoading };
|
||||
};
|
||||
367
apps/mobile/src/screens/editor/tiptap/useEditor.ts
Normal file
367
apps/mobile/src/screens/editor/tiptap/useEditor.ts
Normal file
@@ -0,0 +1,367 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import WebView from 'react-native-webview';
|
||||
import { DDS } from '../../../services/device-detection';
|
||||
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
|
||||
import { useEditorStore } from '../../../stores/stores';
|
||||
import { useThemeStore } from '../../../stores/theme';
|
||||
import { db } from '../../../utils/database';
|
||||
import { MMKV } from '../../../utils/database/mmkv';
|
||||
import { eOnLoadNote } from '../../../utils/events';
|
||||
import { tabBarRef } from '../../../utils/global-refs';
|
||||
import { timeConverter } from '../../../utils/time';
|
||||
import Commands from './commands';
|
||||
import { EditorState } from './types';
|
||||
import { defaultState, EditorEvents, isEditorLoaded, makeSessionId, post } from './utils';
|
||||
|
||||
type Note = {
|
||||
[name: string]: any;
|
||||
id: string | null;
|
||||
type: string;
|
||||
contentId: string;
|
||||
title: string;
|
||||
locked: boolean;
|
||||
conflicted: boolean;
|
||||
dateEdited: number;
|
||||
};
|
||||
|
||||
type Content = {
|
||||
data?: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
type SavePayload = {
|
||||
title?: string;
|
||||
id?: string | null;
|
||||
data?: Content['data'];
|
||||
type?: Content['type'];
|
||||
sessionId?: string | null;
|
||||
};
|
||||
|
||||
export const useEditor = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [sessionId, setSessionId] = useState<string>(makeSessionId());
|
||||
const editorRef = useRef<WebView>();
|
||||
const currentNote = useRef<Note | null>();
|
||||
const currentContent = useRef<Content | null>();
|
||||
const timers = useRef<{ [name: string]: NodeJS.Timeout }>({});
|
||||
const commands = useMemo(() => new Commands(editorRef), []);
|
||||
const sessionHistoryId = useRef<number>();
|
||||
const state = useRef<Partial<EditorState>>(defaultState);
|
||||
console.log('state: ', defaultState);
|
||||
const postMessage = useCallback(
|
||||
async (type: string, data: any) => await post(editorRef, type, data),
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let unsub = useThemeStore.subscribe(state => {
|
||||
postMessage(EditorEvents.theme, state.colors);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsub();
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('sessionId:', sessionId);
|
||||
commands.setSessionId(sessionId);
|
||||
if (sessionId) {
|
||||
(async () => {
|
||||
if (!state.current?.ready) return;
|
||||
onReady();
|
||||
})();
|
||||
}
|
||||
}, [sessionId, loading]);
|
||||
|
||||
const overlay = (show: boolean, data = { type: 'new' }) => {
|
||||
eSendEvent('loadingNote', show ? currentNote.current || data : false);
|
||||
};
|
||||
|
||||
const onReady = useCallback(async () => {
|
||||
if (!(await isEditorLoaded(editorRef))) {
|
||||
console.log('reload editor');
|
||||
overlay(true);
|
||||
setLoading(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
const withTimer = useCallback((id: string, fn: () => void, duration: number) => {
|
||||
clearTimeout(timers.current[id]);
|
||||
timers.current[id] = setTimeout(fn, duration);
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
currentNote.current = null;
|
||||
currentContent.current = null;
|
||||
useEditorStore.getState().setCurrentlyEditingNote(null);
|
||||
commands.clearContent();
|
||||
}, []);
|
||||
|
||||
const saveNote = useCallback(
|
||||
async ({ title, id, data, type, sessionId: currentSessionId }: SavePayload) => {
|
||||
console.log('saving note', id);
|
||||
try {
|
||||
if (id && !db.notes?.note(id)) {
|
||||
useEditorStore.getState().setCurrentlyEditingNote(null);
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
let note = id ? (db.notes?.note(id)?.data as Note) : null;
|
||||
let locked = note?.locked;
|
||||
if (note?.conflicted) return;
|
||||
|
||||
if (!sessionHistoryId.current) {
|
||||
if (note) {
|
||||
sessionHistoryId.current = note.dateEdited;
|
||||
} else {
|
||||
sessionHistoryId.current = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
let noteData: Partial<Note> = {
|
||||
id,
|
||||
sessionId: sessionHistoryId.current
|
||||
};
|
||||
|
||||
if (title) {
|
||||
noteData.title = title;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
noteData.content = {
|
||||
data: data,
|
||||
type: type
|
||||
};
|
||||
}
|
||||
|
||||
if (!locked) {
|
||||
id = await db.notes?.add(noteData);
|
||||
if (!note && id) {
|
||||
currentNote.current = db.notes?.note(id).data as Note;
|
||||
state.current?.onNoteCreated && state.current.onNoteCreated(id);
|
||||
}
|
||||
|
||||
if (useEditorStore.getState().currentEditingNote !== id) {
|
||||
setTimeout(() => {
|
||||
id && useEditorStore.getState().setCurrentlyEditingNote(id);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
//@ts-ignore
|
||||
noteData.contentId = note.contentId;
|
||||
//@ts-ignore
|
||||
await db.vault?.save(noteData);
|
||||
}
|
||||
if (id && sessionId === currentSessionId) {
|
||||
note = db.notes?.note(id)?.data as Note;
|
||||
commands.setStatus(timeConverter(note.dateEdited), 'Saved');
|
||||
}
|
||||
|
||||
return id;
|
||||
} catch (e) {
|
||||
console.log('error saving: ', e);
|
||||
}
|
||||
},
|
||||
[commands, reset]
|
||||
);
|
||||
|
||||
const loadContent = useCallback(async (note: Note) => {
|
||||
currentNote.current = note;
|
||||
if (note.locked) {
|
||||
currentContent.current = {
|
||||
data: note.content.data,
|
||||
type: note.content.type
|
||||
};
|
||||
} else {
|
||||
let data = await db.content?.raw(note.contentId);
|
||||
if (data) {
|
||||
data = await db.content?.insertPlaceholders(data, 'placeholder.svg');
|
||||
currentContent.current = {
|
||||
data: data.data,
|
||||
type: data.type
|
||||
};
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
const loadNote = useCallback(
|
||||
async (item: Note) => {
|
||||
console.log('loading note', item.type);
|
||||
state.current.currentlyEditing = true;
|
||||
const editorState = useEditorStore.getState();
|
||||
|
||||
if (item && item.type === 'new') {
|
||||
currentNote.current && reset();
|
||||
setSessionId(makeSessionId());
|
||||
commands.focus();
|
||||
} else {
|
||||
if (!item.forced && currentNote.current?.id === item.id) return;
|
||||
overlay(true, item);
|
||||
currentNote.current && reset();
|
||||
await loadContent(item);
|
||||
setSessionId(makeSessionId(item));
|
||||
editorState.setCurrentlyEditingNote(item.id);
|
||||
currentNote.current = item;
|
||||
commands.setStatus(timeConverter(item.dateEdited), 'Saved');
|
||||
await postMessage(EditorEvents.title, item.title);
|
||||
await postMessage(EditorEvents.html, currentContent.current?.data);
|
||||
overlay(false);
|
||||
}
|
||||
},
|
||||
[setSessionId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOnLoadNote, loadNote);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eOnLoadNote, loadNote);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onMessage = useCallback(
|
||||
event => {
|
||||
let message = event.nativeEvent.data;
|
||||
message = JSON.parse(message);
|
||||
|
||||
console.log(message.type);
|
||||
|
||||
if (message.sessionId !== sessionId && message.type !== EditorEvents.status) {
|
||||
console.log(
|
||||
'useEditor: message recieved from invalid session',
|
||||
message.type,
|
||||
sessionId,
|
||||
message.sessionId
|
||||
);
|
||||
return;
|
||||
}
|
||||
switch (message.type) {
|
||||
case EditorEvents.logger:
|
||||
console.log(message.type, message.value);
|
||||
break;
|
||||
case 'editor-event:content':
|
||||
saveContent({
|
||||
type: message.type,
|
||||
content: message.value
|
||||
});
|
||||
break;
|
||||
case 'editor-event:selection':
|
||||
break;
|
||||
case 'editor-event:title':
|
||||
saveContent({
|
||||
type: message.type,
|
||||
title: message.value
|
||||
});
|
||||
break;
|
||||
}
|
||||
if (message.type.startsWith('native:')) {
|
||||
eSendEvent(message.type, message);
|
||||
}
|
||||
},
|
||||
[sessionId]
|
||||
);
|
||||
|
||||
const saveContent = useCallback(
|
||||
({ title, content, type }: { title?: string; content?: string; type: string }) => {
|
||||
if (type === EditorEvents.content) {
|
||||
currentContent.current = {
|
||||
data: content,
|
||||
type: 'tiny'
|
||||
};
|
||||
}
|
||||
let params = {
|
||||
title,
|
||||
data: content,
|
||||
type: 'tiptap',
|
||||
sessionId,
|
||||
id: currentNote.current?.id
|
||||
};
|
||||
|
||||
withTimer(
|
||||
currentNote.current?.id || 'newnote',
|
||||
() => {
|
||||
if (currentNote.current && !params.id) {
|
||||
params.id = currentNote.current?.id;
|
||||
}
|
||||
saveNote(params);
|
||||
},
|
||||
300
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const onLoad = useCallback(() => {
|
||||
console.log('on editor load');
|
||||
state.current.ready = true;
|
||||
onReady();
|
||||
postMessage(EditorEvents.theme, useThemeStore.getState().colors);
|
||||
if (currentNote.current) {
|
||||
console.log('force reload note');
|
||||
loadNote({ ...currentNote.current, forced: true });
|
||||
} else {
|
||||
restoreEditorState();
|
||||
}
|
||||
}, [state, currentNote, loadNote]);
|
||||
|
||||
async function restoreEditorState() {
|
||||
let appState = await MMKV.getItem('appState');
|
||||
if (appState) {
|
||||
appState = JSON.parse(appState);
|
||||
|
||||
if (
|
||||
//@ts-ignore
|
||||
appState.editing &&
|
||||
//@ts-ignore
|
||||
appState.note &&
|
||||
//@ts-ignore
|
||||
!appState.note.locked &&
|
||||
//@ts-ignore
|
||||
appState.note.id &&
|
||||
//@ts-ignore
|
||||
Date.now() < appState.timestamp + 3600000
|
||||
) {
|
||||
state.current.isRestoringState = true;
|
||||
//@ts-ignore
|
||||
overlay(true, appState.note);
|
||||
state.current.currentlyEditing = true;
|
||||
if (!DDS.isTab) {
|
||||
tabBarRef.current?.goToPage(1);
|
||||
}
|
||||
setTimeout(() => {
|
||||
//@ts-ignore
|
||||
loadNote(appState.note);
|
||||
}, 1);
|
||||
MMKV.removeItem('appState');
|
||||
state.current.movedAway = false;
|
||||
eSendEvent('load_overlay', 'hide_editor');
|
||||
state.current.isRestoringState = false;
|
||||
return;
|
||||
}
|
||||
state.current.isRestoringState = false;
|
||||
return;
|
||||
}
|
||||
state.current.isRestoringState = false;
|
||||
}
|
||||
|
||||
return {
|
||||
onMessage,
|
||||
ref: editorRef,
|
||||
onLoad,
|
||||
commands,
|
||||
reset,
|
||||
loading,
|
||||
setLoading,
|
||||
state,
|
||||
sessionId,
|
||||
setSessionId,
|
||||
note: currentNote,
|
||||
onReady
|
||||
};
|
||||
};
|
||||
@@ -1,60 +0,0 @@
|
||||
import { createRef } from 'react';
|
||||
import { Platform } from 'react-native';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
|
||||
import { useEditorStore } from '../../../stores/stores';
|
||||
import { sleep } from '../../../utils/time';
|
||||
import commands from './commands';
|
||||
export const textInput = createRef();
|
||||
|
||||
export const EditorEvents = {
|
||||
html: 'native:html',
|
||||
title: 'native:title',
|
||||
theme: 'native:theme',
|
||||
titleplaceholder: 'native:titleplaceholder',
|
||||
logger: 'native:logger',
|
||||
status: 'native:status'
|
||||
};
|
||||
|
||||
function randId(prefix) {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
.replace('0.', prefix || '');
|
||||
}
|
||||
|
||||
export function makeSessionId(item) {
|
||||
return item?.id ? item.id + randId('_session_') : randId('session_');
|
||||
}
|
||||
|
||||
export async function isEditorLoaded(ref) {
|
||||
return await post(ref, EditorEvents.status);
|
||||
}
|
||||
|
||||
export async function post(ref, type, value = null) {
|
||||
let sessionId = useEditorStore.getState().sessionId;
|
||||
if (!sessionId) {
|
||||
console.warn('post called without sessionId of type:', type);
|
||||
return;
|
||||
}
|
||||
let message = {
|
||||
type,
|
||||
value,
|
||||
sessionId: sessionId
|
||||
};
|
||||
ref.current?.postMessage(JSON.stringify(message));
|
||||
let response = await getResponse(type);
|
||||
console.log('post: ', type, 'result:', response);
|
||||
return response;
|
||||
}
|
||||
|
||||
const getResponse = async type => {
|
||||
return new Promise(resolve => {
|
||||
let callback = data => {
|
||||
eUnSubscribeEvent(type, callback);
|
||||
resolve(data);
|
||||
};
|
||||
eSubscribeEvent(type, callback);
|
||||
setTimeout(() => {
|
||||
resolve(false);
|
||||
}, 5000);
|
||||
});
|
||||
};
|
||||
78
apps/mobile/src/screens/editor/tiptap/utils.ts
Normal file
78
apps/mobile/src/screens/editor/tiptap/utils.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { createRef, MutableRefObject } from 'react';
|
||||
import { TextInput } from 'react-native';
|
||||
import WebView from 'react-native-webview';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../../../services/event-manager';
|
||||
import { useEditorType } from './types';
|
||||
export const textInput = createRef<TextInput>();
|
||||
export const editorController = createRef<useEditorType>();
|
||||
|
||||
export const defaultState = {
|
||||
movedAway: true
|
||||
};
|
||||
|
||||
export function editorState() {
|
||||
if (!editorController.current?.state.current) {
|
||||
console.warn('Editor state not ready');
|
||||
}
|
||||
return editorController.current?.state.current || defaultState;
|
||||
}
|
||||
|
||||
export const EditorEvents: { [name: string]: string } = {
|
||||
html: 'native:html',
|
||||
title: 'native:title',
|
||||
theme: 'native:theme',
|
||||
titleplaceholder: 'native:titleplaceholder',
|
||||
logger: 'native:logger',
|
||||
status: 'native:status'
|
||||
};
|
||||
|
||||
function randId(prefix: string) {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
.replace('0.', prefix || '');
|
||||
}
|
||||
|
||||
export function makeSessionId(item?: any) {
|
||||
return item?.id ? item.id + randId('_session_') : randId('session_');
|
||||
}
|
||||
|
||||
export async function isEditorLoaded(ref: MutableRefObject<WebView | undefined>) {
|
||||
return await post(ref, EditorEvents.status);
|
||||
}
|
||||
|
||||
export async function post(ref: MutableRefObject<WebView | undefined>, type: string, value = null) {
|
||||
let sessionId = editorController.current?.sessionId;
|
||||
if (!sessionId) {
|
||||
console.warn('post called without sessionId of type:', type);
|
||||
return;
|
||||
}
|
||||
let message = {
|
||||
type,
|
||||
value,
|
||||
sessionId: sessionId
|
||||
};
|
||||
ref.current?.postMessage(JSON.stringify(message));
|
||||
let response = await getResponse(type);
|
||||
console.log('post: ', type, sessionId, 'result:', !!response);
|
||||
return response;
|
||||
}
|
||||
|
||||
type WebviewResponseData = {
|
||||
[name: string]: any;
|
||||
sessionId: string | null;
|
||||
type: string;
|
||||
value: any;
|
||||
};
|
||||
|
||||
const getResponse = async (type: string): Promise<WebviewResponseData | false> => {
|
||||
return new Promise(resolve => {
|
||||
let callback = (data: WebviewResponseData) => {
|
||||
eUnSubscribeEvent(type, callback);
|
||||
resolve(data);
|
||||
};
|
||||
eSubscribeEvent(type, callback);
|
||||
setTimeout(() => {
|
||||
resolve(false);
|
||||
}, 5000);
|
||||
});
|
||||
};
|
||||
@@ -9,11 +9,12 @@ import { eSendEvent } from '../../services/event-manager';
|
||||
import Navigation from '../../services/navigation';
|
||||
import SearchService from '../../services/search';
|
||||
import { useNoteStore } from '../../stores/stores';
|
||||
import { editing, InteractionManager } from '../../utils';
|
||||
import { InteractionManager } from '../../utils';
|
||||
import { db } from '../../utils/database';
|
||||
import { eOnLoadNote } from '../../utils/events';
|
||||
import { tabBarRef } from '../../utils/global-refs';
|
||||
import { getNote } from '../editor/Functions';
|
||||
import { editorState } from '../editor/tiptap/utils';
|
||||
|
||||
export const Home = ({ navigation }) => {
|
||||
const notes = useNoteStore(state => state.notes);
|
||||
@@ -83,8 +84,8 @@ export const Home = ({ navigation }) => {
|
||||
if (!DDS.isTab) {
|
||||
if (getNote()) {
|
||||
eSendEvent(eOnLoadNote, { type: 'new' });
|
||||
editing.currentlyEditing = true;
|
||||
editing.movedAway = false;
|
||||
editorState().currentlyEditing = true;
|
||||
editorState().movedAway = false;
|
||||
}
|
||||
tabBarRef.current?.goToPage(1);
|
||||
} else {
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { groupArray } from 'notes-core/utils/grouping';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { FloatingButton } from '../../components/container/floating-button';
|
||||
import { ContainerHeader } from '../../components/container/containerheader';
|
||||
import { FloatingButton } from '../../components/container/floating-button';
|
||||
import { Header } from '../../components/header';
|
||||
import { MoveNotes } from '../../components/sheets/move-notes/movenote';
|
||||
import SelectionHeader from '../../components/selection-header';
|
||||
import List from '../../components/list';
|
||||
import { useThemeStore } from '../../stores/theme';
|
||||
import { useNoteStore } from '../../stores/stores';
|
||||
import SelectionHeader from '../../components/selection-header';
|
||||
import { MoveNotes } from '../../components/sheets/move-notes/movenote';
|
||||
import { DDS } from '../../services/device-detection';
|
||||
import { eSendEvent, eSubscribeEvent, eUnSubscribeEvent } from '../../services/event-manager';
|
||||
import Navigation from '../../services/navigation';
|
||||
import SearchService from '../../services/search';
|
||||
import { editing, InteractionManager } from '../../utils';
|
||||
import { useMenuStore, useNoteStore } from '../../stores/stores';
|
||||
import { useThemeStore } from '../../stores/theme';
|
||||
import { InteractionManager } from '../../utils';
|
||||
import { db } from '../../utils/database';
|
||||
import { eOnLoadNote, eOpenAddTopicDialog, refreshNotesPage } from '../../utils/events';
|
||||
import { openLinkInBrowser } from '../../utils/functions';
|
||||
import { tabBarRef } from '../../utils/global-refs';
|
||||
import { getNote } from '../editor/Functions';
|
||||
import { editorState } from '../editor/tiptap/utils';
|
||||
|
||||
const getNotes = (params, group = true) => {
|
||||
if (!params) return [];
|
||||
@@ -46,6 +47,44 @@ function getAlias(params) {
|
||||
return alias || '';
|
||||
}
|
||||
|
||||
async function onNoteCreated(id, params) {
|
||||
if (!params.current || !id) return;
|
||||
switch (params.current.type) {
|
||||
case 'topic': {
|
||||
await db.notes.move(
|
||||
{
|
||||
topic: params.current.id,
|
||||
id: params.current.notebook
|
||||
},
|
||||
id
|
||||
);
|
||||
editorState().onNoteCreated = null;
|
||||
Navigation.setRoutesToUpdate([
|
||||
Navigation.routeNames.Notebooks,
|
||||
Navigation.routeNames.NotesPage,
|
||||
Navigation.routeNames.Notebook
|
||||
]);
|
||||
break;
|
||||
}
|
||||
case 'tag': {
|
||||
await db.notes.note(id).tag(params.current.id);
|
||||
editorState().onNoteCreated = null;
|
||||
Navigation.setRoutesToUpdate([Navigation.routeNames.Tags, Navigation.routeNames.NotesPage]);
|
||||
break;
|
||||
}
|
||||
case 'color': {
|
||||
await db.notes.note(id).color(params.current.id);
|
||||
editorState().onNoteCreated = null;
|
||||
Navigation.setRoutesToUpdate([Navigation.routeNames.NotesPage]);
|
||||
useMenuStore.getState().setColorNotes();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const Notes = ({ route, navigation }) => {
|
||||
const colors = useThemeStore(state => state.colors);
|
||||
const params = useRef(route?.params);
|
||||
@@ -70,19 +109,13 @@ export const Notes = ({ route, navigation }) => {
|
||||
eSubscribeEvent(refreshNotesPage, init);
|
||||
return () => {
|
||||
eUnSubscribeEvent(refreshNotesPage, init);
|
||||
editing.actionAfterFirstSave = {
|
||||
type: null
|
||||
};
|
||||
editorState().onNoteCreated = null;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const setActionAfterFirstSave = () => {
|
||||
if (params.current?.get === 'monographs') return;
|
||||
editing.actionAfterFirstSave = {
|
||||
type: params.current?.type,
|
||||
id: params.current?.id,
|
||||
notebook: params.current?.notebookId
|
||||
};
|
||||
editorState().onNoteCreated = id => onNoteCreated(id, params);
|
||||
};
|
||||
|
||||
const init = data => {
|
||||
@@ -110,18 +143,14 @@ export const Notes = ({ route, navigation }) => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
setNotes([]);
|
||||
}, 150);
|
||||
editing.actionAfterFirstSave = {
|
||||
type: null
|
||||
};
|
||||
editorState().onNoteCreated = null;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
navigation.addListener('focus', onFocus);
|
||||
navigation.addListener('blur', onBlur);
|
||||
return () => {
|
||||
editing.actionAfterFirstSave = {
|
||||
type: null
|
||||
};
|
||||
editorState().onNoteCreated = null;
|
||||
navigation.removeListener('focus', onFocus);
|
||||
navigation.removeListener('blur', onBlur);
|
||||
};
|
||||
@@ -162,8 +191,8 @@ export const Notes = ({ route, navigation }) => {
|
||||
if (!DDS.isTab) {
|
||||
if (getNote()) {
|
||||
eSendEvent(eOnLoadNote, { type: 'new' });
|
||||
editing.currentlyEditing = true;
|
||||
editing.movedAway = false;
|
||||
editorState().currentlyEditing = true;
|
||||
editorState().movedAway = false;
|
||||
}
|
||||
tabBarRef.current?.goToPage(1);
|
||||
} else {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useNoteStore } from '../stores/stores';
|
||||
import { DDS } from './device-detection';
|
||||
import { eSendEvent } from './event-manager';
|
||||
import SettingsService from './settings';
|
||||
import { editorState } from '../screens/editor/tiptap/utils';
|
||||
|
||||
const NOTIFICATION_TAG = 'notesnook';
|
||||
const CHANNEL_ID = 'com.streetwriters.notesnook';
|
||||
@@ -36,7 +37,7 @@ function init() {
|
||||
if (Platform.OS === 'ios') return;
|
||||
PushNotification.configure({
|
||||
onNotification: async function (notification) {
|
||||
editing.movedAway = false;
|
||||
editorState().movedAway = false;
|
||||
MMKV.removeItem('appState');
|
||||
if (useNoteStore?.getState()?.loading === false) {
|
||||
//@ts-ignore
|
||||
|
||||
@@ -3,16 +3,15 @@ import { groupArray } from 'notes-core/utils/grouping';
|
||||
import { Dimensions, Platform } from 'react-native';
|
||||
import create from 'zustand';
|
||||
import { APP_VERSION } from '../../version';
|
||||
import { endSearch } from '../screens/editor/tiny/toolbar/commands';
|
||||
import { eSubscribeEvent, eUnSubscribeEvent } from '../services/event-manager';
|
||||
import PremiumService from '../services/premium';
|
||||
import { history } from '../utils';
|
||||
import { ACCENT } from '../utils/color-scheme';
|
||||
import { SUBSCRIPTION_STATUS } from '../utils/constants';
|
||||
import { db } from '../utils/database';
|
||||
import { MMKV } from '../utils/database/mmkv';
|
||||
import layoutmanager from '../utils/layout-manager';
|
||||
import { EditorWebView } from '../screens/editor/Functions';
|
||||
import tiny from '../screens/editor/tiny/tiny';
|
||||
import { endSearch } from '../screens/editor/tiny/toolbar/commands';
|
||||
import {
|
||||
Announcement,
|
||||
EditorStore,
|
||||
@@ -28,7 +27,6 @@ import {
|
||||
TrashStore,
|
||||
UserStore
|
||||
} from './interfaces';
|
||||
import { ACCENT } from '../utils/color-scheme';
|
||||
|
||||
export const useNoteStore = create<NoteStore>((set, get) => ({
|
||||
notes: [],
|
||||
@@ -185,18 +183,18 @@ export const useAttachmentStore = create<AttachmentStore>((set, get) => ({
|
||||
let _p = get().progress;
|
||||
if (!_p) return;
|
||||
_p[hash] = null;
|
||||
tiny.call(
|
||||
EditorWebView,
|
||||
`
|
||||
(function() {
|
||||
let progress = ${JSON.stringify({
|
||||
loaded: 1,
|
||||
total: 1,
|
||||
hash
|
||||
})}
|
||||
tinymce.activeEditor._updateAttachmentProgress(progress);
|
||||
})()`
|
||||
);
|
||||
// tiny.call(
|
||||
// EditorWebView,
|
||||
// `
|
||||
// (function() {
|
||||
// let progress = ${JSON.stringify({
|
||||
// loaded: 1,
|
||||
// total: 1,
|
||||
// hash
|
||||
// })}
|
||||
// tinymce.activeEditor._updateAttachmentProgress(progress);
|
||||
// })()`
|
||||
// );
|
||||
set({ progress: { ..._p } });
|
||||
},
|
||||
setProgress: (sent, total, hash, recieved, type) => {
|
||||
@@ -204,14 +202,14 @@ export const useAttachmentStore = create<AttachmentStore>((set, get) => ({
|
||||
if (!_p) return;
|
||||
_p[hash] = { sent, total, hash, recieved, type };
|
||||
let progress = { total, hash, loaded: type === 'download' ? recieved : sent };
|
||||
tiny.call(
|
||||
EditorWebView,
|
||||
`
|
||||
(function() {
|
||||
let progress = ${JSON.stringify(progress)}
|
||||
tinymce.activeEditor._updateAttachmentProgress(progress);
|
||||
})()`
|
||||
);
|
||||
// tiny.call(
|
||||
// EditorWebView,
|
||||
// `
|
||||
// (function() {
|
||||
// let progress = ${JSON.stringify(progress)}
|
||||
// tinymce.activeEditor._updateAttachmentProgress(progress);
|
||||
// })()`
|
||||
// );
|
||||
set({ progress: { ..._p } });
|
||||
},
|
||||
encryptionProgress: 0,
|
||||
@@ -321,14 +319,14 @@ export const useEditorStore = create<EditorStore>((set, get) => ({
|
||||
set({ searchSelection: value, searchReplace: true });
|
||||
};
|
||||
eSubscribeEvent('selectionvalue', func);
|
||||
tiny.call(
|
||||
EditorWebView,
|
||||
`(function() {
|
||||
if (editor) {
|
||||
reactNativeEventHandler('selectionvalue',editor.selection.getContent());
|
||||
}
|
||||
})();`
|
||||
);
|
||||
// tiny.call(
|
||||
// EditorWebView,
|
||||
// `(function() {
|
||||
// if (editor) {
|
||||
// reactNativeEventHandler('selectionvalue',editor.selection.getContent());
|
||||
// }
|
||||
// })();`
|
||||
// );
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -13,13 +13,7 @@ import * as RNIap from 'react-native-iap';
|
||||
import { enabled } from 'react-native-privacy-snapshot';
|
||||
import { doInBackground, editing } from '..';
|
||||
import { Walkthrough } from '../../components/walkthroughs';
|
||||
import {
|
||||
EditorWebView,
|
||||
getNote,
|
||||
getWebviewInit,
|
||||
updateNoteInEditor
|
||||
} from '../../screens/editor/Functions';
|
||||
import tiny from '../../screens/editor/tiny/tiny';
|
||||
import { editorState } from '../../screens/editor/tiptap/utils';
|
||||
import Backup from '../../services/backup';
|
||||
import BiometricService from '../../services/biometrics';
|
||||
import {
|
||||
@@ -42,6 +36,7 @@ import {
|
||||
clearAllStores,
|
||||
initialize,
|
||||
useAttachmentStore,
|
||||
useEditorStore,
|
||||
useMessageStore,
|
||||
useNoteStore,
|
||||
useSettingStore,
|
||||
@@ -73,15 +68,15 @@ export const useAppEvents = () => {
|
||||
|
||||
const onMediaDownloaded = ({ hash, groupId, src }) => {
|
||||
if (groupId?.startsWith('monograph')) return;
|
||||
tiny.call(
|
||||
EditorWebView,
|
||||
`
|
||||
(function(){
|
||||
let image = ${JSON.stringify({ hash, src })};
|
||||
tinymce.activeEditor._replaceImage(image);
|
||||
})();
|
||||
`
|
||||
);
|
||||
// tiny.call(
|
||||
// EditorWebView,
|
||||
// `
|
||||
// (function(){
|
||||
// let image = ${JSON.stringify({ hash, src })};
|
||||
// tinymce.activeEditor._replaceImage(image);
|
||||
// })();
|
||||
// `
|
||||
// );
|
||||
};
|
||||
|
||||
const onLoadingAttachmentProgress = data => {
|
||||
@@ -170,8 +165,10 @@ export const useAppEvents = () => {
|
||||
initialize();
|
||||
setLastSynced(await db.lastSynced());
|
||||
setSyncing(false);
|
||||
if (getNote()) {
|
||||
await updateNoteInEditor();
|
||||
let id = useEditorStore.getState().currentEditingNote;
|
||||
let note = id && db.notes.note(id).data;
|
||||
if (note) {
|
||||
//await updateNoteInEditor();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -407,9 +404,7 @@ export const useAppEvents = () => {
|
||||
await reconnectSSE();
|
||||
|
||||
await checkIntentState();
|
||||
if (getWebviewInit()) {
|
||||
await MMKV.removeItem('appState');
|
||||
}
|
||||
await MMKV.removeItem('appState');
|
||||
let user = await db.user.getUser();
|
||||
if (user && !user.isEmailConfirmed) {
|
||||
try {
|
||||
@@ -421,7 +416,9 @@ export const useAppEvents = () => {
|
||||
}
|
||||
} else {
|
||||
refValues.current.prevState = 'background';
|
||||
if (getNote()?.locked && SettingsService.get().appLockMode === 'background') {
|
||||
let id = useEditorStore.getState().currentEditingNote;
|
||||
let note = id && db.notes.note(id).data;
|
||||
if (note?.locked && SettingsService.get().appLockMode === 'background') {
|
||||
eSendEvent(eClearEditor);
|
||||
}
|
||||
await storeAppState();
|
||||
@@ -462,12 +459,14 @@ export const useAppEvents = () => {
|
||||
}
|
||||
|
||||
async function storeAppState() {
|
||||
if (editing.currentlyEditing) {
|
||||
if (getNote()?.locked) return;
|
||||
if (editorState().currentlyEditing) {
|
||||
let id = useEditorStore.getState().currentEditingNote;
|
||||
let note = id && db.notes.note(id).data;
|
||||
if (note?.locked) return;
|
||||
let state = JSON.stringify({
|
||||
editing: editing.currentlyEditing,
|
||||
note: getNote(),
|
||||
movedAway: editing.movedAway,
|
||||
editing: editorState().currentlyEditing,
|
||||
note: note,
|
||||
movedAway: editorState().movedAway,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
await MMKV.setItem('appState', state);
|
||||
@@ -490,7 +489,9 @@ export const useAppEvents = () => {
|
||||
eSendEvent(refreshNotesPage);
|
||||
}
|
||||
if (notesAddedFromIntent || shareExtensionOpened) {
|
||||
eSendEvent('loadingNote', getNote());
|
||||
let id = useEditorStore.getState().currentEditingNote;
|
||||
let note = id && db.notes.note(id).data;
|
||||
eSendEvent('loadingNote', note);
|
||||
eSendEvent('webviewreset', true);
|
||||
MMKV.removeItem('shareExtensionOpened');
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export function timeSince(date: number) {
|
||||
return Math.floor(seconds) < 0 ? '0s ago' : Math.floor(seconds) + 's ago';
|
||||
}
|
||||
|
||||
export const timeConverter = (timestamp: number) => {
|
||||
export const timeConverter = (timestamp: number | undefined | null) => {
|
||||
if (!timestamp) return;
|
||||
let d = new Date(timestamp), // Convert the passed timestamp to milliseconds
|
||||
yyyy = d.getFullYear(),
|
||||
|
||||
Reference in New Issue
Block a user