mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Refactor: Suggestion plugin for slate (#19825)
This commit is contained in:
parent
3119f35715
commit
80a6853fd4
@ -15,8 +15,6 @@ import IndentationPlugin from './slate-plugins/indentation';
|
|||||||
import ClipboardPlugin from './slate-plugins/clipboard';
|
import ClipboardPlugin from './slate-plugins/clipboard';
|
||||||
import RunnerPlugin from './slate-plugins/runner';
|
import RunnerPlugin from './slate-plugins/runner';
|
||||||
import SuggestionsPlugin, { SuggestionsState } from './slate-plugins/suggestions';
|
import SuggestionsPlugin, { SuggestionsState } from './slate-plugins/suggestions';
|
||||||
import { Typeahead } from './Typeahead';
|
|
||||||
|
|
||||||
import { makeValue, SCHEMA } from '@grafana/ui';
|
import { makeValue, SCHEMA } from '@grafana/ui';
|
||||||
|
|
||||||
export interface QueryFieldProps {
|
export interface QueryFieldProps {
|
||||||
@ -66,8 +64,6 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
mounted: boolean;
|
mounted: boolean;
|
||||||
runOnChangeDebounced: Function;
|
runOnChangeDebounced: Function;
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
// Is required by SuggestionsPlugin
|
|
||||||
typeaheadRef: Typeahead;
|
|
||||||
lastExecutedValue: Value | null = null;
|
lastExecutedValue: Value | null = null;
|
||||||
|
|
||||||
constructor(props: QueryFieldProps, context: Context<any>) {
|
constructor(props: QueryFieldProps, context: Context<any>) {
|
||||||
@ -80,7 +76,7 @@ export class QueryField extends React.PureComponent<QueryFieldProps, QueryFieldS
|
|||||||
// Base plugins
|
// Base plugins
|
||||||
this.plugins = [
|
this.plugins = [
|
||||||
NewlinePlugin(),
|
NewlinePlugin(),
|
||||||
SuggestionsPlugin({ onTypeahead, cleanText, portalOrigin, onWillApplySuggestion, component: this }),
|
SuggestionsPlugin({ onTypeahead, cleanText, portalOrigin, onWillApplySuggestion }),
|
||||||
ClearPlugin(),
|
ClearPlugin(),
|
||||||
RunnerPlugin({ handler: this.runOnChangeAndRunQuery }),
|
RunnerPlugin({ handler: this.runOnChangeAndRunQuery }),
|
||||||
SelectionShortcutsPlugin(),
|
SelectionShortcutsPlugin(),
|
||||||
|
@ -6,7 +6,7 @@ import { Editor as CoreEditor } from 'slate';
|
|||||||
import { Plugin as SlatePlugin } from '@grafana/slate-react';
|
import { Plugin as SlatePlugin } from '@grafana/slate-react';
|
||||||
import { TypeaheadOutput, CompletionItem, CompletionItemGroup } from 'app/types';
|
import { TypeaheadOutput, CompletionItem, CompletionItemGroup } from 'app/types';
|
||||||
|
|
||||||
import { QueryField, TypeaheadInput } from '../QueryField';
|
import { TypeaheadInput } from '../QueryField';
|
||||||
import TOKEN_MARK from '@grafana/ui/src/slate-plugins/slate-prism/TOKEN_MARK';
|
import TOKEN_MARK from '@grafana/ui/src/slate-plugins/slate-prism/TOKEN_MARK';
|
||||||
import { TypeaheadWithTheme, Typeahead } from '../Typeahead';
|
import { TypeaheadWithTheme, Typeahead } from '../Typeahead';
|
||||||
|
|
||||||
@ -14,6 +14,12 @@ import { makeFragment } from '@grafana/ui';
|
|||||||
|
|
||||||
export const TYPEAHEAD_DEBOUNCE = 100;
|
export const TYPEAHEAD_DEBOUNCE = 100;
|
||||||
|
|
||||||
|
// Commands added to the editor by this plugin.
|
||||||
|
interface SuggestionsPluginCommands {
|
||||||
|
selectSuggestion: (suggestion: CompletionItem) => CoreEditor;
|
||||||
|
applyTypeahead: (suggestion: CompletionItem) => CoreEditor;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SuggestionsState {
|
export interface SuggestionsState {
|
||||||
groupedItems: CompletionItemGroup[];
|
groupedItems: CompletionItemGroup[];
|
||||||
typeaheadPrefix: string;
|
typeaheadPrefix: string;
|
||||||
@ -21,28 +27,33 @@ export interface SuggestionsState {
|
|||||||
typeaheadText: string;
|
typeaheadText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default function SuggestionsPlugin({
|
||||||
|
onTypeahead,
|
||||||
|
cleanText,
|
||||||
|
onWillApplySuggestion,
|
||||||
|
portalOrigin,
|
||||||
|
}: {
|
||||||
|
onTypeahead: (typeahead: TypeaheadInput) => Promise<TypeaheadOutput>;
|
||||||
|
cleanText?: (text: string) => string;
|
||||||
|
onWillApplySuggestion?: (suggestion: string, state: SuggestionsState) => string;
|
||||||
|
portalOrigin: string;
|
||||||
|
}): SlatePlugin {
|
||||||
|
let typeaheadRef: Typeahead;
|
||||||
let state: SuggestionsState = {
|
let state: SuggestionsState = {
|
||||||
groupedItems: [],
|
groupedItems: [],
|
||||||
typeaheadPrefix: '',
|
typeaheadPrefix: '',
|
||||||
typeaheadContext: '',
|
typeaheadContext: '',
|
||||||
typeaheadText: '',
|
typeaheadText: '',
|
||||||
};
|
};
|
||||||
|
const handleTypeaheadDebounced = debounce(handleTypeahead, TYPEAHEAD_DEBOUNCE);
|
||||||
|
|
||||||
|
const setState = (update: Partial<SuggestionsState>) => {
|
||||||
|
state = {
|
||||||
|
...state,
|
||||||
|
...update,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default function SuggestionsPlugin({
|
|
||||||
onTypeahead,
|
|
||||||
cleanText,
|
|
||||||
onWillApplySuggestion,
|
|
||||||
syntax,
|
|
||||||
portalOrigin,
|
|
||||||
component,
|
|
||||||
}: {
|
|
||||||
onTypeahead: (typeahead: TypeaheadInput) => Promise<TypeaheadOutput>;
|
|
||||||
cleanText?: (text: string) => string;
|
|
||||||
onWillApplySuggestion?: (suggestion: string, state: SuggestionsState) => string;
|
|
||||||
syntax?: string;
|
|
||||||
portalOrigin: string;
|
|
||||||
component: QueryField; // Need to attach typeaheadRef here
|
|
||||||
}): SlatePlugin {
|
|
||||||
return {
|
return {
|
||||||
onBlur: (event, editor, next) => {
|
onBlur: (event, editor, next) => {
|
||||||
state = {
|
state = {
|
||||||
@ -88,7 +99,7 @@ export default function SuggestionsPlugin({
|
|||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
if (hasSuggestions) {
|
if (hasSuggestions) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
component.typeaheadRef.moveMenuIndex(event.key === 'ArrowDown' ? 1 : -1);
|
typeaheadRef.moveMenuIndex(event.key === 'ArrowDown' ? 1 : -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,14 +109,14 @@ export default function SuggestionsPlugin({
|
|||||||
case 'Tab': {
|
case 'Tab': {
|
||||||
if (hasSuggestions) {
|
if (hasSuggestions) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return component.typeaheadRef.insertSuggestion();
|
return typeaheadRef.insertSuggestion();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
handleTypeahead(editor, onTypeahead, cleanText);
|
handleTypeaheadDebounced(editor, setState, onTypeahead, cleanText);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,7 +133,7 @@ export default function SuggestionsPlugin({
|
|||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const ed = editor.applyTypeahead(suggestion);
|
const ed = editor.applyTypeahead(suggestion);
|
||||||
handleTypeahead(editor, onTypeahead, cleanText);
|
handleTypeaheadDebounced(editor, setState, onTypeahead, cleanText);
|
||||||
return ed;
|
return ed;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -186,13 +197,12 @@ export default function SuggestionsPlugin({
|
|||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
<TypeaheadWithTheme
|
<TypeaheadWithTheme
|
||||||
menuRef={(el: Typeahead) => (component.typeaheadRef = el)}
|
menuRef={(el: Typeahead) => (typeaheadRef = el)}
|
||||||
origin={portalOrigin}
|
origin={portalOrigin}
|
||||||
prefix={state.typeaheadPrefix}
|
prefix={state.typeaheadPrefix}
|
||||||
isOpen={!!state.groupedItems.length}
|
isOpen={!!state.groupedItems.length}
|
||||||
groupedItems={state.groupedItems}
|
groupedItems={state.groupedItems}
|
||||||
//@ts-ignore
|
onSelectSuggestion={(editor as CoreEditor & SuggestionsPluginCommands).selectSuggestion}
|
||||||
onSelectSuggestion={editor.selectSuggestion}
|
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -200,12 +210,12 @@ export default function SuggestionsPlugin({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTypeahead = debounce(
|
const handleTypeahead = async (
|
||||||
async (
|
|
||||||
editor: CoreEditor,
|
editor: CoreEditor,
|
||||||
|
onStateChange: (state: Partial<SuggestionsState>) => void,
|
||||||
onTypeahead?: (typeahead: TypeaheadInput) => Promise<TypeaheadOutput>,
|
onTypeahead?: (typeahead: TypeaheadInput) => Promise<TypeaheadOutput>,
|
||||||
cleanText?: (text: string) => string
|
cleanText?: (text: string) => string
|
||||||
) => {
|
): Promise<void> => {
|
||||||
if (!onTypeahead) {
|
if (!onTypeahead) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -298,16 +308,13 @@ const handleTypeahead = debounce(
|
|||||||
})
|
})
|
||||||
.filter(group => group.items && group.items.length); // Filter out empty groups
|
.filter(group => group.items && group.items.length); // Filter out empty groups
|
||||||
|
|
||||||
state = {
|
onStateChange({
|
||||||
...state,
|
|
||||||
groupedItems: filteredSuggestions,
|
groupedItems: filteredSuggestions,
|
||||||
typeaheadPrefix: prefix,
|
typeaheadPrefix: prefix,
|
||||||
typeaheadContext: context,
|
typeaheadContext: context,
|
||||||
typeaheadText: text,
|
typeaheadText: text,
|
||||||
};
|
});
|
||||||
|
|
||||||
// Bogus edit to force re-render
|
// Bogus edit to force re-render
|
||||||
return editor.blur().focus();
|
editor.blur().focus();
|
||||||
},
|
};
|
||||||
TYPEAHEAD_DEBOUNCE
|
|
||||||
);
|
|
||||||
|
Loading…
Reference in New Issue
Block a user