mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
Plugins: Fixed issue with dialog being empty in some cases
This commit is contained in:
parent
a808281dd2
commit
adde092ea6
@ -763,12 +763,27 @@ packages/app-desktop/services/plugins/UserWebviewDialog.js.map
|
||||
packages/app-desktop/services/plugins/UserWebviewDialogButtonBar.d.ts
|
||||
packages/app-desktop/services/plugins/UserWebviewDialogButtonBar.js
|
||||
packages/app-desktop/services/plugins/UserWebviewDialogButtonBar.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useContentSize.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useContentSize.js
|
||||
packages/app-desktop/services/plugins/hooks/useContentSize.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useHtmlLoader.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useHtmlLoader.js
|
||||
packages/app-desktop/services/plugins/hooks/useHtmlLoader.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useScriptLoader.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useScriptLoader.js
|
||||
packages/app-desktop/services/plugins/hooks/useScriptLoader.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useSubmitHandler.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useSubmitHandler.js
|
||||
packages/app-desktop/services/plugins/hooks/useSubmitHandler.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useThemeCss.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useThemeCss.js
|
||||
packages/app-desktop/services/plugins/hooks/useThemeCss.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useViewIsReady.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useViewIsReady.js
|
||||
packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
|
||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.d.ts
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js.map
|
||||
|
15
.gitignore
vendored
15
.gitignore
vendored
@ -755,12 +755,27 @@ packages/app-desktop/services/plugins/UserWebviewDialog.js.map
|
||||
packages/app-desktop/services/plugins/UserWebviewDialogButtonBar.d.ts
|
||||
packages/app-desktop/services/plugins/UserWebviewDialogButtonBar.js
|
||||
packages/app-desktop/services/plugins/UserWebviewDialogButtonBar.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useContentSize.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useContentSize.js
|
||||
packages/app-desktop/services/plugins/hooks/useContentSize.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useHtmlLoader.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useHtmlLoader.js
|
||||
packages/app-desktop/services/plugins/hooks/useHtmlLoader.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useScriptLoader.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useScriptLoader.js
|
||||
packages/app-desktop/services/plugins/hooks/useScriptLoader.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useSubmitHandler.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useSubmitHandler.js
|
||||
packages/app-desktop/services/plugins/hooks/useSubmitHandler.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useThemeCss.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useThemeCss.js
|
||||
packages/app-desktop/services/plugins/hooks/useThemeCss.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useViewIsReady.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useViewIsReady.js
|
||||
packages/app-desktop/services/plugins/hooks/useViewIsReady.js.map
|
||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.d.ts
|
||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js
|
||||
packages/app-desktop/services/plugins/hooks/useWebviewToPluginMessages.js.map
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.d.ts
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js
|
||||
packages/app-desktop/services/spellChecker/SpellCheckerServiceDriverNative.js.map
|
||||
|
@ -27,7 +27,7 @@ joplin.plugins.register({
|
||||
const result2 = await dialogs.open(handle2);
|
||||
alert('Got result: ' + JSON.stringify(result2));
|
||||
|
||||
const handle3 = await dialogs.create();
|
||||
const handle3 = await dialogs.create('myDialog3');
|
||||
await dialogs.setHtml(handle3, `
|
||||
<p>Testing dialog with form elements</p>
|
||||
<form name="user">
|
||||
|
@ -1,7 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { useRef, useEffect, useState, useImperativeHandle, forwardRef } from 'react';
|
||||
import { useRef, useImperativeHandle, forwardRef } from 'react';
|
||||
import useViewIsReady from './hooks/useViewIsReady';
|
||||
import useThemeCss from './hooks/useThemeCss';
|
||||
import useContentSize from './hooks/useContentSize';
|
||||
import useSubmitHandler from './hooks/useSubmitHandler';
|
||||
import useHtmlLoader from './hooks/useHtmlLoader';
|
||||
import useWebviewToPluginMessages from './hooks/useWebviewToPluginMessages';
|
||||
import useScriptLoader from './hooks/useScriptLoader';
|
||||
const styled = require('styled-components').default;
|
||||
|
||||
export interface Props {
|
||||
@ -16,11 +21,8 @@ export interface Props {
|
||||
fitToContent?: boolean;
|
||||
borderBottom?: boolean;
|
||||
theme?: any;
|
||||
}
|
||||
|
||||
interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
onSubmit?: any;
|
||||
onDismiss?: any;
|
||||
}
|
||||
|
||||
const StyledFrame = styled.iframe`
|
||||
@ -61,19 +63,6 @@ function UserWebview(props: Props, ref: any) {
|
||||
const viewRef = useRef(null);
|
||||
const isReady = useViewIsReady(viewRef);
|
||||
const cssFilePath = useThemeCss({ pluginId: props.pluginId, themeId: props.themeId });
|
||||
const [contentSize, setContentSize] = useState<Size>({ width: minWidth, height: minHeight });
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
formData: function() {
|
||||
if (viewRef.current) {
|
||||
return serializeForms(viewRef.current.contentWindow.document);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
function frameWindow() {
|
||||
if (!viewRef.current) return null;
|
||||
@ -86,86 +75,54 @@ function UserWebview(props: Props, ref: any) {
|
||||
win.postMessage({ target: 'webview', name, args }, '*');
|
||||
}
|
||||
|
||||
function updateContentSize() {
|
||||
const win = frameWindow();
|
||||
if (!win) return null;
|
||||
|
||||
const rect = win.document.getElementById('joplin-plugin-content').getBoundingClientRect();
|
||||
|
||||
let w = rect.width;
|
||||
let h = rect.height;
|
||||
if (w < minWidth) w = minWidth;
|
||||
if (h < minHeight) h = minHeight;
|
||||
|
||||
const newSize = { width: w, height: h };
|
||||
|
||||
setContentSize((current: Size) => {
|
||||
if (current.width === newSize.width && current.height === newSize.height) return current;
|
||||
return newSize;
|
||||
});
|
||||
|
||||
return newSize;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isReady) return () => {};
|
||||
let cancelled = false;
|
||||
postMessage('setHtml', { html: props.html });
|
||||
|
||||
setTimeout(() => {
|
||||
if (cancelled) return;
|
||||
updateContentSize();
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
formData: function() {
|
||||
if (viewRef.current) {
|
||||
return serializeForms(frameWindow().document);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
};
|
||||
}, [props.html, isReady]);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isReady) return;
|
||||
postMessage('setScripts', { scripts: props.scripts });
|
||||
}, [props.scripts, isReady]);
|
||||
const htmlHash = useHtmlLoader(
|
||||
frameWindow(),
|
||||
isReady,
|
||||
postMessage,
|
||||
props.html
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isReady || !cssFilePath) return;
|
||||
postMessage('setScript', { script: cssFilePath, key: 'themeCss' });
|
||||
}, [isReady, cssFilePath]);
|
||||
const contentSize = useContentSize(
|
||||
frameWindow(),
|
||||
htmlHash,
|
||||
minWidth,
|
||||
minHeight,
|
||||
props.fitToContent,
|
||||
isReady
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
function onMessage(event: any) {
|
||||
if (!event.data || event.data.target !== 'plugin') return;
|
||||
props.onMessage({
|
||||
pluginId: props.pluginId,
|
||||
viewId: props.viewId,
|
||||
message: event.data.message,
|
||||
});
|
||||
}
|
||||
useSubmitHandler(
|
||||
frameWindow(),
|
||||
props.onSubmit,
|
||||
props.onDismiss,
|
||||
htmlHash
|
||||
);
|
||||
|
||||
viewRef.current.contentWindow.addEventListener('message', onMessage);
|
||||
useWebviewToPluginMessages(
|
||||
frameWindow(),
|
||||
props.onMessage,
|
||||
props.pluginId,
|
||||
props.viewId
|
||||
);
|
||||
|
||||
return () => {
|
||||
viewRef.current.contentWindow.removeEventListener('message', onMessage);
|
||||
};
|
||||
}, [props.onMessage, props.pluginId, props.viewId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.fitToContent || !isReady) return () => {};
|
||||
|
||||
// The only reliable way to make sure that the iframe has the same dimensions
|
||||
// as its content is to poll the dimensions at regular intervals. Other methods
|
||||
// work most of the time but will fail in various edge cases. Most reliable way
|
||||
// is probably iframe-resizer package, but still with 40 unfixed bugs.
|
||||
//
|
||||
// Polling in our case is fine since this is only used when displaying plugin
|
||||
// dialogs, which should be short lived. updateContentSize() is also optimised
|
||||
// to do nothing when size hasn't changed.
|
||||
const updateFrameSizeIID = setInterval(updateContentSize, 2000);
|
||||
|
||||
return () => {
|
||||
clearInterval(updateFrameSizeIID);
|
||||
};
|
||||
}, [props.fitToContent, isReady, minWidth, minHeight]);
|
||||
useScriptLoader(
|
||||
postMessage,
|
||||
isReady,
|
||||
props.scripts,
|
||||
cssFilePath
|
||||
);
|
||||
|
||||
return <StyledFrame
|
||||
id={props.viewId}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import * as React from 'react';
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { ButtonSpec, DialogResult } from '@joplin/lib/services/plugins/api/types';
|
||||
import PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||
import WebviewController from '@joplin/lib/services/plugins/WebviewController';
|
||||
import * as React from 'react';
|
||||
import { useRef } from 'react';
|
||||
import UserWebview, { Props as UserWebviewProps } from './UserWebview';
|
||||
import UserWebviewDialogButtonBar from './UserWebviewDialogButtonBar';
|
||||
const styled = require('styled-components').default;
|
||||
@ -49,6 +49,18 @@ function defaultButtons(): ButtonSpec[] {
|
||||
];
|
||||
}
|
||||
|
||||
function findSubmitButton(buttons: ButtonSpec[]): ButtonSpec | null {
|
||||
return buttons.find((b: ButtonSpec) => {
|
||||
return ['ok', 'yes', 'confirm', 'submit'].includes(b.id);
|
||||
});
|
||||
}
|
||||
|
||||
function findDismissButton(buttons: ButtonSpec[]): ButtonSpec | null {
|
||||
return buttons.find((b: ButtonSpec) => {
|
||||
return ['cancel', 'no', 'reject'].includes(b.id);
|
||||
});
|
||||
}
|
||||
|
||||
export default function UserWebviewDialog(props: Props) {
|
||||
const webviewRef = useRef(null);
|
||||
|
||||
@ -68,6 +80,20 @@ export default function UserWebviewDialog(props: Props) {
|
||||
};
|
||||
});
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
const submitButton = findSubmitButton(buttons);
|
||||
if (submitButton) {
|
||||
submitButton.onClick();
|
||||
}
|
||||
}, [buttons]);
|
||||
|
||||
const onDismiss = useCallback(() => {
|
||||
const dismissButton = findDismissButton(buttons);
|
||||
if (dismissButton) {
|
||||
dismissButton.onClick();
|
||||
}
|
||||
}, [buttons]);
|
||||
|
||||
return (
|
||||
<StyledRoot>
|
||||
<Dialog>
|
||||
@ -82,6 +108,8 @@ export default function UserWebviewDialog(props: Props) {
|
||||
themeId={props.themeId}
|
||||
borderBottom={false}
|
||||
fitToContent={true}
|
||||
onSubmit={onSubmit}
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
</UserWebViewWrapper>
|
||||
<UserWebviewDialogButtonBar buttons={buttons}/>
|
||||
|
@ -57,6 +57,13 @@ const webviewApi = {
|
||||
const ipc = {
|
||||
setHtml: (args) => {
|
||||
contentElement.innerHTML = args.html;
|
||||
|
||||
console.debug('UserWebView frame: setting html to', args.html);
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
console.debug('UserWebView frame: setting html callback', args.hash);
|
||||
window.postMessage({ target: 'UserWebview', message: 'htmlIsSet', hash: args.hash }, '*');
|
||||
});
|
||||
},
|
||||
|
||||
setScript: (args) => {
|
||||
@ -102,8 +109,14 @@ const webviewApi = {
|
||||
}
|
||||
}));
|
||||
|
||||
// Send a message to the containing component to notify
|
||||
// it that the view content is fully ready.
|
||||
window.postMessage({ target: 'UserWebview', message: 'ready' }, '*');
|
||||
// Send a message to the containing component to notify it that the
|
||||
// view content is fully ready.
|
||||
//
|
||||
// Need to send it with a delay to make sure all listeners are
|
||||
// ready when the message is sent.
|
||||
window.requestAnimationFrame(() => {
|
||||
console.debug('UserWebView frame: calling isReady');
|
||||
window.postMessage({ target: 'UserWebview', message: 'ready' }, '*');
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -0,0 +1,61 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
hash: string;
|
||||
}
|
||||
|
||||
export default function(frameWindow: any, htmlHash: string, minWidth: number, minHeight: number, fitToContent: boolean, isReady: boolean) {
|
||||
const [contentSize, setContentSize] = useState<Size>({
|
||||
width: minWidth,
|
||||
height: minHeight,
|
||||
hash: '',
|
||||
});
|
||||
|
||||
function updateContentSize(hash: string) {
|
||||
if (!frameWindow) return;
|
||||
|
||||
const rect = frameWindow.document.getElementById('joplin-plugin-content').getBoundingClientRect();
|
||||
|
||||
let w = rect.width;
|
||||
let h = rect.height;
|
||||
if (w < minWidth) w = minWidth;
|
||||
if (h < minHeight) h = minHeight;
|
||||
|
||||
const newSize = { width: w, height: h, hash: hash };
|
||||
|
||||
setContentSize((current: Size) => {
|
||||
if (current.width === newSize.width && current.height === newSize.height && current.hash === hash) return current;
|
||||
return newSize;
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
updateContentSize(htmlHash);
|
||||
}, [htmlHash]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!fitToContent || !isReady) return () => {};
|
||||
|
||||
function onTick() {
|
||||
updateContentSize(htmlHash);
|
||||
}
|
||||
|
||||
// The only reliable way to make sure that the iframe has the same dimensions
|
||||
// as its content is to poll the dimensions at regular intervals. Other methods
|
||||
// work most of the time but will fail in various edge cases. Most reliable way
|
||||
// is probably iframe-resizer package, but still with 40 unfixed bugs.
|
||||
//
|
||||
// Polling in our case is fine since this is only used when displaying plugin
|
||||
// dialogs, which should be short lived. updateContentSize() is also optimised
|
||||
// to do nothing when size hasn't changed.
|
||||
const updateFrameSizeIID = setInterval(onTick, 100);
|
||||
|
||||
return () => {
|
||||
clearInterval(updateFrameSizeIID);
|
||||
};
|
||||
}, [fitToContent, isReady, minWidth, minHeight, htmlHash]);
|
||||
|
||||
return contentSize;
|
||||
}
|
51
packages/app-desktop/services/plugins/hooks/useHtmlLoader.ts
Normal file
51
packages/app-desktop/services/plugins/hooks/useHtmlLoader.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
const md5 = require('md5');
|
||||
|
||||
export default function(frameWindow: any, isReady: boolean, postMessage: Function, html: string) {
|
||||
const [loadedHtmlHash, setLoadedHtmlHash] = useState('');
|
||||
|
||||
const htmlHash = useMemo(() => {
|
||||
return md5(html);
|
||||
}, [html]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!frameWindow) return () => {};
|
||||
|
||||
function onMessage(event: any) {
|
||||
const data = event.data;
|
||||
|
||||
if (!data || data.target !== 'UserWebview') return;
|
||||
|
||||
console.info('useHtmlLoader: message', data);
|
||||
|
||||
// We only update if the HTML that was loaded is the same as
|
||||
// the active one. Otherwise it means the content has been
|
||||
// changed between the moment it was set by the user and the
|
||||
// moment it was loaded in the view.
|
||||
if (data.message === 'htmlIsSet' && data.hash === htmlHash) {
|
||||
setLoadedHtmlHash(data.hash);
|
||||
}
|
||||
}
|
||||
|
||||
frameWindow.addEventListener('message', onMessage);
|
||||
|
||||
return () => {
|
||||
frameWindow.removeEventListener('message', onMessage);
|
||||
};
|
||||
}, [frameWindow, htmlHash]);
|
||||
|
||||
useEffect(() => {
|
||||
console.info('useHtmlLoader: isReady', isReady);
|
||||
|
||||
if (!isReady) return;
|
||||
|
||||
console.info('useHtmlLoader: setHtml', htmlHash, html);
|
||||
|
||||
postMessage('setHtml', {
|
||||
hash: htmlHash,
|
||||
html: html,
|
||||
});
|
||||
}, [html, htmlHash, isReady]);
|
||||
|
||||
return loadedHtmlHash;
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export default function(postMessage: Function, isReady: boolean, scripts: string[], cssFilePath: string) {
|
||||
useEffect(() => {
|
||||
if (!isReady) return;
|
||||
postMessage('setScripts', { scripts: scripts });
|
||||
}, [scripts, isReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isReady || !cssFilePath) return;
|
||||
postMessage('setScript', { script: cssFilePath, key: 'themeCss' });
|
||||
}, [isReady, cssFilePath]);
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export default function(frameWindow: any, onSubmit: Function, onDismiss: Function, loadedHtmlHash: string) {
|
||||
useEffect(() => {
|
||||
if (!frameWindow) return () => {};
|
||||
|
||||
function onFormSubmit(event: any) {
|
||||
event.preventDefault();
|
||||
if (onSubmit) onSubmit();
|
||||
}
|
||||
|
||||
function onKeyDown(event: any) {
|
||||
if (event.key === 'Escape') {
|
||||
if (onDismiss) onDismiss();
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
if (onSubmit) onSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
frameWindow.document.addEventListener('submit', onFormSubmit);
|
||||
frameWindow.document.addEventListener('keydown', onKeyDown);
|
||||
|
||||
return () => {
|
||||
if (frameWindow) frameWindow.document.removeEventListener('submit', onFormSubmit);
|
||||
if (frameWindow) frameWindow.document.removeEventListener('keydown', onKeyDown);
|
||||
};
|
||||
}, [frameWindow, loadedHtmlHash, onSubmit, onDismiss]);
|
||||
}
|
@ -9,7 +9,10 @@ export default function useViewIsReady(viewRef: any) {
|
||||
const [iframeContentReady, setIFrameContentReady] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
console.debug('useViewIsReady ============== Setup Listeners');
|
||||
|
||||
function onIFrameReady() {
|
||||
console.debug('useViewIsReady: onIFrameReady');
|
||||
setIFrameReady(true);
|
||||
}
|
||||
|
||||
@ -18,6 +21,8 @@ export default function useViewIsReady(viewRef: any) {
|
||||
|
||||
if (!data || data.target !== 'UserWebview') return;
|
||||
|
||||
console.debug('useViewIsReady: message', data);
|
||||
|
||||
if (data.message === 'ready') {
|
||||
setIFrameContentReady(true);
|
||||
}
|
||||
@ -25,6 +30,8 @@ export default function useViewIsReady(viewRef: any) {
|
||||
|
||||
const iframeDocument = viewRef.current.contentWindow.document;
|
||||
|
||||
console.debug('useViewIsReady readyState', iframeDocument.readyState);
|
||||
|
||||
if (iframeDocument.readyState === 'complete') {
|
||||
onIFrameReady();
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export default function(frameWindow: any, onMessage: Function, pluginId: string, viewId: string) {
|
||||
useEffect(() => {
|
||||
if (!frameWindow) return () => {};
|
||||
|
||||
function onMessage(event: any) {
|
||||
if (!event.data || event.data.target !== 'plugin') return;
|
||||
onMessage({
|
||||
pluginId: pluginId,
|
||||
viewId: viewId,
|
||||
message: event.data.message,
|
||||
});
|
||||
}
|
||||
|
||||
frameWindow.addEventListener('message', onMessage);
|
||||
|
||||
return () => {
|
||||
frameWindow.removeEventListener('message', onMessage);
|
||||
};
|
||||
}, [onMessage, pluginId, viewId]);
|
||||
}
|
@ -4,12 +4,33 @@ import WebviewController, { ContainerType } from '../WebviewController';
|
||||
import { ButtonSpec, ViewHandle, DialogResult } from './types';
|
||||
|
||||
/**
|
||||
* Allows creating and managing dialogs. A dialog is modal window that contains a webview and a row of buttons. You can update the update the webview using the `setHtml` method.
|
||||
* Dialogs are hidden by default and you need to call `open()` to open them. Once the user clicks on a button, the `open` call will return an object indicating what button was clicked
|
||||
* on. If your HTML content included one or more form, a `formData` object will also be included with the key/value for each form.
|
||||
* There is currently no "close" method since the dialog should be thought as a modal one and thus can only be closed by clicking on one of the buttons.
|
||||
* Allows creating and managing dialogs. A dialog is modal window that
|
||||
* contains a webview and a row of buttons. You can update the update the
|
||||
* webview using the `setHtml` method. Dialogs are hidden by default and
|
||||
* you need to call `open()` to open them. Once the user clicks on a
|
||||
* button, the `open` call will return an object indicating what button was
|
||||
* clicked on.
|
||||
*
|
||||
* [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/dialog)
|
||||
* ## Retrieving form values
|
||||
*
|
||||
* If your HTML content included one or more forms, a `formData` object
|
||||
* will also be included with the key/value for each form.
|
||||
*
|
||||
* ## Special button IDs
|
||||
*
|
||||
* The following buttons IDs have a special meaning:
|
||||
*
|
||||
* - `ok`, `yes`, `submit`, `confirm`: They are considered "submit" buttons
|
||||
* - `cancel`, `no`, `reject`: They are considered "dismiss" buttons
|
||||
*
|
||||
* This information is used by the application to determine what action
|
||||
* should be done when the user presses "Enter" or "Escape" within the
|
||||
* dialog. If they press "Enter", the first "submit" button will be
|
||||
* automatically clicked. If they press "Escape" the first "dismiss" button
|
||||
* will be automatically clicked.
|
||||
*
|
||||
* [View the demo
|
||||
* plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/dialog)
|
||||
*/
|
||||
export default class JoplinViewsDialogs {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user