You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-16 00:14:34 +02:00
Mobile: Add support for plugin editor views (#11831)
Co-authored-by: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com>
This commit is contained in:
@ -456,7 +456,6 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/restoreNote.js
|
|||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/revealResourceFile.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/revealResourceFile.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/search.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/search.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/setTags.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/setTags.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showEditorPlugin.js
|
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showModalMessage.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showModalMessage.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteContentProperties.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteContentProperties.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteProperties.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteProperties.js
|
||||||
@ -465,7 +464,6 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareFolderDialog
|
|||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareNoteDialog.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareNoteDialog.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.test.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.test.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditorPlugin.js
|
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditors.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditors.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleLayoutMoveMode.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleLayoutMoveMode.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleMenuBar.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleMenuBar.js
|
||||||
@ -1022,7 +1020,9 @@ packages/lib/commands/openMasterPasswordDialog.js
|
|||||||
packages/lib/commands/permanentlyDeleteNote.js
|
packages/lib/commands/permanentlyDeleteNote.js
|
||||||
packages/lib/commands/renderMarkup.test.js
|
packages/lib/commands/renderMarkup.test.js
|
||||||
packages/lib/commands/renderMarkup.js
|
packages/lib/commands/renderMarkup.js
|
||||||
|
packages/lib/commands/showEditorPlugin.js
|
||||||
packages/lib/commands/synchronize.js
|
packages/lib/commands/synchronize.js
|
||||||
|
packages/lib/commands/toggleEditorPlugin.js
|
||||||
packages/lib/components/EncryptionConfigScreen/utils.test.js
|
packages/lib/components/EncryptionConfigScreen/utils.test.js
|
||||||
packages/lib/components/EncryptionConfigScreen/utils.js
|
packages/lib/components/EncryptionConfigScreen/utils.js
|
||||||
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
|
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
|
||||||
@ -1258,6 +1258,7 @@ packages/lib/services/ocr/utils/filterOcrText.js
|
|||||||
packages/lib/services/ocr/utils/types.js
|
packages/lib/services/ocr/utils/types.js
|
||||||
packages/lib/services/plugins/BasePlatformImplementation.js
|
packages/lib/services/plugins/BasePlatformImplementation.js
|
||||||
packages/lib/services/plugins/BasePluginRunner.js
|
packages/lib/services/plugins/BasePluginRunner.js
|
||||||
|
packages/lib/services/plugins/EditorPluginHandler.js
|
||||||
packages/lib/services/plugins/MenuController.js
|
packages/lib/services/plugins/MenuController.js
|
||||||
packages/lib/services/plugins/MenuItemController.js
|
packages/lib/services/plugins/MenuItemController.js
|
||||||
packages/lib/services/plugins/Plugin.js
|
packages/lib/services/plugins/Plugin.js
|
||||||
@ -1304,6 +1305,7 @@ packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
|
|||||||
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
|
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
|
||||||
packages/lib/services/plugins/utils/getPluginSettingKeyPrefix.js
|
packages/lib/services/plugins/utils/getPluginSettingKeyPrefix.js
|
||||||
packages/lib/services/plugins/utils/getPluginSettingValue.js
|
packages/lib/services/plugins/utils/getPluginSettingValue.js
|
||||||
|
packages/lib/services/plugins/utils/getShownPluginEditorView.js
|
||||||
packages/lib/services/plugins/utils/isCompatible/getDefaultPlatforms.js
|
packages/lib/services/plugins/utils/isCompatible/getDefaultPlatforms.js
|
||||||
packages/lib/services/plugins/utils/isCompatible/index.test.js
|
packages/lib/services/plugins/utils/isCompatible/index.test.js
|
||||||
packages/lib/services/plugins/utils/isCompatible/index.js
|
packages/lib/services/plugins/utils/isCompatible/index.js
|
||||||
|
6
.gitignore
vendored
6
.gitignore
vendored
@ -431,7 +431,6 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/restoreNote.js
|
|||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/revealResourceFile.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/revealResourceFile.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/search.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/search.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/setTags.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/setTags.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showEditorPlugin.js
|
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showModalMessage.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showModalMessage.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteContentProperties.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteContentProperties.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteProperties.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showNoteProperties.js
|
||||||
@ -440,7 +439,6 @@ packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareFolderDialog
|
|||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareNoteDialog.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showShareNoteDialog.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.test.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.test.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/showSpellCheckerMenu.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditorPlugin.js
|
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditors.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleEditors.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleLayoutMoveMode.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleLayoutMoveMode.js
|
||||||
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleMenuBar.js
|
packages/app-desktop/gui/WindowCommandsAndDialogs/commands/toggleMenuBar.js
|
||||||
@ -997,7 +995,9 @@ packages/lib/commands/openMasterPasswordDialog.js
|
|||||||
packages/lib/commands/permanentlyDeleteNote.js
|
packages/lib/commands/permanentlyDeleteNote.js
|
||||||
packages/lib/commands/renderMarkup.test.js
|
packages/lib/commands/renderMarkup.test.js
|
||||||
packages/lib/commands/renderMarkup.js
|
packages/lib/commands/renderMarkup.js
|
||||||
|
packages/lib/commands/showEditorPlugin.js
|
||||||
packages/lib/commands/synchronize.js
|
packages/lib/commands/synchronize.js
|
||||||
|
packages/lib/commands/toggleEditorPlugin.js
|
||||||
packages/lib/components/EncryptionConfigScreen/utils.test.js
|
packages/lib/components/EncryptionConfigScreen/utils.test.js
|
||||||
packages/lib/components/EncryptionConfigScreen/utils.js
|
packages/lib/components/EncryptionConfigScreen/utils.js
|
||||||
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
|
packages/lib/components/shared/NoteList/getEmptyFolderMessage.js
|
||||||
@ -1233,6 +1233,7 @@ packages/lib/services/ocr/utils/filterOcrText.js
|
|||||||
packages/lib/services/ocr/utils/types.js
|
packages/lib/services/ocr/utils/types.js
|
||||||
packages/lib/services/plugins/BasePlatformImplementation.js
|
packages/lib/services/plugins/BasePlatformImplementation.js
|
||||||
packages/lib/services/plugins/BasePluginRunner.js
|
packages/lib/services/plugins/BasePluginRunner.js
|
||||||
|
packages/lib/services/plugins/EditorPluginHandler.js
|
||||||
packages/lib/services/plugins/MenuController.js
|
packages/lib/services/plugins/MenuController.js
|
||||||
packages/lib/services/plugins/MenuItemController.js
|
packages/lib/services/plugins/MenuItemController.js
|
||||||
packages/lib/services/plugins/Plugin.js
|
packages/lib/services/plugins/Plugin.js
|
||||||
@ -1279,6 +1280,7 @@ packages/lib/services/plugins/utils/getPluginIssueReportUrl.js
|
|||||||
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
|
packages/lib/services/plugins/utils/getPluginNamespacedSettingKey.js
|
||||||
packages/lib/services/plugins/utils/getPluginSettingKeyPrefix.js
|
packages/lib/services/plugins/utils/getPluginSettingKeyPrefix.js
|
||||||
packages/lib/services/plugins/utils/getPluginSettingValue.js
|
packages/lib/services/plugins/utils/getPluginSettingValue.js
|
||||||
|
packages/lib/services/plugins/utils/getShownPluginEditorView.js
|
||||||
packages/lib/services/plugins/utils/isCompatible/getDefaultPlatforms.js
|
packages/lib/services/plugins/utils/isCompatible/getDefaultPlatforms.js
|
||||||
packages/lib/services/plugins/utils/isCompatible/index.test.js
|
packages/lib/services/plugins/utils/isCompatible/index.test.js
|
||||||
packages/lib/services/plugins/utils/isCompatible/index.js
|
packages/lib/services/plugins/utils/isCompatible/index.js
|
||||||
|
@ -52,10 +52,8 @@ import Logger from '@joplin/utils/Logger';
|
|||||||
import usePluginEditorView from './utils/usePluginEditorView';
|
import usePluginEditorView from './utils/usePluginEditorView';
|
||||||
import { stateUtils } from '@joplin/lib/reducer';
|
import { stateUtils } from '@joplin/lib/reducer';
|
||||||
import { WindowIdContext } from '../NewWindowOrIFrame';
|
import { WindowIdContext } from '../NewWindowOrIFrame';
|
||||||
import { EditorActivationCheckFilterObject } from '@joplin/lib/services/plugins/api/types';
|
|
||||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||||
import WebviewController from '@joplin/lib/services/plugins/WebviewController';
|
import EditorPluginHandler from '@joplin/lib/services/plugins/EditorPluginHandler';
|
||||||
import AsyncActionQueue, { IntervalType } from '@joplin/lib/AsyncActionQueue';
|
|
||||||
import useResourceUnwatcher from './utils/useResourceUnwatcher';
|
import useResourceUnwatcher from './utils/useResourceUnwatcher';
|
||||||
import StatusBar from './StatusBar';
|
import StatusBar from './StatusBar';
|
||||||
|
|
||||||
@ -72,15 +70,6 @@ const toolbarButtonUtils = new ToolbarButtonUtils(CommandService.instance());
|
|||||||
const onDragOver: React.DragEventHandler = event => event.preventDefault();
|
const onDragOver: React.DragEventHandler = event => event.preventDefault();
|
||||||
let editorIdCounter = 0;
|
let editorIdCounter = 0;
|
||||||
|
|
||||||
const makeNoteUpdateAction = (shownEditorViewIds: string[]) => {
|
|
||||||
return async () => {
|
|
||||||
for (const viewId of shownEditorViewIds) {
|
|
||||||
const controller = PluginService.instance().viewControllerByViewId(viewId) as WebviewController;
|
|
||||||
if (controller) controller.emitUpdate();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function NoteEditorContent(props: NoteEditorProps) {
|
function NoteEditorContent(props: NoteEditorProps) {
|
||||||
const [showRevisions, setShowRevisions] = useState(false);
|
const [showRevisions, setShowRevisions] = useState(false);
|
||||||
const [titleHasBeenManuallyChanged, setTitleHasBeenManuallyChanged] = useState(false);
|
const [titleHasBeenManuallyChanged, setTitleHasBeenManuallyChanged] = useState(false);
|
||||||
@ -90,7 +79,10 @@ function NoteEditorContent(props: NoteEditorProps) {
|
|||||||
const titleInputRef = useRef<HTMLInputElement>();
|
const titleInputRef = useRef<HTMLInputElement>();
|
||||||
const isMountedRef = useRef(true);
|
const isMountedRef = useRef(true);
|
||||||
const noteSearchBarRef = useRef(null);
|
const noteSearchBarRef = useRef(null);
|
||||||
const viewUpdateAsyncQueue_ = useRef<AsyncActionQueue>(new AsyncActionQueue(100, IntervalType.Fixed));
|
|
||||||
|
const editorPluginHandler = useMemo(() => {
|
||||||
|
return new EditorPluginHandler(PluginService.instance());
|
||||||
|
}, []);
|
||||||
|
|
||||||
const shownEditorViewIds = props['plugins.shownEditorViewIds'];
|
const shownEditorViewIds = props['plugins.shownEditorViewIds'];
|
||||||
|
|
||||||
@ -114,25 +106,15 @@ function NoteEditorContent(props: NoteEditorProps) {
|
|||||||
|
|
||||||
const effectiveNoteId = useEffectiveNoteId(props);
|
const effectiveNoteId = useEffectiveNoteId(props);
|
||||||
|
|
||||||
useAsyncEffect(async (event) => {
|
useAsyncEffect(async (_event) => {
|
||||||
if (!props.startupPluginsLoaded) return;
|
if (!props.startupPluginsLoaded) return;
|
||||||
|
await editorPluginHandler.emitActivationCheck();
|
||||||
let filterObject: EditorActivationCheckFilterObject = {
|
}, [effectiveNoteId, editorPluginHandler, props.startupPluginsLoaded]);
|
||||||
activatedEditors: [],
|
|
||||||
};
|
|
||||||
filterObject = await eventManager.filterEmit('editorActivationCheck', filterObject);
|
|
||||||
if (event.cancelled) return;
|
|
||||||
|
|
||||||
for (const editor of filterObject.activatedEditors) {
|
|
||||||
const controller = PluginService.instance().pluginById(editor.pluginId).viewController(editor.viewId) as WebviewController;
|
|
||||||
controller.setActive(editor.isActive);
|
|
||||||
}
|
|
||||||
}, [effectiveNoteId, props.startupPluginsLoaded]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!props.startupPluginsLoaded) return;
|
if (!props.startupPluginsLoaded) return;
|
||||||
viewUpdateAsyncQueue_.current.push(makeNoteUpdateAction(shownEditorViewIds));
|
editorPluginHandler.emitUpdate(shownEditorViewIds);
|
||||||
}, [effectiveNoteId, shownEditorViewIds, props.startupPluginsLoaded]);
|
}, [effectiveNoteId, editorPluginHandler, shownEditorViewIds, props.startupPluginsLoaded]);
|
||||||
|
|
||||||
const { editorPlugin, editorView } = usePluginEditorView(props.plugins, shownEditorViewIds);
|
const { editorPlugin, editorView } = usePluginEditorView(props.plugins, shownEditorViewIds);
|
||||||
const builtInEditorVisible = !editorPlugin;
|
const builtInEditorVisible = !editorPlugin;
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
import { PluginStates } from '@joplin/lib/services/plugins/reducer';
|
||||||
import getActivePluginEditorView from '@joplin/lib/services/plugins/utils/getActivePluginEditorView';
|
import getShownPluginEditorView from '@joplin/lib/services/plugins/utils/getShownPluginEditorView';
|
||||||
|
|
||||||
// If a plugin editor should be shown for the current note, this function will return the plugin and
|
// If a plugin editor should be shown for the current note, this function will return the plugin and
|
||||||
// associated view.
|
// associated view.
|
||||||
export default (plugins: PluginStates, shownEditorViewIds: string[]) => {
|
export default (plugins: PluginStates, shownEditorViewIds: string[]) => {
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const { editorPlugin, editorView } = getActivePluginEditorView(plugins);
|
return getShownPluginEditorView(plugins, shownEditorViewIds);
|
||||||
if (editorView) {
|
|
||||||
if (!shownEditorViewIds.includes(editorView.id)) return { editorPlugin: null, editorView: null };
|
|
||||||
}
|
|
||||||
return { editorPlugin, editorView };
|
|
||||||
}, [plugins, shownEditorViewIds]);
|
}, [plugins, shownEditorViewIds]);
|
||||||
};
|
};
|
||||||
|
@ -28,7 +28,6 @@ import * as restoreNote from './restoreNote';
|
|||||||
import * as revealResourceFile from './revealResourceFile';
|
import * as revealResourceFile from './revealResourceFile';
|
||||||
import * as search from './search';
|
import * as search from './search';
|
||||||
import * as setTags from './setTags';
|
import * as setTags from './setTags';
|
||||||
import * as showEditorPlugin from './showEditorPlugin';
|
|
||||||
import * as showModalMessage from './showModalMessage';
|
import * as showModalMessage from './showModalMessage';
|
||||||
import * as showNoteContentProperties from './showNoteContentProperties';
|
import * as showNoteContentProperties from './showNoteContentProperties';
|
||||||
import * as showNoteProperties from './showNoteProperties';
|
import * as showNoteProperties from './showNoteProperties';
|
||||||
@ -36,7 +35,6 @@ import * as showPrompt from './showPrompt';
|
|||||||
import * as showShareFolderDialog from './showShareFolderDialog';
|
import * as showShareFolderDialog from './showShareFolderDialog';
|
||||||
import * as showShareNoteDialog from './showShareNoteDialog';
|
import * as showShareNoteDialog from './showShareNoteDialog';
|
||||||
import * as showSpellCheckerMenu from './showSpellCheckerMenu';
|
import * as showSpellCheckerMenu from './showSpellCheckerMenu';
|
||||||
import * as toggleEditorPlugin from './toggleEditorPlugin';
|
|
||||||
import * as toggleEditors from './toggleEditors';
|
import * as toggleEditors from './toggleEditors';
|
||||||
import * as toggleLayoutMoveMode from './toggleLayoutMoveMode';
|
import * as toggleLayoutMoveMode from './toggleLayoutMoveMode';
|
||||||
import * as toggleMenuBar from './toggleMenuBar';
|
import * as toggleMenuBar from './toggleMenuBar';
|
||||||
@ -78,7 +76,6 @@ const index: any[] = [
|
|||||||
revealResourceFile,
|
revealResourceFile,
|
||||||
search,
|
search,
|
||||||
setTags,
|
setTags,
|
||||||
showEditorPlugin,
|
|
||||||
showModalMessage,
|
showModalMessage,
|
||||||
showNoteContentProperties,
|
showNoteContentProperties,
|
||||||
showNoteProperties,
|
showNoteProperties,
|
||||||
@ -86,7 +83,6 @@ const index: any[] = [
|
|||||||
showShareFolderDialog,
|
showShareFolderDialog,
|
||||||
showShareNoteDialog,
|
showShareNoteDialog,
|
||||||
showSpellCheckerMenu,
|
showSpellCheckerMenu,
|
||||||
toggleEditorPlugin,
|
|
||||||
toggleEditors,
|
toggleEditors,
|
||||||
toggleLayoutMoveMode,
|
toggleLayoutMoveMode,
|
||||||
toggleMenuBar,
|
toggleMenuBar,
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||||
TEMP_PATH=~/src/plugin-tests
|
TEMP_PATH=~/src/plugin-tests
|
||||||
NEED_COMPILING=1
|
NEED_COMPILING=1
|
||||||
PLUGIN_PATH=~/src/joplin/packages/app-cli/tests/support/plugins/toast
|
PLUGIN_PATH=~/src/plugin-yesyoucan
|
||||||
|
|
||||||
if [[ $NEED_COMPILING == 1 ]]; then
|
if [[ $NEED_COMPILING == 1 ]]; then
|
||||||
mkdir -p "$TEMP_PATH"
|
mkdir -p "$TEMP_PATH"
|
||||||
|
@ -25,6 +25,7 @@ import WebBetaButton from './WebBetaButton';
|
|||||||
|
|
||||||
import Menu, { MenuOptionType } from './Menu';
|
import Menu, { MenuOptionType } from './Menu';
|
||||||
import shim from '@joplin/lib/shim';
|
import shim from '@joplin/lib/shim';
|
||||||
|
import CommandService from '@joplin/lib/services/CommandService';
|
||||||
export { MenuOptionType };
|
export { MenuOptionType };
|
||||||
|
|
||||||
// Rather than applying a padding to the whole bar, it is applied to each
|
// Rather than applying a padding to the whole bar, it is applied to each
|
||||||
@ -67,6 +68,7 @@ interface ScreenHeaderProps {
|
|||||||
showSideMenuButton?: boolean;
|
showSideMenuButton?: boolean;
|
||||||
showSearchButton?: boolean;
|
showSearchButton?: boolean;
|
||||||
showContextMenuButton?: boolean;
|
showContextMenuButton?: boolean;
|
||||||
|
showPluginEditorButton?: boolean;
|
||||||
showBackButton?: boolean;
|
showBackButton?: boolean;
|
||||||
|
|
||||||
saveButtonDisabled?: boolean;
|
saveButtonDisabled?: boolean;
|
||||||
@ -419,6 +421,25 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
|
const renderTogglePluginEditorButton = (styles: any, onPress: OnPressCallback, disabled: boolean) => {
|
||||||
|
if (!this.props.showPluginEditorButton) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
onPress={onPress}
|
||||||
|
disabled={disabled}
|
||||||
|
|
||||||
|
themeId={themeId}
|
||||||
|
description={_('Toggle plugin editor')}
|
||||||
|
contentWrapperStyle={disabled ? styles.iconButtonDisabled : styles.iconButton}
|
||||||
|
|
||||||
|
iconName='ionicon eye'
|
||||||
|
iconStyle={styles.topIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
function deleteButton(styles: any, onPress: OnPressCallback, disabled: boolean) {
|
function deleteButton(styles: any, onPress: OnPressCallback, disabled: boolean) {
|
||||||
return (
|
return (
|
||||||
@ -604,12 +625,14 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
|||||||
const restoreButtonComp = selectedFolderInTrash && this.props.noteSelectionEnabled ? restoreButton(this.styles(), () => this.restoreButton_press(), headerItemDisabled) : null;
|
const restoreButtonComp = selectedFolderInTrash && this.props.noteSelectionEnabled ? restoreButton(this.styles(), () => this.restoreButton_press(), headerItemDisabled) : null;
|
||||||
const duplicateButtonComp = !selectedFolderInTrash && this.props.noteSelectionEnabled ? duplicateButton(this.styles(), () => this.duplicateButton_press(), headerItemDisabled) : null;
|
const duplicateButtonComp = !selectedFolderInTrash && this.props.noteSelectionEnabled ? duplicateButton(this.styles(), () => this.duplicateButton_press(), headerItemDisabled) : null;
|
||||||
const sortButtonComp = !this.props.noteSelectionEnabled && this.props.sortButton_press ? sortButton(this.styles(), () => this.props.sortButton_press()) : null;
|
const sortButtonComp = !this.props.noteSelectionEnabled && this.props.sortButton_press ? sortButton(this.styles(), () => this.props.sortButton_press()) : null;
|
||||||
|
const togglePluginEditorButton = renderTogglePluginEditorButton(this.styles(), () => CommandService.instance().execute('toggleEditorPlugin'), false);
|
||||||
|
|
||||||
// To allow the notebook dropdown (and perhaps other components) to have sufficient
|
// To allow the notebook dropdown (and perhaps other components) to have sufficient
|
||||||
// space while in use, we allow certain buttons to be hidden.
|
// space while in use, we allow certain buttons to be hidden.
|
||||||
const hideableRightComponents = <>
|
const hideableRightComponents = <>
|
||||||
{pluginPanelsComp}
|
{pluginPanelsComp}
|
||||||
{betaIconComp}
|
{betaIconComp}
|
||||||
|
{togglePluginEditorButton}
|
||||||
</>;
|
</>;
|
||||||
|
|
||||||
const titleComp = createTitleComponent(hideableRightComponents);
|
const titleComp = createTitleComponent(hideableRightComponents);
|
||||||
|
@ -28,7 +28,7 @@ const PluginDialogManager: React.FC<Props> = props => {
|
|||||||
|
|
||||||
const dialogs: ReactElement[] = [];
|
const dialogs: ReactElement[] = [];
|
||||||
for (const viewInfo of viewInfos) {
|
for (const viewInfo of viewInfos) {
|
||||||
if (viewInfo.view.containerType === ContainerType.Panel || !viewInfo.view.opened) {
|
if (viewInfo.view.containerType !== ContainerType.Dialog || !viewInfo.view.opened) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ import { getNoteCallbackUrl } from '@joplin/lib/callbackUrlUtils';
|
|||||||
import { AppState } from '../../../utils/types';
|
import { AppState } from '../../../utils/types';
|
||||||
import restoreItems from '@joplin/lib/services/trash/restoreItems';
|
import restoreItems from '@joplin/lib/services/trash/restoreItems';
|
||||||
import { getDisplayParentTitle } from '@joplin/lib/services/trash';
|
import { getDisplayParentTitle } from '@joplin/lib/services/trash';
|
||||||
import { PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
|
import { PluginHtmlContents, PluginStates, utils as pluginUtils } from '@joplin/lib/services/plugins/reducer';
|
||||||
import debounce from '../../../utils/debounce';
|
import debounce from '../../../utils/debounce';
|
||||||
import { focus } from '@joplin/lib/utils/focusHandler';
|
import { focus } from '@joplin/lib/utils/focusHandler';
|
||||||
import CommandService, { RegisteredRuntime } from '@joplin/lib/services/CommandService';
|
import CommandService, { RegisteredRuntime } from '@joplin/lib/services/CommandService';
|
||||||
@ -63,6 +63,11 @@ import { DialogContext, DialogControl } from '../../DialogManager';
|
|||||||
import { CommandRuntimeProps, EditorMode, PickerResponse } from './types';
|
import { CommandRuntimeProps, EditorMode, PickerResponse } from './types';
|
||||||
import commands from './commands';
|
import commands from './commands';
|
||||||
import { AttachFileAction, AttachFileOptions } from './commands/attachFile';
|
import { AttachFileAction, AttachFileOptions } from './commands/attachFile';
|
||||||
|
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||||
|
import PluginUserWebView from '../../plugins/dialogs/PluginUserWebView';
|
||||||
|
import getShownPluginEditorView from '@joplin/lib/services/plugins/utils/getShownPluginEditorView';
|
||||||
|
import getActivePluginEditorView from '@joplin/lib/services/plugins/utils/getActivePluginEditorView';
|
||||||
|
import EditorPluginHandler from '@joplin/lib/services/plugins/EditorPluginHandler';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
const emptyArray: any[] = [];
|
const emptyArray: any[] = [];
|
||||||
@ -97,6 +102,9 @@ interface Props extends BaseProps {
|
|||||||
highlightedWords: string[];
|
highlightedWords: string[];
|
||||||
noteHash: string;
|
noteHash: string;
|
||||||
toolbarEnabled: boolean;
|
toolbarEnabled: boolean;
|
||||||
|
'plugins.shownEditorViewIds': string[];
|
||||||
|
pluginHtmlContents: PluginHtmlContents;
|
||||||
|
editorNoteReloadTimeRequest: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComponentProps extends Props {
|
interface ComponentProps extends Props {
|
||||||
@ -122,6 +130,7 @@ interface State {
|
|||||||
imageEditorResourceFilepath: string;
|
imageEditorResourceFilepath: string;
|
||||||
noteResources: Record<string, ResourceInfo>;
|
noteResources: Record<string, ResourceInfo>;
|
||||||
newAndNoTitleChangeNoteId: boolean|null;
|
newAndNoTitleChangeNoteId: boolean|null;
|
||||||
|
noteLastLoadTime: number;
|
||||||
|
|
||||||
undoRedoButtonState: {
|
undoRedoButtonState: {
|
||||||
canUndo: boolean;
|
canUndo: boolean;
|
||||||
@ -162,6 +171,7 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
public dialogbox: any;
|
public dialogbox: any;
|
||||||
private commandRegistration_: RegisteredRuntime|null = null;
|
private commandRegistration_: RegisteredRuntime|null = null;
|
||||||
|
private editorPluginHandler_ = new EditorPluginHandler(PluginService.instance());
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
public static navigationOptions(): any {
|
public static navigationOptions(): any {
|
||||||
@ -189,6 +199,7 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
|||||||
noteResources: {},
|
noteResources: {},
|
||||||
imageEditorResourceFilepath: null,
|
imageEditorResourceFilepath: null,
|
||||||
newAndNoTitleChangeNoteId: null,
|
newAndNoTitleChangeNoteId: null,
|
||||||
|
noteLastLoadTime: Date.now(),
|
||||||
|
|
||||||
undoRedoButtonState: {
|
undoRedoButtonState: {
|
||||||
canUndo: false,
|
canUndo: false,
|
||||||
@ -551,6 +562,12 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
|||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.editorPluginHandler_.emitActivationCheck();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.editorPluginHandler_.emitUpdate(this.props['plugins.shownEditorViewIds']);
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
@ -610,6 +627,17 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
|||||||
noteHash: noteHash,
|
noteHash: noteHash,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props['plugins.shownEditorViewIds'] !== prevProps['plugins.shownEditorViewIds']) {
|
||||||
|
const { editorPlugin } = getShownPluginEditorView(this.props.plugins, this.props['plugins.shownEditorViewIds']);
|
||||||
|
if (!editorPlugin && this.props.editorNoteReloadTimeRequest > this.state.noteLastLoadTime) {
|
||||||
|
void shared.reloadNote(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevProps.noteId && this.props.noteId && prevProps.noteId !== this.props.noteId) {
|
||||||
|
void this.editorPluginHandler_.emitActivationCheck();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
@ -1420,6 +1448,8 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
|||||||
// multiple times.
|
// multiple times.
|
||||||
this.registerCommands();
|
this.registerCommands();
|
||||||
|
|
||||||
|
const { editorPlugin, editorView } = getShownPluginEditorView(this.props.plugins, this.props['plugins.shownEditorViewIds']);
|
||||||
|
|
||||||
if (this.state.isLoading) {
|
if (this.state.isLoading) {
|
||||||
return (
|
return (
|
||||||
<View style={this.styles().screen}>
|
<View style={this.styles().screen}>
|
||||||
@ -1448,10 +1478,25 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
|||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderPluginEditor = () => {
|
||||||
|
return <PluginUserWebView
|
||||||
|
viewInfo={{ plugin: editorPlugin, view: editorView }}
|
||||||
|
themeId={this.props.themeId}
|
||||||
|
onLoadEnd={() => {}}
|
||||||
|
pluginHtmlContents={this.props.pluginHtmlContents}
|
||||||
|
setDialogControl={() => {}}
|
||||||
|
style={{}}
|
||||||
|
/>;
|
||||||
|
};
|
||||||
|
|
||||||
// Currently keyword highlighting is supported only when FTS is available.
|
// Currently keyword highlighting is supported only when FTS is available.
|
||||||
const keywords = this.props.searchQuery && !!this.props.ftsEnabled ? this.props.highlightedWords : emptyArray;
|
const keywords = this.props.searchQuery && !!this.props.ftsEnabled ? this.props.highlightedWords : emptyArray;
|
||||||
|
|
||||||
let bodyComponent = null;
|
let bodyComponent = null;
|
||||||
|
|
||||||
|
if (editorView) {
|
||||||
|
bodyComponent = renderPluginEditor();
|
||||||
|
} else {
|
||||||
if (this.state.mode === 'view') {
|
if (this.state.mode === 'view') {
|
||||||
// Note: as of 2018-12-29 it's important not to display the viewer if the note body is empty,
|
// Note: as of 2018-12-29 it's important not to display the viewer if the note body is empty,
|
||||||
// to avoid the HACK_webviewLoadingState related bug.
|
// to avoid the HACK_webviewLoadingState related bug.
|
||||||
@ -1542,6 +1587,7 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
|||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const renderActionButton = () => {
|
const renderActionButton = () => {
|
||||||
if (this.state.voiceTypingDialogShown) return null;
|
if (this.state.voiceTypingDialogShown) return null;
|
||||||
@ -1596,6 +1642,8 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
|||||||
return <VoiceTypingDialog locale={currentLocale()} onText={this.voiceTypingDialog_onText} onDismiss={this.voiceTypingDialog_onDismiss}/>;
|
return <VoiceTypingDialog locale={currentLocale()} onText={this.voiceTypingDialog_onText} onDismiss={this.voiceTypingDialog_onDismiss}/>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { editorPlugin: activeEditorPlugin } = getActivePluginEditorView(this.props.plugins);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={this.rootStyle(this.props.themeId).root}>
|
<View style={this.rootStyle(this.props.themeId).root}>
|
||||||
<ScreenHeader
|
<ScreenHeader
|
||||||
@ -1608,6 +1656,7 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
|
|||||||
showSearchButton={false}
|
showSearchButton={false}
|
||||||
showUndoButton={(this.state.undoRedoButtonState.canUndo || this.state.undoRedoButtonState.canRedo) && this.state.mode === 'edit'}
|
showUndoButton={(this.state.undoRedoButtonState.canUndo || this.state.undoRedoButtonState.canRedo) && this.state.mode === 'edit'}
|
||||||
showRedoButton={this.state.undoRedoButtonState.canRedo && this.state.mode === 'edit'}
|
showRedoButton={this.state.undoRedoButtonState.canRedo && this.state.mode === 'edit'}
|
||||||
|
showPluginEditorButton={!!activeEditorPlugin}
|
||||||
undoButtonDisabled={!this.state.undoRedoButtonState.canUndo && this.state.undoRedoButtonState.canRedo}
|
undoButtonDisabled={!this.state.undoRedoButtonState.canUndo && this.state.undoRedoButtonState.canRedo}
|
||||||
onUndoButtonPress={this.screenHeader_undoButtonPress}
|
onUndoButtonPress={this.screenHeader_undoButtonPress}
|
||||||
onRedoButtonPress={this.screenHeader_redoButtonPress}
|
onRedoButtonPress={this.screenHeader_redoButtonPress}
|
||||||
@ -1655,6 +1704,9 @@ const NoteScreen = connect((state: AppState) => {
|
|||||||
provisionalNoteIds: state.provisionalNoteIds,
|
provisionalNoteIds: state.provisionalNoteIds,
|
||||||
highlightedWords: state.highlightedWords,
|
highlightedWords: state.highlightedWords,
|
||||||
plugins: state.pluginService.plugins,
|
plugins: state.pluginService.plugins,
|
||||||
|
'plugins.shownEditorViewIds': state.settings['plugins.shownEditorViewIds'] || [],
|
||||||
|
pluginHtmlContents: state.pluginService.pluginHtmlContents,
|
||||||
|
editorNoteReloadTimeRequest: state.editorNoteReloadTimeRequest,
|
||||||
|
|
||||||
// What we call "beta editor" in this component is actually the (now
|
// What we call "beta editor" in this component is actually the (now
|
||||||
// default) CodeMirror editor. That should be refactored to make it less
|
// default) CodeMirror editor. That should be refactored to make it less
|
||||||
|
@ -288,7 +288,14 @@ class NotesScreenComponent extends BaseScreenComponent<ComponentProps, State> {
|
|||||||
|
|
||||||
inert={accessibilityHidden}
|
inert={accessibilityHidden}
|
||||||
>
|
>
|
||||||
<ScreenHeader title={iconString + title} showBackButton={false} sortButton_press={this.sortButton_press} folderPickerOptions={this.folderPickerOptions()} showSearchButton={true} showSideMenuButton={true} />
|
<ScreenHeader
|
||||||
|
title={iconString + title}
|
||||||
|
showBackButton={false}
|
||||||
|
sortButton_press={this.sortButton_press}
|
||||||
|
folderPickerOptions={this.folderPickerOptions()}
|
||||||
|
showSearchButton={true}
|
||||||
|
showSideMenuButton={true}
|
||||||
|
/>
|
||||||
<NoteList />
|
<NoteList />
|
||||||
{actionButtonComp}
|
{actionButtonComp}
|
||||||
</AccessibleView>
|
</AccessibleView>
|
||||||
|
@ -5,7 +5,9 @@ import * as historyForward from './historyForward';
|
|||||||
import * as openMasterPasswordDialog from './openMasterPasswordDialog';
|
import * as openMasterPasswordDialog from './openMasterPasswordDialog';
|
||||||
import * as permanentlyDeleteNote from './permanentlyDeleteNote';
|
import * as permanentlyDeleteNote from './permanentlyDeleteNote';
|
||||||
import * as renderMarkup from './renderMarkup';
|
import * as renderMarkup from './renderMarkup';
|
||||||
|
import * as showEditorPlugin from './showEditorPlugin';
|
||||||
import * as synchronize from './synchronize';
|
import * as synchronize from './synchronize';
|
||||||
|
import * as toggleEditorPlugin from './toggleEditorPlugin';
|
||||||
|
|
||||||
const index: any[] = [
|
const index: any[] = [
|
||||||
deleteNote,
|
deleteNote,
|
||||||
@ -14,7 +16,9 @@ const index: any[] = [
|
|||||||
openMasterPasswordDialog,
|
openMasterPasswordDialog,
|
||||||
permanentlyDeleteNote,
|
permanentlyDeleteNote,
|
||||||
renderMarkup,
|
renderMarkup,
|
||||||
|
showEditorPlugin,
|
||||||
synchronize,
|
synchronize,
|
||||||
|
toggleEditorPlugin,
|
||||||
];
|
];
|
||||||
|
|
||||||
export default index;
|
export default index;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
import { CommandContext, CommandDeclaration, CommandRuntime } from '../services/CommandService';
|
||||||
import Setting from '@joplin/lib/models/Setting';
|
import Setting from '../models/Setting';
|
||||||
import getActivePluginEditorView from '@joplin/lib/services/plugins/utils/getActivePluginEditorView';
|
import getActivePluginEditorView from '../services/plugins/utils/getActivePluginEditorView';
|
||||||
import Logger from '@joplin/utils/Logger';
|
import Logger from '@joplin/utils/Logger';
|
||||||
|
|
||||||
const logger = Logger.create('showEditorPlugin');
|
const logger = Logger.create('showEditorPlugin');
|
@ -1,7 +1,7 @@
|
|||||||
import { CommandContext, CommandDeclaration, CommandRuntime } from '@joplin/lib/services/CommandService';
|
import { CommandContext, CommandDeclaration, CommandRuntime } from '../services/CommandService';
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '../locale';
|
||||||
import Setting from '@joplin/lib/models/Setting';
|
import Setting from '../models/Setting';
|
||||||
import getActivePluginEditorView from '@joplin/lib/services/plugins/utils/getActivePluginEditorView';
|
import getActivePluginEditorView from '../services/plugins/utils/getActivePluginEditorView';
|
||||||
import Logger from '@joplin/utils/Logger';
|
import Logger from '@joplin/utils/Logger';
|
||||||
|
|
||||||
const logger = Logger.create('toggleEditorPlugin');
|
const logger = Logger.create('toggleEditorPlugin');
|
||||||
@ -24,14 +24,26 @@ export const runtime = (): CommandRuntime => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const idx = shownEditorViewIds.indexOf(editorView.id);
|
const idx = shownEditorViewIds.indexOf(editorView.id);
|
||||||
|
let hasBeenHidden = false;
|
||||||
|
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
shownEditorViewIds.push(editorView.id);
|
shownEditorViewIds.push(editorView.id);
|
||||||
} else {
|
} else {
|
||||||
shownEditorViewIds.splice(idx, 1);
|
shownEditorViewIds.splice(idx, 1);
|
||||||
|
hasBeenHidden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info('New shown editor views: ', shownEditorViewIds);
|
||||||
|
|
||||||
Setting.setValue('plugins.shownEditorViewIds', shownEditorViewIds);
|
Setting.setValue('plugins.shownEditorViewIds', shownEditorViewIds);
|
||||||
|
|
||||||
|
if (hasBeenHidden) {
|
||||||
|
// When the plugin editor goes from visible to hidden, we need to reload the note
|
||||||
|
// because it may have been changed via the data API.
|
||||||
|
context.dispatch({
|
||||||
|
type: 'EDITOR_NOTE_NEEDS_RELOAD',
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
@ -67,6 +67,8 @@ interface Shared {
|
|||||||
installResourceHandling?: (refreshResourceHandler: any)=> void;
|
installResourceHandling?: (refreshResourceHandler: any)=> void;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
uninstallResourceHandling?: (refreshResourceHandler: any)=> void;
|
uninstallResourceHandling?: (refreshResourceHandler: any)=> void;
|
||||||
|
|
||||||
|
reloadNote?: (comp: BaseNoteScreenComponent)=> Promise<NoteEntity>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shared: Shared = {};
|
const shared: Shared = {};
|
||||||
@ -268,7 +270,7 @@ shared.isModified = function(comp: BaseNoteScreenComponent) {
|
|||||||
return !!Object.getOwnPropertyNames(diff).length;
|
return !!Object.getOwnPropertyNames(diff).length;
|
||||||
};
|
};
|
||||||
|
|
||||||
shared.initState = async function(comp: BaseNoteScreenComponent) {
|
shared.reloadNote = async (comp: BaseNoteScreenComponent) => {
|
||||||
const isProvisionalNote = comp.props.provisionalNoteIds.includes(comp.props.noteId);
|
const isProvisionalNote = comp.props.provisionalNoteIds.includes(comp.props.noteId);
|
||||||
|
|
||||||
const note = await Note.load(comp.props.noteId);
|
const note = await Note.load(comp.props.noteId);
|
||||||
@ -292,6 +294,7 @@ shared.initState = async function(comp: BaseNoteScreenComponent) {
|
|||||||
fromShare: !!comp.props.sharedData,
|
fromShare: !!comp.props.sharedData,
|
||||||
noteResources: await shared.attachedResources(note ? note.body : ''),
|
noteResources: await shared.attachedResources(note ? note.body : ''),
|
||||||
readOnly: itemIsReadOnlySync(ModelType.Note, ItemChange.SOURCE_UNSPECIFIED, note as ItemSlice, Setting.value('sync.userId'), BaseItem.syncShareCache),
|
readOnly: itemIsReadOnlySync(ModelType.Note, ItemChange.SOURCE_UNSPECIFIED, note as ItemSlice, Setting.value('sync.userId'), BaseItem.syncShareCache),
|
||||||
|
noteLastLoadTime: Date.now(),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Handle the case where a non-existent note is loaded. This can happen briefly after deleting a note.
|
// Handle the case where a non-existent note is loaded. This can happen briefly after deleting a note.
|
||||||
@ -304,9 +307,16 @@ shared.initState = async function(comp: BaseNoteScreenComponent) {
|
|||||||
fromShare,
|
fromShare,
|
||||||
noteResources: [],
|
noteResources: [],
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
|
noteLastLoadTime: Date.now(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return note;
|
||||||
|
};
|
||||||
|
|
||||||
|
shared.initState = async function(comp: BaseNoteScreenComponent) {
|
||||||
|
const note = await shared.reloadNote(comp);
|
||||||
|
|
||||||
if (comp.props.sharedData) {
|
if (comp.props.sharedData) {
|
||||||
if (comp.props.sharedData.title) {
|
if (comp.props.sharedData.title) {
|
||||||
this.noteComponent_change(comp, 'title', comp.props.sharedData.title);
|
this.noteComponent_change(comp, 'title', comp.props.sharedData.title);
|
||||||
|
@ -170,6 +170,7 @@ export interface State extends WindowState {
|
|||||||
mustUpgradeAppMessage: string;
|
mustUpgradeAppMessage: string;
|
||||||
mustAuthenticate: boolean;
|
mustAuthenticate: boolean;
|
||||||
toast: Toast | null;
|
toast: Toast | null;
|
||||||
|
editorNoteReloadTimeRequest: number;
|
||||||
|
|
||||||
allowSelectionInOtherFolders: boolean;
|
allowSelectionInOtherFolders: boolean;
|
||||||
|
|
||||||
@ -241,6 +242,7 @@ export const defaultState: State = {
|
|||||||
mustUpgradeAppMessage: '',
|
mustUpgradeAppMessage: '',
|
||||||
mustAuthenticate: false,
|
mustAuthenticate: false,
|
||||||
allowSelectionInOtherFolders: false,
|
allowSelectionInOtherFolders: false,
|
||||||
|
editorNoteReloadTimeRequest: 0,
|
||||||
|
|
||||||
pluginService: pluginServiceDefaultState,
|
pluginService: pluginServiceDefaultState,
|
||||||
shareService: shareServiceDefaultState,
|
shareService: shareServiceDefaultState,
|
||||||
@ -1512,6 +1514,12 @@ const reducer = produce((draft: Draft<State> = defaultState, action: any) => {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'EDITOR_NOTE_NEEDS_RELOAD':
|
||||||
|
{
|
||||||
|
draft.editorNoteReloadTimeRequest = Date.now();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case 'TOAST_SHOW':
|
case 'TOAST_SHOW':
|
||||||
draft.toast = {
|
draft.toast = {
|
||||||
duration: 6000,
|
duration: 6000,
|
||||||
|
52
packages/lib/services/plugins/EditorPluginHandler.ts
Normal file
52
packages/lib/services/plugins/EditorPluginHandler.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// The goal of this class is to simplify the integration of the `joplin.views.editor` plugin logic
|
||||||
|
// in the desktop and mobile app. See here for more information:
|
||||||
|
//
|
||||||
|
// packages/lib/services/plugins/api/JoplinViewsEditor.ts
|
||||||
|
|
||||||
|
import Logger from '@joplin/utils/Logger';
|
||||||
|
import AsyncActionQueue, { IntervalType } from '../../AsyncActionQueue';
|
||||||
|
import eventManager from '../../eventManager';
|
||||||
|
import { EditorActivationCheckFilterObject } from './api/types';
|
||||||
|
import type PluginService from './PluginService';
|
||||||
|
import WebviewController from './WebviewController';
|
||||||
|
|
||||||
|
const logger = Logger.create('EditorPluginHandler');
|
||||||
|
|
||||||
|
const makeNoteUpdateAction = (pluginService: PluginService, shownEditorViewIds: string[]) => {
|
||||||
|
return async () => {
|
||||||
|
for (const viewId of shownEditorViewIds) {
|
||||||
|
const controller = pluginService.viewControllerByViewId(viewId) as WebviewController;
|
||||||
|
if (controller) controller.emitUpdate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class {
|
||||||
|
|
||||||
|
private pluginService_: PluginService;
|
||||||
|
private viewUpdateAsyncQueue_ = new AsyncActionQueue(100, IntervalType.Fixed);
|
||||||
|
|
||||||
|
public constructor(pluginService: PluginService) {
|
||||||
|
this.pluginService_ = pluginService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public emitUpdate(shownEditorViewIds: string[]) {
|
||||||
|
logger.info('emitUpdate:', shownEditorViewIds);
|
||||||
|
this.viewUpdateAsyncQueue_.push(makeNoteUpdateAction(this.pluginService_, shownEditorViewIds));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async emitActivationCheck() {
|
||||||
|
let filterObject: EditorActivationCheckFilterObject = {
|
||||||
|
activatedEditors: [],
|
||||||
|
};
|
||||||
|
filterObject = await eventManager.filterEmit('editorActivationCheck', filterObject);
|
||||||
|
|
||||||
|
logger.info('emitActivationCheck: responses:', filterObject);
|
||||||
|
|
||||||
|
for (const editor of filterObject.activatedEditors) {
|
||||||
|
const controller = this.pluginService_.pluginById(editor.pluginId).viewController(editor.viewId) as WebviewController;
|
||||||
|
controller.setActive(editor.isActive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -279,7 +279,7 @@ export default class WebviewController extends ViewController {
|
|||||||
return this.storeView.opened;
|
return this.storeView.opened;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async isVisible(): Promise<boolean> {
|
public isVisible(): boolean {
|
||||||
if (!this.storeView.opened) return false;
|
if (!this.storeView.opened) return false;
|
||||||
const shownEditorViewIds: string[] = this.store.getState().settings['plugins.shownEditorViewIds'];
|
const shownEditorViewIds: string[] = this.store.getState().settings['plugins.shownEditorViewIds'];
|
||||||
return shownEditorViewIds.includes(this.handle);
|
return shownEditorViewIds.includes(this.handle);
|
||||||
|
@ -93,10 +93,10 @@ export default class JoplinViewsEditors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the editor can potentially be activated - this for example when the current note
|
* Emitted when the editor can potentially be activated - this is for example when the current
|
||||||
* is changed, or when the application is opened. At that point should can check the current
|
* note is changed, or when the application is opened. At that point you should check the
|
||||||
* note and decide whether your editor should be activated or not. If it should return `true`,
|
* current note and decide whether your editor should be activated or not. If it should, return
|
||||||
* otherwise return `false`.
|
* `true`, otherwise return `false`.
|
||||||
*/
|
*/
|
||||||
public async onActivationCheck(handle: ViewHandle, callback: ActivationCheckCallback): Promise<void> {
|
public async onActivationCheck(handle: ViewHandle, callback: ActivationCheckCallback): Promise<void> {
|
||||||
const handler: FilterHandler<EditorActivationCheckFilterObject> = async (object) => {
|
const handler: FilterHandler<EditorActivationCheckFilterObject> = async (object) => {
|
||||||
@ -118,7 +118,7 @@ export default class JoplinViewsEditors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emitted when the editor content should be updated. This for example when the currently
|
* Emitted when your editor content should be updated. This is for example when the currently
|
||||||
* selected note changes, or when the user makes the editor visible.
|
* selected note changes, or when the user makes the editor visible.
|
||||||
*/
|
*/
|
||||||
public async onUpdate(handle: ViewHandle, callback: UpdateCallback): Promise<void> {
|
public async onUpdate(handle: ViewHandle, callback: UpdateCallback): Promise<void> {
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import { PluginStates } from '../reducer';
|
||||||
|
import getActivePluginEditorView from './getActivePluginEditorView';
|
||||||
|
|
||||||
|
export default (plugins: PluginStates, shownEditorViewIds: string[]) => {
|
||||||
|
const { editorPlugin, editorView } = getActivePluginEditorView(plugins);
|
||||||
|
if (editorView) {
|
||||||
|
if (!shownEditorViewIds.includes(editorView.id)) return { editorPlugin: null, editorView: null };
|
||||||
|
}
|
||||||
|
return { editorPlugin, editorView };
|
||||||
|
};
|
@ -18,29 +18,46 @@ Variables follow the naming convention `--joplin-{property}` and are used in you
|
|||||||
|
|
||||||
## Icons
|
## Icons
|
||||||
|
|
||||||
In addition to variables, you have access to a set of standard font assets that ship with Joplin. These include:
|
On desktop, your plugin view will have access to icons used by the app. It is however not recommended to use them because they may change in future versions. And it will also make your plugin incompatible with the mobile app (which does not expose any icon library).
|
||||||
|
|
||||||
* [Roboto](https://fonts.google.com/specimen/Roboto?preview.text_type=custom) - (the standard UI font, `font-family` referenced above)
|
Instead a recommended approach is to add Font Awesome in your plugin project, and to import only the icons you'll need. To do so using React, follow these instructions:
|
||||||
* [Font Awesome](https://fontawesome.com/icons?d=gallery&p=2&m=free) - icon library
|
|
||||||
* [icoMoon](https://icomoon.io/#preview-free) - icon library (subset, see [style.css](https://github.com/laurent22/joplin/blob/dev/packages/app-desktop/style/icons/style.css))
|
|
||||||
|
|
||||||
To display an icon, use CSS and HTML like the following.
|
**Install Font Awesome:**
|
||||||
|
|
||||||
```css
|
```shell
|
||||||
/* style icons to match the theme */
|
npm install --save @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/free-regular-svg-icons @fortawesome/react-fontawesome
|
||||||
.toolbarIcon {
|
|
||||||
font-size: var(--joplin-toolbar-icon-size);
|
|
||||||
}
|
|
||||||
.primary {
|
|
||||||
color: var(--joplin-color);
|
|
||||||
}
|
|
||||||
.secondary {
|
|
||||||
color: var(--joplin-color2);
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```html
|
**Import and load the icons:**
|
||||||
<i class="toolbarIcon primary fas fa-music"></i> Font Awesome music icon
|
|
||||||
<br />
|
From one of your top TypeScript files:
|
||||||
<i class="toolbarIcon secondary icon-notebooks"></i> icoMoon notebook icon
|
|
||||||
|
```typescript
|
||||||
|
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||||
|
|
||||||
|
// Import the specific icons you want to use
|
||||||
|
import { faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { faCheckCircle } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
|
||||||
|
// Add the icons to the library
|
||||||
|
library.add(faTimes, faCheckCircle);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Use Font Awesome React Components:**
|
||||||
|
|
||||||
|
```JSX
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FontAwesomeIcon icon="times" />
|
||||||
|
<FontAwesomeIcon icon={['far', 'check-circle']} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are not using React, just ask ChatGPT on how to do the above using you preferred JS framework.
|
Reference in New Issue
Block a user