mirror of
https://github.com/frappe/books.git
synced 2024-12-22 19:09:01 +00:00
feat: add autocomplete for linklabels
- update editor styling
This commit is contained in:
parent
ec9cc7f2b4
commit
11c714a957
@ -20,6 +20,7 @@
|
||||
"test": "scripts/test.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.4.2",
|
||||
"@codemirror/lang-vue": "^0.1.1",
|
||||
"@popperjs/core": "^2.10.2",
|
||||
"better-sqlite3": "^7.5.3",
|
||||
|
@ -178,7 +178,7 @@
|
||||
>
|
||||
<div class="flex">
|
||||
<!-- Hint Section Header -->
|
||||
<div class="border-r" v-if="hint">
|
||||
<div class="border-r" v-if="hints">
|
||||
<h2 class="text-base font-semibold p-4 border-b">
|
||||
{{ t`Value Keys` }}
|
||||
</h2>
|
||||
@ -186,7 +186,7 @@
|
||||
class="overflow-auto custom-scroll p-4"
|
||||
style="max-height: 80vh; width: 25vw"
|
||||
>
|
||||
<TemplateBuilderHint :hint="hint" />
|
||||
<TemplateBuilderHint :hints="hints" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -196,11 +196,12 @@
|
||||
{{ t`Template` }}
|
||||
</h2>
|
||||
<TemplateEditor
|
||||
class="overflow-auto custom-scroll"
|
||||
style="max-height: 80vh; width: 65vw"
|
||||
v-if="!templateCollapsed && typeof doc.template === 'string'"
|
||||
v-model.lazy="doc.template"
|
||||
:disabled="!doc.isCustom"
|
||||
:hints="hints ?? undefined"
|
||||
class="overflow-auto custom-scroll"
|
||||
style="max-height: 80vh; width: 65vw"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -253,14 +254,14 @@ export default defineComponent({
|
||||
return {
|
||||
doc: null,
|
||||
showEditor: false,
|
||||
hint: null,
|
||||
hints: undefined,
|
||||
values: null,
|
||||
templateCollapsed: false,
|
||||
helpersCollapsed: true,
|
||||
displayDoc: null,
|
||||
scale: 0.65,
|
||||
} as {
|
||||
hint: null | Record<string, unknown>;
|
||||
hints?: Record<string, unknown>;
|
||||
values: null | PrintValues;
|
||||
doc: PrintTemplate | null;
|
||||
showEditor: boolean;
|
||||
@ -344,7 +345,7 @@ export default defineComponent({
|
||||
},
|
||||
async setDisplayDoc(value: string) {
|
||||
if (!value) {
|
||||
this.hint = null;
|
||||
delete this.hints;
|
||||
this.values = null;
|
||||
this.displayDoc = null;
|
||||
return;
|
||||
@ -356,7 +357,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const displayDoc = await getDocFromNameIfExistsElseNew(schemaName, value);
|
||||
this.hint = getPrintTemplatePropHints(displayDoc);
|
||||
this.hints = getPrintTemplatePropHints(displayDoc);
|
||||
this.values = await getPrintTemplatePropValues(displayDoc);
|
||||
this.displayDoc = displayDoc;
|
||||
},
|
||||
|
@ -55,7 +55,7 @@
|
||||
<div v-if="!r.collapsed && typeof r.value === 'object'">
|
||||
<TemplateBuilderHint
|
||||
:prefix="getKey(r)"
|
||||
:hint="Array.isArray(r.value) ? r.value[0] : r.value"
|
||||
:hints="Array.isArray(r.value) ? r.value[0] : r.value"
|
||||
:level="level + 1"
|
||||
/>
|
||||
</div>
|
||||
@ -74,7 +74,7 @@ export default defineComponent({
|
||||
name: 'TemplateBuilderHint',
|
||||
props: {
|
||||
prefix: { type: String, default: '' },
|
||||
hint: { type: Object, required: true },
|
||||
hints: { type: Object, required: true },
|
||||
level: { type: Number, default: 0 },
|
||||
},
|
||||
data() {
|
||||
@ -83,7 +83,7 @@ export default defineComponent({
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.rows = Object.entries(this.hint)
|
||||
this.rows = Object.entries(this.hints)
|
||||
.map(([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
|
@ -2,8 +2,13 @@
|
||||
<div ref="container" class="bg-white text-gray-900"></div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { autocompletion, CompletionContext } from '@codemirror/autocomplete';
|
||||
import { vue } from '@codemirror/lang-vue';
|
||||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import {
|
||||
HighlightStyle,
|
||||
syntaxHighlighting,
|
||||
syntaxTree,
|
||||
} from '@codemirror/language';
|
||||
import { Compartment, EditorState } from '@codemirror/state';
|
||||
import { EditorView, ViewUpdate } from '@codemirror/view';
|
||||
import { tags } from '@lezer/highlight';
|
||||
@ -24,6 +29,7 @@ export default defineComponent({
|
||||
modelModifiers: { type: Object, default: () => ({}) },
|
||||
modelValue: { type: String, required: true },
|
||||
disabled: { type: Boolean, default: false },
|
||||
hints: { type: Object },
|
||||
},
|
||||
watch: {
|
||||
disabled(value: boolean) {
|
||||
@ -44,14 +50,15 @@ export default defineComponent({
|
||||
const highlightStyle = HighlightStyle.define([
|
||||
{ tag: tags.typeName, color: uicolors.pink[600] },
|
||||
{ tag: tags.angleBracket, color: uicolors.pink[600] },
|
||||
{ tag: tags.attributeName, color: uicolors.gray[700] },
|
||||
{ tag: tags.attributeValue, color: uicolors.blue[700] },
|
||||
{ tag: tags.comment, color: uicolors.gray[500] },
|
||||
{ tag: tags.keyword, color: uicolors.blue[500] },
|
||||
{ tag: tags.variableName, color: uicolors.yellow[600] },
|
||||
{ tag: tags.string, color: uicolors.pink[700] },
|
||||
{ tag: tags.attributeName, color: uicolors.gray[600] },
|
||||
{ tag: tags.attributeValue, color: uicolors.blue[600] },
|
||||
{ tag: tags.comment, color: uicolors.gray[500], fontStyle: 'italic' },
|
||||
{ tag: tags.keyword, color: uicolors.pink[500] },
|
||||
{ tag: tags.variableName, color: uicolors.blue[700] },
|
||||
{ tag: tags.string, color: uicolors.pink[600] },
|
||||
{ tag: tags.content, color: uicolors.gray[700] },
|
||||
]);
|
||||
const completions = getCompletionsFromHints(this.hints ?? {});
|
||||
|
||||
const view = new EditorView({
|
||||
doc: this.modelValue,
|
||||
@ -62,6 +69,7 @@ export default defineComponent({
|
||||
basicSetup,
|
||||
vue(),
|
||||
syntaxHighlighting(highlightStyle),
|
||||
autocompletion({ override: [completions] }),
|
||||
],
|
||||
parent: this.container,
|
||||
});
|
||||
@ -107,8 +115,101 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function getCompletionsFromHints(hints: Record<string, unknown>) {
|
||||
const options = hintsToCompletionOptions(hints);
|
||||
return function completions(context: CompletionContext) {
|
||||
let word = context.matchBefore(/\w*/);
|
||||
if (word == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const node = syntaxTree(context.state).resolveInner(context.pos);
|
||||
const aptLocation = ['ScriptAttributeValue', 'SingleExpression'];
|
||||
|
||||
if (!aptLocation.includes(node.name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (word.from === word.to && !context.explicit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
from: word.from,
|
||||
options,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
type CompletionOption = {
|
||||
label: string;
|
||||
type: string;
|
||||
detail: string;
|
||||
};
|
||||
|
||||
function hintsToCompletionOptions(
|
||||
hints: object,
|
||||
prefix?: string
|
||||
): CompletionOption[] {
|
||||
prefix ??= '';
|
||||
const list: CompletionOption[] = [];
|
||||
|
||||
for (const [key, value] of Object.entries(hints)) {
|
||||
const option = getCompletionOption(key, value, prefix);
|
||||
if (option === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(option)) {
|
||||
list.push(...option);
|
||||
continue;
|
||||
}
|
||||
|
||||
list.push(option);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
function getCompletionOption(
|
||||
key: string,
|
||||
value: unknown,
|
||||
prefix: string
|
||||
): null | CompletionOption | CompletionOption[] {
|
||||
let label = key;
|
||||
if (prefix.length) {
|
||||
label = prefix + '.' + key;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return {
|
||||
label,
|
||||
type: 'variable',
|
||||
detail: 'Child Table',
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return {
|
||||
label,
|
||||
type: 'variable',
|
||||
detail: value,
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
return hintsToCompletionOptions(value, label);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.cm-line {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.cm-gutter {
|
||||
@apply bg-gray-50;
|
||||
}
|
||||
@ -121,4 +222,32 @@ export default defineComponent({
|
||||
.cm-activeLineGutter {
|
||||
background-color: #e5f3ff67 !important;
|
||||
}
|
||||
|
||||
.cm-tooltip-autocomplete {
|
||||
background-color: white !important;
|
||||
border: 1px solid theme('colors.gray.200') !important;
|
||||
@apply rounded shadow-lg overflow-hidden text-gray-900;
|
||||
}
|
||||
|
||||
.cm-tooltip-autocomplete [aria-selected] {
|
||||
color: #334155 !important;
|
||||
background-color: theme('colors.blue.100') !important;
|
||||
}
|
||||
|
||||
.cm-panels {
|
||||
border-top: 1px solid theme('colors.gray.200') !important;
|
||||
background-color: theme('colors.gray.50') !important;
|
||||
color: theme('colors.gray.800') !important;
|
||||
}
|
||||
|
||||
.cm-button {
|
||||
background-image: none !important;
|
||||
background-color: theme('colors.gray.200') !important;
|
||||
color: theme('colors.gray.700') !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.cm-textfield {
|
||||
border: 1px solid theme('colors.gray.200') !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -962,7 +962,7 @@
|
||||
"@babel/helper-validator-identifier" "^7.15.7"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@codemirror/autocomplete@^6.0.0":
|
||||
"@codemirror/autocomplete@^6.0.0", "@codemirror/autocomplete@^6.4.2":
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.4.2.tgz#938b25223bd21f97b2a6d85474643355f98b505b"
|
||||
integrity sha512-8WE2xp+D0MpWEv5lZ6zPW1/tf4AGb358T5GWYiKEuCP8MvFfT3tH2mIF9Y2yr2e3KbHuSvsVhosiEyqCpiJhZQ==
|
||||
|
Loading…
Reference in New Issue
Block a user