You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-06-24 23:26:50 +02:00
Plugins: Allow retrieving form values from dialogs
This commit is contained in:
@ -7,7 +7,7 @@ joplin.plugins.register({
|
|||||||
const handle = await dialogs.create();
|
const handle = await dialogs.create();
|
||||||
await dialogs.setHtml(handle, '<p>Testing dialog with default buttons</p><p>Second line</p><p>Third line</p>');
|
await dialogs.setHtml(handle, '<p>Testing dialog with default buttons</p><p>Second line</p><p>Third line</p>');
|
||||||
const result = await dialogs.open(handle);
|
const result = await dialogs.open(handle);
|
||||||
alert('This button was clicked: ' + result);
|
alert('Got result: ' + JSON.stringify(result));
|
||||||
|
|
||||||
const handle2 = await dialogs.create();
|
const handle2 = await dialogs.create();
|
||||||
await dialogs.setHtml(handle2, '<p>Testing dialog with custom buttons</p><p>Second line</p><p>Third line</p>');
|
await dialogs.setHtml(handle2, '<p>Testing dialog with custom buttons</p><p>Second line</p><p>Third line</p>');
|
||||||
@ -25,7 +25,20 @@ joplin.plugins.register({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const result2 = await dialogs.open(handle2);
|
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, `
|
||||||
|
<p>Testing dialog with form elements</p>
|
||||||
|
<form name="user">
|
||||||
|
Name: <input type="text" name="name"/>
|
||||||
|
<br/>
|
||||||
|
Email: <input type="text" name="email"/>
|
||||||
|
</form>
|
||||||
|
`);
|
||||||
|
|
||||||
|
const result3 = await dialogs.open(handle3);
|
||||||
|
alert('Got result: ' + JSON.stringify(result3));
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
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 useViewIsReady from './hooks/useViewIsReady';
|
||||||
import useThemeCss from './hooks/useThemeCss';
|
import useThemeCss from './hooks/useThemeCss';
|
||||||
const styled = require('styled-components').default;
|
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'};
|
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 minWidth = props.minWidth ? props.minWidth : 200;
|
||||||
const minHeight = props.minHeight ? props.minHeight : 20;
|
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 cssFilePath = useThemeCss({ pluginId: props.pluginId, themeId: props.themeId });
|
||||||
const [contentSize, setContentSize] = useState<Size>({ width: minWidth, height: minHeight });
|
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() {
|
function frameWindow() {
|
||||||
if (!viewRef.current) return null;
|
if (!viewRef.current) return null;
|
||||||
return viewRef.current.contentWindow;
|
return viewRef.current.contentWindow;
|
||||||
@ -143,3 +177,5 @@ export default function UserWebview(props: Props) {
|
|||||||
borderBottom={props.borderBottom}
|
borderBottom={props.borderBottom}
|
||||||
></StyledFrame>;
|
></StyledFrame>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default forwardRef(UserWebview);
|
||||||
|
@ -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 PluginService from '@joplin/lib/services/plugins/PluginService';
|
||||||
import WebviewController from '@joplin/lib/services/plugins/WebviewController';
|
import WebviewController from '@joplin/lib/services/plugins/WebviewController';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { useRef } from 'react';
|
||||||
import UserWebview, { Props as UserWebviewProps } from './UserWebview';
|
import UserWebview, { Props as UserWebviewProps } from './UserWebview';
|
||||||
import UserWebviewDialogButtonBar from './UserWebviewDialogButtonBar';
|
import UserWebviewDialogButtonBar from './UserWebviewDialogButtonBar';
|
||||||
const styled = require('styled-components').default;
|
const styled = require('styled-components').default;
|
||||||
@ -49,6 +50,8 @@ function defaultButtons(): ButtonSpec[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function UserWebviewDialog(props: Props) {
|
export default function UserWebviewDialog(props: Props) {
|
||||||
|
const webviewRef = useRef(null);
|
||||||
|
|
||||||
function viewController(): WebviewController {
|
function viewController(): WebviewController {
|
||||||
return PluginService.instance().pluginById(props.pluginId).viewController(props.viewId) as WebviewController;
|
return PluginService.instance().pluginById(props.pluginId).viewController(props.viewId) as WebviewController;
|
||||||
}
|
}
|
||||||
@ -57,7 +60,10 @@ export default function UserWebviewDialog(props: Props) {
|
|||||||
return {
|
return {
|
||||||
...b,
|
...b,
|
||||||
onClick: () => {
|
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) {
|
|||||||
<Dialog>
|
<Dialog>
|
||||||
<UserWebViewWrapper>
|
<UserWebViewWrapper>
|
||||||
<UserWebview
|
<UserWebview
|
||||||
|
ref={webviewRef}
|
||||||
html={props.html}
|
html={props.html}
|
||||||
scripts={props.scripts}
|
scripts={props.scripts}
|
||||||
onMessage={props.onMessage}
|
onMessage={props.onMessage}
|
||||||
|
@ -3,5 +3,6 @@
|
|||||||
# This is a convenient way to build and test a plugin demo.
|
# This is a convenient way to build and test a plugin demo.
|
||||||
# It could be used to develop plugins too.
|
# It could be used to develop plugins too.
|
||||||
|
|
||||||
PLUGIN_PATH=/home/laurent/source/joplin/packages/app-cli/tests/support/plugins/content_script
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/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"
|
npm i --prefix="$PLUGIN_PATH" && npm start -- --dev-plugins "$PLUGIN_PATH"
|
@ -1,6 +1,6 @@
|
|||||||
import ViewController from './ViewController';
|
import ViewController from './ViewController';
|
||||||
import shim from '../../shim';
|
import shim from '../../shim';
|
||||||
import { ButtonId, ButtonSpec } from './api/types';
|
import { ButtonSpec, DialogResult } from './api/types';
|
||||||
const { toSystemSlashes } = require('../../path-utils');
|
const { toSystemSlashes } = require('../../path-utils');
|
||||||
|
|
||||||
export enum ContainerType {
|
export enum ContainerType {
|
||||||
@ -99,7 +99,7 @@ export default class WebviewController extends ViewController {
|
|||||||
// Specific to dialogs
|
// Specific to dialogs
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
|
|
||||||
public async open(): Promise<ButtonId> {
|
public async open(): Promise<DialogResult> {
|
||||||
this.setStoreProp('opened', true);
|
this.setStoreProp('opened', true);
|
||||||
|
|
||||||
return new Promise((resolve: Function, reject: Function) => {
|
return new Promise((resolve: Function, reject: Function) => {
|
||||||
@ -111,7 +111,7 @@ export default class WebviewController extends ViewController {
|
|||||||
this.setStoreProp('opened', false);
|
this.setStoreProp('opened', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeWithResponse(result: ButtonId) {
|
public closeWithResponse(result: DialogResult) {
|
||||||
this.close();
|
this.close();
|
||||||
this.closeResponse_.resolve(result);
|
this.closeResponse_.resolve(result);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import Plugin from '../Plugin';
|
import Plugin from '../Plugin';
|
||||||
import createViewHandle from '../utils/createViewHandle';
|
import createViewHandle from '../utils/createViewHandle';
|
||||||
import WebviewController, { ContainerType } from '../WebviewController';
|
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.
|
* 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
|
* 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
|
||||||
* 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.
|
* 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)
|
* [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
|
* Opens the dialog
|
||||||
*/
|
*/
|
||||||
async open(handle: ViewHandle): Promise<ButtonId> {
|
async open(handle: ViewHandle): Promise<DialogResult> {
|
||||||
return this.controller(handle).open();
|
return this.controller(handle).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,6 +261,11 @@ export interface EditorCommand {
|
|||||||
value?: any;
|
value?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DialogResult {
|
||||||
|
id: ButtonId;
|
||||||
|
formData?: any;
|
||||||
|
}
|
||||||
|
|
||||||
// =================================================================
|
// =================================================================
|
||||||
// Settings types
|
// Settings types
|
||||||
// =================================================================
|
// =================================================================
|
||||||
|
Reference in New Issue
Block a user