You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-06-15 23:00:36 +02:00
@ -21,7 +21,7 @@ import { Command } from './types';
|
|||||||
* and look at the `execute()` command.
|
* and look at the `execute()` command.
|
||||||
*/
|
*/
|
||||||
export default class JoplinCommands {
|
export default class JoplinCommands {
|
||||||
/**
|
/**
|
||||||
* <span class="platform-desktop">desktop</span> Executes the given
|
* <span class="platform-desktop">desktop</span> Executes the given
|
||||||
* command.
|
* command.
|
||||||
*
|
*
|
||||||
@ -40,8 +40,8 @@ export default class JoplinCommands {
|
|||||||
* await joplin.commands.execute('newFolder', "SOME_FOLDER_ID");
|
* await joplin.commands.execute('newFolder', "SOME_FOLDER_ID");
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
execute(commandName: string, ...args: any[]): Promise<any | void>;
|
execute(commandName: string, ...args: any[]): Promise<any | void>;
|
||||||
/**
|
/**
|
||||||
* <span class="platform-desktop">desktop</span> Registers a new command.
|
* <span class="platform-desktop">desktop</span> Registers a new command.
|
||||||
*
|
*
|
||||||
* ```typescript
|
* ```typescript
|
||||||
@ -57,5 +57,5 @@ export default class JoplinCommands {
|
|||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
register(command: Command): Promise<void>;
|
register(command: Command): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,6 @@
|
|||||||
* so for now disable filters.
|
* so for now disable filters.
|
||||||
*/
|
*/
|
||||||
export default class JoplinFilters {
|
export default class JoplinFilters {
|
||||||
on(name: string, callback: Function): Promise<void>;
|
on(name: string, callback: Function): Promise<void>;
|
||||||
off(name: string, callback: Function): Promise<void>;
|
off(name: string, callback: Function): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,6 @@ import { ExportModule, ImportModule } from './types';
|
|||||||
* You may also want to refer to the Joplin API documentation to see the list of properties for each item (note, notebook, etc.) - https://joplinapp.org/api/references/rest_api/
|
* You may also want to refer to the Joplin API documentation to see the list of properties for each item (note, notebook, etc.) - https://joplinapp.org/api/references/rest_api/
|
||||||
*/
|
*/
|
||||||
export default class JoplinInterop {
|
export default class JoplinInterop {
|
||||||
registerExportModule(module: ExportModule): Promise<void>;
|
registerExportModule(module: ExportModule): Promise<void>;
|
||||||
registerImportModule(module: ImportModule): Promise<void>;
|
registerImportModule(module: ImportModule): Promise<void>;
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ export interface ChangeEvent {
|
|||||||
*/
|
*/
|
||||||
keys: string[];
|
keys: string[];
|
||||||
}
|
}
|
||||||
export declare type ChangeHandler = (event: ChangeEvent) => void;
|
export declare type ChangeHandler = (event: ChangeEvent)=> void;
|
||||||
/**
|
/**
|
||||||
* This API allows registering new settings and setting sections, as well as getting and setting settings. Once a setting has been registered it will appear in the config screen and be editable by the user.
|
* This API allows registering new settings and setting sections, as well as getting and setting settings. Once a setting has been registered it will appear in the config screen and be editable by the user.
|
||||||
*
|
*
|
||||||
|
@ -3,7 +3,7 @@ import { Disposable } from './types';
|
|||||||
declare enum ItemChangeEventType {
|
declare enum ItemChangeEventType {
|
||||||
Create = 1,
|
Create = 1,
|
||||||
Update = 2,
|
Update = 2,
|
||||||
Delete = 3
|
Delete = 3,
|
||||||
}
|
}
|
||||||
interface ItemChangeEvent {
|
interface ItemChangeEvent {
|
||||||
id: string;
|
id: string;
|
||||||
@ -12,8 +12,8 @@ interface ItemChangeEvent {
|
|||||||
interface SyncStartEvent {
|
interface SyncStartEvent {
|
||||||
withErrors: boolean;
|
withErrors: boolean;
|
||||||
}
|
}
|
||||||
declare type ItemChangeHandler = (event: ItemChangeEvent) => void;
|
declare type ItemChangeHandler = (event: ItemChangeEvent)=> void;
|
||||||
declare type SyncStartHandler = (event: SyncStartEvent) => void;
|
declare type SyncStartHandler = (event: SyncStartEvent)=> void;
|
||||||
/**
|
/**
|
||||||
* The workspace service provides access to all the parts of Joplin that
|
* The workspace service provides access to all the parts of Joplin that
|
||||||
* are being worked on - i.e. the currently selected notes or notebooks as
|
* are being worked on - i.e. the currently selected notes or notebooks as
|
||||||
|
@ -330,57 +330,16 @@ export enum SettingItemType {
|
|||||||
export interface SettingItem {
|
export interface SettingItem {
|
||||||
value: any;
|
value: any;
|
||||||
type: SettingItemType;
|
type: SettingItemType;
|
||||||
|
|
||||||
label: string;
|
|
||||||
description?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A public setting will appear in the Configuration screen and will be
|
|
||||||
* modifiable by the user. A private setting however will not appear there,
|
|
||||||
* and can only be changed programmatically. You may use this to store some
|
|
||||||
* values that you do not want to directly expose.
|
|
||||||
*/
|
|
||||||
public: boolean;
|
public: boolean;
|
||||||
|
label: string;
|
||||||
|
|
||||||
/**
|
description?: string;
|
||||||
* You would usually set this to a section you would have created
|
|
||||||
* specifically for the plugin.
|
|
||||||
*/
|
|
||||||
section?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* To create a setting with multiple options, set this property to `true`.
|
|
||||||
* That setting will render as a dropdown list in the configuration screen.
|
|
||||||
*/
|
|
||||||
isEnum?: boolean;
|
isEnum?: boolean;
|
||||||
|
section?: string;
|
||||||
/**
|
options?: any;
|
||||||
* This property is required when `isEnum` is `true`. In which case, it
|
|
||||||
* should contain a map of value => label.
|
|
||||||
*/
|
|
||||||
options?: Record<any, any>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reserved property. Not used at the moment.
|
|
||||||
*/
|
|
||||||
appTypes?: string[];
|
appTypes?: string[];
|
||||||
|
|
||||||
/**
|
|
||||||
* Set this to `true` to store secure data, such as passwords. Any such
|
|
||||||
* setting will be stored in the system keychain if one is available.
|
|
||||||
*/
|
|
||||||
secure?: boolean;
|
secure?: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* An advanced setting will be moved under the "Advanced" button in the
|
|
||||||
* config screen.
|
|
||||||
*/
|
|
||||||
advanced?: boolean;
|
advanced?: boolean;
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the min, max and step values if you want to restrict an int setting
|
|
||||||
* to a particular range.
|
|
||||||
*/
|
|
||||||
minimum?: number;
|
minimum?: number;
|
||||||
maximum?: number;
|
maximum?: number;
|
||||||
step?: number;
|
step?: number;
|
||||||
|
@ -12,13 +12,16 @@ joplin.plugins.register({
|
|||||||
);
|
);
|
||||||
|
|
||||||
await joplin.commands.register({
|
await joplin.commands.register({
|
||||||
name: 'editor.printSomething',
|
name: 'printSomething',
|
||||||
label: 'Print some random string',
|
label: 'Print some random string',
|
||||||
execute: async () => {
|
execute: async () => {
|
||||||
alert('mathMode.printSomething not implemented by Editor yet');
|
await joplin.commands.execute('editor.execCommand', {
|
||||||
|
name: 'printSomething',
|
||||||
|
args: ['Anything']
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await joplin.views.menuItems.create('printSomethingButton', 'editor.printSomething', MenuItemLocation.Tools, { accelerator: 'Ctrl+Alt+Shift+U' });
|
await joplin.views.menuItems.create('printSomethingButton', 'printSomething', MenuItemLocation.Tools, { accelerator: 'Ctrl+Alt+Shift+U' });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -6,9 +6,9 @@ function plugin(CodeMirror) {
|
|||||||
// Once created here it can be called by any other codemirror command
|
// Once created here it can be called by any other codemirror command
|
||||||
// using cm.execCommand(stringName) or register a joplin command called 'editor.printSomething'
|
// using cm.execCommand(stringName) or register a joplin command called 'editor.printSomething'
|
||||||
// through the joplin.commands api
|
// through the joplin.commands api
|
||||||
CodeMirror.commands.printSomething = function(cm) {
|
CodeMirror.defineExtension('printSomething', function(something) {
|
||||||
console.log("Something");
|
console.log(something);
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"name": "joplin-match-highlighter",
|
"name": "joplin-match-highlighter",
|
||||||
"description": "Adds support for the Codemirror match highlighter to the Joplin CodeView editor.",
|
"description": "Adds support for the Codemirror match highlighter to the Joplin CodeView editor.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": "CalebJohn",
|
"author": "JoplinTeam",
|
||||||
"app_min_version": "1.4",
|
"app_min_version": "1.7",
|
||||||
"homepage_url": "joplinapp.org"
|
"homepage_url": "joplinapp.org"
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { useState, useEffect, useRef, forwardRef, useCallback, useImperativeHand
|
|||||||
import { EditorCommand, NoteBodyEditorProps } from '../../utils/types';
|
import { EditorCommand, NoteBodyEditorProps } from '../../utils/types';
|
||||||
import { commandAttachFileToBody, handlePasteEvent } from '../../utils/resourceHandling';
|
import { commandAttachFileToBody, handlePasteEvent } from '../../utils/resourceHandling';
|
||||||
import { ScrollOptions, ScrollOptionTypes } from '../../utils/types';
|
import { ScrollOptions, ScrollOptionTypes } from '../../utils/types';
|
||||||
|
import { CommandValue } from '../../utils/types';
|
||||||
import { useScrollHandler, usePrevious, cursorPositionToTextOffset, useRootSize } from './utils';
|
import { useScrollHandler, usePrevious, cursorPositionToTextOffset, useRootSize } from './utils';
|
||||||
import Toolbar from './Toolbar';
|
import Toolbar from './Toolbar';
|
||||||
import styles_ from './styles';
|
import styles_ from './styles';
|
||||||
@ -218,6 +219,15 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
|||||||
textCheckbox: () => addListItem('- [ ] ', _('List item')),
|
textCheckbox: () => addListItem('- [ ] ', _('List item')),
|
||||||
textHeading: () => addListItem('## ', ''),
|
textHeading: () => addListItem('## ', ''),
|
||||||
textHorizontalRule: () => addListItem('* * *'),
|
textHorizontalRule: () => addListItem('* * *'),
|
||||||
|
'editor.execCommand': (value: CommandValue) => {
|
||||||
|
if (editorRef.current[value.name]) {
|
||||||
|
if (!('args' in value)) value.args = [];
|
||||||
|
|
||||||
|
editorRef.current[value.name](...value.args);
|
||||||
|
} else {
|
||||||
|
reg.logger().warn('CodeMirror execCommand: unsupported command: ', value.name);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (commands[cmd.name]) {
|
if (commands[cmd.name]) {
|
||||||
|
@ -25,6 +25,9 @@ export default function useKeymap(CodeMirror: any) {
|
|||||||
CodeMirror.Vim.defineAction('insertListElement', CodeMirror.commands.vimInsertListElement);
|
CodeMirror.Vim.defineAction('insertListElement', CodeMirror.commands.vimInsertListElement);
|
||||||
CodeMirror.Vim.mapCommand('o', 'action', 'insertListElement', { after: true }, { context: 'normal', isEdit: true, interlaceInsertRepeat: true });
|
CodeMirror.Vim.mapCommand('o', 'action', 'insertListElement', { after: true }, { context: 'normal', isEdit: true, interlaceInsertRepeat: true });
|
||||||
}
|
}
|
||||||
|
function isEditorCommand(command: string) {
|
||||||
|
return command.startsWith('editor.');
|
||||||
|
}
|
||||||
|
|
||||||
// Converts a command of the form editor.command to just command
|
// Converts a command of the form editor.command to just command
|
||||||
function editorCommandToCodeMirror(command: String) {
|
function editorCommandToCodeMirror(command: String) {
|
||||||
@ -91,7 +94,7 @@ export default function useKeymap(CodeMirror: any) {
|
|||||||
|
|
||||||
|
|
||||||
CodeMirror.defineExtension('supportsCommand', function(cmd: EditorCommand) {
|
CodeMirror.defineExtension('supportsCommand', function(cmd: EditorCommand) {
|
||||||
return CommandService.isEditorCommand(cmd.name) && editorCommandToCodeMirror(cmd.name) in CodeMirror.commands;
|
return isEditorCommand(cmd.name) && editorCommandToCodeMirror(cmd.name) in CodeMirror.commands;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Used when an editor command is executed using the CommandService.instance().execute
|
// Used when an editor command is executed using the CommandService.instance().execute
|
||||||
|
@ -249,6 +249,12 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
|||||||
editor.insertContent(result.html);
|
editor.insertContent(result.html);
|
||||||
} else if (cmd.name === 'editor.focus') {
|
} else if (cmd.name === 'editor.focus') {
|
||||||
editor.focus();
|
editor.focus();
|
||||||
|
} else if (cmd.name === 'editor.execCommand') {
|
||||||
|
if (!('ui' in cmd.value)) cmd.value.ui = false;
|
||||||
|
if (!('value' in cmd.value)) cmd.value.value = null;
|
||||||
|
if (!('args' in cmd.value)) cmd.value.args = {};
|
||||||
|
|
||||||
|
editor.execCommand(cmd.value.name, cmd.value.ui, cmd.value.value, cmd.value.args);
|
||||||
} else if (cmd.name === 'dropItems') {
|
} else if (cmd.name === 'dropItems') {
|
||||||
if (cmd.value.type === 'notes') {
|
if (cmd.value.type === 'notes') {
|
||||||
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, cmd.value.markdownTags.join('\n'), markupRenderOptions({ bodyOnly: true }));
|
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, cmd.value.markdownTags.join('\n'), markupRenderOptions({ bodyOnly: true }));
|
||||||
|
@ -233,7 +233,6 @@ function NoteEditor(props: NoteEditorProps) {
|
|||||||
|
|
||||||
useWindowCommandHandler({
|
useWindowCommandHandler({
|
||||||
dispatch: props.dispatch,
|
dispatch: props.dispatch,
|
||||||
plugins: props.plugins,
|
|
||||||
formNote,
|
formNote,
|
||||||
setShowLocalSearch,
|
setShowLocalSearch,
|
||||||
noteSearchBarRef,
|
noteSearchBarRef,
|
||||||
|
@ -131,6 +131,9 @@ const declarations: CommandDeclaration[] = [
|
|||||||
{
|
{
|
||||||
name: 'editor.focus',
|
name: 'editor.focus',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'editor.execCommand',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default declarations;
|
export default declarations;
|
||||||
|
@ -159,3 +159,10 @@ export interface EditorCommand {
|
|||||||
name: string;
|
name: string;
|
||||||
value: any;
|
value: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CommandValue {
|
||||||
|
name: string;
|
||||||
|
args?: any; // Should be an array for CodeMirror or an object for TinyMCE
|
||||||
|
ui?: boolean; // For TinyMCE only
|
||||||
|
value?: any; // For TinyMCE only
|
||||||
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { FormNote, ScrollOptionTypes } from './types';
|
import { FormNote, ScrollOptionTypes } from './types';
|
||||||
|
import editorCommandDeclarations from '../commands/editorCommandDeclarations';
|
||||||
import CommandService, { CommandDeclaration, CommandRuntime, CommandContext } from '@joplin/lib/services/CommandService';
|
import CommandService, { CommandDeclaration, CommandRuntime, CommandContext } from '@joplin/lib/services/CommandService';
|
||||||
import time from '@joplin/lib/time';
|
import time from '@joplin/lib/time';
|
||||||
import { reg } from '@joplin/lib/registry';
|
import { reg } from '@joplin/lib/registry';
|
||||||
@ -19,7 +20,6 @@ interface HookDependencies {
|
|||||||
titleInputRef: any;
|
titleInputRef: any;
|
||||||
saveNoteAndWait: Function;
|
saveNoteAndWait: Function;
|
||||||
setFormNote: Function;
|
setFormNote: Function;
|
||||||
plugins: any;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function editorCommandRuntime(declaration: CommandDeclaration, editorRef: any, setFormNote: Function): CommandRuntime {
|
function editorCommandRuntime(declaration: CommandDeclaration, editorRef: any, setFormNote: Function): CommandRuntime {
|
||||||
@ -61,10 +61,10 @@ function editorCommandRuntime(declaration: CommandDeclaration, editorRef: any, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function useWindowCommandHandler(dependencies: HookDependencies) {
|
export default function useWindowCommandHandler(dependencies: HookDependencies) {
|
||||||
const { setShowLocalSearch, noteSearchBarRef, editorRef, titleInputRef, setFormNote, plugins } = dependencies;
|
const { setShowLocalSearch, noteSearchBarRef, editorRef, titleInputRef, setFormNote } = dependencies;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
for (const declaration of CommandService.instance().editorCommandDeclarations()) {
|
for (const declaration of editorCommandDeclarations) {
|
||||||
CommandService.instance().registerRuntime(declaration.name, editorCommandRuntime(declaration, editorRef, setFormNote));
|
CommandService.instance().registerRuntime(declaration.name, editorCommandRuntime(declaration, editorRef, setFormNote));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ export default function useWindowCommandHandler(dependencies: HookDependencies)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
for (const declaration of CommandService.instance().editorCommandDeclarations()) {
|
for (const declaration of editorCommandDeclarations) {
|
||||||
CommandService.instance().unregisterRuntime(declaration.name);
|
CommandService.instance().unregisterRuntime(declaration.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,5 +88,5 @@ export default function useWindowCommandHandler(dependencies: HookDependencies)
|
|||||||
CommandService.instance().unregisterRuntime(command.declaration.name);
|
CommandService.instance().unregisterRuntime(command.declaration.name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [editorRef, setShowLocalSearch, noteSearchBarRef, titleInputRef, plugins]);
|
}, [editorRef, setShowLocalSearch, noteSearchBarRef, titleInputRef]);
|
||||||
}
|
}
|
||||||
|
@ -309,40 +309,4 @@ export default class CommandService extends BaseService {
|
|||||||
const command = this.commandByName(commandName, { mustExist: false });
|
const command = this.commandByName(commandName, { mustExist: false });
|
||||||
return !!command;
|
return !!command;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isEditorCommand(commandName: string) {
|
|
||||||
return (commandName.indexOf('editor.') === 0 ||
|
|
||||||
// These commands are grandfathered in, but in the future
|
|
||||||
// all editor commands should start with "editor."
|
|
||||||
commandName === 'insertText' ||
|
|
||||||
commandName === 'scrollToHash' ||
|
|
||||||
commandName === 'textCopy' ||
|
|
||||||
commandName === 'textCut' ||
|
|
||||||
commandName === 'textPaste' ||
|
|
||||||
commandName === 'textSelectAll' ||
|
|
||||||
commandName === 'textBold' ||
|
|
||||||
commandName === 'textItalic' ||
|
|
||||||
commandName === 'textLink' ||
|
|
||||||
commandName === 'textCode' ||
|
|
||||||
commandName === 'attachFile' ||
|
|
||||||
commandName === 'textNumberedList' ||
|
|
||||||
commandName === 'textBulletedList' ||
|
|
||||||
commandName === 'textCheckbox' ||
|
|
||||||
commandName === 'textHeading' ||
|
|
||||||
commandName === 'textHorizontalRule' ||
|
|
||||||
commandName === 'insertDateTime' ||
|
|
||||||
commandName === 'selectedText' ||
|
|
||||||
commandName === 'replaceSelection'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public editorCommandDeclarations(): CommandDeclaration[] {
|
|
||||||
const output = [];
|
|
||||||
|
|
||||||
for (const name in this.commands_) {
|
|
||||||
if (CommandService.isEditorCommand(name)) { output.push(this.commands_[name].declaration); }
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -33,16 +33,27 @@ export default class ToolbarButtonUtils {
|
|||||||
return this.service_;
|
return this.service_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Editor commands will focus the editor after they're executed
|
||||||
private isEditorCommand(commandName: string) {
|
private isEditorCommand(commandName: string) {
|
||||||
return CommandService.isEditorCommand(commandName) && !(
|
return (commandName.indexOf('editor.') === 0 ||
|
||||||
// These commands are attached to the editor runtime,
|
// These commands are grandfathered in, but in the future
|
||||||
// but they either handle focus themselves or don't need
|
// all editor commands should start with "editor."
|
||||||
// to focus the editor
|
// WARNING: Some commands such as textLink are not defined here
|
||||||
commandName === 'textLink' ||
|
// because they are more complex and handle focus manually
|
||||||
commandName === 'insertText' ||
|
commandName === 'textCopy' ||
|
||||||
commandName === 'scrollToHash' ||
|
commandName === 'textCut' ||
|
||||||
commandName === 'selectedText' ||
|
commandName === 'textPaste' ||
|
||||||
commandName === 'replaceSelection'
|
commandName === 'textSelectAll' ||
|
||||||
|
commandName === 'textBold' ||
|
||||||
|
commandName === 'textItalic' ||
|
||||||
|
commandName === 'textCode' ||
|
||||||
|
commandName === 'attachFile' ||
|
||||||
|
commandName === 'textNumberedList' ||
|
||||||
|
commandName === 'textBulletedList' ||
|
||||||
|
commandName === 'textCheckbox' ||
|
||||||
|
commandName === 'textHeading' ||
|
||||||
|
commandName === 'textHorizontalRule' ||
|
||||||
|
commandName === 'insertDateTime'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user