From da0fd1c4b566f5f8e4bc35fbd2611de7efd9b546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Tue, 19 Nov 2024 10:04:50 +0100 Subject: [PATCH 01/30] feat(supersearch): Add pills --- .../lib/components/QualifierComponent.svelte | 105 +++++++++++++++++ .../src/lib/components/SuperSearch.svelte | 4 +- .../src/lib/extensions/qualifier.ts | 106 ++++++++++++++++++ 3 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 packages/supersearch/src/lib/components/QualifierComponent.svelte create mode 100644 packages/supersearch/src/lib/extensions/qualifier.ts diff --git a/packages/supersearch/src/lib/components/QualifierComponent.svelte b/packages/supersearch/src/lib/components/QualifierComponent.svelte new file mode 100644 index 000000000..03e87265f --- /dev/null +++ b/packages/supersearch/src/lib/components/QualifierComponent.svelte @@ -0,0 +1,105 @@ + + + + + {qualifier.keyLabel || qualifier.key} + + + {qualifier.operator} + + + {qualifier.valueLabel || qualifier.value} + + + + X + +  + + \ No newline at end of file diff --git a/packages/supersearch/src/lib/components/SuperSearch.svelte b/packages/supersearch/src/lib/components/SuperSearch.svelte index be8729343..486ba9473 100644 --- a/packages/supersearch/src/lib/components/SuperSearch.svelte +++ b/packages/supersearch/src/lib/components/SuperSearch.svelte @@ -13,6 +13,7 @@ TransformFunction, ResultItem } from '$lib/types/superSearch.js'; + import { qualifierPlugin } from '$lib/extensions/qualifier.js'; interface Props { name: string; @@ -64,7 +65,8 @@ submitFormOnEnterKey(form), preventNewLine({ replaceWithSpace: true }), ...(language ? [language] : []), - placeholderCompartment.of(placeholderExtension(placeholder)) + placeholderCompartment.of(placeholderExtension(placeholder)), + qualifierPlugin ]; function handleClickCollapsed() { diff --git a/packages/supersearch/src/lib/extensions/qualifier.ts b/packages/supersearch/src/lib/extensions/qualifier.ts new file mode 100644 index 000000000..7b15a50cc --- /dev/null +++ b/packages/supersearch/src/lib/extensions/qualifier.ts @@ -0,0 +1,106 @@ +import { + Decoration, + EditorView, + ViewPlugin, + ViewUpdate, + WidgetType, + type DecorationSet +} from '@codemirror/view'; +import { syntaxTree } from '@codemirror/language'; +import { mount } from 'svelte'; +import QualifierComponent from '$lib/components/QualifierComponent.svelte'; + +export type Qualifier = { + key: string; + keyLabel?: string; + value: string; + valueLabel?: string; + operator: string; +} + +class QualifierWidget extends WidgetType { + constructor( + readonly qualifier: Qualifier, + readonly range: { from: number; to: number } + ) { + super(); + } + + // eq(other: QualifierWidget) { + // return ( + // this.qualifier.type == other.qualifier.type && + // this.qualifier.value == other.qualifier.value && + // this.range == other.range + // ); + // } + + toDOM() { + const container = document.createElement('span'); + container.style.cssText = `position: relative;`; + mount(QualifierComponent, { + target: container, + props: { + qualifier: this.qualifier, + range: this.range + } + }); + return container; + } +} + +function qualifiers(view: EditorView) { + const widgets = []; + + syntaxTree(view.state).iterate({ + enter: (node) => { + if (node.name === 'Qualifier') { + const qKeyNode = node.node.getChild('QualifierKey'); + const qOperatorNode = qKeyNode?.nextSibling; + const qValueNode = node.node.getChild('QualifierValue'); + + const doc = view.state.doc.toString(); + + const qKeyText = doc.slice(qKeyNode?.from, qKeyNode?.to) + const qOperatorText = doc.slice(qOperatorNode?.from, qOperatorNode?.to) + const qValueText = doc.slice(qValueNode?.from, qValueNode?.to) + + const qualifier = { key: qKeyText, value: qValueText, operator: qOperatorText }; + const range = { from: node.from, to: node.to }; + + const decoration = Decoration.replace({ + inclusiveStart: true, + inclusiveEnd: true, + widget: new QualifierWidget(qualifier, range) + }); + widgets.push(decoration.range(node.from, node.to)); + } + } + }); + return Decoration.set(widgets); +} + +export const qualifierPlugin = ViewPlugin.fromClass( + class { + decorations: DecorationSet; + constructor(view: EditorView) { + this.decorations = qualifiers(view); + } + + update(update: ViewUpdate) { + if ( + update.docChanged || + update.viewportChanged || + syntaxTree(update.startState) != syntaxTree(update.state) + ) { + this.decorations = qualifiers(update.view); + } + } + }, + { + decorations: (instance) => instance.decorations, + provide: (plugin) => + EditorView.atomicRanges.of((view) => { + return view.plugin(plugin)?.decorations || Decoration.none; + }) + } +); \ No newline at end of file From 16606ce40d3434344731df5fcf6b5e8b45595629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Tue, 19 Nov 2024 10:07:03 +0100 Subject: [PATCH 02/30] format --- .../lib/components/QualifierComponent.svelte | 164 ++++++++--------- .../src/lib/extensions/qualifier.ts | 166 +++++++++--------- 2 files changed, 165 insertions(+), 165 deletions(-) diff --git a/packages/supersearch/src/lib/components/QualifierComponent.svelte b/packages/supersearch/src/lib/components/QualifierComponent.svelte index 03e87265f..5f71fe463 100644 --- a/packages/supersearch/src/lib/components/QualifierComponent.svelte +++ b/packages/supersearch/src/lib/components/QualifierComponent.svelte @@ -1,105 +1,105 @@ - - {qualifier.keyLabel || qualifier.key} - - - {qualifier.operator} - - - {qualifier.valueLabel || qualifier.value} - - - - X - + + {qualifier.keyLabel || qualifier.key} + + + {qualifier.operator} + + + {qualifier.valueLabel || qualifier.value} + + + + X +   \ No newline at end of file + &:hover { + background: rgba(0, 0, 0, 0.05); + } + } + diff --git a/packages/supersearch/src/lib/extensions/qualifier.ts b/packages/supersearch/src/lib/extensions/qualifier.ts index 7b15a50cc..e17c4754e 100644 --- a/packages/supersearch/src/lib/extensions/qualifier.ts +++ b/packages/supersearch/src/lib/extensions/qualifier.ts @@ -1,106 +1,106 @@ import { - Decoration, - EditorView, - ViewPlugin, - ViewUpdate, - WidgetType, - type DecorationSet + Decoration, + EditorView, + ViewPlugin, + ViewUpdate, + WidgetType, + type DecorationSet } from '@codemirror/view'; import { syntaxTree } from '@codemirror/language'; import { mount } from 'svelte'; import QualifierComponent from '$lib/components/QualifierComponent.svelte'; export type Qualifier = { - key: string; - keyLabel?: string; - value: string; - valueLabel?: string; - operator: string; -} + key: string; + keyLabel?: string; + value: string; + valueLabel?: string; + operator: string; +}; class QualifierWidget extends WidgetType { - constructor( - readonly qualifier: Qualifier, - readonly range: { from: number; to: number } - ) { - super(); - } + constructor( + readonly qualifier: Qualifier, + readonly range: { from: number; to: number } + ) { + super(); + } - // eq(other: QualifierWidget) { - // return ( - // this.qualifier.type == other.qualifier.type && - // this.qualifier.value == other.qualifier.value && - // this.range == other.range - // ); - // } + // eq(other: QualifierWidget) { + // return ( + // this.qualifier.type == other.qualifier.type && + // this.qualifier.value == other.qualifier.value && + // this.range == other.range + // ); + // } - toDOM() { - const container = document.createElement('span'); - container.style.cssText = `position: relative;`; - mount(QualifierComponent, { - target: container, - props: { - qualifier: this.qualifier, - range: this.range - } - }); - return container; - } + toDOM() { + const container = document.createElement('span'); + container.style.cssText = `position: relative;`; + mount(QualifierComponent, { + target: container, + props: { + qualifier: this.qualifier, + range: this.range + } + }); + return container; + } } function qualifiers(view: EditorView) { - const widgets = []; + const widgets = []; - syntaxTree(view.state).iterate({ - enter: (node) => { - if (node.name === 'Qualifier') { - const qKeyNode = node.node.getChild('QualifierKey'); - const qOperatorNode = qKeyNode?.nextSibling; - const qValueNode = node.node.getChild('QualifierValue'); + syntaxTree(view.state).iterate({ + enter: (node) => { + if (node.name === 'Qualifier') { + const qKeyNode = node.node.getChild('QualifierKey'); + const qOperatorNode = qKeyNode?.nextSibling; + const qValueNode = node.node.getChild('QualifierValue'); - const doc = view.state.doc.toString(); + const doc = view.state.doc.toString(); - const qKeyText = doc.slice(qKeyNode?.from, qKeyNode?.to) - const qOperatorText = doc.slice(qOperatorNode?.from, qOperatorNode?.to) - const qValueText = doc.slice(qValueNode?.from, qValueNode?.to) + const qKeyText = doc.slice(qKeyNode?.from, qKeyNode?.to); + const qOperatorText = doc.slice(qOperatorNode?.from, qOperatorNode?.to); + const qValueText = doc.slice(qValueNode?.from, qValueNode?.to); - const qualifier = { key: qKeyText, value: qValueText, operator: qOperatorText }; - const range = { from: node.from, to: node.to }; + const qualifier = { key: qKeyText, value: qValueText, operator: qOperatorText }; + const range = { from: node.from, to: node.to }; - const decoration = Decoration.replace({ - inclusiveStart: true, - inclusiveEnd: true, - widget: new QualifierWidget(qualifier, range) - }); - widgets.push(decoration.range(node.from, node.to)); - } - } - }); - return Decoration.set(widgets); + const decoration = Decoration.replace({ + inclusiveStart: true, + inclusiveEnd: true, + widget: new QualifierWidget(qualifier, range) + }); + widgets.push(decoration.range(node.from, node.to)); + } + } + }); + return Decoration.set(widgets); } export const qualifierPlugin = ViewPlugin.fromClass( - class { - decorations: DecorationSet; - constructor(view: EditorView) { - this.decorations = qualifiers(view); - } + class { + decorations: DecorationSet; + constructor(view: EditorView) { + this.decorations = qualifiers(view); + } - update(update: ViewUpdate) { - if ( - update.docChanged || - update.viewportChanged || - syntaxTree(update.startState) != syntaxTree(update.state) - ) { - this.decorations = qualifiers(update.view); - } - } - }, - { - decorations: (instance) => instance.decorations, - provide: (plugin) => - EditorView.atomicRanges.of((view) => { - return view.plugin(plugin)?.decorations || Decoration.none; - }) - } -); \ No newline at end of file + update(update: ViewUpdate) { + if ( + update.docChanged || + update.viewportChanged || + syntaxTree(update.startState) != syntaxTree(update.state) + ) { + this.decorations = qualifiers(update.view); + } + } + }, + { + decorations: (instance) => instance.decorations, + provide: (plugin) => + EditorView.atomicRanges.of((view) => { + return view.plugin(plugin)?.decorations || Decoration.none; + }) + } +); From 75d12320933d1279bb332783448dab77fb545446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Tue, 19 Nov 2024 14:09:40 +0100 Subject: [PATCH 03/30] Emit & dispatch pill value --- .../lib/components/QualifierComponent.svelte | 73 ++++++++++++++----- .../src/lib/extensions/qualifier.ts | 37 ++++++---- 2 files changed, 79 insertions(+), 31 deletions(-) diff --git a/packages/supersearch/src/lib/components/QualifierComponent.svelte b/packages/supersearch/src/lib/components/QualifierComponent.svelte index 5f71fe463..7c8bf9434 100644 --- a/packages/supersearch/src/lib/components/QualifierComponent.svelte +++ b/packages/supersearch/src/lib/components/QualifierComponent.svelte @@ -4,14 +4,58 @@ // import IconClose from '~icons/mdi/remove'; import { page } from '$app/stores'; import type { Qualifier } from '$lib/extensions/qualifier.js'; + import { onDestroy, onMount } from 'svelte'; type QualifierWidgetProps = { qualifier: Qualifier; range: { from: number; to: number }; + update: any }; - let { qualifier, range }: QualifierWidgetProps = $props(); + let { qualifier, range, update }: QualifierWidgetProps = $props(); + // can't edit the value if we show the label, i.e 'Astrid Lindgren' + const isEditable = !qualifier.valueLabel; + let editableElem : HTMLElement | undefined; + let prevValue = qualifier.value + + onMount(() => { + console.log('mounted component') + if (isEditable && !qualifier.value) { + if (editableElem) { + editableElem && focusValueInput(editableElem); + editableElem.addEventListener('focusout', syncValue) + } + } + }) + + onDestroy(() => { + // remove event listeners? + console.log('destroying') + }) + + // take over cursor, input from codemirror + function focusValueInput(elem: HTMLElement) { + elem?.focus(); + elem && window.getSelection()?.selectAllChildren(elem); + window.getSelection()?.collapseToStart(); + } + + function syncValue() { + console.log('syncing...') + let value = editableElem?.innerText; + if (prevValue !== value) { + console.log('emitting', value) + update({ + range, + text: `${qualifier.key}${qualifier.operator}"${value}"` + }) + prevValue = value; + } + + } + + // FIX: no hardcoded _q let removeUrl = $derived.by(() => { const url = new URL($page.url); const _q = $page.url.searchParams.get('_q'); @@ -29,7 +73,7 @@ {qualifier.operator} - + {qualifier.valueLabel || qualifier.value} @@ -41,8 +85,7 @@ \ No newline at end of file diff --git a/packages/supersearch/src/lib/components/QualifierRemove.svelte b/packages/supersearch/src/lib/components/QualifierRemove.svelte new file mode 100644 index 000000000..12e253576 --- /dev/null +++ b/packages/supersearch/src/lib/components/QualifierRemove.svelte @@ -0,0 +1,20 @@ + + + + + X + \ No newline at end of file diff --git a/packages/supersearch/src/lib/extensions/qualifierPlugin.ts b/packages/supersearch/src/lib/extensions/qualifierPlugin.ts index 3ec4b11a1..2a0039b1d 100644 --- a/packages/supersearch/src/lib/extensions/qualifierPlugin.ts +++ b/packages/supersearch/src/lib/extensions/qualifierPlugin.ts @@ -6,10 +6,11 @@ import { WidgetType, type DecorationSet } from '@codemirror/view'; -import type { Range } from '@codemirror/state'; +import { Annotation, EditorState, Transaction, type Range } from '@codemirror/state'; import { syntaxTree } from '@codemirror/language'; import { mount } from 'svelte'; -import QualifierComponent from '$lib/components/QualifierComponent.svelte'; +import QualifierRemove from '$lib/components/QualifierRemove.svelte'; +import QualifierKey from '$lib/components/QualifierKey.svelte'; export type Qualifier = { key: string; @@ -17,82 +18,146 @@ export type Qualifier = { operator: string; }; -type Update = { - range: { from: number, to: number }, - text: string -} +// type Update = { +// range: { from: number, to: number }, +// text: string +// } + +// const ANNOTATION_VALUE = 'qualifierSync'; +// const qualifierAnnotation = Annotation.define(); -class QualifierWidget extends WidgetType { - constructor( - readonly qualifier: Qualifier, - readonly range: { from: number; to: number }, - readonly update: (e: Update) => void - ) { +class ButtonWidget extends WidgetType { + constructor(readonly range: { from: number; to: number }) { super(); } - - // prevent re-mounting component if qualifier unchanged - eq(other: QualifierWidget) { - return ( - JSON.stringify(this.qualifier) == JSON.stringify(other.qualifier) && - JSON.stringify(this.range) === JSON.stringify(other.range) - ); + toDOM(): HTMLElement { + const container = document.createElement('span'); + container.style.cssText = `position: relative;`; + mount(QualifierRemove, { + props: { + range: this.range + }, + target: container, + }); + return container; } +} - toDOM() { +class QualifierKeyWidget extends WidgetType { + constructor(readonly key: string, readonly operator: string) { + super(); + } + eq(widget: WidgetType): boolean { + // todo + } + toDOM(): HTMLElement { const container = document.createElement('span'); container.style.cssText = `position: relative;`; - mount(QualifierComponent, { - target: container, + mount(QualifierKey, { props: { - qualifier: this.qualifier, - range: this.range, - update: this.update, - } + key: this.key, + operator: this.operator + }, + target: container, }); return container; } } +// class QualifierWidget extends WidgetType { +// constructor( +// readonly qualifier: Qualifier, +// readonly range: { from: number; to: number }, +// readonly update: (e: Update) => void +// ) { +// super(); +// } + +// // prevent re-mounting component if qualifier unchanged +// eq(other: QualifierWidget) { +// return ( +// JSON.stringify(this.qualifier) == JSON.stringify(other.qualifier) && +// JSON.stringify(this.range) === JSON.stringify(other.range) +// ); +// } + +// updateDOM(dom: HTMLElement, view: EditorView): boolean { +// // prevent svelte component from re-mounting? +// } + +// toDOM() { +// const container = document.createElement('span'); +// container.style.cssText = `position: relative;`; +// mount(QualifierComponent, { +// target: container, +// props: { +// qualifier: this.qualifier, +// range: this.range, +// update: this.update, +// } +// }); +// return container; +// } +// } + function getQualifiers(view: EditorView) { const widgets : Range[] = []; + const doc = view.state.doc.toString(); syntaxTree(view.state).iterate({ enter: (node) => { if (node.name === 'Qualifier') { + // create button widget + const removeBtnDecoration = Decoration.widget({ + widget: new ButtonWidget({ from: node.from, to: node.to }), + side: 1 + }) + widgets.push(removeBtnDecoration.range(node.to)) + + //create qualifier key widget const qKeyNode = node.node.getChild('QualifierKey'); const qOperatorNode = qKeyNode?.nextSibling; - const qValueNode = node.node.getChild('QualifierValue'); - const doc = view.state.doc.toString(); - - const qualifier = { - key: doc.slice(qKeyNode?.from, qKeyNode?.to), - value: qValueNode ? doc.slice(qValueNode?.from, qValueNode?.to) : undefined, - operator: doc.slice(qOperatorNode?.from, qOperatorNode?.to) - } - const range = { from: node.from, to: node.to }; - - const decoration = Decoration.replace({ - inclusiveStart: true, - inclusiveEnd: true, - widget: new QualifierWidget(qualifier, range, onUpdateQualifierValue(view)) - }); - /// range ?? - widgets.push(decoration.range(range.from, range.to)); + const operator = doc.slice(qOperatorNode?.from, qOperatorNode?.to) + const qKey = doc.slice(qKeyNode?.from, qKeyNode?.to); + const qualifierKeyecoration = Decoration.replace({ + widget: new QualifierKeyWidget(qKey, operator) + }) + widgets.push(qualifierKeyecoration.range(qKeyNode?.from, qOperatorNode?.to)) + + // const qKeyNode = node.node.getChild('QualifierKey'); + // const qOperatorNode = qKeyNode?.nextSibling; + // const qValueNode = node.node.getChild('QualifierValue'); + // const doc = view.state.doc.toString(); + + // const qualifier = { + // key: doc.slice(qKeyNode?.from, qKeyNode?.to), + // value: qValueNode ? doc.slice(qValueNode?.from, qValueNode?.to) : undefined, + // operator: doc.slice(qOperatorNode?.from, qOperatorNode?.to) + // } + // const range = { from: node.from, to: node.to }; + + // const decoration = Decoration.replace({ + // inclusiveStart: true, + // inclusiveEnd: true, + // widget: new QualifierWidget(qualifier, range, onUpdateQualifierValue(view)) + // }); + // /// range ?? + // widgets.push(decoration.range(range.from, range.to)); } } }); return Decoration.set(widgets); } -function onUpdateQualifierValue(view: EditorView) { - return (e: Update) => { - console.log('recieved!', e) - view?.dispatch({ - changes: { from: e.range.from, to: e.range.to, insert: e.text } - }) - } -} +// function onUpdateQualifierValue(view: EditorView) { +// return (e: Update) => { +// console.log('recieved!', e) +// view?.dispatch({ +// changes: { from: e.range.from, to: e.range.to, insert: e.text }, +// annotations: qualifierAnnotation.of(ANNOTATION_VALUE) +// }) +// } +// } export const qualifierPlugin = ViewPlugin.fromClass( class { @@ -102,21 +167,30 @@ export const qualifierPlugin = ViewPlugin.fromClass( } update(update: ViewUpdate) { - if ( - update.docChanged || - update.viewportChanged || - syntaxTree(update.startState) != syntaxTree(update.state) - ) { - // placeholderMatcher.updateDeco equiv? + // console.log(isSvelteUpdate(update)) // check if update caused by svelte dispatch + if (update.docChanged || syntaxTree(update.startState) != syntaxTree(update.state)) { this.qualifiers = getQualifiers(update.view); } } }, { decorations: (instance) => instance.qualifiers, + eventHandlers: { + // ?? + }, provide: (plugin) => EditorView.atomicRanges.of((view) => { + console.log('atomic ranges', view.plugin(plugin)?.qualifiers) return view.plugin(plugin)?.qualifiers || Decoration.none; }) } ); + +// function isSvelteUpdate(update: ViewUpdate): boolean { +// for (const transaction of update.transactions) { +// if (transaction.annotation(qualifierAnnotation) === ANNOTATION_VALUE) { +// return true; +// } +// } +// return false; +// } \ No newline at end of file From a8e88e9d947ddd81e3b3984232706f6cb7afd918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Fri, 22 Nov 2024 15:44:43 +0100 Subject: [PATCH 08/30] Atomic/non-atomic ranges --- lxl-web/src/lib/styles/lxlquery.css | 6 +- .../src/lib/components/QualifierKey.svelte | 16 +++-- .../src/lib/components/QualifierRemove.svelte | 17 +++-- .../src/lib/extensions/qualifierPlugin.ts | 64 +++++++++++-------- 4 files changed, 63 insertions(+), 40 deletions(-) diff --git a/lxl-web/src/lib/styles/lxlquery.css b/lxl-web/src/lib/styles/lxlquery.css index f292e349a..575c3cba5 100644 --- a/lxl-web/src/lib/styles/lxlquery.css +++ b/lxl-web/src/lib/styles/lxlquery.css @@ -1,5 +1,7 @@ -.lxlq.qualifier { - color: green; + +.qualifier { + background: rgba(14, 113, 128, 0.15); + padding: 2px 5px; } .lxlq.qualifier-key { diff --git a/packages/supersearch/src/lib/components/QualifierKey.svelte b/packages/supersearch/src/lib/components/QualifierKey.svelte index 0a72b270e..9e93f150b 100644 --- a/packages/supersearch/src/lib/components/QualifierKey.svelte +++ b/packages/supersearch/src/lib/components/QualifierKey.svelte @@ -2,15 +2,17 @@ const {key, operator} = $props(); - - {key} - - - {operator} + + + {key} + + + {operator} + \ No newline at end of file diff --git a/packages/supersearch/src/lib/components/QualifierRemove.svelte b/packages/supersearch/src/lib/components/QualifierRemove.svelte index 12e253576..07a014b08 100644 --- a/packages/supersearch/src/lib/components/QualifierRemove.svelte +++ b/packages/supersearch/src/lib/components/QualifierRemove.svelte @@ -13,8 +13,17 @@ }); + + + + X + + - - - X - \ No newline at end of file + + \ No newline at end of file diff --git a/packages/supersearch/src/lib/extensions/qualifierPlugin.ts b/packages/supersearch/src/lib/extensions/qualifierPlugin.ts index 2a0039b1d..ed913a141 100644 --- a/packages/supersearch/src/lib/extensions/qualifierPlugin.ts +++ b/packages/supersearch/src/lib/extensions/qualifierPlugin.ts @@ -27,7 +27,7 @@ export type Qualifier = { // const qualifierAnnotation = Annotation.define(); class ButtonWidget extends WidgetType { - constructor(readonly range: { from: number; to: number }) { + constructor(readonly range: { from: number; to: number }, readonly atomic: boolean) { super(); } toDOM(): HTMLElement { @@ -44,7 +44,7 @@ class ButtonWidget extends WidgetType { } class QualifierKeyWidget extends WidgetType { - constructor(readonly key: string, readonly operator: string) { + constructor(readonly key: string, readonly operator: string, readonly atomic: boolean ) { super(); } eq(widget: WidgetType): boolean { @@ -107,20 +107,29 @@ function getQualifiers(view: EditorView) { syntaxTree(view.state).iterate({ enter: (node) => { if (node.name === 'Qualifier') { - // create button widget + // Mark decoration to create wrapper element, non-atomic + const qualifierMark = Decoration.mark({ + class: 'qualifier', + inclusive: true, + atomic: false // invented! + }) + widgets.push(qualifierMark.range(node.from, node.to)) + + // Button widget - atomic const removeBtnDecoration = Decoration.widget({ - widget: new ButtonWidget({ from: node.from, to: node.to }), + widget: new ButtonWidget({ from: node.from, to: node.to }, true), side: 1 }) widgets.push(removeBtnDecoration.range(node.to)) - //create qualifier key widget + // Qualifier key + operator widget - atomic const qKeyNode = node.node.getChild('QualifierKey'); const qOperatorNode = qKeyNode?.nextSibling; const operator = doc.slice(qOperatorNode?.from, qOperatorNode?.to) const qKey = doc.slice(qKeyNode?.from, qKeyNode?.to); + const qualifierKeyecoration = Decoration.replace({ - widget: new QualifierKeyWidget(qKey, operator) + widget: new QualifierKeyWidget(qKey, operator, true) }) widgets.push(qualifierKeyecoration.range(qKeyNode?.from, qOperatorNode?.to)) @@ -146,19 +155,9 @@ function getQualifiers(view: EditorView) { } } }); - return Decoration.set(widgets); + return Decoration.set(widgets, true); // true = sort } -// function onUpdateQualifierValue(view: EditorView) { -// return (e: Update) => { -// console.log('recieved!', e) -// view?.dispatch({ -// changes: { from: e.range.from, to: e.range.to, insert: e.text }, -// annotations: qualifierAnnotation.of(ANNOTATION_VALUE) -// }) -// } -// } - export const qualifierPlugin = ViewPlugin.fromClass( class { qualifiers: DecorationSet; @@ -167,7 +166,6 @@ export const qualifierPlugin = ViewPlugin.fromClass( } update(update: ViewUpdate) { - // console.log(isSvelteUpdate(update)) // check if update caused by svelte dispatch if (update.docChanged || syntaxTree(update.startState) != syntaxTree(update.state)) { this.qualifiers = getQualifiers(update.view); } @@ -180,17 +178,29 @@ export const qualifierPlugin = ViewPlugin.fromClass( }, provide: (plugin) => EditorView.atomicRanges.of((view) => { - console.log('atomic ranges', view.plugin(plugin)?.qualifiers) - return view.plugin(plugin)?.qualifiers || Decoration.none; + const filteredRanges = view.plugin(plugin)?.qualifiers.update({ filter: filterOutAtomic }) + return filteredRanges || Decoration.none; }) } ); -// function isSvelteUpdate(update: ViewUpdate): boolean { -// for (const transaction of update.transactions) { -// if (transaction.annotation(qualifierAnnotation) === ANNOTATION_VALUE) { -// return true; +// function insertQuotes(view: EditorView) { +// syntaxTree(view.state).iterate({ +// enter: (node) => { +// if (node.name === 'Qualifier') { +// const value = node.node.getChild('QualifierValue') +// if (!value) { +// console.log('yes!') +// view.dispatch({ +// changes: { from: node.to, to: node.to, insert: '""' } +// }) +// } +// } // } -// } -// return false; -// } \ No newline at end of file +// }) +// } + +// filter out non-atomics using custom property 'atomic' +function filterOutAtomic(from: number, to: number, decoration: Decoration) { + return decoration.spec?.atomic || decoration.spec?.widget?.atomic; +} \ No newline at end of file From ae68bd5ac0a02e9e6d68b63ab97e5f4337f542b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Fri, 22 Nov 2024 23:21:21 +0100 Subject: [PATCH 09/30] Add eq functions etc --- lxl-web/src/lib/styles/lxlquery.css | 8 +- .../codemirror-lang-lxlquery/src/index.ts | 8 +- .../src/syntax.grammar | 10 +- .../lib/components/QualifierComponent.svelte | 131 ------------------ .../src/lib/extensions/qualifierPlugin.ts | 56 +++----- 5 files changed, 27 insertions(+), 186 deletions(-) delete mode 100644 packages/supersearch/src/lib/components/QualifierComponent.svelte diff --git a/lxl-web/src/lib/styles/lxlquery.css b/lxl-web/src/lib/styles/lxlquery.css index 575c3cba5..81334c33c 100644 --- a/lxl-web/src/lib/styles/lxlquery.css +++ b/lxl-web/src/lib/styles/lxlquery.css @@ -1,4 +1,3 @@ - .qualifier { background: rgba(14, 113, 128, 0.15); padding: 2px 5px; @@ -12,15 +11,10 @@ color: green; } -.lxlq.equal-operator, -.lxlq.compare-operator { +.lxlq.qualifier-operator { color: darkolivegreen; } -.lxlq.boolean-operator { - color: #935493; -} - .lxlq.boolean-query { color: purple; } diff --git a/packages/codemirror-lang-lxlquery/src/index.ts b/packages/codemirror-lang-lxlquery/src/index.ts index eb7adba5e..68f93937e 100644 --- a/packages/codemirror-lang-lxlquery/src/index.ts +++ b/packages/codemirror-lang-lxlquery/src/index.ts @@ -7,10 +7,8 @@ const customTags = { Qualifier: Tag.define('Qualifier'), QualifierKey: Tag.define('QualifierKey'), QualifierValue: Tag.define('QualifierValue'), - EqualOperator: Tag.define('EqualOperator'), - CompareOperator: Tag.define('CompareOperator'), + QualifierOperator: Tag.define('QualifierOperator'), BooleanQuery: Tag.define('BooleanQuery'), - BooleanOperator: Tag.define('BooleanOperator'), Wildcard: Tag.define('Wildcard') }; @@ -27,9 +25,7 @@ const highlighter = tagHighlighter( { tag: customTags.Qualifier, class: 'qualifier' }, { tag: customTags.QualifierKey, class: 'qualifier-key' }, { tag: customTags.QualifierValue, class: 'qualifier-value' }, - { tag: customTags.EqualOperator, class: 'equal-operator' }, - { tag: customTags.CompareOperator, class: 'compare-operator' }, - { tag: customTags.BooleanOperator, class: 'boolean-operator' }, + { tag: customTags.QualifierOperator, class: 'qualifier-operator' }, { tag: customTags.BooleanQuery, class: 'boolean-query' }, { tag: customTags.Wildcard, class: 'wildcard' } ], diff --git a/packages/codemirror-lang-lxlquery/src/syntax.grammar b/packages/codemirror-lang-lxlquery/src/syntax.grammar index 707ba38c7..de652e98d 100644 --- a/packages/codemirror-lang-lxlquery/src/syntax.grammar +++ b/packages/codemirror-lang-lxlquery/src/syntax.grammar @@ -17,7 +17,7 @@ BooleanQuery { } Qualifier { - (QualifierKey (EqualOperator | CompareOperator) QualifierValue) | reserved + (QualifierKey QualifierOperator QualifierValue) | reserved } QualifierKey { @@ -28,6 +28,10 @@ QualifierValue { freetext | Group } +QualifierOperator { + equalOperator | compareOperator +} + freetext { (identifier | string | number) Wildcard? } @@ -39,9 +43,9 @@ freetext { number { @digit+ } - EqualOperator { ":" | "=" } + equalOperator { ":" | "=" } - CompareOperator { ">" | "<" | ">=" | "<=" } + compareOperator { ">" | "<" | ">=" | "<=" } booleanOperator { "AND" | "OR" | "NOT" } diff --git a/packages/supersearch/src/lib/components/QualifierComponent.svelte b/packages/supersearch/src/lib/components/QualifierComponent.svelte deleted file mode 100644 index dca382cab..000000000 --- a/packages/supersearch/src/lib/components/QualifierComponent.svelte +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - {qualifier.operator} - - - {valueDisplay} - - - - - diff --git a/packages/supersearch/src/lib/extensions/qualifierPlugin.ts b/packages/supersearch/src/lib/extensions/qualifierPlugin.ts index ed913a141..792e2956d 100644 --- a/packages/supersearch/src/lib/extensions/qualifierPlugin.ts +++ b/packages/supersearch/src/lib/extensions/qualifierPlugin.ts @@ -6,7 +6,7 @@ import { WidgetType, type DecorationSet } from '@codemirror/view'; -import { Annotation, EditorState, Transaction, type Range } from '@codemirror/state'; +import { type Range } from '@codemirror/state'; import { syntaxTree } from '@codemirror/language'; import { mount } from 'svelte'; import QualifierRemove from '$lib/components/QualifierRemove.svelte'; @@ -18,18 +18,14 @@ export type Qualifier = { operator: string; }; -// type Update = { -// range: { from: number, to: number }, -// text: string -// } - -// const ANNOTATION_VALUE = 'qualifierSync'; -// const qualifierAnnotation = Annotation.define(); - -class ButtonWidget extends WidgetType { +class RemoveWidget extends WidgetType { constructor(readonly range: { from: number; to: number }, readonly atomic: boolean) { super(); } + eq(other: RemoveWidget) { + return (this.range.from === other.range.from && this.range.to === other.range.to) + } + // is there a way to pass new props to component instead of re-mounting every time range changes? toDOM(): HTMLElement { const container = document.createElement('span'); container.style.cssText = `position: relative;`; @@ -47,8 +43,8 @@ class QualifierKeyWidget extends WidgetType { constructor(readonly key: string, readonly operator: string, readonly atomic: boolean ) { super(); } - eq(widget: WidgetType): boolean { - // todo + eq(other: QualifierKeyWidget): boolean { + return (this.key === other.key && this.operator === other.operator); } toDOM(): HTMLElement { const container = document.createElement('span'); @@ -115,43 +111,25 @@ function getQualifiers(view: EditorView) { }) widgets.push(qualifierMark.range(node.from, node.to)) - // Button widget - atomic - const removeBtnDecoration = Decoration.widget({ - widget: new ButtonWidget({ from: node.from, to: node.to }, true), + // Remove decoration (x-button) widget - atomic + const removeDecoration = Decoration.widget({ + widget: new RemoveWidget({ from: node.from, to: node.to }, true), side: 1 }) - widgets.push(removeBtnDecoration.range(node.to)) + widgets.push(removeDecoration.range(node.to)) // Qualifier key + operator widget - atomic const qKeyNode = node.node.getChild('QualifierKey'); - const qOperatorNode = qKeyNode?.nextSibling; - const operator = doc.slice(qOperatorNode?.from, qOperatorNode?.to) + const qOperatorNode = node.node.getChild('QualifierOperator'); + const operator = doc.slice(qOperatorNode?.from, qOperatorNode?.to); const qKey = doc.slice(qKeyNode?.from, qKeyNode?.to); const qualifierKeyecoration = Decoration.replace({ widget: new QualifierKeyWidget(qKey, operator, true) }) - widgets.push(qualifierKeyecoration.range(qKeyNode?.from, qOperatorNode?.to)) - - // const qKeyNode = node.node.getChild('QualifierKey'); - // const qOperatorNode = qKeyNode?.nextSibling; - // const qValueNode = node.node.getChild('QualifierValue'); - // const doc = view.state.doc.toString(); - - // const qualifier = { - // key: doc.slice(qKeyNode?.from, qKeyNode?.to), - // value: qValueNode ? doc.slice(qValueNode?.from, qValueNode?.to) : undefined, - // operator: doc.slice(qOperatorNode?.from, qOperatorNode?.to) - // } - // const range = { from: node.from, to: node.to }; - - // const decoration = Decoration.replace({ - // inclusiveStart: true, - // inclusiveEnd: true, - // widget: new QualifierWidget(qualifier, range, onUpdateQualifierValue(view)) - // }); - // /// range ?? - // widgets.push(decoration.range(range.from, range.to)); + if (qKeyNode) { + widgets.push(qualifierKeyecoration.range(qKeyNode?.from, qOperatorNode?.to)) + } } } }); From 39cfe104f0cafccff4e8c207265cd77842ba2c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Mon, 25 Nov 2024 11:03:28 +0100 Subject: [PATCH 10/30] Insert quotes on empty value --- .../src/lib/components/QualifierKey.svelte | 22 +-- .../src/lib/components/QualifierRemove.svelte | 13 +- .../src/lib/components/SuperSearch.svelte | 5 +- .../src/lib/extensions/qualifierPlugin.ts | 149 ++++++++---------- 4 files changed, 89 insertions(+), 100 deletions(-) diff --git a/packages/supersearch/src/lib/components/QualifierKey.svelte b/packages/supersearch/src/lib/components/QualifierKey.svelte index 9e93f150b..0ff277b1f 100644 --- a/packages/supersearch/src/lib/components/QualifierKey.svelte +++ b/packages/supersearch/src/lib/components/QualifierKey.svelte @@ -1,18 +1,18 @@ - - {key} - - - {operator} - + + {key} + + + {operator} + \ No newline at end of file + .qualifier-key-container { + display: inline-flex; + } + diff --git a/packages/supersearch/src/lib/components/QualifierRemove.svelte b/packages/supersearch/src/lib/components/QualifierRemove.svelte index 07a014b08..742e4fb03 100644 --- a/packages/supersearch/src/lib/components/QualifierRemove.svelte +++ b/packages/supersearch/src/lib/components/QualifierRemove.svelte @@ -1,8 +1,8 @@ + @@ -20,10 +20,9 @@ - \ No newline at end of file + diff --git a/packages/supersearch/src/lib/components/SuperSearch.svelte b/packages/supersearch/src/lib/components/SuperSearch.svelte index f21c9a50d..ac7d63098 100644 --- a/packages/supersearch/src/lib/components/SuperSearch.svelte +++ b/packages/supersearch/src/lib/components/SuperSearch.svelte @@ -14,7 +14,7 @@ ResultItem } from '$lib/types/superSearch.js'; import { qualifierPlugin } from '$lib/extensions/qualifierPlugin.js'; - import { defaultKeymap } from "@codemirror/commands"; + import { defaultKeymap } from '@codemirror/commands'; interface Props { name: string; @@ -63,8 +63,7 @@ }); const extensions = [ - // For atomic ranges to work. TODO customize - keymap.of(defaultKeymap), + keymap.of(defaultKeymap), // Needed for atomic ranges to work. Maybe we can use a subset? submitFormOnEnterKey(form), preventNewLine({ replaceWithSpace: true }), ...(language ? [language] : []), diff --git a/packages/supersearch/src/lib/extensions/qualifierPlugin.ts b/packages/supersearch/src/lib/extensions/qualifierPlugin.ts index 792e2956d..4d379d25d 100644 --- a/packages/supersearch/src/lib/extensions/qualifierPlugin.ts +++ b/packages/supersearch/src/lib/extensions/qualifierPlugin.ts @@ -6,7 +6,13 @@ import { WidgetType, type DecorationSet } from '@codemirror/view'; -import { type Range } from '@codemirror/state'; +import { + EditorState, + Transaction, + type ChangeSpec, + type Range, + type TransactionSpec +} from '@codemirror/state'; import { syntaxTree } from '@codemirror/language'; import { mount } from 'svelte'; import QualifierRemove from '$lib/components/QualifierRemove.svelte'; @@ -19,11 +25,14 @@ export type Qualifier = { }; class RemoveWidget extends WidgetType { - constructor(readonly range: { from: number; to: number }, readonly atomic: boolean) { + constructor( + readonly range: { from: number; to: number }, + readonly atomic: boolean + ) { super(); } eq(other: RemoveWidget) { - return (this.range.from === other.range.from && this.range.to === other.range.to) + return this.range.from === other.range.from && this.range.to === other.range.to; } // is there a way to pass new props to component instead of re-mounting every time range changes? toDOM(): HTMLElement { @@ -33,18 +42,22 @@ class RemoveWidget extends WidgetType { props: { range: this.range }, - target: container, + target: container }); return container; } } class QualifierKeyWidget extends WidgetType { - constructor(readonly key: string, readonly operator: string, readonly atomic: boolean ) { + constructor( + readonly key: string, + readonly operator: string, + readonly atomic: boolean + ) { super(); } eq(other: QualifierKeyWidget): boolean { - return (this.key === other.key && this.operator === other.operator); + return this.key === other.key && this.operator === other.operator; } toDOM(): HTMLElement { const container = document.createElement('span'); @@ -54,50 +67,14 @@ class QualifierKeyWidget extends WidgetType { key: this.key, operator: this.operator }, - target: container, + target: container }); return container; } } -// class QualifierWidget extends WidgetType { -// constructor( -// readonly qualifier: Qualifier, -// readonly range: { from: number; to: number }, -// readonly update: (e: Update) => void -// ) { -// super(); -// } - -// // prevent re-mounting component if qualifier unchanged -// eq(other: QualifierWidget) { -// return ( -// JSON.stringify(this.qualifier) == JSON.stringify(other.qualifier) && -// JSON.stringify(this.range) === JSON.stringify(other.range) -// ); -// } - -// updateDOM(dom: HTMLElement, view: EditorView): boolean { -// // prevent svelte component from re-mounting? -// } - -// toDOM() { -// const container = document.createElement('span'); -// container.style.cssText = `position: relative;`; -// mount(QualifierComponent, { -// target: container, -// props: { -// qualifier: this.qualifier, -// range: this.range, -// update: this.update, -// } -// }); -// return container; -// } -// } - function getQualifiers(view: EditorView) { - const widgets : Range[] = []; + const widgets: Range[] = []; const doc = view.state.doc.toString(); syntaxTree(view.state).iterate({ @@ -105,18 +82,18 @@ function getQualifiers(view: EditorView) { if (node.name === 'Qualifier') { // Mark decoration to create wrapper element, non-atomic const qualifierMark = Decoration.mark({ - class: 'qualifier', - inclusive: true, + class: 'qualifier', + inclusive: true, atomic: false // invented! - }) - widgets.push(qualifierMark.range(node.from, node.to)) + }); + widgets.push(qualifierMark.range(node.from, node.to)); // Remove decoration (x-button) widget - atomic const removeDecoration = Decoration.widget({ widget: new RemoveWidget({ from: node.from, to: node.to }, true), side: 1 - }) - widgets.push(removeDecoration.range(node.to)) + }); + widgets.push(removeDecoration.range(node.to)); // Qualifier key + operator widget - atomic const qKeyNode = node.node.getChild('QualifierKey'); @@ -126,9 +103,9 @@ function getQualifiers(view: EditorView) { const qualifierKeyecoration = Decoration.replace({ widget: new QualifierKeyWidget(qKey, operator, true) - }) + }); if (qKeyNode) { - widgets.push(qualifierKeyecoration.range(qKeyNode?.from, qOperatorNode?.to)) + widgets.push(qualifierKeyecoration.range(qKeyNode?.from, qOperatorNode?.to)); } } } @@ -136,6 +113,41 @@ function getQualifiers(view: EditorView) { return Decoration.set(widgets, true); // true = sort } +/** + * Moves cursor into an empty quote on falsy qualifier value + */ +const insertQuotes = (tr: Transaction) => { + let foundEmptyQValue = false; + const changes: TransactionSpec = { + changes: { + from: tr.state.selection.main.head, + to: tr.state.selection.main.head, + insert: '""' + }, + sequential: true, + selection: { anchor: tr.state.selection.main.head + 1 } + }; + syntaxTree(tr.state).iterate({ + enter: (node) => { + if (node.name === 'Qualifier') { + const qValue = node.node.getChild('QualifierValue'); + if (!qValue) { + foundEmptyQValue = true; + return true; + } + } + } + }); + return foundEmptyQValue ? [tr, changes] : tr; +}; + +/** + * filter out non-atomics using custom property 'atomic' + */ +const filterAtomic = (from: number, to: number, decoration: Decoration) => { + return decoration.spec?.atomic || decoration.spec?.widget?.atomic; +}; + export const qualifierPlugin = ViewPlugin.fromClass( class { qualifiers: DecorationSet; @@ -151,34 +163,13 @@ export const qualifierPlugin = ViewPlugin.fromClass( }, { decorations: (instance) => instance.qualifiers, - eventHandlers: { - // ?? - }, - provide: (plugin) => + eventHandlers: {}, + provide: (plugin) => [ EditorView.atomicRanges.of((view) => { - const filteredRanges = view.plugin(plugin)?.qualifiers.update({ filter: filterOutAtomic }) + const filteredRanges = view.plugin(plugin)?.qualifiers.update({ filter: filterAtomic }); return filteredRanges || Decoration.none; - }) + }), + EditorState.transactionFilter.of(insertQuotes) + ] } ); - -// function insertQuotes(view: EditorView) { -// syntaxTree(view.state).iterate({ -// enter: (node) => { -// if (node.name === 'Qualifier') { -// const value = node.node.getChild('QualifierValue') -// if (!value) { -// console.log('yes!') -// view.dispatch({ -// changes: { from: node.to, to: node.to, insert: '""' } -// }) -// } -// } -// } -// }) -// } - -// filter out non-atomics using custom property 'atomic' -function filterOutAtomic(from: number, to: number, decoration: Decoration) { - return decoration.spec?.atomic || decoration.spec?.widget?.atomic; -} \ No newline at end of file From 68accdd0a8bbcb05573b5e8cce7d246386dd4bdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Mon, 25 Nov 2024 11:47:53 +0100 Subject: [PATCH 11/30] Fix --- packages/supersearch/src/lib/extensions/qualifierPlugin.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/supersearch/src/lib/extensions/qualifierPlugin.ts b/packages/supersearch/src/lib/extensions/qualifierPlugin.ts index 4d379d25d..04a912494 100644 --- a/packages/supersearch/src/lib/extensions/qualifierPlugin.ts +++ b/packages/supersearch/src/lib/extensions/qualifierPlugin.ts @@ -9,7 +9,6 @@ import { import { EditorState, Transaction, - type ChangeSpec, type Range, type TransactionSpec } from '@codemirror/state'; From 3161b7c5af793c4e8f44f8224aa7bb39730e5ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Mon, 25 Nov 2024 12:58:05 +0100 Subject: [PATCH 12/30] Remove type import --- packages/supersearch/src/lib/components/SuperSearch.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/supersearch/src/lib/components/SuperSearch.svelte b/packages/supersearch/src/lib/components/SuperSearch.svelte index ac7d63098..9c176e9e5 100644 --- a/packages/supersearch/src/lib/components/SuperSearch.svelte +++ b/packages/supersearch/src/lib/components/SuperSearch.svelte @@ -2,7 +2,7 @@ import type { Snippet } from 'svelte'; import CodeMirror, { type ChangeCodeMirrorEvent } from '$lib/components/CodeMirror.svelte'; import { EditorView, placeholder as placeholderExtension, keymap } from '@codemirror/view'; - import { Compartment, type Extension } from '@codemirror/state'; + import { Compartment } from '@codemirror/state'; import { type LanguageSupport } from '@codemirror/language'; import submitFormOnEnterKey from '$lib/extensions/submitFormOnEnterKey.js'; import preventNewLine from '$lib/extensions/preventNewLine.js'; From bf5497dd5a09e04b4b8fe77274cac9ba56a5c870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Mon, 25 Nov 2024 13:05:55 +0100 Subject: [PATCH 13/30] Fix tests after language update --- .../codemirror-lang-lxlquery/test/cases.txt | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/codemirror-lang-lxlquery/test/cases.txt b/packages/codemirror-lang-lxlquery/test/cases.txt index a4b030d9e..13515aca6 100644 --- a/packages/codemirror-lang-lxlquery/test/cases.txt +++ b/packages/codemirror-lang-lxlquery/test/cases.txt @@ -32,7 +32,7 @@ Query(Wildcard) ==> Query( - Qualifier(QualifierKey,EqualOperator,QualifierValue) + Qualifier(QualifierKey,QualifierOperator,QualifierValue) ) @@ -44,8 +44,8 @@ Query( ==> Query( - Qualifier(QualifierKey,CompareOperator,QualifierValue) - Qualifier(QualifierKey,CompareOperator,QualifierValue) + Qualifier(QualifierKey,QualifierOperator,QualifierValue) + Qualifier(QualifierKey,QualifierOperator,QualifierValue) ) @@ -56,7 +56,7 @@ Query( ==> Query( - Qualifier(QualifierKey,EqualOperator,QualifierValue) + Qualifier(QualifierKey,QualifierOperator,QualifierValue) ) @@ -67,7 +67,7 @@ contributor:"Astrid Lindgren" ==> Query( - Qualifier(QualifierKey,EqualOperator,QualifierValue) + Qualifier(QualifierKey,QualifierOperator,QualifierValue) ) @@ -78,7 +78,7 @@ contributor:"libris:fcrtpljz1qp2bdv#it" ==> Query( - Qualifier(QualifierKey,EqualOperator,QualifierValue) + Qualifier(QualifierKey,QualifierOperator,QualifierValue) ) @@ -89,7 +89,7 @@ sommar OR vinter NOT vår ==> Query( - BooleanQuery(BooleanOperator,BooleanOperator) + BooleanQuery ) @@ -99,9 +99,7 @@ OR AND sommar ==> -Query( - ⚠(BooleanOperator),⚠(BooleanOperator) -) +Query(⚠) # Combined: Qualifier and Freetext @@ -111,7 +109,7 @@ contributor:"Astrid Lindgren" Pippi Långstrump ==> Query( - Qualifier(QualifierKey,EqualOperator,QualifierValue) + Qualifier(QualifierKey,QualifierOperator,QualifierValue) ) @@ -122,7 +120,7 @@ träd* bibliografi:"sigel:DST" NOT typ:Text ==> Query( - Wildcard,BooleanQuery(Qualifier(...),BooleanOperator,Qualifier(...)) + Wildcard,BooleanQuery(Qualifier(...),Qualifier(...)) ) @@ -134,9 +132,9 @@ pippi långstrump språk:(engelska OR franska) ÅR>2002 Query( Qualifier( - QualifierKey,EqualOperator,QualifierValue( + QualifierKey,QualifierOperator,QualifierValue( Group( - BooleanQuery(BooleanOperator) + BooleanQuery ) ) ), From ed0fb5ae277f2d9ced29821922f5e6b648dc29a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Mon, 25 Nov 2024 13:34:13 +0100 Subject: [PATCH 14/30] Replace defaultKeymap with standardKeymap --- packages/supersearch/src/lib/components/SuperSearch.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/supersearch/src/lib/components/SuperSearch.svelte b/packages/supersearch/src/lib/components/SuperSearch.svelte index 9c176e9e5..f8ff9b23f 100644 --- a/packages/supersearch/src/lib/components/SuperSearch.svelte +++ b/packages/supersearch/src/lib/components/SuperSearch.svelte @@ -14,7 +14,7 @@ ResultItem } from '$lib/types/superSearch.js'; import { qualifierPlugin } from '$lib/extensions/qualifierPlugin.js'; - import { defaultKeymap } from '@codemirror/commands'; + import { standardKeymap } from '@codemirror/commands'; interface Props { name: string; @@ -63,7 +63,7 @@ }); const extensions = [ - keymap.of(defaultKeymap), // Needed for atomic ranges to work. Maybe we can use a subset? + keymap.of(standardKeymap), // Needed for atomic ranges to work. Maybe we can use a subset? submitFormOnEnterKey(form), preventNewLine({ replaceWithSpace: true }), ...(language ? [language] : []), From 55d16eff807cbf1d50e4b4331281577bea14bc19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Mon, 25 Nov 2024 14:35:15 +0100 Subject: [PATCH 15/30] remove component range prop fix --- packages/supersearch/src/lib/components/QualifierRemove.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/supersearch/src/lib/components/QualifierRemove.svelte b/packages/supersearch/src/lib/components/QualifierRemove.svelte index 742e4fb03..de17d30ce 100644 --- a/packages/supersearch/src/lib/components/QualifierRemove.svelte +++ b/packages/supersearch/src/lib/components/QualifierRemove.svelte @@ -1,7 +1,7 @@ From 278eb14f743ffc235e6c967391a241c9bf841ebe Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Wed, 27 Nov 2024 11:02:14 +0100 Subject: [PATCH 21/30] Make extension prop optional --- packages/supersearch/src/lib/components/SuperSearch.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/supersearch/src/lib/components/SuperSearch.svelte b/packages/supersearch/src/lib/components/SuperSearch.svelte index 1f0c81af1..8c034dbf0 100644 --- a/packages/supersearch/src/lib/components/SuperSearch.svelte +++ b/packages/supersearch/src/lib/components/SuperSearch.svelte @@ -25,7 +25,7 @@ queryFn?: QueryFunction; paginationQueryFn?: PaginationQueryFunction; transformFn?: TransformFunction; - extensions: Extension[]; + extensions?: Extension[]; resultItem?: Snippet<[ResultItem]>; } From bc41c534cb85798b6687f334fd3de6d658dcbc82 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Wed, 27 Nov 2024 11:08:28 +0100 Subject: [PATCH 22/30] Add lxlquery language and qualifier plugin to demo app --- package-lock.json | 1 + packages/supersearch/package.json | 1 + packages/supersearch/src/routes/+page.svelte | 6 ++++++ 3 files changed, 8 insertions(+) diff --git a/package-lock.json b/package-lock.json index 06ff10e1e..2b8073165 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38283,6 +38283,7 @@ "@sveltejs/vite-plugin-svelte": "^4.0.0", "@types/eslint": "^9.6.0", "codemirror": "^6.0.1", + "codemirror-lang-lxlquery": "^0.1.0", "eslint": "^9.7.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.36.0", diff --git a/packages/supersearch/package.json b/packages/supersearch/package.json index ce032d3b7..fb422c9f8 100644 --- a/packages/supersearch/package.json +++ b/packages/supersearch/package.json @@ -47,6 +47,7 @@ "@sveltejs/vite-plugin-svelte": "^4.0.0", "@types/eslint": "^9.6.0", "codemirror": "^6.0.1", + "codemirror-lang-lxlquery": "^0.1.0", "eslint": "^9.7.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-svelte": "^2.36.0", diff --git a/packages/supersearch/src/routes/+page.svelte b/packages/supersearch/src/routes/+page.svelte index 3810d859a..c3e354a8f 100644 --- a/packages/supersearch/src/routes/+page.svelte +++ b/packages/supersearch/src/routes/+page.svelte @@ -1,5 +1,7 @@ - - - {key} - - - {operator} - + + {label || key}{operator} diff --git a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts index bc3d9a8ca..a66c229d1 100644 --- a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts +++ b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts @@ -48,6 +48,7 @@ class QualifierKeyWidget extends WidgetType { constructor( readonly key: string, readonly operator: string, + readonly label: string | undefined, readonly atomic: boolean ) { super(); @@ -61,7 +62,8 @@ class QualifierKeyWidget extends WidgetType { mount(QualifierKey, { props: { key: this.key, - operator: this.operator + operator: this.operator, + label: this.label }, target: container }); @@ -98,7 +100,7 @@ function getQualifiers(view: EditorView) { const qKey = doc.slice(qKeyNode?.from, qKeyNode?.to); const qualifierKeyecoration = Decoration.replace({ - widget: new QualifierKeyWidget(qKey, operator, true) + widget: new QualifierKeyWidget(qKey, operator, qKey, true) }); if (qKeyNode) { widgets.push(qualifierKeyecoration.range(qKeyNode?.from, qOperatorNode?.to)); From 21ef63c8dd6651ae66ccb935ea9ed40ab7e6763e Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Wed, 27 Nov 2024 16:50:07 +0100 Subject: [PATCH 26/30] Capitalize lezer rules for qualifiers --- .../src/syntax.grammar | 20 ++++++------- .../lxlQualifierPlugin/QualifierKey.svelte | 2 ++ .../extensions/lxlQualifierPlugin/index.ts | 29 ++++++++++++------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/packages/codemirror-lang-lxlquery/src/syntax.grammar b/packages/codemirror-lang-lxlquery/src/syntax.grammar index de652e98d..24010b778 100644 --- a/packages/codemirror-lang-lxlquery/src/syntax.grammar +++ b/packages/codemirror-lang-lxlquery/src/syntax.grammar @@ -21,31 +21,31 @@ Qualifier { } QualifierKey { - identifier | string + Identifier | String } QualifierValue { - freetext | Group + Identifier | String | Number | Group } QualifierOperator { - equalOperator | compareOperator + EqualOperator | CompareOperator } freetext { - (identifier | string | number) Wildcard? + (Identifier | String | Number) Wildcard? } @tokens { - identifier { $[a-zA-ZåäöÅÄÖ]+ } + Identifier { $[a-zA-ZåäöÅÄÖ]+ } - string { '"' (!["\\] | "\\" _)* '"' } + String { '"' (!["\\] | "\\" _)* '"' } - number { @digit+ } + Number { @digit+ } - equalOperator { ":" | "=" } + EqualOperator { ":" | "=" } - compareOperator { ">" | "<" | ">=" | "<=" } + CompareOperator { ">" | "<" | ">=" | "<=" } booleanOperator { "AND" | "OR" | "NOT" } @@ -55,7 +55,7 @@ freetext { space { @whitespace+ } - @precedence {booleanOperator, reserved, identifier} + @precedence { booleanOperator, reserved, Identifier } } @detectDelim \ No newline at end of file diff --git a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierKey.svelte b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierKey.svelte index 36cce4f14..2a42ad646 100644 --- a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierKey.svelte +++ b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierKey.svelte @@ -3,6 +3,8 @@ key: string; operator: string; label?: string; + keyType?: string; + operatorType?: string; } const { key, operator, label }: Props = $props(); diff --git a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts index a66c229d1..850d20314 100644 --- a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts +++ b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts @@ -49,6 +49,8 @@ class QualifierKeyWidget extends WidgetType { readonly key: string, readonly operator: string, readonly label: string | undefined, + readonly keyType: string | undefined, + readonly operatorType: string | undefined, readonly atomic: boolean ) { super(); @@ -63,7 +65,9 @@ class QualifierKeyWidget extends WidgetType { props: { key: this.key, operator: this.operator, - label: this.label + label: this.label, + keyType: this.keyType, + operatorType: this.operatorType }, target: container }); @@ -94,16 +98,21 @@ function getQualifiers(view: EditorView) { widgets.push(removeDecoration.range(node.to)); // Qualifier key + operator widget - atomic - const qKeyNode = node.node.getChild('QualifierKey'); - const qOperatorNode = node.node.getChild('QualifierOperator'); - const operator = doc.slice(qOperatorNode?.from, qOperatorNode?.to); - const qKey = doc.slice(qKeyNode?.from, qKeyNode?.to); + const keyNode = node.node.getChild('QualifierKey'); + const operatorNode = node.node.getChild('QualifierOperator'); - const qualifierKeyecoration = Decoration.replace({ - widget: new QualifierKeyWidget(qKey, operator, qKey, true) - }); - if (qKeyNode) { - widgets.push(qualifierKeyecoration.range(qKeyNode?.from, qOperatorNode?.to)); + if (keyNode && operatorNode) { + const keyDecoration = Decoration.replace({ + widget: new QualifierKeyWidget( + doc.slice(keyNode?.from, keyNode?.to), + doc.slice(operatorNode?.from, operatorNode?.to), + doc.slice(keyNode?.from, keyNode?.to), // label should be found using vocab + keyNode.firstChild?.type.name, + operatorNode.firstChild?.type.name, + true + ) + }); + widgets.push(keyDecoration.range(keyNode?.from, operatorNode?.to)); } } } From e0c4e4a8283b254d7712f0272004dc8ba97faeb2 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Wed, 27 Nov 2024 16:54:26 +0100 Subject: [PATCH 27/30] Use visibleRanges to only render widgets when visible --- .../extensions/lxlQualifierPlugin/index.ts | 71 ++++++++++--------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts index 850d20314..055bc35c7 100644 --- a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts +++ b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/index.ts @@ -79,44 +79,49 @@ function getQualifiers(view: EditorView) { const widgets: Range[] = []; const doc = view.state.doc.toString(); - syntaxTree(view.state).iterate({ - enter: (node) => { - if (node.name === 'Qualifier') { - // Mark decoration to create wrapper element, non-atomic - const qualifierMark = Decoration.mark({ - class: 'qualifier', - inclusive: true, - atomic: false // invented! - }); - widgets.push(qualifierMark.range(node.from, node.to)); + for (const { from, to } of view.visibleRanges) { + syntaxTree(view.state).iterate({ + from, + to, + enter: (node) => { + if (node.name === 'Qualifier') { + // Mark decoration to create wrapper element, non-atomic + const qualifierMark = Decoration.mark({ + class: 'qualifier', + inclusive: true, + atomic: false // invented! + }); + widgets.push(qualifierMark.range(node.from, node.to)); - // Remove decoration (x-button) widget - atomic - const removeDecoration = Decoration.widget({ - widget: new RemoveWidget({ from: node.from, to: node.to }, true), - side: 1 - }); - widgets.push(removeDecoration.range(node.to)); + // Remove decoration (x-button) widget - atomic + const removeDecoration = Decoration.widget({ + widget: new RemoveWidget({ from: node.from, to: node.to }, true), + side: 1 + }); + widgets.push(removeDecoration.range(node.to)); - // Qualifier key + operator widget - atomic - const keyNode = node.node.getChild('QualifierKey'); - const operatorNode = node.node.getChild('QualifierOperator'); + // Qualifier key + operator widget - atomic + const keyNode = node.node.getChild('QualifierKey'); + const operatorNode = node.node.getChild('QualifierOperator'); - if (keyNode && operatorNode) { - const keyDecoration = Decoration.replace({ - widget: new QualifierKeyWidget( - doc.slice(keyNode?.from, keyNode?.to), - doc.slice(operatorNode?.from, operatorNode?.to), - doc.slice(keyNode?.from, keyNode?.to), // label should be found using vocab - keyNode.firstChild?.type.name, - operatorNode.firstChild?.type.name, - true - ) - }); - widgets.push(keyDecoration.range(keyNode?.from, operatorNode?.to)); + if (keyNode && operatorNode) { + const keyDecoration = Decoration.replace({ + widget: new QualifierKeyWidget( + doc.slice(keyNode?.from, keyNode?.to), + doc.slice(operatorNode?.from, operatorNode?.to), + doc.slice(keyNode?.from, keyNode?.to), // label should be found using vocab + keyNode.firstChild?.type.name, + operatorNode.firstChild?.type.name, + true + ) + }); + widgets.push(keyDecoration.range(keyNode?.from, operatorNode?.to)); + } } } - } - }); + }); + } + return Decoration.set(widgets, true); // true = sort } From 89c3dc9a55b87d32f18dd3171aa0ff4b4f8a53c3 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Wed, 27 Nov 2024 16:58:07 +0100 Subject: [PATCH 28/30] Prepend qualifier classes with lxl --- .../extensions/lxlQualifierPlugin/QualifierKey.svelte | 4 ++-- .../lxlQualifierPlugin/QualifierRemove.svelte | 4 ++-- .../src/lib/extensions/lxlQualifierPlugin/index.ts | 2 +- packages/supersearch/src/routes/+page.svelte | 10 +++------- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierKey.svelte b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierKey.svelte index 2a42ad646..702ae8233 100644 --- a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierKey.svelte +++ b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierKey.svelte @@ -10,12 +10,12 @@ const { key, operator, label }: Props = $props(); - + {label || key}{operator} diff --git a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierRemove.svelte b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierRemove.svelte index a528a66c3..49aa7a764 100644 --- a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierRemove.svelte +++ b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierRemove.svelte @@ -15,7 +15,7 @@ }); - + X @@ -23,7 +23,7 @@ From a498da40044f6ed466a5949acdfadb3da6226f0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesper=20Engstr=C3=B6m?= Date: Thu, 28 Nov 2024 15:29:59 +0100 Subject: [PATCH 29/30] Use uniform lxl- classnames, remove som reduntant classes from lang package --- lxl-web/src/lib/styles/lxlquery.css | 19 +++++--------- .../codemirror-lang-lxlquery/src/index.ts | 26 +++++-------------- .../lxlQualifierPlugin/QualifierRemove.svelte | 7 ----- packages/supersearch/src/routes/+page.svelte | 4 +++ 4 files changed, 17 insertions(+), 39 deletions(-) diff --git a/lxl-web/src/lib/styles/lxlquery.css b/lxl-web/src/lib/styles/lxlquery.css index 81334c33c..63c411d15 100644 --- a/lxl-web/src/lib/styles/lxlquery.css +++ b/lxl-web/src/lib/styles/lxlquery.css @@ -1,24 +1,17 @@ -.qualifier { +.lxl-qualifier, +.lxl-qualifier-remove { background: rgba(14, 113, 128, 0.15); padding: 2px 5px; } -.lxlq.qualifier-key { +.lxl-qualifier-key { color: green; } -.lxlq.qualifier-value { - color: green; -} - -.lxlq.qualifier-operator { - color: darkolivegreen; -} - -.lxlq.boolean-query { +.lxl-boolean-query { color: purple; } -.lxlq.wildcard { - color: orange; +.lxl-wildcard { + color: purple; } diff --git a/packages/codemirror-lang-lxlquery/src/index.ts b/packages/codemirror-lang-lxlquery/src/index.ts index 68f93937e..ec129bda0 100644 --- a/packages/codemirror-lang-lxlquery/src/index.ts +++ b/packages/codemirror-lang-lxlquery/src/index.ts @@ -4,10 +4,6 @@ import { styleTags, Tag, tagHighlighter } from '@lezer/highlight'; // custom tags attached to the language parser const customTags = { - Qualifier: Tag.define('Qualifier'), - QualifierKey: Tag.define('QualifierKey'), - QualifierValue: Tag.define('QualifierValue'), - QualifierOperator: Tag.define('QualifierOperator'), BooleanQuery: Tag.define('BooleanQuery'), Wildcard: Tag.define('Wildcard') }; @@ -20,24 +16,16 @@ export const lxlQueryLanguage = LRLanguage.define({ languageData: {} }); -const highlighter = tagHighlighter( - [ - { tag: customTags.Qualifier, class: 'qualifier' }, - { tag: customTags.QualifierKey, class: 'qualifier-key' }, - { tag: customTags.QualifierValue, class: 'qualifier-value' }, - { tag: customTags.QualifierOperator, class: 'qualifier-operator' }, - { tag: customTags.BooleanQuery, class: 'boolean-query' }, - { tag: customTags.Wildcard, class: 'wildcard' } - ], - { - all: 'lxlq' - } -); +const highlighter = tagHighlighter([ + // adding qualifier classes handled by sypersearch/lxlQualifier plugin + { tag: customTags.BooleanQuery, class: 'lxl-boolean-query' }, + { tag: customTags.Wildcard, class: 'lxl-wildcard' } +]); const highlighterExtension = syntaxHighlighting(highlighter); /** - * Libris XL query language together with a highlighter extension + * Libris XL query language together with a highlighter extension * that adds CSS classes for certain nodes */ -export const lxlQuery = new LanguageSupport(lxlQueryLanguage, highlighterExtension) +export const lxlQuery = new LanguageSupport(lxlQueryLanguage, highlighterExtension); diff --git a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierRemove.svelte b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierRemove.svelte index 49aa7a764..f8fc5d39b 100644 --- a/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierRemove.svelte +++ b/packages/supersearch/src/lib/extensions/lxlQualifierPlugin/QualifierRemove.svelte @@ -21,10 +21,3 @@ X - - diff --git a/packages/supersearch/src/routes/+page.svelte b/packages/supersearch/src/routes/+page.svelte index 5c078aa2a..62437b6c0 100644 --- a/packages/supersearch/src/routes/+page.svelte +++ b/packages/supersearch/src/routes/+page.svelte @@ -112,4 +112,8 @@ :global(.lxl-qualifier-value) { background: lightcyan; } + + :global(.lxl-boolean-query, .lxl-wildcard) { + color: purple; + } From bc03b9827d887a1b86a8dcfaca98a7a7a5c16dd0 Mon Sep 17 00:00:00 2001 From: Johan Bisse Mattsson Date: Thu, 28 Nov 2024 17:05:18 +0100 Subject: [PATCH 30/30] Fix tests We will ha to revisit this again... --- .../codemirror-lang-lxlquery/test/cases.txt | 41 +++++++++++-------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/packages/codemirror-lang-lxlquery/test/cases.txt b/packages/codemirror-lang-lxlquery/test/cases.txt index 13515aca6..df8e74d93 100644 --- a/packages/codemirror-lang-lxlquery/test/cases.txt +++ b/packages/codemirror-lang-lxlquery/test/cases.txt @@ -4,7 +4,7 @@ Astrid lindgren ==> -Query() +Query(Identifier Identifier) # Freetext string @@ -13,7 +13,7 @@ Query() ==> -Query() +Query(String) # Freetext with Wildcard @@ -22,7 +22,7 @@ Astrid lind* ==> -Query(Wildcard) +Query(Identifier Identifier Wildcard) # Qualifier with EqualOperator @@ -32,7 +32,7 @@ Query(Wildcard) ==> Query( - Qualifier(QualifierKey,QualifierOperator,QualifierValue) + Qualifier(QualifierKey(Identifier), QualifierOperator(EqualOperator), QualifierValue(Number)) ) @@ -44,8 +44,8 @@ Query( ==> Query( - Qualifier(QualifierKey,QualifierOperator,QualifierValue) - Qualifier(QualifierKey,QualifierOperator,QualifierValue) + Qualifier(QualifierKey(Identifier), QualifierOperator(CompareOperator), QualifierValue(Number)) + Qualifier(QualifierKey(Identifier), QualifierOperator(CompareOperator), QualifierValue(Number)) ) @@ -56,7 +56,7 @@ Query( ==> Query( - Qualifier(QualifierKey,QualifierOperator,QualifierValue) + Qualifier(QualifierKey(String),QualifierOperator(EqualOperator),QualifierValue(Identifier)) ) @@ -67,7 +67,7 @@ contributor:"Astrid Lindgren" ==> Query( - Qualifier(QualifierKey,QualifierOperator,QualifierValue) + Qualifier(QualifierKey(Identifier), QualifierOperator(EqualOperator), QualifierValue(String)) ) @@ -78,7 +78,7 @@ contributor:"libris:fcrtpljz1qp2bdv#it" ==> Query( - Qualifier(QualifierKey,QualifierOperator,QualifierValue) + Qualifier(QualifierKey(Identifier), QualifierOperator(EqualOperator), QualifierValue(String)) ) @@ -89,7 +89,7 @@ sommar OR vinter NOT vår ==> Query( - BooleanQuery + BooleanQuery(Identifier, Identifier, Identifier) ) @@ -99,7 +99,7 @@ OR AND sommar ==> -Query(⚠) +Query(⚠, ⚠, Identifier) # Combined: Qualifier and Freetext @@ -109,7 +109,7 @@ contributor:"Astrid Lindgren" Pippi Långstrump ==> Query( - Qualifier(QualifierKey,QualifierOperator,QualifierValue) + Qualifier(QualifierKey(Identifier), QualifierOperator(EqualOperator), QualifierValue(String)) Identifier Identifier ) @@ -120,7 +120,7 @@ träd* bibliografi:"sigel:DST" NOT typ:Text ==> Query( - Wildcard,BooleanQuery(Qualifier(...),Qualifier(...)) + Identifier, Wildcard, BooleanQuery(Qualifier(QualifierKey(Identifier), QualifierOperator(EqualOperator), QualifierValue(String)), Qualifier(QualifierKey(Identifier), QualifierOperator(EqualOperator), QualifierValue(Identifier))) ) @@ -131,14 +131,19 @@ pippi långstrump språk:(engelska OR franska) ÅR>2002 ==> Query( + Identifier, + Identifier, Qualifier( - QualifierKey,QualifierOperator,QualifierValue( + QualifierKey(Identifier), QualifierOperator(EqualOperator), QualifierValue( Group( - BooleanQuery + BooleanQuery( + Identifier, + Identifier + ) ) ) ), - Qualifier(...) + Qualifier(QualifierKey(Identifier), QualifierOperator(CompareOperator), QualifierValue(Number)) ) @@ -157,7 +162,7 @@ Pippi includeEplikt ==> -Query(Qualifier) +Query(Identifier, Qualifier) # Other filters: include preliminary @@ -166,4 +171,4 @@ includePreliminary Pippi ==> -Query(Qualifier) \ No newline at end of file +Query(Qualifier, Identifier) \ No newline at end of file