diff --git a/packages/app-cli/tests/support/plugins/dialog/src/index.ts b/packages/app-cli/tests/support/plugins/dialog/src/index.ts index 6ee5b1db5..b1e6ed4a2 100644 --- a/packages/app-cli/tests/support/plugins/dialog/src/index.ts +++ b/packages/app-cli/tests/support/plugins/dialog/src/index.ts @@ -7,7 +7,7 @@ joplin.plugins.register({ const handle = await dialogs.create(); await dialogs.setHtml(handle, '

Testing dialog with default buttons

Second line

Third line

'); const result = await dialogs.open(handle); - alert('This button was clicked: ' + result); + alert('Got result: ' + JSON.stringify(result)); const handle2 = await dialogs.create(); await dialogs.setHtml(handle2, '

Testing dialog with custom buttons

Second line

Third line

'); @@ -25,7 +25,20 @@ joplin.plugins.register({ ]); const result2 = await dialogs.open(handle2); - alert('This button was clicked: ' + result2); + alert('Got result: ' + JSON.stringify(result2)); + const handle3 = await dialogs.create(); + await dialogs.setHtml(handle3, ` +

Testing dialog with form elements

+
+ Name: +
+ Email: +
+ `); + + const result3 = await dialogs.open(handle3); + alert('Got result: ' + JSON.stringify(result3)); }, + }); diff --git a/packages/app-desktop/services/plugins/UserWebview.tsx b/packages/app-desktop/services/plugins/UserWebview.tsx index 6e23c518a..7f0c22891 100644 --- a/packages/app-desktop/services/plugins/UserWebview.tsx +++ b/packages/app-desktop/services/plugins/UserWebview.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useRef, useEffect, useState } from 'react'; +import { useRef, useEffect, useState, useImperativeHandle, forwardRef } from 'react'; import useViewIsReady from './hooks/useViewIsReady'; import useThemeCss from './hooks/useThemeCss'; const styled = require('styled-components').default; @@ -32,7 +32,29 @@ const StyledFrame = styled.iframe` border-bottom: ${(props: Props) => props.borderBottom ? `1px solid ${props.theme.dividerColor}` : 'none'}; `; -export default function UserWebview(props: Props) { +function serializeForm(form: any) { + const output: any = {}; + const formData = new FormData(form); + for (const key of formData.keys()) { + output[key] = formData.get(key); + } + return output; +} + +function serializeForms(document: any) { + const forms = document.getElementsByTagName('form'); + const output: any = {}; + let untitledIndex = 0; + + for (const form of forms) { + const name = `${form.getAttribute('name')}` || (`form${untitledIndex++}`); + output[name] = serializeForm(form); + } + + return output; +} + +function UserWebview(props: Props, ref: any) { const minWidth = props.minWidth ? props.minWidth : 200; const minHeight = props.minHeight ? props.minHeight : 20; @@ -41,6 +63,18 @@ export default function UserWebview(props: Props) { const cssFilePath = useThemeCss({ pluginId: props.pluginId, themeId: props.themeId }); const [contentSize, setContentSize] = useState({ 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; return viewRef.current.contentWindow; @@ -143,3 +177,5 @@ export default function UserWebview(props: Props) { borderBottom={props.borderBottom} >; } + +export default forwardRef(UserWebview); diff --git a/packages/app-desktop/services/plugins/UserWebviewDialog.tsx b/packages/app-desktop/services/plugins/UserWebviewDialog.tsx index c6882e6c3..00a07d44c 100644 --- a/packages/app-desktop/services/plugins/UserWebviewDialog.tsx +++ b/packages/app-desktop/services/plugins/UserWebviewDialog.tsx @@ -1,7 +1,8 @@ -import { ButtonSpec } from '@joplin/lib/services/plugins/api/types'; +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 +50,8 @@ function defaultButtons(): ButtonSpec[] { } export default function UserWebviewDialog(props: Props) { + const webviewRef = useRef(null); + function viewController(): WebviewController { return PluginService.instance().pluginById(props.pluginId).viewController(props.viewId) as WebviewController; } @@ -57,7 +60,10 @@ export default function UserWebviewDialog(props: Props) { return { ...b, onClick: () => { - viewController().closeWithResponse(b.id); + const response: DialogResult = { id: b.id }; + const formData = webviewRef.current.formData(); + if (formData && Object.keys(formData).length) response.formData = formData; + viewController().closeWithResponse(response); }, }; }); @@ -67,6 +73,7 @@ export default function UserWebviewDialog(props: Props) { /dev/null 2>&1 && pwd )" +PLUGIN_PATH="$SCRIPT_DIR/../app-cli/tests/support/plugins/dialog" npm i --prefix="$PLUGIN_PATH" && npm start -- --dev-plugins "$PLUGIN_PATH" \ No newline at end of file diff --git a/packages/lib/services/plugins/WebviewController.ts b/packages/lib/services/plugins/WebviewController.ts index 30de94011..ac85676f9 100644 --- a/packages/lib/services/plugins/WebviewController.ts +++ b/packages/lib/services/plugins/WebviewController.ts @@ -1,6 +1,6 @@ import ViewController from './ViewController'; import shim from '../../shim'; -import { ButtonId, ButtonSpec } from './api/types'; +import { ButtonSpec, DialogResult } from './api/types'; const { toSystemSlashes } = require('../../path-utils'); export enum ContainerType { @@ -99,7 +99,7 @@ export default class WebviewController extends ViewController { // Specific to dialogs // --------------------------------------------- - public async open(): Promise { + public async open(): Promise { this.setStoreProp('opened', true); return new Promise((resolve: Function, reject: Function) => { @@ -111,7 +111,7 @@ export default class WebviewController extends ViewController { this.setStoreProp('opened', false); } - public closeWithResponse(result: ButtonId) { + public closeWithResponse(result: DialogResult) { this.close(); this.closeResponse_.resolve(result); } diff --git a/packages/lib/services/plugins/api/JoplinViewsDialogs.ts b/packages/lib/services/plugins/api/JoplinViewsDialogs.ts index 8abda423c..1fbabb9d8 100644 --- a/packages/lib/services/plugins/api/JoplinViewsDialogs.ts +++ b/packages/lib/services/plugins/api/JoplinViewsDialogs.ts @@ -1,12 +1,13 @@ import Plugin from '../Plugin'; import createViewHandle from '../utils/createViewHandle'; import WebviewController, { ContainerType } from '../WebviewController'; -import { ButtonSpec, ViewHandle, ButtonId } from './types'; +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 and provide the button ID that was - * clicked on. 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. + * 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. * * [View the demo plugin](https://github.com/laurent22/joplin/tree/dev/packages/app-cli/tests/support/plugins/dialog) */ @@ -61,7 +62,7 @@ export default class JoplinViewsDialogs { /** * Opens the dialog */ - async open(handle: ViewHandle): Promise { + async open(handle: ViewHandle): Promise { return this.controller(handle).open(); } diff --git a/packages/lib/services/plugins/api/types.ts b/packages/lib/services/plugins/api/types.ts index e4da32369..d5f0dad55 100644 --- a/packages/lib/services/plugins/api/types.ts +++ b/packages/lib/services/plugins/api/types.ts @@ -261,6 +261,11 @@ export interface EditorCommand { value?: any; } +export interface DialogResult { + id: ButtonId; + formData?: any; +} + // ================================================================= // Settings types // =================================================================