diff --git a/rsconcept/frontend/src/components/RSInput/RSInput.tsx b/rsconcept/frontend/src/components/RSInput/RSInput.tsx index edd650a5..19ba811b 100644 --- a/rsconcept/frontend/src/components/RSInput/RSInput.tsx +++ b/rsconcept/frontend/src/components/RSInput/RSInput.tsx @@ -82,7 +82,7 @@ const RSInput = forwardRef( caret: colors.fgDefault }, styles: [ - { tag: tags.name, color: colors.fgPurple, cursor: schema ? 'default' : 'text' }, // GlobalID + { tag: tags.name, color: colors.fgPurple, cursor: schema ? 'default' : cursor }, // GlobalID { tag: tags.variableName, color: colors.fgGreen }, // LocalID { tag: tags.propertyName, color: colors.fgTeal }, // Radical { tag: tags.keyword, color: colors.fgBlue }, // keywords @@ -92,7 +92,7 @@ const RSInput = forwardRef( { tag: tags.brace, color: colors.fgPurple, fontWeight: '600' } // braces (curly brackets) ] }), - [disabled, colors, darkMode, schema] + [disabled, colors, darkMode, schema, cursor] ); const editorExtensions = useMemo( @@ -101,7 +101,7 @@ const RSInput = forwardRef( RSLanguage, ccBracketMatching(darkMode), ...(!schema || !onOpenEdit ? [] : [rsNavigation(schema, onOpenEdit)]), - ...(noTooltip || !schema ? [] : [rsHoverTooltip(schema)]) + ...(noTooltip || !schema ? [] : [rsHoverTooltip(schema, onOpenEdit !== undefined)]) ], [darkMode, schema, noTooltip, onOpenEdit] ); diff --git a/rsconcept/frontend/src/components/RSInput/clickNavigation.ts b/rsconcept/frontend/src/components/RSInput/clickNavigation.ts index 2ee94a6b..9e26ac92 100644 --- a/rsconcept/frontend/src/components/RSInput/clickNavigation.ts +++ b/rsconcept/frontend/src/components/RSInput/clickNavigation.ts @@ -4,7 +4,7 @@ import { EditorView } from '@uiw/react-codemirror'; import { ConstituentaID, IRSForm } from '@/models/rsform'; import { findAliasAt } from '@/utils/codemirror'; -const globalsNavigation = (schema: IRSForm, onOpenEdit: (cstID: ConstituentaID) => void) => { +const navigationProducer = (schema: IRSForm, onOpenEdit: (cstID: ConstituentaID) => void) => { return EditorView.domEventHandlers({ click: (event: MouseEvent, view: EditorView) => { if (!event.ctrlKey) { @@ -34,5 +34,5 @@ const globalsNavigation = (schema: IRSForm, onOpenEdit: (cstID: ConstituentaID) }; export function rsNavigation(schema: IRSForm, onOpenEdit: (cstID: ConstituentaID) => void): Extension { - return [globalsNavigation(schema, onOpenEdit)]; + return [navigationProducer(schema, onOpenEdit)]; } diff --git a/rsconcept/frontend/src/components/RSInput/tooltip.ts b/rsconcept/frontend/src/components/RSInput/tooltip.ts index 95239a53..69545683 100644 --- a/rsconcept/frontend/src/components/RSInput/tooltip.ts +++ b/rsconcept/frontend/src/components/RSInput/tooltip.ts @@ -5,7 +5,7 @@ import { IRSForm } from '@/models/rsform'; import { findAliasAt } from '@/utils/codemirror'; import { domTooltipConstituenta } from '@/utils/codemirror'; -const globalsHoverTooltip = (schema: IRSForm) => { +const tooltipProducer = (schema: IRSForm, canClick?: boolean) => { return hoverTooltip((view, pos) => { const { alias, start, end } = findAliasAt(pos, view.state); if (!alias) { @@ -16,11 +16,11 @@ const globalsHoverTooltip = (schema: IRSForm) => { pos: start, end: end, above: false, - create: () => domTooltipConstituenta(cst) + create: () => domTooltipConstituenta(cst, canClick) }; }); }; -export function rsHoverTooltip(schema: IRSForm): Extension { - return [globalsHoverTooltip(schema)]; +export function rsHoverTooltip(schema: IRSForm, canClick?: boolean): Extension { + return [tooltipProducer(schema, canClick)]; } diff --git a/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx b/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx index 6a684a8e..bdfe33fe 100644 --- a/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx +++ b/rsconcept/frontend/src/components/RefsInput/RefsInput.tsx @@ -13,10 +13,11 @@ import Label from '@/components/ui/Label'; import { useConceptOptions } from '@/context/OptionsContext'; import DlgEditReference from '@/dialogs/DlgEditReference'; import { ReferenceType } from '@/models/language'; -import { IRSForm } from '@/models/rsform'; +import { ConstituentaID, IRSForm } from '@/models/rsform'; import { CodeMirrorWrapper } from '@/utils/codemirror'; import { PARAMETER } from '@/utils/constants'; +import { refsNavigation } from './clickNavigation'; import { NaturalLanguage, ReferenceTokens } from './parse'; import { RefEntity } from './parse/parser.terms'; import { refsHoverTooltip } from './tooltip'; @@ -65,6 +66,7 @@ interface RefsInputInputProps label?: string; onChange?: (newValue: string) => void; schema?: IRSForm; + onOpenEdit?: (cstID: ConstituentaID) => void; disabled?: boolean; initialValue?: string; @@ -73,7 +75,23 @@ interface RefsInputInputProps } const RefsInput = forwardRef( - ({ id, label, disabled, schema, initialValue, value, resolved, onFocus, onBlur, onChange, ...restProps }, ref) => { + ( + { + id, // prettier: split-lines + label, + disabled, + schema, + onOpenEdit, + initialValue, + value, + resolved, + onFocus, + onBlur, + onChange, + ...restProps + }, + ref + ) => { const { darkMode, colors } = useConceptOptions(); const [isFocused, setIsFocused] = useState(false); @@ -114,9 +132,10 @@ const RefsInput = forwardRef( EditorView.lineWrapping, EditorView.contentAttributes.of({ spellcheck: 'true' }), NaturalLanguage, - ...(schema ? [refsHoverTooltip(schema, colors)] : []) + ...(!schema || !onOpenEdit ? [] : [refsNavigation(schema, onOpenEdit)]), + ...(schema ? [refsHoverTooltip(schema, colors, onOpenEdit !== undefined)] : []) ], - [schema, colors] + [schema, colors, onOpenEdit] ); function handleChange(newValue: string) { diff --git a/rsconcept/frontend/src/components/RefsInput/clickNavigation.ts b/rsconcept/frontend/src/components/RefsInput/clickNavigation.ts new file mode 100644 index 00000000..3a3a4a14 --- /dev/null +++ b/rsconcept/frontend/src/components/RefsInput/clickNavigation.ts @@ -0,0 +1,38 @@ +import { Extension } from '@codemirror/state'; +import { EditorView } from '@uiw/react-codemirror'; + +import { ConstituentaID, IRSForm } from '@/models/rsform'; +import { findReferenceAt } from '@/utils/codemirror'; + +const navigationProducer = (schema: IRSForm, onOpenEdit: (cstID: ConstituentaID) => void) => { + return EditorView.domEventHandlers({ + click: (event: MouseEvent, view: EditorView) => { + if (!event.ctrlKey) { + return; + } + + const pos = view.posAtCoords({ x: event.clientX, y: event.clientY }); + if (!pos) { + return; + } + + const parse = findReferenceAt(pos, view.state); + if (!parse || !('entity' in parse.ref)) { + return; + } + + const cst = schema.cstByAlias.get(parse.ref.entity); + if (!cst) { + return; + } + + event.preventDefault(); + event.stopPropagation(); + onOpenEdit(cst.id); + } + }); +}; + +export function refsNavigation(schema: IRSForm, onOpenEdit: (cstID: ConstituentaID) => void): Extension { + return [navigationProducer(schema, onOpenEdit)]; +} diff --git a/rsconcept/frontend/src/components/RefsInput/tooltip.ts b/rsconcept/frontend/src/components/RefsInput/tooltip.ts index bddca913..fa9cbc0f 100644 --- a/rsconcept/frontend/src/components/RefsInput/tooltip.ts +++ b/rsconcept/frontend/src/components/RefsInput/tooltip.ts @@ -2,65 +2,58 @@ import { syntaxTree } from '@codemirror/language'; import { Extension } from '@codemirror/state'; import { hoverTooltip } from '@codemirror/view'; -import { parseEntityReference, parseSyntacticReference } from '@/models/languageAPI'; +import { IEntityReference, ISyntacticReference } from '@/models/language'; import { IRSForm } from '@/models/rsform'; import { IColorTheme } from '@/styling/color'; import { domTooltipEntityReference, domTooltipSyntacticReference, findContainedNodes, - findEnvelopingNodes + findReferenceAt } from '@/utils/codemirror'; -import { ReferenceTokens } from './parse'; -import { RefEntity, RefSyntactic } from './parse/parser.terms'; +import { RefEntity } from './parse/parser.terms'; -export const globalsHoverTooltip = (schema: IRSForm, colors: IColorTheme) => { +export const tooltipProducer = (schema: IRSForm, colors: IColorTheme, canClick?: boolean) => { return hoverTooltip((view, pos) => { - const nodes = findEnvelopingNodes(pos, pos, syntaxTree(view.state), ReferenceTokens); - if (nodes.length !== 1) { + const parse = findReferenceAt(pos, view.state); + if (!parse) { return null; } - const start = nodes[0].from; - const end = nodes[0].to; - const text = view.state.doc.sliceString(start, end); - if (nodes[0].type.id === RefEntity) { - const ref = parseEntityReference(text); - const cst = schema.cstByAlias.get(ref.entity); + + if ('entity' in parse.ref) { + const cst = schema.cstByAlias.get(parse.ref.entity); return { - pos: start, - end: end, + pos: parse.start, + end: parse.end, above: false, - create: () => domTooltipEntityReference(ref, cst, colors) + create: () => domTooltipEntityReference(parse.ref as IEntityReference, cst, colors, canClick) }; - } else if (nodes[0].type.id === RefSyntactic) { - const ref = parseSyntacticReference(text); + } else { let masterText: string | undefined = undefined; - if (ref.offset > 0) { - const entities = findContainedNodes(end, view.state.doc.length, syntaxTree(view.state), [RefEntity]); - if (ref.offset <= entities.length) { - const master = entities[ref.offset - 1]; + if (parse.ref.offset > 0) { + const entities = findContainedNodes(parse.end, view.state.doc.length, syntaxTree(view.state), [RefEntity]); + if (parse.ref.offset <= entities.length) { + const master = entities[parse.ref.offset - 1]; masterText = view.state.doc.sliceString(master.from, master.to); } } else { - const entities = findContainedNodes(0, start, syntaxTree(view.state), [RefEntity]); - if (-ref.offset <= entities.length) { - const master = entities[-ref.offset - 1]; + const entities = findContainedNodes(0, parse.start, syntaxTree(view.state), [RefEntity]); + if (-parse.ref.offset <= entities.length) { + const master = entities[-parse.ref.offset - 1]; masterText = view.state.doc.sliceString(master.from, master.to); } } return { - pos: start, - end: end, + pos: parse.start, + end: parse.end, above: false, - create: () => domTooltipSyntacticReference(ref, masterText) + create: () => domTooltipSyntacticReference(parse.ref as ISyntacticReference, masterText, canClick) }; - } else { - return null; } }); }; -export function refsHoverTooltip(schema: IRSForm, colors: IColorTheme): Extension { - return [globalsHoverTooltip(schema, colors)]; +export function refsHoverTooltip(schema: IRSForm, colors: IColorTheme, canClick?: boolean): Extension { + return [tooltipProducer(schema, colors, canClick)]; } diff --git a/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/TemplateTab.tsx b/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/TemplateTab.tsx index 890b43d7..e42726cf 100644 --- a/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/TemplateTab.tsx +++ b/rsconcept/frontend/src/dialogs/DlgConstituentaTemplate/TemplateTab.tsx @@ -24,7 +24,7 @@ interface TemplateTabProps { function TemplateTab({ state, partialUpdate }: TemplateTabProps) { const { templates, retrieveTemplate } = useLibrary(); - const [category, setCategory] = useState(undefined); + const [templateSchema, setTemplateSchema] = useState(undefined); const [filteredData, setFilteredData] = useState([]); @@ -48,16 +48,16 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) { ); const categorySelector = useMemo((): { value: number; label: string }[] => { - if (!category) { + if (!templateSchema) { return []; } - return category.items + return templateSchema.items .filter(cst => cst.cst_type === CATEGORY_CST_TYPE) .map(cst => ({ value: cst.id, label: cst.term_raw })); - }, [category]); + }, [templateSchema]); useEffect(() => { if (templates.length > 0 && !state.templateID) { @@ -67,22 +67,22 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) { useEffect(() => { if (!state.templateID) { - setCategory(undefined); + setTemplateSchema(undefined); } else { - retrieveTemplate(state.templateID, setCategory); + retrieveTemplate(state.templateID, setTemplateSchema); } }, [state.templateID, retrieveTemplate]); useEffect(() => { - if (!category) { + if (!templateSchema) { return; } - let data = category.items; + let data = templateSchema.items; if (state.filterCategory) { - data = applyFilterCategory(state.filterCategory, category); + data = applyFilterCategory(state.filterCategory, templateSchema); } setFilteredData(data); - }, [state.filterCategory, category]); + }, [state.filterCategory, templateSchema]); return ( @@ -93,14 +93,16 @@ function TemplateTab({ state, partialUpdate }: TemplateTabProps) { className='flex-grow border-none' options={categorySelector} value={ - state.filterCategory && category + state.filterCategory && templateSchema ? { value: state.filterCategory.id, label: state.filterCategory.term_raw } : null } - onChange={data => partialUpdate({ filterCategory: data ? category?.cstByID.get(data?.value) : undefined })} + onChange={data => + partialUpdate({ filterCategory: data ? templateSchema?.cstByID.get(data?.value) : undefined }) + } isClearable /> Начальная форма: ${ref.nominal}`; dom.appendChild(nominal); + if (canClick) { + const clickTip = document.createElement('p'); + clickTip.className = 'w-full text-center text-xs mt-2'; + clickTip.innerHTML = 'Ctrl + пробел для редактирования'; + dom.appendChild(clickTip); + } + return { dom: dom }; }