mirror of
https://github.com/zadam/trilium.git
synced 2025-10-20 15:19:01 +02:00
feat(react/settings): port code mime types
This commit is contained in:
parent
4a1d379ab4
commit
2ba3666e23
@ -1749,10 +1749,6 @@ button.close:hover {
|
|||||||
flex-grow: 0 !important;
|
flex-grow: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-mime-types {
|
|
||||||
column-width: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
}
|
}
|
||||||
|
@ -233,11 +233,6 @@ div.note-detail-empty {
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options-section .options-mime-types {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.options-section .form-group {
|
.options-section .form-group {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,15 @@ import Column from "../../react/Column";
|
|||||||
import FormCheckbox from "../../react/FormCheckbox";
|
import FormCheckbox from "../../react/FormCheckbox";
|
||||||
import FormGroup from "../../react/FormGroup";
|
import FormGroup from "../../react/FormGroup";
|
||||||
import FormSelect from "../../react/FormSelect";
|
import FormSelect from "../../react/FormSelect";
|
||||||
import { useTriliumOption, useTriliumOptionBool } from "../../react/hooks";
|
import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "../../react/hooks";
|
||||||
import OptionsSection from "./components/OptionsSection";
|
import OptionsSection from "./components/OptionsSection";
|
||||||
import { useEffect, useMemo, useRef } from "preact/hooks";
|
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||||
import codeNoteSample from "./samples/code_note.txt?raw";
|
import codeNoteSample from "./samples/code_note.txt?raw";
|
||||||
import { DEFAULT_PREFIX } from "../abstract_code_type_widget";
|
import { DEFAULT_PREFIX } from "../abstract_code_type_widget";
|
||||||
|
import { MimeType } from "@triliumnext/commons";
|
||||||
|
import mime_types from "../../../services/mime_types";
|
||||||
|
import CheckboxList from "./components/CheckboxList";
|
||||||
|
import { CSSProperties, memo } from "preact/compat";
|
||||||
|
|
||||||
const SAMPLE_MIME = "application/typescript";
|
const SAMPLE_MIME = "application/typescript";
|
||||||
|
|
||||||
@ -17,6 +21,7 @@ export default function CodeNoteSettings() {
|
|||||||
<>
|
<>
|
||||||
<Editor />
|
<Editor />
|
||||||
<Appearance />
|
<Appearance />
|
||||||
|
<CodeMimeTypes />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -116,4 +121,45 @@ function CodeNotePreview({ themeName, wordWrapping }: { themeName: string, wordW
|
|||||||
style={{ margin: 0, height: "200px" }}
|
style={{ margin: 0, height: "200px" }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CodeMimeTypes() {
|
||||||
|
const [ codeNotesMimeTypes, setCodeNotesMimeTypes ] = useTriliumOptionJson<string[]>("codeNotesMimeTypes");
|
||||||
|
const sectionStyle = useMemo(() => ({ marginBottom: "1em", breakInside: "avoid-column" }), []);
|
||||||
|
const groupedMimeTypes: Record<string, MimeType[]> = useMemo(() => {
|
||||||
|
mime_types.loadMimeTypes();
|
||||||
|
|
||||||
|
const ungroupedMimeTypes = Array.from(mime_types.getMimeTypes());
|
||||||
|
const plainTextMimeType = ungroupedMimeTypes.shift();
|
||||||
|
const result: Record<string, MimeType[]> = {};
|
||||||
|
ungroupedMimeTypes.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
|
|
||||||
|
result[""] = [ plainTextMimeType! ];
|
||||||
|
for (const mimeType of ungroupedMimeTypes) {
|
||||||
|
const initial = mimeType.title.charAt(0).toUpperCase();
|
||||||
|
if (!result[initial]) {
|
||||||
|
result[initial] = [];
|
||||||
|
}
|
||||||
|
result[initial].push(mimeType);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OptionsSection title={t("code_mime_types.title")}>
|
||||||
|
<ul class="options-mime-types" style={{ listStyleType: "none", columnWidth: "250px" }}>
|
||||||
|
{Object.entries(groupedMimeTypes).map(([ initial, mimeTypes ]) => (
|
||||||
|
<section style={sectionStyle}>
|
||||||
|
{ initial && <h5>{initial}</h5> }
|
||||||
|
<CheckboxList
|
||||||
|
values={mimeTypes}
|
||||||
|
keyProperty="mime" titleProperty="title"
|
||||||
|
currentValue={codeNotesMimeTypes} onChange={setCodeNotesMimeTypes}
|
||||||
|
columnWidth="inherit"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</OptionsSection>
|
||||||
|
)
|
||||||
}
|
}
|
@ -1,104 +0,0 @@
|
|||||||
import { t } from "../../../../services/i18n.js";
|
|
||||||
import OptionsWidget from "../options_widget.js";
|
|
||||||
import mimeTypesService from "../../../../services/mime_types.js";
|
|
||||||
import type { OptionMap } from "@triliumnext/commons";
|
|
||||||
|
|
||||||
const TPL = /*html*/`
|
|
||||||
<div class="options-section">
|
|
||||||
<h4>${t("code_mime_types.title")}</h4>
|
|
||||||
|
|
||||||
<ul class="options-mime-types" style="list-style-type: none;"></ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.options-mime-types section,
|
|
||||||
.options-mime-types > li:first-of-type {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`;
|
|
||||||
|
|
||||||
let idCtr = 1; // global, since this can be shown in multiple dialogs
|
|
||||||
|
|
||||||
interface MimeType {
|
|
||||||
title: string;
|
|
||||||
mime: string;
|
|
||||||
enabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
type GroupedMimes = Record<string, MimeType[]>;
|
|
||||||
|
|
||||||
function groupMimeTypesAlphabetically(ungroupedMimeTypes: MimeType[]) {
|
|
||||||
const result: GroupedMimes = {};
|
|
||||||
ungroupedMimeTypes = ungroupedMimeTypes.toSorted((a, b) => a.title.localeCompare(b.title));
|
|
||||||
|
|
||||||
for (const mimeType of ungroupedMimeTypes) {
|
|
||||||
const initial = mimeType.title.charAt(0).toUpperCase();
|
|
||||||
if (!result[initial]) {
|
|
||||||
result[initial] = [];
|
|
||||||
}
|
|
||||||
result[initial].push(mimeType);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class CodeMimeTypesOptions extends OptionsWidget {
|
|
||||||
|
|
||||||
private $mimeTypes!: JQuery<HTMLElement>;
|
|
||||||
|
|
||||||
doRender() {
|
|
||||||
this.$widget = $(TPL);
|
|
||||||
this.$mimeTypes = this.$widget.find(".options-mime-types");
|
|
||||||
}
|
|
||||||
|
|
||||||
async optionsLoaded(options: OptionMap) {
|
|
||||||
this.$mimeTypes.empty();
|
|
||||||
mimeTypesService.loadMimeTypes();
|
|
||||||
|
|
||||||
const ungroupedMimeTypes = Array.from(mimeTypesService.getMimeTypes());
|
|
||||||
const plainTextMimeType = ungroupedMimeTypes.shift();
|
|
||||||
const groupedMimeTypes = groupMimeTypesAlphabetically(ungroupedMimeTypes);
|
|
||||||
|
|
||||||
// Plain text is displayed at the top intentionally.
|
|
||||||
if (plainTextMimeType) {
|
|
||||||
const $plainEl = this.#buildSelectionForMimeType(plainTextMimeType);
|
|
||||||
$plainEl.find("input").attr("disabled", "");
|
|
||||||
this.$mimeTypes.append($plainEl);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [initial, mimeTypes] of Object.entries(groupedMimeTypes)) {
|
|
||||||
const $section = $("<section>");
|
|
||||||
$section.append($("<h5>").text(initial));
|
|
||||||
|
|
||||||
for (const mimeType of mimeTypes) {
|
|
||||||
$section.append(this.#buildSelectionForMimeType(mimeType));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$mimeTypes.append($section);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async save() {
|
|
||||||
const enabledMimeTypes: string[] = [];
|
|
||||||
|
|
||||||
this.$mimeTypes.find("input:checked").each((i, el) => {
|
|
||||||
const mimeType = this.$widget.find(el).attr("data-mime-type");
|
|
||||||
if (mimeType) {
|
|
||||||
enabledMimeTypes.push(mimeType);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.updateOption("codeNotesMimeTypes", JSON.stringify(enabledMimeTypes));
|
|
||||||
}
|
|
||||||
|
|
||||||
#buildSelectionForMimeType(mimeType: MimeType) {
|
|
||||||
const id = "code-mime-type-" + idCtr++;
|
|
||||||
|
|
||||||
const checkbox = $(`<label class="tn-checkbox">`)
|
|
||||||
.append($('<input type="checkbox" class="form-check-input">').attr("id", id).attr("data-mime-type", mimeType.mime).prop("checked", mimeType.enabled))
|
|
||||||
.on("change", () => this.save())
|
|
||||||
.append(mimeType.title);
|
|
||||||
|
|
||||||
return $("<li>").append(checkbox);
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,9 +4,10 @@ interface CheckboxListProps<T> {
|
|||||||
titleProperty?: keyof T;
|
titleProperty?: keyof T;
|
||||||
currentValue: string[];
|
currentValue: string[];
|
||||||
onChange: (newValues: string[]) => void;
|
onChange: (newValues: string[]) => void;
|
||||||
|
columnWidth?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CheckboxList<T>({ values, keyProperty, titleProperty, currentValue, onChange }: CheckboxListProps<T>) {
|
export default function CheckboxList<T>({ values, keyProperty, titleProperty, currentValue, onChange, columnWidth }: CheckboxListProps<T>) {
|
||||||
function toggleValue(value: string) {
|
function toggleValue(value: string) {
|
||||||
if (currentValue.includes(value)) {
|
if (currentValue.includes(value)) {
|
||||||
// Already there, needs removing.
|
// Already there, needs removing.
|
||||||
@ -18,7 +19,7 @@ export default function CheckboxList<T>({ values, keyProperty, titleProperty, cu
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul style={{ listStyleType: "none", marginBottom: 0, columnWidth: "400px" }}>
|
<ul style={{ listStyleType: "none", marginBottom: 0, columnWidth: columnWidth ?? "400px" }}>
|
||||||
{values.map(value => (
|
{values.map(value => (
|
||||||
<li>
|
<li>
|
||||||
<label className="tn-checkbox">
|
<label className="tn-checkbox">
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
import OptionsWidget from "../options_widget.js";
|
|
||||||
import { t } from "../../../../services/i18n.js";
|
|
||||||
import type { OptionMap } from "@triliumnext/commons";
|
|
||||||
import utils from "../../../../services/utils.js";
|
|
||||||
import keyboardActionsService from "../../../../services/keyboard_actions.js";
|
|
||||||
import linkService from "../../../.././services/link.js";
|
|
||||||
|
|
||||||
const TPL = /*html*/`
|
|
||||||
<div class="options-section">
|
|
||||||
<p class="description use-tn-links">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="form-group row align-items-center">
|
|
||||||
<div class="col-6">
|
|
||||||
<label for="custom-date-time-format">${t("custom_date_time_format.format_string")}</label>
|
|
||||||
<input type="text" id="custom-date-time-format" class="form-control custom-date-time-format" placeholder="YYYY-MM-DD HH:mm">
|
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<label>${t("custom_date_time_format.formatted_time")}</label>
|
|
||||||
<div class="formatted-date"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default class DateTimeFormatOptions extends OptionsWidget {
|
|
||||||
|
|
||||||
private $formatInput!: JQuery<HTMLInputElement>;
|
|
||||||
private $formattedDate!: JQuery<HTMLInputElement>;
|
|
||||||
|
|
||||||
doRender() {
|
|
||||||
this.$widget = $(TPL);
|
|
||||||
|
|
||||||
this.$formatInput = this.$widget.find("input.custom-date-time-format");
|
|
||||||
this.$formattedDate = this.$widget.find(".formatted-date");
|
|
||||||
|
|
||||||
this.$formatInput.on("input", () => {
|
|
||||||
const dateString = utils.formatDateTime(new Date(), this.$formatInput.val());
|
|
||||||
this.$formattedDate.text(dateString);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$formatInput.on('blur keydown', (e) => {
|
|
||||||
if (e.type === 'blur' || (e.type === 'keydown' && e.key === 'Enter')) {
|
|
||||||
this.updateOption("customDateTimeFormat", this.$formatInput.val());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.$widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
async optionsLoaded(options: OptionMap) {
|
|
||||||
const action = await keyboardActionsService.getAction("");
|
|
||||||
const shortcutKey = (action.effectiveShortcuts ?? []).join(", ");
|
|
||||||
const $link = await linkService.createLink("_hidden/_options/_optionsShortcuts", {
|
|
||||||
"title": shortcutKey,
|
|
||||||
"showTooltip": false
|
|
||||||
});
|
|
||||||
this.$widget.find(".description").find("kbd").replaceWith($link);
|
|
||||||
|
|
||||||
const customDateTimeFormat = options.customDateTimeFormat || "YYYY-MM-DD HH:mm";
|
|
||||||
this.$formatInput.val(customDateTimeFormat);
|
|
||||||
const dateString = utils.formatDateTime(new Date(), customDateTimeFormat);
|
|
||||||
this.$formattedDate.text(dateString);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user