Work on binding drawer typing.
This commit is contained in:
parent
2fdf2289ed
commit
0b909b434a
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let tooltip = ""
|
export let tooltip = ""
|
||||||
export let muted
|
export let muted = undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TooltipWrapper {tooltip} {size}>
|
<TooltipWrapper {tooltip} {size}>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { Label } from "@budibase/bbui"
|
import { Label } from "@budibase/bbui"
|
||||||
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
import { onMount, createEventDispatcher, onDestroy } from "svelte"
|
||||||
import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates"
|
import { FIND_ANY_HBS_REGEX } from "@budibase/string-templates"
|
||||||
|
@ -12,7 +12,6 @@
|
||||||
completionStatus,
|
completionStatus,
|
||||||
} from "@codemirror/autocomplete"
|
} from "@codemirror/autocomplete"
|
||||||
import {
|
import {
|
||||||
EditorView,
|
|
||||||
lineNumbers,
|
lineNumbers,
|
||||||
keymap,
|
keymap,
|
||||||
highlightSpecialChars,
|
highlightSpecialChars,
|
||||||
|
@ -25,6 +24,7 @@
|
||||||
MatchDecorator,
|
MatchDecorator,
|
||||||
ViewPlugin,
|
ViewPlugin,
|
||||||
Decoration,
|
Decoration,
|
||||||
|
EditorView,
|
||||||
} from "@codemirror/view"
|
} from "@codemirror/view"
|
||||||
import {
|
import {
|
||||||
bracketMatching,
|
bracketMatching,
|
||||||
|
@ -44,12 +44,14 @@
|
||||||
import { javascript } from "@codemirror/lang-javascript"
|
import { javascript } from "@codemirror/lang-javascript"
|
||||||
import { EditorModes } from "./"
|
import { EditorModes } from "./"
|
||||||
import { themeStore } from "@/stores/portal"
|
import { themeStore } from "@/stores/portal"
|
||||||
|
import type { EditorMode } from "@budibase/types"
|
||||||
|
|
||||||
export let label
|
export let label: string | undefined = undefined
|
||||||
export let completions = []
|
// TODO: work out what best type fits this
|
||||||
export let mode = EditorModes.Handlebars
|
export let completions: any[] = []
|
||||||
export let value = ""
|
export let mode: EditorMode = EditorModes.Handlebars
|
||||||
export let placeholder = null
|
export let value: string | null = ""
|
||||||
|
export let placeholder: string | null = null
|
||||||
export let autocompleteEnabled = true
|
export let autocompleteEnabled = true
|
||||||
export let autofocus = false
|
export let autofocus = false
|
||||||
export let jsBindingWrapping = true
|
export let jsBindingWrapping = true
|
||||||
|
@ -58,8 +60,8 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
let textarea
|
let textarea: HTMLDivElement | undefined
|
||||||
let editor
|
let editor: EditorView | undefined
|
||||||
let mounted = false
|
let mounted = false
|
||||||
let isEditorInitialised = false
|
let isEditorInitialised = false
|
||||||
let queuedRefresh = false
|
let queuedRefresh = false
|
||||||
|
@ -71,7 +73,7 @@
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (autofocus && isEditorInitialised) {
|
if (autofocus && isEditorInitialised) {
|
||||||
editor.focus()
|
editor?.focus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +90,7 @@
|
||||||
isDark = !currentTheme.includes("light")
|
isDark = !currentTheme.includes("light")
|
||||||
|
|
||||||
// Issue theme compartment update
|
// Issue theme compartment update
|
||||||
editor.dispatch({
|
editor?.dispatch({
|
||||||
effects: themeConfig.reconfigure([...(isDark ? [oneDark] : [])]),
|
effects: themeConfig.reconfigure([...(isDark ? [oneDark] : [])]),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -100,17 +102,24 @@
|
||||||
/**
|
/**
|
||||||
* Will refresh the editor contents only after
|
* Will refresh the editor contents only after
|
||||||
* it has been fully initialised
|
* it has been fully initialised
|
||||||
* @param value {string} the editor value
|
|
||||||
*/
|
*/
|
||||||
const refresh = (value, initialised, mounted) => {
|
const refresh = (
|
||||||
|
value: string | null,
|
||||||
|
initialised?: boolean,
|
||||||
|
mounted?: boolean
|
||||||
|
) => {
|
||||||
if (!initialised || !mounted) {
|
if (!initialised || !mounted) {
|
||||||
queuedRefresh = true
|
queuedRefresh = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editor.state.doc.toString() !== value || queuedRefresh) {
|
if (
|
||||||
|
editor &&
|
||||||
|
value &&
|
||||||
|
(editor.state.doc.toString() !== value || queuedRefresh)
|
||||||
|
) {
|
||||||
editor.dispatch({
|
editor.dispatch({
|
||||||
changes: { from: 0, to: editor.state.doc.length, insert: value },
|
changes: { from: 0, to: editor.state.doc.length, insert: value! },
|
||||||
})
|
})
|
||||||
queuedRefresh = false
|
queuedRefresh = false
|
||||||
}
|
}
|
||||||
|
@ -118,17 +127,22 @@
|
||||||
|
|
||||||
// Export a function to expose caret position
|
// Export a function to expose caret position
|
||||||
export const getCaretPosition = () => {
|
export const getCaretPosition = () => {
|
||||||
const selection_range = editor.state.selection.ranges[0]
|
const selection_range = editor?.state.selection.ranges[0]
|
||||||
return {
|
return {
|
||||||
start: selection_range.from,
|
start: selection_range?.from,
|
||||||
end: selection_range.to,
|
end: selection_range?.to,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const insertAtPos = opts => {
|
export const insertAtPos = (opts: {
|
||||||
|
start: number
|
||||||
|
end?: number
|
||||||
|
value: string
|
||||||
|
cursor: { anchor: number }
|
||||||
|
}) => {
|
||||||
// Updating the value inside.
|
// Updating the value inside.
|
||||||
// Retain focus
|
// Retain focus
|
||||||
editor.dispatch({
|
editor?.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from: opts.start || editor.state.doc.length,
|
from: opts.start || editor.state.doc.length,
|
||||||
to: opts.end || editor.state.doc.length,
|
to: opts.end || editor.state.doc.length,
|
||||||
|
@ -192,7 +206,7 @@
|
||||||
|
|
||||||
const indentWithTabCustom = {
|
const indentWithTabCustom = {
|
||||||
key: "Tab",
|
key: "Tab",
|
||||||
run: view => {
|
run: (view: EditorView) => {
|
||||||
if (completionStatus(view.state) === "active") {
|
if (completionStatus(view.state) === "active") {
|
||||||
acceptCompletion(view)
|
acceptCompletion(view)
|
||||||
return true
|
return true
|
||||||
|
@ -200,7 +214,7 @@
|
||||||
indentMore(view)
|
indentMore(view)
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
shift: view => {
|
shift: (view: EditorView) => {
|
||||||
indentLess(view)
|
indentLess(view)
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
@ -232,7 +246,8 @@
|
||||||
|
|
||||||
// None of this is reactive, but it never has been, so we just assume most
|
// None of this is reactive, but it never has been, so we just assume most
|
||||||
// config flags aren't changed at runtime
|
// config flags aren't changed at runtime
|
||||||
const buildExtensions = base => {
|
// TODO: work out type for base
|
||||||
|
const buildExtensions = (base: any[]) => {
|
||||||
let complete = [...base]
|
let complete = [...base]
|
||||||
|
|
||||||
if (autocompleteEnabled) {
|
if (autocompleteEnabled) {
|
||||||
|
@ -242,7 +257,7 @@
|
||||||
closeOnBlur: true,
|
closeOnBlur: true,
|
||||||
icons: false,
|
icons: false,
|
||||||
optionClass: completion =>
|
optionClass: completion =>
|
||||||
completion.simple
|
"simple" in completion && completion.simple
|
||||||
? "autocomplete-option-simple"
|
? "autocomplete-option-simple"
|
||||||
: "autocomplete-option",
|
: "autocomplete-option",
|
||||||
})
|
})
|
||||||
|
@ -308,7 +323,7 @@
|
||||||
keymap.of(buildKeymap()),
|
keymap.of(buildKeymap()),
|
||||||
EditorView.domEventHandlers({
|
EditorView.domEventHandlers({
|
||||||
blur: () => {
|
blur: () => {
|
||||||
dispatch("blur", editor.state.doc.toString())
|
dispatch("blur", editor?.state.doc.toString())
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
EditorView.updateListener.of(v => {
|
EditorView.updateListener.of(v => {
|
||||||
|
@ -347,7 +362,7 @@
|
||||||
|
|
||||||
{#if label}
|
{#if label}
|
||||||
<div>
|
<div>
|
||||||
<Label small>{label}</Label>
|
<Label size="S">{label}</Label>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
|
@ -31,20 +31,21 @@
|
||||||
import { capitalise } from "@/helpers"
|
import { capitalise } from "@/helpers"
|
||||||
import { Utils, JsonFormatter } from "@budibase/frontend-core"
|
import { Utils, JsonFormatter } from "@budibase/frontend-core"
|
||||||
import { licensing } from "@/stores/portal"
|
import { licensing } from "@/stores/portal"
|
||||||
import {
|
import { BindingMode, EditorMode, SidePanel } from "@budibase/types"
|
||||||
|
import type {
|
||||||
EnrichedBinding,
|
EnrichedBinding,
|
||||||
BindingMode,
|
|
||||||
SidePanel,
|
|
||||||
BindingCompletion,
|
BindingCompletion,
|
||||||
Snippet,
|
Snippet,
|
||||||
Helper,
|
Helper,
|
||||||
|
CaretPositionFn,
|
||||||
|
InsertAtPositionFn,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { CompletionContext } from "@codemirror/autocomplete"
|
import type { CompletionContext } from "@codemirror/autocomplete"
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
export let bindings: EnrichedBinding[] = []
|
export let bindings: EnrichedBinding[] = []
|
||||||
export let value = ""
|
export let value: string = ""
|
||||||
export let allowHBS = true
|
export let allowHBS = true
|
||||||
export let allowJS = false
|
export let allowJS = false
|
||||||
export let allowHelpers = true
|
export let allowHelpers = true
|
||||||
|
@ -58,14 +59,10 @@
|
||||||
let mode: BindingMode | null
|
let mode: BindingMode | null
|
||||||
let sidePanel: SidePanel | null
|
let sidePanel: SidePanel | null
|
||||||
let initialValueJS = value?.startsWith?.("{{ js ")
|
let initialValueJS = value?.startsWith?.("{{ js ")
|
||||||
let jsValue = initialValueJS ? value : null
|
let jsValue: string | null = initialValueJS ? value : null
|
||||||
let hbsValue = initialValueJS ? null : value
|
let hbsValue: string | null = initialValueJS ? null : value
|
||||||
let getCaretPosition: () => { start: number; end: number } | undefined
|
let getCaretPosition: CaretPositionFn | undefined
|
||||||
let insertAtPos: (_: {
|
let insertAtPos: InsertAtPositionFn | undefined
|
||||||
start: number
|
|
||||||
end: number
|
|
||||||
value: string
|
|
||||||
}) => void | undefined
|
|
||||||
let targetMode: BindingMode | null = null
|
let targetMode: BindingMode | null = null
|
||||||
let expressionResult: string | undefined | null
|
let expressionResult: string | undefined | null
|
||||||
let expressionError: string | undefined | null
|
let expressionError: string | undefined | null
|
||||||
|
@ -83,7 +80,9 @@
|
||||||
$: usingJS = mode === BindingMode.JavaScript
|
$: usingJS = mode === BindingMode.JavaScript
|
||||||
$: editorMode =
|
$: editorMode =
|
||||||
mode === BindingMode.JavaScript ? EditorModes.JS : EditorModes.Handlebars
|
mode === BindingMode.JavaScript ? EditorModes.JS : EditorModes.Handlebars
|
||||||
$: editorValue = editorMode === EditorModes.JS ? jsValue : hbsValue
|
$: editorValue = (editorMode === EditorModes.JS ? jsValue : hbsValue) as
|
||||||
|
| string
|
||||||
|
| null
|
||||||
$: runtimeExpression = readableToRuntimeBinding(enrichedBindings, value)
|
$: runtimeExpression = readableToRuntimeBinding(enrichedBindings, value)
|
||||||
$: requestEval(runtimeExpression, context, snippets)
|
$: requestEval(runtimeExpression, context, snippets)
|
||||||
$: bindingCompletions = bindingsToCompletions(enrichedBindings, editorMode)
|
$: bindingCompletions = bindingsToCompletions(enrichedBindings, editorMode)
|
||||||
|
@ -300,6 +299,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const addSnippet = (snippet: Snippet) =>
|
||||||
|
bindingHelpers.onSelectSnippet(snippet)
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Set the initial mode appropriately
|
// Set the initial mode appropriately
|
||||||
const initialValueMode = initialValueJS
|
const initialValueMode = initialValueJS
|
||||||
|
@ -365,7 +367,7 @@
|
||||||
{:else if mode === BindingMode.JavaScript}
|
{:else if mode === BindingMode.JavaScript}
|
||||||
{#key jsCompletions}
|
{#key jsCompletions}
|
||||||
<CodeEditor
|
<CodeEditor
|
||||||
value={decodeJSBinding(jsValue)}
|
value={jsValue ? decodeJSBinding(jsValue) : jsValue}
|
||||||
on:change={onChangeJSValue}
|
on:change={onChangeJSValue}
|
||||||
completions={jsCompletions}
|
completions={jsCompletions}
|
||||||
mode={EditorModes.JS}
|
mode={EditorModes.JS}
|
||||||
|
@ -419,13 +421,10 @@
|
||||||
{expressionResult}
|
{expressionResult}
|
||||||
{expressionError}
|
{expressionError}
|
||||||
{evaluating}
|
{evaluating}
|
||||||
expression={editorValue}
|
expression={editorValue ? editorValue : ""}
|
||||||
/>
|
/>
|
||||||
{:else if sidePanel === SidePanel.Snippets}
|
{:else if sidePanel === SidePanel.Snippets}
|
||||||
<SnippetSidePanel
|
<SnippetSidePanel {addSnippet} {snippets} />
|
||||||
addSnippet={snippet => bindingHelpers.onSelectSnippet(snippet)}
|
|
||||||
{snippets}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,3 +15,12 @@ export enum BindingMode {
|
||||||
Text = "Text",
|
Text = "Text",
|
||||||
JavaScript = "JavaScript",
|
JavaScript = "JavaScript",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CaretPositionFn = () => { start: number; end: number }
|
||||||
|
|
||||||
|
export type InsertAtPositionFn = (_: {
|
||||||
|
start: number
|
||||||
|
end?: number
|
||||||
|
value: string
|
||||||
|
cursor?: { anchor: number }
|
||||||
|
}) => void
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
type EditorMode =
|
interface JSEditorMode {
|
||||||
| {
|
name: "javascript"
|
||||||
key: "JS"
|
json: boolean
|
||||||
name: "javascript"
|
match: RegExp
|
||||||
json: boolean
|
}
|
||||||
match: RegExp
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
key: "Handlebars"
|
|
||||||
name: "handlebars"
|
|
||||||
base: "text/html"
|
|
||||||
match: RegExp
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
key: "Text"
|
|
||||||
name: "text/html"
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EditorModesMap = { [M in EditorMode as M["key"]]: Omit<M, "key"> }
|
interface HBSEditorMode {
|
||||||
|
name: "handlebars"
|
||||||
|
base: "text/html"
|
||||||
|
match: RegExp
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HTMLEditorMode {
|
||||||
|
name: "text/html"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EditorMode = JSEditorMode | HBSEditorMode | HTMLEditorMode
|
||||||
|
|
||||||
|
type EditorModeMapBase =
|
||||||
|
| (JSEditorMode & { key: "JS" })
|
||||||
|
| (HBSEditorMode & { key: "Handlebars" })
|
||||||
|
| (HTMLEditorMode & { key: "Text" })
|
||||||
|
|
||||||
|
export type EditorModesMap = {
|
||||||
|
[M in EditorModeMapBase as M["key"]]: Omit<M, "key">
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue