This commit is contained in:
MidoriKurage 2026-06-30 07:03:08 -05:00 committed by GitHub
commit 9c97b5e008
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 177 additions and 0 deletions

View File

@ -222,12 +222,22 @@ function focus() {
editor.value?.commands.focus()
}
function insertText(text: string) {
if (!editor.value) return
editor.value.commands.insertContent(text + ' ')
const content = editor.value.getText()
const processed = processContent(content)
lastEmittedValue = processed
emit('update:modelValue', processed)
}
onBeforeUnmount(() => {
editor.value?.destroy()
})
defineExpose({
focus,
insertText,
})
</script>

View File

@ -0,0 +1,153 @@
<template>
<div id="filter-quick-keywords" class="keywords mbe-2">
<div class="keywords-section">
<span class="keywords-label">{{ $t('filters.keywords.fields') }}</span>
<div class="keywords-buttons">
<BaseButton
v-for="field in filterFields"
:key="field"
v-tooltip="{
content: $t(`filters.query.help.fields.${field}`),
container: '#filter-quick-keywords',
}"
size="small"
variant="ghost"
class="keyword-btn"
@click.prevent.stop="$emit('insert', field + ' = ')"
>
{{ field }}
</BaseButton>
</div>
</div>
<div class="keywords-section">
<span class="keywords-label">{{ $t('filters.keywords.operators') }}</span>
<div class="keywords-buttons">
<BaseButton
v-for="op in filterOperators"
:key="op"
v-tooltip="{
content: $t(`filters.query.help.operators.${operatorKey(op)}`),
container: '#filter-quick-keywords',
}"
size="small"
variant="ghost"
class="keyword-btn"
@click.prevent.stop="$emit('insert', ' ' + op + ' ')"
>
{{ op }}
</BaseButton>
</div>
</div>
<div class="keywords-section">
<span class="keywords-label">{{ $t('filters.keywords.join') }}</span>
<div class="keywords-buttons">
<BaseButton
v-for="join in filterJoinOperators"
:key="join"
v-tooltip="{
content: $t(`filters.query.help.logicalOperators.${joinOperatorKey(join)}`),
container: '#filter-quick-keywords',
}"
size="small"
variant="ghost"
class="keyword-btn"
@click.prevent.stop="$emit('insert', join + ' ')"
>
{{ join }}
</BaseButton>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import BaseButton from '@/components/base/BaseButton.vue'
import {
AVAILABLE_FILTER_FIELDS,
FILTER_OPERATORS,
FILTER_JOIN_OPERATOR,
} from '@/helpers/filters'
defineEmits<{
insert: [text: string],
}>()
const filterFields = AVAILABLE_FILTER_FIELDS
const filterOperators = FILTER_OPERATORS
const filterJoinOperators = FILTER_JOIN_OPERATOR
// Map operator symbols to i18n keys
function operatorKey(op: string): string {
const mapping: Record<string, string> = {
'!=': 'notEqual',
'=': 'equal',
'>': 'greaterThan',
'>=': 'greaterThanOrEqual',
'<': 'lessThan',
'<=': 'lessThanOrEqual',
like: 'like',
'not in': 'notIn',
in: 'in',
'?=': 'equal',
}
return mapping[op] || 'equal'
}
// Map join operators to i18n keys
function joinOperatorKey(join: string): string {
const mapping: Record<string, string> = {
'&&': 'and',
'||': 'or',
'(': 'parentheses',
')': 'parentheses',
}
return mapping[join] || 'and'
}
</script>
<style lang="scss" scoped>
.keywords {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.5rem;
background: var(--grey-100);
border-radius: var(--radius);
}
.keywords-section {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.keywords-label {
font-size: 0.75rem;
font-weight: 600;
color: var(--grey-600);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.keywords-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
}
.keyword-btn {
font-family: monospace;
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
background: var(--white);
border: 1px solid var(--grey-300);
border-radius: var(--radius);
transition: all var(--transition);
&:hover {
background: var(--primary-light);
border-color: var(--primary);
color: var(--primary);
}
}
</style>

View File

@ -13,6 +13,9 @@
class="mbe-2"
@update:modelValue="() => change('modelValue')"
/>
<FilterQuickKeywords @insert="insertFilterKeyword" />
<div
v-if="filterFromView"
class="tw:text-sm mbe-2"
@ -68,6 +71,7 @@ import {
} from '@/helpers/filters'
import FilterInputDocs from '@/components/input/filter/FilterInputDocs.vue'
import FilterInput from '@/components/input/filter/FilterInput.vue'
import FilterQuickKeywords from '@/components/input/filter/FilterQuickKeywords.vue'
const props = withDefaults(defineProps<{
modelValue: TaskFilterParams,
@ -122,6 +126,11 @@ const projectStore = useProjectStore()
const filterInputRef = ref()
function insertFilterKeyword(text: string) {
filterInputRef.value?.insertText(text)
filterInputRef.value?.focus()
}
// Using watchDebounced to prevent the filter re-triggering itself.
watch(
() => props.modelValue,

View File

@ -573,6 +573,11 @@
"noResults": "No results",
"fromView": "The current view has a filter set as well:",
"fromViewBoth": "It will be used in combination with what you enter here.",
"keywords": {
"fields": "Fields",
"operators": "Operators",
"join": "Combine"
},
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",