Compare commits

...

5 Commits

8 changed files with 118 additions and 62 deletions

View File

@@ -1,10 +1,11 @@
import { ensureMimeTypes, highlight, highlightAuto, loadTheme, Themes, type AutoHighlightResult, type HighlightResult, type Theme } from "@triliumnext/highlightjs";
import { MimeType } from "@triliumnext/commons";
import { type AutoHighlightResult, ensureMimeTypes, highlight, highlightAuto, type HighlightResult, loadTheme, type Theme,Themes } from "@triliumnext/highlightjs";
import { copyText, copyTextWithToast } from "./clipboard_ext.js";
import { t } from "./i18n.js";
import mime_types from "./mime_types.js";
import options from "./options.js";
import { t } from "./i18n.js";
import { copyText, copyTextWithToast } from "./clipboard_ext.js";
import { isShare } from "./utils.js";
import { MimeType } from "@triliumnext/commons";
let highlightingLoaded = false;
@@ -76,13 +77,15 @@ export async function applySingleBlockSyntaxHighlight($codeBlock: JQuery<HTMLEle
}
export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
if (highlightingLoaded) {
if (!mimeTypeHint && highlightingLoaded) {
return;
}
// Load theme.
const currentThemeName = String(options.get("codeBlockTheme"));
await loadHighlightingTheme(currentThemeName);
if (!highlightingLoaded) {
const currentThemeName = String(options.get("codeBlockTheme"));
await loadHighlightingTheme(currentThemeName);
}
// Load mime types.
let mimeTypes: MimeType[];
@@ -94,7 +97,7 @@ export async function ensureMimeTypesForHighlighting(mimeTypeHint?: string) {
enabled: true,
mime: mimeTypeHint.replace("-", "/")
}
]
];
} else {
mimeTypes = mime_types.getMimeTypes();
}
@@ -124,9 +127,9 @@ export function isSyntaxHighlightEnabled() {
if (!isShare) {
const theme = options.get("codeBlockTheme");
return !!theme && theme !== "none";
} else {
return true;
}
return true;
}
/**

View File

@@ -217,6 +217,7 @@ function LabelInput({ inputId, ...props }: CellProps & { inputId: string }) {
id={inputId}
type={LABEL_MAPPINGS[definition.labelType ?? "text"]}
value={valueAttr.value}
checked={definition.labelType === "boolean" ? valueAttr.value === "true" : undefined}
placeholder={t("promoted_attributes.unset-field-placeholder")}
data-attribute-id={valueAttr.attributeId}
data-attribute-type={valueAttr.type}

View File

@@ -1,12 +1,15 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { TypeWidgetProps } from "./type_widget";
import render from "../../services/render";
import { refToJQuerySelector } from "../react/react_utils";
import Alert from "../react/Alert";
import "./Render.css";
import { useEffect, useRef, useState } from "preact/hooks";
import attributes from "../../services/attributes";
import { t } from "../../services/i18n";
import RawHtml from "../react/RawHtml";
import render from "../../services/render";
import Alert from "../react/Alert";
import { useTriliumEvent } from "../react/hooks";
import RawHtml from "../react/RawHtml";
import { refToJQuerySelector } from "../react/react_utils";
import { TypeWidgetProps } from "./type_widget";
export default function Render({ note, noteContext, ntxId }: TypeWidgetProps) {
const contentRef = useRef<HTMLDivElement>(null);
@@ -31,6 +34,13 @@ export default function Render({ note, noteContext, ntxId }: TypeWidgetProps) {
refresh();
});
// Refresh on attribute change.
useTriliumEvent("entitiesReloaded", ({ loadResults }) => {
if (loadResults.getAttributeRows().some(a => a.type === "relation" && a.name === "renderNote" && attributes.isAffecting(a, note))) {
refresh();
}
});
// Integration with search.
useTriliumEvent("executeWithContentElement", ({ resolve, ntxId: eventNtxId }) => {
if (eventNtxId !== ntxId) return;

View File

@@ -8,7 +8,7 @@ import { DEFAULT_GUTTER_SIZE } from "../../../services/resizer";
import utils, { isMobile } from "../../../services/utils";
import ActionButton, { ActionButtonProps } from "../../react/ActionButton";
import Admonition from "../../react/Admonition";
import { useNoteLabelBoolean, useTriliumOption } from "../../react/hooks";
import { useNoteBlob, useNoteLabelBoolean, useTriliumOption } from "../../react/hooks";
import { EditableCode, EditableCodeProps } from "../code/Code";
export interface SplitEditorProps extends EditableCodeProps {
@@ -30,12 +30,22 @@ export interface SplitEditorProps extends EditableCodeProps {
* - Can display errors to the user via {@link setError}.
* - Horizontal or vertical orientation for the editor/preview split, adjustable via the switch split orientation button floating button.
*/
export default function SplitEditor({ note, error, splitOptions, previewContent, previewButtons, className, editorBefore, forceOrientation, ...editorProps }: SplitEditorProps) {
const splitEditorOrientation = useSplitOrientation(forceOrientation);
const [ readOnly ] = useNoteLabelBoolean(note, "readOnly");
const containerRef = useRef<HTMLDivElement>(null);
export default function SplitEditor(props: SplitEditorProps) {
const [ readOnly ] = useNoteLabelBoolean(props.note, "readOnly");
const editor = (!readOnly &&
if (readOnly) {
return <ReadOnlyView {...props} />;
}
return <EditorWithSplit {...props} />;
}
function EditorWithSplit({ note, error, splitOptions, previewContent, previewButtons, className, editorBefore, forceOrientation, ...editorProps }: SplitEditorProps) {
const containerRef = useRef<HTMLDivElement>(null);
const splitEditorOrientation = useSplitOrientation(forceOrientation);
const editor = (
<div className="note-detail-split-editor-col">
{editorBefore}
<div className="note-detail-split-editor">
@@ -53,19 +63,14 @@ export default function SplitEditor({ note, error, splitOptions, previewContent,
</div>
);
const preview = (
<div className="note-detail-split-preview-col">
<div className={`note-detail-split-preview ${error ? "on-error" : ""}`}>
{previewContent}
</div>
<div className="btn-group btn-group-sm map-type-switcher content-floating-buttons preview-buttons bottom-right" role="group">
{previewButtons}
</div>
</div>
);
const preview = <PreviewContainer
error={error}
previewContent={previewContent}
previewButtons={previewButtons}
/>;
useEffect(() => {
if (!utils.isDesktop() || !containerRef.current || readOnly) return;
if (!utils.isDesktop() || !containerRef.current) return;
const elements = Array.from(containerRef.current?.children) as HTMLElement[];
const splitInstance = Split(elements, {
rtl: glob.isRtl,
@@ -76,10 +81,10 @@ export default function SplitEditor({ note, error, splitOptions, previewContent,
});
return () => splitInstance.destroy();
}, [ readOnly, splitEditorOrientation ]);
}, [ splitEditorOrientation ]);
return (
<div ref={containerRef} className={`note-detail-split note-detail-printable ${`split-${splitEditorOrientation}`} ${readOnly ? "split-read-only" : ""} ${className ?? ""}`}>
<div ref={containerRef} className={`note-detail-split note-detail-printable ${`split-${splitEditorOrientation}`} ${className ?? ""}`}>
{splitEditorOrientation === "horizontal"
? <>{editor}{preview}</>
: <>{preview}{editor}</>}
@@ -87,6 +92,43 @@ export default function SplitEditor({ note, error, splitOptions, previewContent,
);
}
function ReadOnlyView({ ...props }: SplitEditorProps) {
const { note, onContentChanged } = props;
const content = useNoteBlob(note);
const onContentChangedRef = useRef(onContentChanged);
useEffect(() => {
onContentChangedRef.current = onContentChanged;
});
useEffect(() => {
onContentChangedRef.current?.(content?.content ?? "");
}, [ content ]);
return (
<div className={`note-detail-split note-detail-printable ${props.className} split-read-only`}>
<PreviewContainer {...props} />
</div>
);
}
function PreviewContainer({ error, previewContent, previewButtons }: {
error?: string | null;
previewContent: ComponentChildren;
previewButtons?: ComponentChildren;
}) {
return (
<div className="note-detail-split-preview-col">
<div className={`note-detail-split-preview ${error ? "on-error" : ""}`}>
{previewContent}
</div>
<div className="btn-group btn-group-sm map-type-switcher content-floating-buttons preview-buttons bottom-right" role="group">
{previewButtons}
</div>
</div>
);
}
export function PreviewButton(props: Omit<ActionButtonProps, "titlePosition">) {
return <ActionButton
{...props}

View File

@@ -1,13 +1,14 @@
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import { t } from "../../../services/i18n";
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
import { RawHtmlBlock } from "../../react/RawHtml";
import server from "../../../services/server";
import svgPanZoom from "svg-pan-zoom";
import { RefObject } from "preact";
import { useElementSize, useTriliumEvent } from "../../react/hooks";
import utils from "../../../services/utils";
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
import svgPanZoom from "svg-pan-zoom";
import { t } from "../../../services/i18n";
import server from "../../../services/server";
import toast from "../../../services/toast";
import utils from "../../../services/utils";
import { useElementSize, useTriliumEvent } from "../../react/hooks";
import { RawHtmlBlock } from "../../react/RawHtml";
import SplitEditor, { PreviewButton, SplitEditorProps } from "./SplitEditor";
interface SvgSplitEditorProps extends Omit<SplitEditorProps, "previewContent"> {
/**
@@ -144,7 +145,7 @@ export default function SvgSplitEditor({ ntxId, note, attachmentName, renderSvg,
}
{...props}
/>
)
);
}
function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg: string | undefined) {
@@ -181,7 +182,7 @@ function useResizer(containerRef: RefObject<HTMLDivElement>, noteId: string, svg
lastPanZoom.current = {
pan: zoomInstance.getPan(),
zoom: zoomInstance.getZoom()
}
};
zoomRef.current = undefined;
zoomInstance.destroy();
};

View File

@@ -191,7 +191,6 @@ function ExperimentalOptions() {
values={filteredExperimentalFeatures}
keyProperty="id"
titleProperty="name"
descriptionProperty="description"
currentValue={enabledExperimentalFeatures} onChange={setEnabledExperimentalFeatures}
/>
</OptionsSection>

View File

@@ -1,17 +1,14 @@
import FormCheckbox from "../../../react/FormCheckbox";
interface CheckboxListProps<T> {
values: T[];
keyProperty: keyof T;
titleProperty?: keyof T;
disabledProperty?: keyof T;
descriptionProperty?: keyof T;
currentValue: string[];
onChange: (newValues: string[]) => void;
columnWidth?: string;
}
export default function CheckboxList<T>({ values, keyProperty, titleProperty, disabledProperty, descriptionProperty, currentValue, onChange, columnWidth }: CheckboxListProps<T>) {
export default function CheckboxList<T>({ values, keyProperty, titleProperty, disabledProperty, currentValue, onChange, columnWidth }: CheckboxListProps<T>) {
function toggleValue(value: string) {
if (currentValue.includes(value)) {
// Already there, needs removing.
@@ -25,17 +22,20 @@ export default function CheckboxList<T>({ values, keyProperty, titleProperty, di
return (
<ul style={{ listStyleType: "none", marginBottom: 0, columnWidth: columnWidth ?? "400px" }}>
{values.map(value => (
<li key={String(value[keyProperty])}>
<FormCheckbox
label={String(value[titleProperty ?? keyProperty] ?? value[keyProperty])}
name={String(value[keyProperty])}
currentValue={currentValue.includes(String(value[keyProperty]))}
disabled={!!(disabledProperty && value[disabledProperty])}
hint={value && (descriptionProperty ? String(value[descriptionProperty]) : undefined)}
onChange={() => toggleValue(String(value[keyProperty]))}
/>
<li>
<label className="tn-checkbox">
<input
type="checkbox"
className="form-check-input"
value={String(value[keyProperty])}
checked={currentValue.includes(String(value[keyProperty]))}
disabled={!!(disabledProperty && value[disabledProperty])}
onChange={e => toggleValue((e.target as HTMLInputElement).value)}
/>
{String(value[titleProperty ?? keyProperty] ?? value[keyProperty])}
</label>
</li>
))}
</ul>
);
}
)
}

View File

@@ -22,7 +22,6 @@ export async function ensureMimeTypes(mimeTypes: MimeType[]) {
continue;
}
registeredMimeTypes.add(mime);
const loader = syntaxDefinitions[mime];
if (!loader) {
unsupportedMimeTypes.add(mime);
@@ -31,6 +30,7 @@ export async function ensureMimeTypes(mimeTypes: MimeType[]) {
const language = (await loader()).default;
hljs.registerLanguage(mime, language);
registeredMimeTypes.add(mime);
}
}