client/status bar panes: improve

This commit is contained in:
Adorian Doran 2025-12-22 23:27:45 +02:00
parent 1f55ff536e
commit bdc0b062d5
5 changed files with 151 additions and 116 deletions

View File

@ -818,7 +818,8 @@
},
"inherited_attribute_list": {
"title": "Inherited Attributes",
"no_inherited_attributes": "No inherited attributes."
"no_inherited_attributes": "No inherited attributes.",
"none": "none"
},
"note_info_widget": {
"note_id": "Note ID",
@ -2203,6 +2204,9 @@
"note_paths_title": "Note paths",
"code_note_switcher": "Change language mode"
},
"attributes_panel": {
"title": "Note Attributes"
},
"right_pane": {
"empty_message": "Nothing to show for this note",
"empty_button": "Hide the panel",

View File

@ -244,20 +244,44 @@
> .attribute-list {
font-size: 0.9em;
.inherited-attributes-widget > div {
padding: 0;
font-size: 0.9em;
.attributes-panel-label {
opacity: .5;
margin-inline-end: 4px;
font-weight: 600;
}
.attribute-list-editor {
padding-block: 0 !important;
padding-inline: 0 100px !important ;
}
.inherited-attributes-widget {
display: inline;
> div {
display: inline;
padding: 0;
}
}
.attribute-list-editor-wrapper {
display: flex;
flex-direction: column-reverse;
padding-bottom: 0 !important;
.attribute-list-editor {
padding-block: 0 !important;
padding-inline: 0 100px !important ;
}
.attribute-errors {
padding: 4px 0;
color: var(--dropdown-item-icon-destructive-color);
font-style: italic;
}
.ck.ck-editor__editable::after {
/* Remove a hidden spinner that causes overflow */
display: none;
}
}
.ck.ck-editor__editable::after {
/* Remove a hidden spinner that causes overflow */
display: none;
}
}
div.similar-notes-widget div.similar-notes-wrapper {

View File

@ -268,7 +268,7 @@ function NoteInfoValue({ text, title, value }: { text: string; title?: string, v
function SimilarNotesPane({ note, similarNotesShown, setSimilarNotesShown }: NoteInfoContext) {
return (similarNotesShown &&
<StatusBarPane title="Similar notes"
<StatusBarPane title={t("similar_notes.title")}
className="similar-notes-pane"
visible={similarNotesShown}
setVisible={setSimilarNotesShown}
@ -371,12 +371,13 @@ function AttributesPane({ note, noteContext, attributesShown, setAttributesShown
}), [ api ]));
return (context &&
<StatusBarPane title="Attributes"
<StatusBarPane title={t("attributes_panel.title")}
className="attribute-list"
visible={attributesShown}
setVisible={setAttributesShown}>
<InheritedAttributesTab {...context} />
<span class="attributes-panel-label">{t("inherited_attribute_list.title")}</span>
<InheritedAttributesTab {...context} emptyListString="inherited_attribute_list.none" />
<AttributeEditor
{...context}

View File

@ -9,7 +9,11 @@ import RawHtml from "../react/RawHtml";
import { joinElements } from "../react/react_utils";
import AttributeDetailWidget from "../attribute_widgets/attribute_detail";
export default function InheritedAttributesTab({ note, componentId }: Pick<TabContext, "note" | "componentId">) {
type InheritedAttributesTabArgs = Pick<TabContext, "note" | "componentId"> & {
emptyListString?: string;
}
export default function InheritedAttributesTab({ note, componentId, emptyListString }: InheritedAttributesTabArgs) {
const [ inheritedAttributes, setInheritedAttributes ] = useState<FAttribute[]>();
const [ attributeDetailWidgetEl, attributeDetailWidget ] = useLegacyWidget(() => new AttributeDetailWidget());
@ -63,7 +67,7 @@ export default function InheritedAttributesTab({ note, componentId }: Pick<TabCo
/>
)), " ")
) : (
<>{t("inherited_attribute_list.no_inherited_attributes")}</>
<>{t(emptyListString ?? "inherited_attribute_list.no_inherited_attributes")}</>
)}
</div>

View File

@ -283,6 +283,7 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
return (
<>
{!hidden && <div
className="attribute-list-editor-wrapper"
ref={wrapperRef}
style="position: relative; padding-top: 10px; padding-bottom: 10px"
onKeyDown={(e) => {
@ -296,106 +297,107 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI
setTimeout(() => save(), 100);
}
}}
>
<CKEditor
apiRef={editorRef}
className="attribute-list-editor"
tabIndex={200}
editor={CKEditorAttributeEditor}
currentValue={currentValue}
config={{
toolbar: { items: [] },
placeholder: t("attribute_editor.placeholder"),
mention: { feeds: mentionSetup },
licenseKey: "GPL",
language: "en"
}}
onChange={(currentValue) => {
currentValueRef.current = currentValue ?? "";
const oldValue = getPreprocessedData(lastSavedContent.current ?? "").trimEnd();
const newValue = getPreprocessedData(currentValue ?? "").trimEnd();
setNeedsSaving(oldValue !== newValue);
setError(undefined);
}}
onClick={(e, pos) => {
if (pos && pos.textNode && pos.textNode.data) {
const clickIndex = getClickIndex(pos);
let parsedAttrs: Attribute[];
try {
parsedAttrs = attribute_parser.lexAndParse(getPreprocessedData(currentValueRef.current), true);
} catch (e: unknown) {
// the input is incorrect because the user messed up with it and now needs to fix it manually
console.log(e);
return null;
}
let matchedAttr: Attribute | null = null;
for (const attr of parsedAttrs) {
if (attr.startIndex && clickIndex > attr.startIndex && attr.endIndex && clickIndex <= attr.endIndex) {
matchedAttr = attr;
break;
}
}
setTimeout(() => {
if (matchedAttr) {
attributeDetailWidget.showAttributeDetail({
allAttributes: parsedAttrs,
attribute: matchedAttr,
isOwned: true,
x: e.pageX,
y: e.pageY
});
setState("showAttributeDetail");
} else {
setState("showHelpTooltip");
}
}, 100);
} else {
setState("showHelpTooltip");
}
}}
onKeyDown={() => attributeDetailWidget.hide()}
onBlur={() => save()}
onInitialized={() => editorRef.current?.focus()}
disableNewlines disableSpellcheck
/>
<div className="attribute-editor-buttons">
{ needsSaving && <ActionButton
icon="bx bx-save"
className="save-attributes-button tn-tool-button"
text={escapeQuotes(t("attribute_editor.save_attributes"))}
onClick={save}
/> }
<ActionButton
icon="bx bx-plus"
className="add-new-attribute-button tn-tool-button"
text={escapeQuotes(t("attribute_editor.add_a_new_attribute"))}
onClick={(e) => {
// Prevent automatic hiding of the context menu due to the button being clicked.
e.stopPropagation();
contextMenu.show<AttributeCommandNames>({
x: e.pageX,
y: e.pageY,
orientation: "left",
items: [
{ title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" },
{ title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" },
{ kind: "separator" },
{ title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" },
{ title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" }
],
selectMenuItemHandler: (item) => handleAddNewAttributeCommand(item.command)
});
> <div style="position: relative;">
<CKEditor
apiRef={editorRef}
className="attribute-list-editor"
tabIndex={200}
editor={CKEditorAttributeEditor}
currentValue={currentValue}
config={{
toolbar: { items: [] },
placeholder: t("attribute_editor.placeholder"),
mention: { feeds: mentionSetup },
licenseKey: "GPL",
language: "en"
}}
onChange={(currentValue) => {
currentValueRef.current = currentValue ?? "";
const oldValue = getPreprocessedData(lastSavedContent.current ?? "").trimEnd();
const newValue = getPreprocessedData(currentValue ?? "").trimEnd();
setNeedsSaving(oldValue !== newValue);
setError(undefined);
}}
onClick={(e, pos) => {
if (pos && pos.textNode && pos.textNode.data) {
const clickIndex = getClickIndex(pos);
let parsedAttrs: Attribute[];
try {
parsedAttrs = attribute_parser.lexAndParse(getPreprocessedData(currentValueRef.current), true);
} catch (e: unknown) {
// the input is incorrect because the user messed up with it and now needs to fix it manually
console.log(e);
return null;
}
let matchedAttr: Attribute | null = null;
for (const attr of parsedAttrs) {
if (attr.startIndex && clickIndex > attr.startIndex && attr.endIndex && clickIndex <= attr.endIndex) {
matchedAttr = attr;
break;
}
}
setTimeout(() => {
if (matchedAttr) {
attributeDetailWidget.showAttributeDetail({
allAttributes: parsedAttrs,
attribute: matchedAttr,
isOwned: true,
x: e.pageX,
y: e.pageY
});
setState("showAttributeDetail");
} else {
setState("showHelpTooltip");
}
}, 100);
} else {
setState("showHelpTooltip");
}
}}
onKeyDown={() => attributeDetailWidget.hide()}
onBlur={() => save()}
onInitialized={() => editorRef.current?.focus()}
disableNewlines disableSpellcheck
/>
<div className="attribute-editor-buttons">
{ needsSaving && <ActionButton
icon="bx bx-save"
className="save-attributes-button tn-tool-button"
text={escapeQuotes(t("attribute_editor.save_attributes"))}
onClick={save}
/> }
<ActionButton
icon="bx bx-plus"
className="add-new-attribute-button tn-tool-button"
text={escapeQuotes(t("attribute_editor.add_a_new_attribute"))}
onClick={(e) => {
// Prevent automatic hiding of the context menu due to the button being clicked.
e.stopPropagation();
contextMenu.show<AttributeCommandNames>({
x: e.pageX,
y: e.pageY,
orientation: "left",
items: [
{ title: t("attribute_editor.add_new_label"), command: "addNewLabel", uiIcon: "bx bx-hash" },
{ title: t("attribute_editor.add_new_relation"), command: "addNewRelation", uiIcon: "bx bx-transfer" },
{ kind: "separator" },
{ title: t("attribute_editor.add_new_label_definition"), command: "addNewLabelDefinition", uiIcon: "bx bx-empty" },
{ title: t("attribute_editor.add_new_relation_definition"), command: "addNewRelationDefinition", uiIcon: "bx bx-empty" }
],
selectMenuItemHandler: (item) => handleAddNewAttributeCommand(item.command)
});
}}
/>
</div>
</div>
{ error && (