Merge branch 'develop' into tray
@@ -22,7 +22,6 @@ import type LoadResults from "../services/load_results.js";
|
||||
import type { Attribute } from "../services/attribute_parser.js";
|
||||
import type NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
|
||||
import type { ContextMenuEvent } from "../menus/context_menu.js";
|
||||
import type TypeWidget from "../widgets/type_widgets/type_widget.js";
|
||||
import type EditableTextTypeWidget from "../widgets/type_widgets/editable_text.js";
|
||||
import type FAttribute from "../entities/fattribute.js";
|
||||
@@ -58,8 +57,8 @@ export interface ContextMenuCommandData extends CommandData {
|
||||
}
|
||||
|
||||
export interface NoteCommandData extends CommandData {
|
||||
notePath?: string;
|
||||
hoistedNoteId?: string;
|
||||
notePath?: string | null;
|
||||
hoistedNoteId?: string | null;
|
||||
viewScope?: ViewScope;
|
||||
}
|
||||
|
||||
@@ -174,9 +173,9 @@ export type CommandMappings = {
|
||||
callback: (value: NoteDetailWidget | PromiseLike<NoteDetailWidget>) => void;
|
||||
};
|
||||
executeWithTextEditor: CommandData &
|
||||
ExecuteCommandData<TextEditor> & {
|
||||
callback?: GetTextEditorCallback;
|
||||
};
|
||||
ExecuteCommandData<TextEditor> & {
|
||||
callback?: GetTextEditorCallback;
|
||||
};
|
||||
executeWithCodeEditor: CommandData & ExecuteCommandData<CodeMirrorInstance>;
|
||||
/**
|
||||
* Called upon when attempting to retrieve the content element of a {@link NoteContext}.
|
||||
@@ -297,16 +296,13 @@ type EventMappings = {
|
||||
noteContext: NoteContext;
|
||||
notePath?: string | null;
|
||||
};
|
||||
noteSwitchedAndActivatedEvent: {
|
||||
noteSwitchedAndActivated: {
|
||||
noteContext: NoteContext;
|
||||
notePath: string;
|
||||
};
|
||||
setNoteContext: {
|
||||
noteContext: NoteContext;
|
||||
};
|
||||
noteTypeMimeChangedEvent: {
|
||||
noteId: string;
|
||||
};
|
||||
reEvaluateHighlightsListWidgetVisibility: {
|
||||
noteId: string | undefined;
|
||||
};
|
||||
@@ -327,14 +323,14 @@ type EventMappings = {
|
||||
noteId: string;
|
||||
ntxId: string | null;
|
||||
};
|
||||
contextsReopenedEvent: {
|
||||
mainNtxId: string;
|
||||
contextsReopened: {
|
||||
mainNtxId: string | null;
|
||||
tabPosition: number;
|
||||
};
|
||||
noteDetailRefreshed: {
|
||||
ntxId?: string | null;
|
||||
};
|
||||
noteContextReorderEvent: {
|
||||
noteContextReorder: {
|
||||
oldMainNtxId: string;
|
||||
newMainNtxId: string;
|
||||
ntxIdsInOrder: string[];
|
||||
@@ -342,7 +338,7 @@ type EventMappings = {
|
||||
newNoteContextCreated: {
|
||||
noteContext: NoteContext;
|
||||
};
|
||||
noteContextRemovedEvent: {
|
||||
noteContextRemoved: {
|
||||
ntxIds: string[];
|
||||
};
|
||||
exportSvg: {
|
||||
@@ -363,12 +359,17 @@ type EventMappings = {
|
||||
relationMapResetPanZoom: { ntxId: string | null | undefined };
|
||||
relationMapResetZoomIn: { ntxId: string | null | undefined };
|
||||
relationMapResetZoomOut: { ntxId: string | null | undefined };
|
||||
activeNoteChangedEvent: {};
|
||||
activeNoteChanged: {};
|
||||
showAddLinkDialog: {
|
||||
textTypeWidget: EditableTextTypeWidget;
|
||||
text: string;
|
||||
};
|
||||
|
||||
openBulkActionsDialog: {
|
||||
selectedOrActiveNoteIds: string[];
|
||||
};
|
||||
cloneNoteIdsTo: {
|
||||
noteIds: string[];
|
||||
};
|
||||
};
|
||||
|
||||
export type EventListener<T extends EventNames> = {
|
||||
|
||||
@@ -66,12 +66,13 @@ export default class Entrypoints extends Component {
|
||||
}
|
||||
|
||||
async toggleNoteHoistingCommand({ noteId = appContext.tabManager.getActiveContextNoteId() }) {
|
||||
if (!noteId) {
|
||||
const activeNoteContext = appContext.tabManager.getActiveContext();
|
||||
|
||||
if (!activeNoteContext || !noteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const noteToHoist = await froca.getNote(noteId);
|
||||
const activeNoteContext = appContext.tabManager.getActiveContext();
|
||||
|
||||
if (noteToHoist?.noteId === activeNoteContext.hoistedNoteId) {
|
||||
await activeNoteContext.unhoist();
|
||||
@@ -83,6 +84,11 @@ export default class Entrypoints extends Component {
|
||||
async hoistNoteCommand({ noteId }: { noteId: string }) {
|
||||
const noteContext = appContext.tabManager.getActiveContext();
|
||||
|
||||
if (!noteContext) {
|
||||
logError("hoistNoteCommand: noteContext is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (noteContext.hoistedNoteId !== noteId) {
|
||||
await noteContext.setHoistedNoteId(noteId);
|
||||
}
|
||||
@@ -174,7 +180,11 @@ export default class Entrypoints extends Component {
|
||||
}
|
||||
|
||||
async runActiveNoteCommand() {
|
||||
const { ntxId, note } = appContext.tabManager.getActiveContext();
|
||||
const noteContext = appContext.tabManager.getActiveContext();
|
||||
if (!noteContext) {
|
||||
return;
|
||||
}
|
||||
const { ntxId, note } = noteContext;
|
||||
|
||||
// ctrl+enter is also used elsewhere, so make sure we're running only when appropriate
|
||||
if (!note || note.type !== "code") {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import appContext from "./app_context.js";
|
||||
import appContext, { type EventData } from "./app_context.js";
|
||||
import noteCreateService from "../services/note_create.js";
|
||||
import treeService from "../services/tree.js";
|
||||
import hoistedNoteService from "../services/hoisted_note.js";
|
||||
@@ -14,23 +14,19 @@ export default class MainTreeExecutors extends Component {
|
||||
return appContext.noteTreeWidget;
|
||||
}
|
||||
|
||||
async cloneNotesToCommand() {
|
||||
async cloneNotesToCommand({ selectedOrActiveNoteIds }: EventData<"cloneNotesTo">) {
|
||||
if (!this.tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedOrActiveNoteIds = this.tree.getSelectedOrActiveNodes().map((node) => node.data.noteId);
|
||||
|
||||
this.triggerCommand("cloneNoteIdsTo", { noteIds: selectedOrActiveNoteIds });
|
||||
}
|
||||
|
||||
async moveNotesToCommand() {
|
||||
async moveNotesToCommand({ selectedOrActiveBranchIds }: EventData<"moveNotesTo">) {
|
||||
if (!this.tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedOrActiveBranchIds = this.tree.getSelectedOrActiveNodes().map((node) => node.data.branchId);
|
||||
|
||||
this.triggerCommand("moveBranchIdsTo", { branchIds: selectedOrActiveBranchIds });
|
||||
}
|
||||
|
||||
|
||||
@@ -4,23 +4,40 @@ import server from "../services/server.js";
|
||||
import options from "../services/options.js";
|
||||
import froca from "../services/froca.js";
|
||||
import treeService from "../services/tree.js";
|
||||
import utils from "../services/utils.js";
|
||||
import NoteContext from "./note_context.js";
|
||||
import appContext from "./app_context.js";
|
||||
import Mutex from "../utils/mutex.js";
|
||||
import linkService from "../services/link.js";
|
||||
import type { EventData } from "./app_context.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
|
||||
interface TabState {
|
||||
contexts: NoteContext[];
|
||||
position: number;
|
||||
}
|
||||
|
||||
interface NoteContextState {
|
||||
ntxId: string;
|
||||
mainNtxId: string | null;
|
||||
notePath: string | null;
|
||||
hoistedNoteId: string;
|
||||
active: boolean;
|
||||
viewScope: Record<string, any>;
|
||||
}
|
||||
|
||||
export default class TabManager extends Component {
|
||||
public children: NoteContext[];
|
||||
public mutex: Mutex;
|
||||
public activeNtxId: string | null;
|
||||
public recentlyClosedTabs: TabState[];
|
||||
public tabsUpdate: SpacedUpdate;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
/** @property {NoteContext[]} */
|
||||
this.children = [];
|
||||
this.mutex = new Mutex();
|
||||
|
||||
this.activeNtxId = null;
|
||||
|
||||
// elements are arrays of {contexts, position}, storing note contexts for each tab (one main context + subcontexts [splits]), and the original position of the tab
|
||||
this.recentlyClosedTabs = [];
|
||||
|
||||
this.tabsUpdate = new SpacedUpdate(async () => {
|
||||
@@ -28,7 +45,9 @@ export default class TabManager extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
const openNoteContexts = this.noteContexts.map((nc) => nc.getPojoState()).filter((t) => !!t);
|
||||
const openNoteContexts = this.noteContexts
|
||||
.map((nc) => nc.getPojoState())
|
||||
.filter((t) => !!t);
|
||||
|
||||
await server.put("options", {
|
||||
openNoteContexts: JSON.stringify(openNoteContexts)
|
||||
@@ -38,13 +57,11 @@ export default class TabManager extends Component {
|
||||
appContext.addBeforeUnloadListener(this);
|
||||
}
|
||||
|
||||
/** @returns {NoteContext[]} */
|
||||
get noteContexts() {
|
||||
get noteContexts(): NoteContext[] {
|
||||
return this.children;
|
||||
}
|
||||
|
||||
/** @type {NoteContext[]} */
|
||||
get mainNoteContexts() {
|
||||
get mainNoteContexts(): NoteContext[] {
|
||||
return this.noteContexts.filter((nc) => !nc.mainNtxId);
|
||||
}
|
||||
|
||||
@@ -53,11 +70,12 @@ export default class TabManager extends Component {
|
||||
const noteContextsToOpen = (appContext.isMainWindow && options.getJson("openNoteContexts")) || [];
|
||||
|
||||
// preload all notes at once
|
||||
await froca.getNotes([...noteContextsToOpen.flatMap((tab) => [treeService.getNoteIdFromUrl(tab.notePath), tab.hoistedNoteId])], true);
|
||||
await froca.getNotes([...noteContextsToOpen.flatMap((tab: NoteContextState) =>
|
||||
[treeService.getNoteIdFromUrl(tab.notePath), tab.hoistedNoteId])], true);
|
||||
|
||||
const filteredNoteContexts = noteContextsToOpen.filter((openTab) => {
|
||||
const filteredNoteContexts = noteContextsToOpen.filter((openTab: NoteContextState) => {
|
||||
const noteId = treeService.getNoteIdFromUrl(openTab.notePath);
|
||||
if (!(noteId in froca.notes)) {
|
||||
if (noteId && !(noteId in froca.notes)) {
|
||||
// note doesn't exist so don't try to open tab for it
|
||||
return false;
|
||||
}
|
||||
@@ -82,7 +100,7 @@ export default class TabManager extends Component {
|
||||
hoistedNoteId: parsedFromUrl.hoistedNoteId || "root",
|
||||
viewScope: parsedFromUrl.viewScope || {}
|
||||
});
|
||||
} else if (!filteredNoteContexts.find((tab) => tab.active)) {
|
||||
} else if (!filteredNoteContexts.find((tab: NoteContextState) => tab.active)) {
|
||||
filteredNoteContexts[0].active = true;
|
||||
}
|
||||
|
||||
@@ -101,21 +119,30 @@ export default class TabManager extends Component {
|
||||
// if there's a notePath in the URL, make sure it's open and active
|
||||
// (useful, for e.g., opening clipped notes from clipper or opening link in an extra window)
|
||||
if (parsedFromUrl.notePath) {
|
||||
await appContext.tabManager.switchToNoteContext(parsedFromUrl.ntxId, parsedFromUrl.notePath, parsedFromUrl.viewScope, parsedFromUrl.hoistedNoteId);
|
||||
await appContext.tabManager.switchToNoteContext(
|
||||
parsedFromUrl.ntxId,
|
||||
parsedFromUrl.notePath,
|
||||
parsedFromUrl.viewScope,
|
||||
parsedFromUrl.hoistedNoteId
|
||||
);
|
||||
} else if (parsedFromUrl.searchString) {
|
||||
await appContext.triggerCommand("searchNotes", {
|
||||
searchString: parsedFromUrl.searchString
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
logError(`Loading note contexts '${options.get("openNoteContexts")}' failed: ${e.message} ${e.stack}`);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
logError(`Loading note contexts '${options.get("openNoteContexts")}' failed: ${e.message} ${e.stack}`);
|
||||
} else {
|
||||
logError(`Loading note contexts '${options.get("openNoteContexts")}' failed: ${String(e)}`);
|
||||
}
|
||||
|
||||
// try to recover
|
||||
await this.openEmptyTab();
|
||||
}
|
||||
}
|
||||
|
||||
noteSwitchedEvent({ noteContext }) {
|
||||
noteSwitchedEvent({ noteContext }: EventData<"noteSwitched">) {
|
||||
if (noteContext.isActive()) {
|
||||
this.setCurrentNavigationStateToHash();
|
||||
}
|
||||
@@ -135,10 +162,10 @@ export default class TabManager extends Component {
|
||||
const activeNoteContext = this.getActiveContext();
|
||||
this.updateDocumentTitle(activeNoteContext);
|
||||
|
||||
this.triggerEvent("activeNoteChanged"); // trigger this even in on popstate event
|
||||
this.triggerEvent("activeNoteChanged", {}); // trigger this even in on popstate event
|
||||
}
|
||||
|
||||
calculateHash() {
|
||||
calculateHash(): string {
|
||||
const activeNoteContext = this.getActiveContext();
|
||||
if (!activeNoteContext) {
|
||||
return "";
|
||||
@@ -152,21 +179,15 @@ export default class TabManager extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
/** @returns {NoteContext[]} */
|
||||
getNoteContexts() {
|
||||
getNoteContexts(): NoteContext[] {
|
||||
return this.noteContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main context is essentially a tab (children are splits), so this returns tabs.
|
||||
* @returns {NoteContext[]}
|
||||
*/
|
||||
getMainNoteContexts() {
|
||||
getMainNoteContexts(): NoteContext[] {
|
||||
return this.noteContexts.filter((nc) => nc.isMainContext());
|
||||
}
|
||||
|
||||
/** @returns {NoteContext} */
|
||||
getNoteContextById(ntxId) {
|
||||
getNoteContextById(ntxId: string | null): NoteContext {
|
||||
const noteContext = this.noteContexts.find((nc) => nc.ntxId === ntxId);
|
||||
|
||||
if (!noteContext) {
|
||||
@@ -176,58 +197,47 @@ export default class TabManager extends Component {
|
||||
return noteContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active context which represents the visible split with focus. Active context can, but doesn't have to be "main".
|
||||
*
|
||||
* @returns {NoteContext}
|
||||
*/
|
||||
getActiveContext() {
|
||||
getActiveContext(): NoteContext | null {
|
||||
return this.activeNtxId ? this.getNoteContextById(this.activeNtxId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active main context which corresponds to the active tab.
|
||||
*
|
||||
* @returns {NoteContext}
|
||||
*/
|
||||
getActiveMainContext() {
|
||||
getActiveMainContext(): NoteContext | null {
|
||||
return this.activeNtxId ? this.getNoteContextById(this.activeNtxId).getMainContext() : null;
|
||||
}
|
||||
|
||||
/** @returns {string|null} */
|
||||
getActiveContextNotePath() {
|
||||
getActiveContextNotePath(): string | null {
|
||||
const activeContext = this.getActiveContext();
|
||||
return activeContext ? activeContext.notePath : null;
|
||||
return activeContext?.notePath ?? null;
|
||||
}
|
||||
|
||||
/** @returns {FNote} */
|
||||
getActiveContextNote() {
|
||||
getActiveContextNote(): FNote | null {
|
||||
const activeContext = this.getActiveContext();
|
||||
return activeContext ? activeContext.note : null;
|
||||
}
|
||||
|
||||
/** @returns {string|null} */
|
||||
getActiveContextNoteId() {
|
||||
getActiveContextNoteId(): string | null {
|
||||
const activeNote = this.getActiveContextNote();
|
||||
|
||||
return activeNote ? activeNote.noteId : null;
|
||||
}
|
||||
|
||||
/** @returns {string|null} */
|
||||
getActiveContextNoteType() {
|
||||
getActiveContextNoteType(): string | null {
|
||||
const activeNote = this.getActiveContextNote();
|
||||
|
||||
return activeNote ? activeNote.type : null;
|
||||
}
|
||||
/** @returns {string|null} */
|
||||
getActiveContextNoteMime() {
|
||||
const activeNote = this.getActiveContextNote();
|
||||
|
||||
getActiveContextNoteMime(): string | null {
|
||||
const activeNote = this.getActiveContextNote();
|
||||
return activeNote ? activeNote.mime : null;
|
||||
}
|
||||
|
||||
async switchToNoteContext(ntxId, notePath, viewScope = {}, hoistedNoteId = null) {
|
||||
const noteContext = this.noteContexts.find((nc) => nc.ntxId === ntxId) || (await this.openEmptyTab());
|
||||
async switchToNoteContext(
|
||||
ntxId: string | null,
|
||||
notePath: string,
|
||||
viewScope: Record<string, any> = {},
|
||||
hoistedNoteId: string | null = null
|
||||
) {
|
||||
const noteContext = this.noteContexts.find((nc) => nc.ntxId === ntxId) ||
|
||||
await this.openEmptyTab();
|
||||
|
||||
await this.activateNoteContext(noteContext.ntxId);
|
||||
|
||||
@@ -242,20 +252,21 @@ export default class TabManager extends Component {
|
||||
|
||||
async openAndActivateEmptyTab() {
|
||||
const noteContext = await this.openEmptyTab();
|
||||
|
||||
await this.activateNoteContext(noteContext.ntxId);
|
||||
|
||||
await noteContext.setEmpty();
|
||||
noteContext.setEmpty();
|
||||
}
|
||||
|
||||
async openEmptyTab(ntxId = null, hoistedNoteId = "root", mainNtxId) {
|
||||
async openEmptyTab(
|
||||
ntxId: string | null = null,
|
||||
hoistedNoteId: string = "root",
|
||||
mainNtxId: string | null = null
|
||||
): Promise<NoteContext> {
|
||||
const noteContext = new NoteContext(ntxId, hoistedNoteId, mainNtxId);
|
||||
|
||||
const existingNoteContext = this.children.find((nc) => nc.ntxId === noteContext.ntxId);
|
||||
|
||||
if (existingNoteContext) {
|
||||
await existingNoteContext.setHoistedNoteId(hoistedNoteId);
|
||||
|
||||
return existingNoteContext;
|
||||
}
|
||||
|
||||
@@ -266,29 +277,37 @@ export default class TabManager extends Component {
|
||||
return noteContext;
|
||||
}
|
||||
|
||||
async openInNewTab(targetNoteId, hoistedNoteId = null) {
|
||||
const noteContext = await this.openEmptyTab(null, hoistedNoteId || this.getActiveContext().hoistedNoteId);
|
||||
async openInNewTab(targetNoteId: string, hoistedNoteId: string | null = null) {
|
||||
const noteContext = await this.openEmptyTab(null, hoistedNoteId || this.getActiveContext()?.hoistedNoteId);
|
||||
|
||||
await noteContext.setNote(targetNoteId);
|
||||
}
|
||||
|
||||
async openInSameTab(targetNoteId, hoistedNoteId = null) {
|
||||
async openInSameTab(targetNoteId: string, hoistedNoteId: string | null = null) {
|
||||
const activeContext = this.getActiveContext();
|
||||
if (!activeContext) return;
|
||||
|
||||
await activeContext.setHoistedNoteId(hoistedNoteId || activeContext.hoistedNoteId);
|
||||
await activeContext.setNote(targetNoteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the requested notePath is within current note hoisting scope then keep the note hoisting also for the new tab.
|
||||
*/
|
||||
async openTabWithNoteWithHoisting(notePath, opts = {}) {
|
||||
async openTabWithNoteWithHoisting(
|
||||
notePath: string,
|
||||
opts: {
|
||||
activate?: boolean | null;
|
||||
ntxId?: string | null;
|
||||
mainNtxId?: string | null;
|
||||
hoistedNoteId?: string | null;
|
||||
viewScope?: Record<string, any> | null;
|
||||
} = {}
|
||||
): Promise<NoteContext> {
|
||||
const noteContext = this.getActiveContext();
|
||||
let hoistedNoteId = "root";
|
||||
|
||||
if (noteContext) {
|
||||
const resolvedNotePath = await treeService.resolveNotePath(notePath, noteContext.hoistedNoteId);
|
||||
|
||||
if (resolvedNotePath.includes(noteContext.hoistedNoteId) || resolvedNotePath.includes("_hidden")) {
|
||||
if (resolvedNotePath?.includes(noteContext.hoistedNoteId) || resolvedNotePath?.includes("_hidden")) {
|
||||
hoistedNoteId = noteContext.hoistedNoteId;
|
||||
}
|
||||
}
|
||||
@@ -298,7 +317,16 @@ export default class TabManager extends Component {
|
||||
return this.openContextWithNote(notePath, opts);
|
||||
}
|
||||
|
||||
async openContextWithNote(notePath, opts = {}) {
|
||||
async openContextWithNote(
|
||||
notePath: string | null,
|
||||
opts: {
|
||||
activate?: boolean | null;
|
||||
ntxId?: string | null;
|
||||
mainNtxId?: string | null;
|
||||
hoistedNoteId?: string | null;
|
||||
viewScope?: Record<string, any> | null;
|
||||
} = {}
|
||||
): Promise<NoteContext> {
|
||||
const activate = !!opts.activate;
|
||||
const ntxId = opts.ntxId || null;
|
||||
const mainNtxId = opts.mainNtxId || null;
|
||||
@@ -306,7 +334,6 @@ export default class TabManager extends Component {
|
||||
const viewScope = opts.viewScope || { viewMode: "default" };
|
||||
|
||||
const noteContext = await this.openEmptyTab(ntxId, hoistedNoteId, mainNtxId);
|
||||
|
||||
if (notePath) {
|
||||
await noteContext.setNote(notePath, {
|
||||
// if activate is false, then send normal noteSwitched event
|
||||
@@ -315,7 +342,7 @@ export default class TabManager extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
if (activate) {
|
||||
if (activate && noteContext.notePath) {
|
||||
this.activateNoteContext(noteContext.ntxId, false);
|
||||
|
||||
await this.triggerEvent("noteSwitchedAndActivated", {
|
||||
@@ -327,21 +354,24 @@ export default class TabManager extends Component {
|
||||
return noteContext;
|
||||
}
|
||||
|
||||
async activateOrOpenNote(noteId) {
|
||||
async activateOrOpenNote(noteId: string) {
|
||||
for (const noteContext of this.getNoteContexts()) {
|
||||
if (noteContext.note && noteContext.note.noteId === noteId) {
|
||||
this.activateNoteContext(noteContext.ntxId);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if no tab with this note has been found we'll create new tab
|
||||
|
||||
await this.openContextWithNote(noteId, { activate: true });
|
||||
}
|
||||
|
||||
async activateNoteContext(ntxId, triggerEvent = true) {
|
||||
async activateNoteContext(ntxId: string | null, triggerEvent: boolean = true) {
|
||||
if (!ntxId) {
|
||||
logError("activateNoteContext: ntxId is null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ntxId === this.activeNtxId) {
|
||||
return;
|
||||
}
|
||||
@@ -359,14 +389,10 @@ export default class TabManager extends Component {
|
||||
this.setCurrentNavigationStateToHash();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ntxId
|
||||
* @returns {Promise<boolean>} true if note context has been removed, false otherwise
|
||||
*/
|
||||
async removeNoteContext(ntxId) {
|
||||
async removeNoteContext(ntxId: string | null): Promise<boolean> {
|
||||
// removing note context is an async process which can take some time, if users presses CTRL-W quickly, two
|
||||
// close events could interleave which would then lead to attempting to activate already removed context.
|
||||
return await this.mutex.runExclusively(async () => {
|
||||
return await this.mutex.runExclusively(async (): Promise<boolean> => {
|
||||
let noteContextToRemove;
|
||||
|
||||
try {
|
||||
@@ -399,7 +425,7 @@ export default class TabManager extends Component {
|
||||
const noteContextsToRemove = noteContextToRemove.getSubContexts();
|
||||
const ntxIdsToRemove = noteContextsToRemove.map((nc) => nc.ntxId);
|
||||
|
||||
await this.triggerEvent("beforeNoteContextRemove", { ntxIds: ntxIdsToRemove });
|
||||
await this.triggerEvent("beforeNoteContextRemove", { ntxIds: ntxIdsToRemove.filter((id) => id !== null) });
|
||||
|
||||
if (!noteContextToRemove.isMainContext()) {
|
||||
const siblings = noteContextToRemove.getMainContext().getSubContexts();
|
||||
@@ -421,12 +447,11 @@ export default class TabManager extends Component {
|
||||
}
|
||||
|
||||
this.removeNoteContexts(noteContextsToRemove);
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
removeNoteContexts(noteContextsToRemove) {
|
||||
removeNoteContexts(noteContextsToRemove: NoteContext[]) {
|
||||
const ntxIdsToRemove = noteContextsToRemove.map((nc) => nc.ntxId);
|
||||
|
||||
const position = this.noteContexts.findIndex((nc) => ntxIdsToRemove.includes(nc.ntxId));
|
||||
@@ -435,12 +460,12 @@ export default class TabManager extends Component {
|
||||
|
||||
this.addToRecentlyClosedTabs(noteContextsToRemove, position);
|
||||
|
||||
this.triggerEvent("noteContextRemoved", { ntxIds: ntxIdsToRemove });
|
||||
this.triggerEvent("noteContextRemoved", { ntxIds: ntxIdsToRemove.filter((id) => id !== null) });
|
||||
|
||||
this.tabsUpdate.scheduleUpdate();
|
||||
}
|
||||
|
||||
addToRecentlyClosedTabs(noteContexts, position) {
|
||||
addToRecentlyClosedTabs(noteContexts: NoteContext[], position: number) {
|
||||
if (noteContexts.length === 1 && noteContexts[0].isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@@ -448,26 +473,42 @@ export default class TabManager extends Component {
|
||||
this.recentlyClosedTabs.push({ contexts: noteContexts, position: position });
|
||||
}
|
||||
|
||||
tabReorderEvent({ ntxIdsInOrder }) {
|
||||
const order = {};
|
||||
tabReorderEvent({ ntxIdsInOrder }: { ntxIdsInOrder: string[] }) {
|
||||
const order: Record<string, number> = {};
|
||||
|
||||
let i = 0;
|
||||
|
||||
for (const ntxId of ntxIdsInOrder) {
|
||||
for (const noteContext of this.getNoteContextById(ntxId).getSubContexts()) {
|
||||
order[noteContext.ntxId] = i++;
|
||||
if (noteContext.ntxId) {
|
||||
order[noteContext.ntxId] = i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.children.sort((a, b) => (order[a.ntxId] < order[b.ntxId] ? -1 : 1));
|
||||
this.children.sort((a, b) => {
|
||||
if (!a.ntxId || !b.ntxId) return 0;
|
||||
return (order[a.ntxId] ?? 0) < (order[b.ntxId] ?? 0) ? -1 : 1;
|
||||
});
|
||||
|
||||
this.tabsUpdate.scheduleUpdate();
|
||||
}
|
||||
|
||||
noteContextReorderEvent({ ntxIdsInOrder, oldMainNtxId, newMainNtxId }) {
|
||||
noteContextReorderEvent({
|
||||
ntxIdsInOrder,
|
||||
oldMainNtxId,
|
||||
newMainNtxId
|
||||
}: {
|
||||
ntxIdsInOrder: string[];
|
||||
oldMainNtxId?: string;
|
||||
newMainNtxId?: string;
|
||||
}) {
|
||||
const order = Object.fromEntries(ntxIdsInOrder.map((v, i) => [v, i]));
|
||||
|
||||
this.children.sort((a, b) => (order[a.ntxId] < order[b.ntxId] ? -1 : 1));
|
||||
this.children.sort((a, b) => {
|
||||
if (!a.ntxId || !b.ntxId) return 0;
|
||||
return (order[a.ntxId] ?? 0) < (order[b.ntxId] ?? 0) ? -1 : 1;
|
||||
});
|
||||
|
||||
if (oldMainNtxId && newMainNtxId) {
|
||||
this.children.forEach((c) => {
|
||||
@@ -485,7 +526,8 @@ export default class TabManager extends Component {
|
||||
}
|
||||
|
||||
async activateNextTabCommand() {
|
||||
const activeMainNtxId = this.getActiveMainContext().ntxId;
|
||||
const activeMainNtxId = this.getActiveMainContext()?.ntxId;
|
||||
if (!activeMainNtxId) return;
|
||||
|
||||
const oldIdx = this.mainNoteContexts.findIndex((nc) => nc.ntxId === activeMainNtxId);
|
||||
const newActiveNtxId = this.mainNoteContexts[oldIdx === this.mainNoteContexts.length - 1 ? 0 : oldIdx + 1].ntxId;
|
||||
@@ -494,7 +536,8 @@ export default class TabManager extends Component {
|
||||
}
|
||||
|
||||
async activatePreviousTabCommand() {
|
||||
const activeMainNtxId = this.getActiveMainContext().ntxId;
|
||||
const activeMainNtxId = this.getActiveMainContext()?.ntxId;
|
||||
if (!activeMainNtxId) return;
|
||||
|
||||
const oldIdx = this.mainNoteContexts.findIndex((nc) => nc.ntxId === activeMainNtxId);
|
||||
const newActiveNtxId = this.mainNoteContexts[oldIdx === 0 ? this.mainNoteContexts.length - 1 : oldIdx - 1].ntxId;
|
||||
@@ -506,9 +549,8 @@ export default class TabManager extends Component {
|
||||
await this.removeNoteContext(this.activeNtxId);
|
||||
}
|
||||
|
||||
beforeUnloadEvent() {
|
||||
beforeUnloadEvent(): boolean {
|
||||
this.tabsUpdate.updateNowIfNecessary();
|
||||
|
||||
return true; // don't block closing the tab, this metadata is not that important
|
||||
}
|
||||
|
||||
@@ -522,7 +564,7 @@ export default class TabManager extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
async closeOtherTabsCommand({ ntxId }) {
|
||||
async closeOtherTabsCommand({ ntxId }: { ntxId: string }) {
|
||||
for (const ntxIdToRemove of this.mainNoteContexts.map((nc) => nc.ntxId)) {
|
||||
if (ntxIdToRemove !== ntxId) {
|
||||
await this.removeNoteContext(ntxIdToRemove);
|
||||
@@ -530,7 +572,7 @@ export default class TabManager extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
async closeRightTabsCommand({ ntxId }) {
|
||||
async closeRightTabsCommand({ ntxId }: { ntxId: string }) {
|
||||
const ntxIds = this.mainNoteContexts.map((nc) => nc.ntxId);
|
||||
const index = ntxIds.indexOf(ntxId);
|
||||
|
||||
@@ -542,11 +584,11 @@ export default class TabManager extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
async closeTabCommand({ ntxId }) {
|
||||
async closeTabCommand({ ntxId }: { ntxId: string }) {
|
||||
await this.removeNoteContext(ntxId);
|
||||
}
|
||||
|
||||
async moveTabToNewWindowCommand({ ntxId }) {
|
||||
async moveTabToNewWindowCommand({ ntxId }: { ntxId: string }) {
|
||||
const { notePath, hoistedNoteId } = this.getNoteContextById(ntxId);
|
||||
|
||||
const removed = await this.removeNoteContext(ntxId);
|
||||
@@ -556,17 +598,16 @@ export default class TabManager extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
async copyTabToNewWindowCommand({ ntxId }) {
|
||||
async copyTabToNewWindowCommand({ ntxId }: { ntxId: string }) {
|
||||
const { notePath, hoistedNoteId } = this.getNoteContextById(ntxId);
|
||||
this.triggerCommand("openInWindow", { notePath, hoistedNoteId });
|
||||
}
|
||||
|
||||
async reopenLastTabCommand() {
|
||||
let closeLastEmptyTab = null;
|
||||
|
||||
await this.mutex.runExclusively(async () => {
|
||||
const closeLastEmptyTab: NoteContext | undefined = await this.mutex.runExclusively(async () => {
|
||||
let closeLastEmptyTab
|
||||
if (this.recentlyClosedTabs.length === 0) {
|
||||
return;
|
||||
return closeLastEmptyTab;
|
||||
}
|
||||
|
||||
if (this.noteContexts.length === 1 && this.noteContexts[0].isEmpty()) {
|
||||
@@ -575,6 +616,8 @@ export default class TabManager extends Component {
|
||||
}
|
||||
|
||||
const lastClosedTab = this.recentlyClosedTabs.pop();
|
||||
if (!lastClosedTab) return closeLastEmptyTab;
|
||||
|
||||
const noteContexts = lastClosedTab.contexts;
|
||||
|
||||
for (const noteContext of noteContexts) {
|
||||
@@ -589,7 +632,7 @@ export default class TabManager extends Component {
|
||||
...this.noteContexts.slice(-noteContexts.length),
|
||||
...this.noteContexts.slice(lastClosedTab.position, -noteContexts.length)
|
||||
];
|
||||
await this.noteContextReorderEvent({ ntxIdsInOrder: ntxsInOrder.map((nc) => nc.ntxId) });
|
||||
this.noteContextReorderEvent({ ntxIdsInOrder: ntxsInOrder.map((nc) => nc.ntxId).filter((id) => id !== null) });
|
||||
|
||||
let mainNtx = noteContexts.find((nc) => nc.isMainContext());
|
||||
if (mainNtx) {
|
||||
@@ -601,13 +644,14 @@ export default class TabManager extends Component {
|
||||
} else {
|
||||
// reopened a single split, need to reorder the pane widget in split note container
|
||||
await this.triggerEvent("contextsReopened", {
|
||||
ntxId: ntxsInOrder[lastClosedTab.position].ntxId,
|
||||
mainNtxId: ntxsInOrder[lastClosedTab.position].ntxId,
|
||||
// this is safe since lastClosedTab.position can never be 0 in this case
|
||||
afterNtxId: ntxsInOrder[lastClosedTab.position - 1].ntxId
|
||||
tabPosition: lastClosedTab.position - 1
|
||||
});
|
||||
}
|
||||
|
||||
const noteContextToActivate = noteContexts.length === 1 ? noteContexts[0] : noteContexts.find((nc) => nc.isMainContext());
|
||||
if (!noteContextToActivate) return closeLastEmptyTab;
|
||||
|
||||
await this.activateNoteContext(noteContextToActivate.ntxId);
|
||||
|
||||
@@ -615,6 +659,7 @@ export default class TabManager extends Component {
|
||||
noteContext: noteContextToActivate,
|
||||
notePath: noteContextToActivate.notePath
|
||||
});
|
||||
return closeLastEmptyTab;
|
||||
});
|
||||
|
||||
if (closeLastEmptyTab) {
|
||||
@@ -626,7 +671,9 @@ export default class TabManager extends Component {
|
||||
this.tabsUpdate.scheduleUpdate();
|
||||
}
|
||||
|
||||
async updateDocumentTitle(activeNoteContext) {
|
||||
async updateDocumentTitle(activeNoteContext: NoteContext | null) {
|
||||
if (!activeNoteContext) return;
|
||||
|
||||
const titleFragments = [
|
||||
// it helps to navigate in history if note title is included in the title
|
||||
await activeNoteContext.getNavigationTitle(),
|
||||
@@ -636,7 +683,7 @@ export default class TabManager extends Component {
|
||||
document.title = titleFragments.join(" - ");
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }) {
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
const activeContext = this.getActiveContext();
|
||||
|
||||
if (activeContext && loadResults.isNoteReloaded(activeContext.noteId)) {
|
||||
@@ -646,7 +693,6 @@ export default class TabManager extends Component {
|
||||
|
||||
async frocaReloadedEvent() {
|
||||
const activeContext = this.getActiveContext();
|
||||
|
||||
if (activeContext) {
|
||||
await this.updateDocumentTitle(activeContext);
|
||||
}
|
||||
2812
src/public/app/doc_notes/en/User Guide/!!!meta.json
generated
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 4.6 KiB |
@@ -18,40 +18,49 @@
|
||||
</p>
|
||||
<p>The Calendar view of Book notes will display each child note in a calendar
|
||||
that has a start date and optionally an end date, as an event.</p>
|
||||
<p>The Calendar view has multiple display modes:</p>
|
||||
<ul>
|
||||
<li>Week view, where all the 7 days of the week (or 5 if the weekends are
|
||||
hidden) are displayed in columns. This mode allows entering and displaying
|
||||
time-specific events, not just all-day events.</li>
|
||||
<li>Month view, where the entire month is displayed and all-day events can
|
||||
be inserted. Both time-specific events and all-day events are listed.</li>
|
||||
<li>Year view, which displays the entire year for quick reference.</li>
|
||||
<li>List view, which displays all the events of a given month in sequence.</li>
|
||||
</ul>
|
||||
<p>Unlike other Book view types, the Calendar view also allows some kind
|
||||
of interaction, such as moving events around as well as creating new ones.</p>
|
||||
<h2>Creating a calendar</h2>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<img src="2_Calendar View_image.png">
|
||||
</td>
|
||||
<td>The Calendar View works only for Book note types. To create a new note,
|
||||
right click on the note tree on the left and select Insert note after,
|
||||
or Insert child note and then select <em>Book</em>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>
|
||||
<img src="3_Calendar View_image.png">
|
||||
</td>
|
||||
<td>Once created, the “View type” of the Book needs changed to “Calendar”,
|
||||
by selecting the “Book Properties” tab in the ribbon.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h2>Creating a new event/note</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<img src="2_Calendar View_image.png">
|
||||
</td>
|
||||
<td>The Calendar View works only for Book note types. To create a new note,
|
||||
right click on the note tree on the left and select Insert note after,
|
||||
or Insert child note and then select <em>Book</em>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>
|
||||
<img src="3_Calendar View_image.png">
|
||||
</td>
|
||||
<td>Once created, the “View type” of the Book needs changed to “Calendar”,
|
||||
by selecting the “Book Properties” tab in the ribbon.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Creating a new event/note</h2>
|
||||
<ul>
|
||||
<li>Clicking on a day will create a new child note and assign it to that particular
|
||||
day.
|
||||
@@ -72,7 +81,7 @@
|
||||
<ul>
|
||||
<li>Hovering the mouse over an event will display information about the note.
|
||||
<br>
|
||||
<img src="9_Calendar View_image.png">
|
||||
<img src="7_Calendar View_image.png">
|
||||
</li>
|
||||
<li>Left clicking the event will go to that note. Middle clicking will open
|
||||
the note in a new tab and right click will offer more options including
|
||||
@@ -83,284 +92,272 @@
|
||||
</ul>
|
||||
<h2>Configuring the calendar</h2>
|
||||
<p>The following attributes can be added to the book type:</p>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>#calendar:hideWeekends</code>
|
||||
</td>
|
||||
<td>When present (regardless of value), it will hide Saturday and Sundays
|
||||
from the calendar.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:weekNumbers</code>
|
||||
</td>
|
||||
<td>When present (regardless of value), it will show the number of the week
|
||||
on the calendar.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:view</code>
|
||||
</td>
|
||||
<td>
|
||||
<p>Which view to display in the calendar:</p>
|
||||
<ul>
|
||||
<li><code>timeGridWeek</code> for the <em>week</em> view;</li>
|
||||
<li><code>dayGridMonth</code> for the <em>month</em> view;</li>
|
||||
<li><code>multiMonthYear</code> for the <em>year</em> view;</li>
|
||||
<li><code>listMonth</code> for the <em>list</em> view.</li>
|
||||
</ul>
|
||||
<p>Any other value will be dismissed and the default view (month) will be
|
||||
used instead.</p>
|
||||
<p>The value of this label is automatically updated when changing the view
|
||||
using the UI buttons.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>~child:template</code>
|
||||
</td>
|
||||
<td>Defines the template for newly created notes in the calendar (via dragging
|
||||
or clicking).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>#calendar:hideWeekends</code>
|
||||
</td>
|
||||
<td>When present (regardless of value), it will hide Saturday and Sundays
|
||||
from the calendar.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:weekNumbers</code>
|
||||
</td>
|
||||
<td>When present (regardless of value), it will show the number of the week
|
||||
on the calendar.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:view</code>
|
||||
</td>
|
||||
<td>
|
||||
<p>Which view to display in the calendar:</p>
|
||||
<ul>
|
||||
<li><code>timeGridWeek</code> for the <em>week</em> view;</li>
|
||||
<li><code>dayGridMonth</code> for the <em>month</em> view;</li>
|
||||
<li><code>multiMonthYear</code> for the <em>year</em> view;</li>
|
||||
<li><code>listMonth</code> for the <em>list</em> view.</li>
|
||||
</ul>
|
||||
<p>Any other value will be dismissed and the default view (month) will be
|
||||
used instead.</p>
|
||||
<p>The value of this label is automatically updated when changing the view
|
||||
using the UI buttons.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>~child:template</code>
|
||||
</td>
|
||||
<td>Defines the template for newly created notes in the calendar (via dragging
|
||||
or clicking).</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>In addition, the first day of the week can be either Sunday or Monday
|
||||
and can be adjusted from the application settings.</p>
|
||||
<h2>Configuring the calendar events</h2>
|
||||
<p>For each note of the calendar, the following attributes can be used:</p>
|
||||
<figure
|
||||
class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>#startDate</code>
|
||||
</td>
|
||||
<td>The date the event starts, which will display it in the calendar. The
|
||||
format is <code>YYYY-MM-DD</code> (year, month and day separated by a minus
|
||||
sign).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#endDate</code>
|
||||
</td>
|
||||
<td>Similar to <code>startDate</code>, mentions the end date if the event spans
|
||||
across multiple days. The date is inclusive, so the end day is also considered.
|
||||
The attribute can be missing for single-day events.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#startTime</code>
|
||||
</td>
|
||||
<td>The time the event starts at. If this value is missing, then the event
|
||||
is considered a full-day event. The format is <code>HH:MM</code> (hours in
|
||||
24-hour format and minutes).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#endTime</code>
|
||||
</td>
|
||||
<td>Similar to <code>startTime</code>, it mentions the time at which the event
|
||||
ends (in relation with <code>endDate</code> if present, or <code>startDate</code>).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#color</code>
|
||||
</td>
|
||||
<td>Displays the event with a specified color (named such as <code>red</code>, <code>gray</code> or
|
||||
hex such as <code>#FF0000</code>). This will also change the color of the
|
||||
note in other places such as the note tree.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:color</code>
|
||||
</td>
|
||||
<td>Similar to <code>#color</code>, but applies the color only for the event
|
||||
in the calendar and not for other places such as the note tree.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#iconClass</code>
|
||||
</td>
|
||||
<td>If present, the icon of the note will be displayed to the left of the
|
||||
event title.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:title</code>
|
||||
</td>
|
||||
<td>Changes the title of an event to point to an attribute of the note other
|
||||
than the title, either a label (e.g. <code>#assignee</code>) or a relation
|
||||
(e.g. <code>~for</code>). See <em>Advanced use-cases</em> for more information.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:displayedAttributes</code>
|
||||
</td>
|
||||
<td>Allows displaying the value of one or more attributes in the calendar
|
||||
like this:
|
||||
<br>
|
||||
<br>
|
||||
<img src="11_Calendar View_image.png">
|
||||
<br>
|
||||
<br><code>#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"</code>
|
||||
<br>
|
||||
<br>It can also be used with relations, case in which it will display the
|
||||
title of the target note:
|
||||
<br>
|
||||
<br><code>~assignee=@My assignee #calendar:displayedAttributes="assignee"</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:startDate</code>
|
||||
</td>
|
||||
<td>Allows using a different label to represent the start date, other than <code>startDate</code> (e.g. <code>expiryDate</code>).
|
||||
The label name <strong>must not be</strong> prefixed with <code>#</code>.
|
||||
If the label is not defined for a note, the default will be used instead.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:endDate</code>
|
||||
</td>
|
||||
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
|
||||
which is being used to read the end date.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:startTime</code>
|
||||
</td>
|
||||
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
|
||||
which is being used to read the start time.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:endTime</code>
|
||||
</td>
|
||||
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
|
||||
which is being used to read the end time.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h2>How the calendar works</h2>
|
||||
<p>
|
||||
<img src="14_Calendar View_image.png">
|
||||
</p>
|
||||
<p>The calendar displays all the child notes of the book that have a <code>#startDate</code>.
|
||||
An <code>#endDate</code> can optionally be added.</p>
|
||||
<p>If editing the start date and end date from the note itself is desirable,
|
||||
the following attributes can be added to the book note:</p><pre><code class="language-text-x-trilium-auto">#viewType=calendar #label:startDate(inheritable)="promoted,alias=Start Date,single,date"
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>#startDate</code>
|
||||
</td>
|
||||
<td>The date the event starts, which will display it in the calendar. The
|
||||
format is <code>YYYY-MM-DD</code> (year, month and day separated by a minus
|
||||
sign).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#endDate</code>
|
||||
</td>
|
||||
<td>Similar to <code>startDate</code>, mentions the end date if the event spans
|
||||
across multiple days. The date is inclusive, so the end day is also considered.
|
||||
The attribute can be missing for single-day events.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#startTime</code>
|
||||
</td>
|
||||
<td>The time the event starts at. If this value is missing, then the event
|
||||
is considered a full-day event. The format is <code>HH:MM</code> (hours in
|
||||
24-hour format and minutes).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#endTime</code>
|
||||
</td>
|
||||
<td>Similar to <code>startTime</code>, it mentions the time at which the event
|
||||
ends (in relation with <code>endDate</code> if present, or <code>startDate</code>).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#color</code>
|
||||
</td>
|
||||
<td>Displays the event with a specified color (named such as <code>red</code>, <code>gray</code> or
|
||||
hex such as <code>#FF0000</code>). This will also change the color of the
|
||||
note in other places such as the note tree.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:color</code>
|
||||
</td>
|
||||
<td>Similar to <code>#color</code>, but applies the color only for the event
|
||||
in the calendar and not for other places such as the note tree.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#iconClass</code>
|
||||
</td>
|
||||
<td>If present, the icon of the note will be displayed to the left of the
|
||||
event title.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:title</code>
|
||||
</td>
|
||||
<td>Changes the title of an event to point to an attribute of the note other
|
||||
than the title, can either a label or a relation (without the <code>#</code> or <code>~</code> symbol).
|
||||
See <em>Use-cases</em> for more information.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:displayedAttributes</code>
|
||||
</td>
|
||||
<td>Allows displaying the value of one or more attributes in the calendar
|
||||
like this:
|
||||
<br>
|
||||
<br>
|
||||
<img src="9_Calendar View_image.png">
|
||||
<br>
|
||||
<br><code>#weight="70" #Mood="Good" #calendar:displayedAttributes="weight,Mood"</code>
|
||||
<br>
|
||||
<br>It can also be used with relations, case in which it will display the
|
||||
title of the target note:
|
||||
<br>
|
||||
<br><code>~assignee=@My assignee #calendar:displayedAttributes="assignee"</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:startDate</code>
|
||||
</td>
|
||||
<td>Allows using a different label to represent the start date, other than <code>startDate</code> (e.g. <code>expiryDate</code>).
|
||||
The label name <strong>must not be</strong> prefixed with <code>#</code>.
|
||||
If the label is not defined for a note, the default will be used instead.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:endDate</code>
|
||||
</td>
|
||||
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
|
||||
which is being used to read the end date.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:startTime</code>
|
||||
</td>
|
||||
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
|
||||
which is being used to read the start time.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>#calendar:endTime</code>
|
||||
</td>
|
||||
<td>Similar to <code>#calendar:startDate</code>, allows changing the attribute
|
||||
which is being used to read the end time.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>How the calendar works</h2>
|
||||
<p>
|
||||
<img src="11_Calendar View_image.png">
|
||||
</p>
|
||||
<p>The calendar displays all the child notes of the book that have a <code>#startDate</code>.
|
||||
An <code>#endDate</code> can optionally be added.</p>
|
||||
<p>If editing the start date and end date from the note itself is desirable,
|
||||
the following attributes can be added to the book note:</p><pre><code class="language-text-x-trilium-auto">#viewType=calendar #label:startDate(inheritable)="promoted,alias=Start Date,single,date"
|
||||
#label:endDate(inheritable)="promoted,alias=End Date,single,date"
|
||||
#hidePromotedAttributes </code></pre>
|
||||
<p>This will result in:</p>
|
||||
<p>
|
||||
<img src="12_Calendar View_image.png">
|
||||
</p>
|
||||
<p>When not used in a Journal, the calendar is recursive. That is, it will
|
||||
look for events not just in its child notes but also in the children of
|
||||
these child notes.</p>
|
||||
<h2>Use-cases</h2>
|
||||
<h3>Using with the Journal / calendar</h3>
|
||||
<p>It is possible to integrate the calendar view into the Journal with day
|
||||
notes. In order to do so change the note type of the Journal note (calendar
|
||||
root) to Book and then select the Calendar View.</p>
|
||||
<p>Based on the <code>#calendarRoot</code> (or <code>#workspaceCalendarRoot</code>)
|
||||
attribute, the calendar will know that it's in a calendar and apply the
|
||||
following:</p>
|
||||
<ul>
|
||||
<li>The calendar events are now rendered based on their <code>dateNote</code> attribute
|
||||
rather than <code>startDate</code>.</li>
|
||||
<li>Interactive editing such as dragging over an empty era or resizing an
|
||||
event is no longer possible.</li>
|
||||
<li>Clicking on the empty space on a date will automatically open that day's
|
||||
note or create it if it does not exist.</li>
|
||||
<li>Direct children of a day note will be displayed on the calendar despite
|
||||
not having a <code>dateNote</code> attribute. Children of the child notes
|
||||
will not be displayed.</li>
|
||||
</ul>
|
||||
<p>
|
||||
<img src="10_Calendar View_image.png">
|
||||
</p>
|
||||
<h3>Using a different attribute as event title</h3>
|
||||
<p>By default, events are displayed on the calendar by their note title.
|
||||
However, it is possible to configure a different attribute to be displayed
|
||||
instead.</p>
|
||||
<p>To do so, assign <code>#calendar:title</code> to the child note (not the
|
||||
calendar/book note), with the value being <code>#name</code> where <code>name</code> can
|
||||
be any label. The attribute can also come through inheritance such as a
|
||||
template attribute. If the note does not have the requested label, the
|
||||
title of the note will be used instead.</p>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="5_Calendar View_image.png">
|
||||
</td>
|
||||
<td>
|
||||
<img src="7_Calendar View_image.png">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<h3>Using a relation attribute as event title</h3>
|
||||
<p>Similarly to using an attribute, use <code>#calendar:title</code> and set
|
||||
it to <code>~name</code> where <code>name</code> is the name of the relation
|
||||
to use.</p>
|
||||
<p>Moreover, if there are more relations of the same name, they will be displayed
|
||||
as multiple events coming from the same note.</p>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="6_Calendar View_image.png">
|
||||
</td>
|
||||
<td>
|
||||
<img src="8_Calendar View_image.png">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p>Note that it's even possible to have a <code>#calendar:title</code> on the
|
||||
target note (e.g. “John Smith”) which will try to render an attribute of
|
||||
it. Note that it's not possible to use a relation here as well for safety
|
||||
reasons (an accidental recursion of attributes could cause the application
|
||||
to loop infinitely).</p>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="13_Calendar View_image.png">
|
||||
</td>
|
||||
<td>
|
||||
<img src="1_Calendar View_image.png">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
<p>This will result in:</p>
|
||||
<p>
|
||||
<img src="10_Calendar View_image.png">
|
||||
</p>
|
||||
<p>When not used in a Journal, the calendar is recursive. That is, it will
|
||||
look for events not just in its child notes but also in the children of
|
||||
these child notes.</p>
|
||||
<h2>Use-cases</h2>
|
||||
<h3>Using with the Journal / calendar</h3>
|
||||
<p>It is possible to integrate the calendar view into the Journal with day
|
||||
notes. In order to do so change the note type of the Journal note (calendar
|
||||
root) to Book and then select the Calendar View.</p>
|
||||
<p>Based on the <code>#calendarRoot</code> (or <code>#workspaceCalendarRoot</code>)
|
||||
attribute, the calendar will know that it's in a calendar and apply the
|
||||
following:</p>
|
||||
<ul>
|
||||
<li>The calendar events are now rendered based on their <code>dateNote</code> attribute
|
||||
rather than <code>startDate</code>.</li>
|
||||
<li>Interactive editing such as dragging over an empty era or resizing an
|
||||
event is no longer possible.</li>
|
||||
<li>Clicking on the empty space on a date will automatically open that day's
|
||||
note or create it if it does not exist.</li>
|
||||
<li>Direct children of a day note will be displayed on the calendar despite
|
||||
not having a <code>dateNote</code> attribute. Children of the child notes
|
||||
will not be displayed.</li>
|
||||
</ul>
|
||||
<p>
|
||||
<img src="8_Calendar View_image.png">
|
||||
</p>
|
||||
<h3>Using a different attribute as event title</h3>
|
||||
<p>By default, events are displayed on the calendar by their note title.
|
||||
However, it is possible to configure a different attribute to be displayed
|
||||
instead.</p>
|
||||
<p>To do so, assign <code>#calendar:title</code> to the child note (not the
|
||||
calendar/book note), with the value being <code>name</code> where <code>name</code> can
|
||||
be any label (make not to add the <code>#</code> prefix). The attribute can
|
||||
also come through inheritance such as a template attribute. If the note
|
||||
does not have the requested label, the title of the note will be used instead.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><pre><code class="language-text-x-trilium-auto">#startDate=2025-02-11 #endDate=2025-02-13 #name="My vacation" #calendar:title="name"</code></pre>
|
||||
</td>
|
||||
<td>
|
||||
<img src="5_Calendar View_image.png">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Using a relation attribute as event title</h3>
|
||||
<p>Similarly to using an attribute, use <code>#calendar:title</code> and set
|
||||
it to <code>name</code> where <code>name</code> is the name of the relation
|
||||
to use.</p>
|
||||
<p>Moreover, if there are more relations of the same name, they will be displayed
|
||||
as multiple events coming from the same note.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>#startDate=2025-02-14 #endDate=2025-02-15 ~for=@John Smith ~for=@Jane Doe #calendar:title="for"</code>
|
||||
</td>
|
||||
<td>
|
||||
<img src="6_Calendar View_image.png">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Note that it's even possible to have a <code>#calendar:title</code> on the
|
||||
target note (e.g. “John Smith”) which will try to render an attribute of
|
||||
it. Note that it's not possible to use a relation here as well for safety
|
||||
reasons (an accidental recursion of attributes could cause the application
|
||||
to loop infinitely).</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>#calendar:title="shortName" #shortName="John S."</code>
|
||||
</td>
|
||||
<td>
|
||||
<img src="1_Calendar View_image.png">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -13,62 +13,59 @@
|
||||
<h1 data-trilium-h1>Admonitions</h1>
|
||||
|
||||
<div class="ck-content">
|
||||
<div>
|
||||
<div>
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:959/547;" src="1_Admonitions_image.png" width="959"
|
||||
height="547">
|
||||
</figure>
|
||||
<p>Admonitions are a way to highlight information to the reader. Other names
|
||||
for it include <em>call-outs</em> and <em>info/warning/alert boxes</em>.</p>
|
||||
<h2>Inserting a new admonition</h2>
|
||||
<h3>From the UI</h3>
|
||||
<p>In the Formatting toolbar:</p>
|
||||
<p>
|
||||
<img src="Admonitions_image.png" width="202" height="194">
|
||||
</p>
|
||||
<h3>Via the keyboard</h3>
|
||||
<p>It's possible to insert an admonition simply by typing:</p>
|
||||
<ul>
|
||||
<li><code>!!! note</code>
|
||||
</li>
|
||||
<li><code>!!! tip</code>
|
||||
</li>
|
||||
<li><code>!!! important</code>
|
||||
</li>
|
||||
<li><code>!!! caution</code>
|
||||
</li>
|
||||
<li><code>!!! warning</code>
|
||||
</li>
|
||||
</ul>
|
||||
<p>In addition to that, it's also possible to type <code>!!! </code> followed
|
||||
by any text, case in which a default admonition type will appear (note)
|
||||
with the entered text inside it.</p>
|
||||
<h2>Interaction</h2>
|
||||
<p>By design, admonitions act very similar to block quotes.</p>
|
||||
<ul>
|
||||
<li>Selecting a text and pressing the admonition button will turn that text
|
||||
into an admonition.</li>
|
||||
<li>If selecting multiple admonitions, pressing the admonition button will
|
||||
automatically merge them into one.</li>
|
||||
</ul>
|
||||
<p>Inside an admonition:</p>
|
||||
<ul>
|
||||
<li>Pressing <kbd>Backspace</kbd> while the admonition is empty will remove
|
||||
it.</li>
|
||||
<li>Pressing <kbd>Enter</kbd> will start a new paragraph. Pressing it twice
|
||||
will exit out of the admonition.</li>
|
||||
<li>Headings and other block content including tables can be inserted inside
|
||||
the admonition.</li>
|
||||
</ul>
|
||||
<h2>Types of admonitions</h2>
|
||||
<p>There are currently five types of admonitions: <em>Note</em>, <em>Tip</em>, <em>Important</em>, <em>Caution</em>, <em>Warning</em>.</p>
|
||||
<p>These types were inspired by GitHub's support for this feature and there
|
||||
are currently no plans for adjusting it or allowing the user to customize
|
||||
them.</p>
|
||||
<h2>Markdown support</h2>
|
||||
<p>The Markdown syntax for admonitions as supported by Trilium is the one
|
||||
that GitHub uses, which is as follows:</p><pre><code class="language-text-x-markdown">> [!NOTE]
|
||||
<p>
|
||||
<img src="1_Admonitions_image.png">
|
||||
</p>
|
||||
<p>Admonitions are a way to highlight information to the reader. Other names
|
||||
for it include <em>call-outs</em> and <em>info/warning/alert boxes</em>.</p>
|
||||
<h2>Inserting a new admonition</h2>
|
||||
<h3>From the UI</h3>
|
||||
<p>In the Formatting toolbar:</p>
|
||||
<p>
|
||||
<img src="Admonitions_image.png">
|
||||
</p>
|
||||
<h3>Via the keyboard</h3>
|
||||
<p>It's possible to insert an admonition simply by typing:</p>
|
||||
<ul>
|
||||
<li><code>!!! note</code>
|
||||
</li>
|
||||
<li><code>!!! tip</code>
|
||||
</li>
|
||||
<li><code>!!! important</code>
|
||||
</li>
|
||||
<li><code>!!! caution</code>
|
||||
</li>
|
||||
<li><code>!!! warning</code>
|
||||
</li>
|
||||
</ul>
|
||||
<p>In addition to that, it's also possible to type <code>!!!</code> followed
|
||||
by any text, case in which a default admonition type will appear (note)
|
||||
with the entered text inside it.</p>
|
||||
<h2>Interaction</h2>
|
||||
<p>By design, admonitions act very similar to block quotes.</p>
|
||||
<ul>
|
||||
<li>Selecting a text and pressing the admonition button will turn that text
|
||||
into an admonition.</li>
|
||||
<li>If selecting multiple admonitions, pressing the admonition button will
|
||||
automatically merge them into one.</li>
|
||||
</ul>
|
||||
<p>Inside an admonition:</p>
|
||||
<ul>
|
||||
<li>Pressing <kbd>Backspace</kbd> while the admonition is empty will remove
|
||||
it.</li>
|
||||
<li>Pressing <kbd>Enter</kbd> will start a new paragraph. Pressing it twice
|
||||
will exit out of the admonition.</li>
|
||||
<li>Headings and other block content including tables can be inserted inside
|
||||
the admonition.</li>
|
||||
</ul>
|
||||
<h2>Types of admonitions</h2>
|
||||
<p>There are currently five types of admonitions: <em>Note</em>, <em>Tip</em>, <em>Important</em>, <em>Caution</em>, <em>Warning</em>.</p>
|
||||
<p>These types were inspired by GitHub's support for this feature and there
|
||||
are currently no plans for adjusting it or allowing the user to customize
|
||||
them.</p>
|
||||
<h2>Markdown support</h2>
|
||||
<p>The Markdown syntax for admonitions as supported by Trilium is the one
|
||||
that GitHub uses, which is as follows:</p><pre><code class="language-text-x-trilium-auto">> [!NOTE]
|
||||
> This is a note.
|
||||
|
||||
> [!TIP]
|
||||
@@ -79,10 +76,8 @@
|
||||
|
||||
> [!CAUTION]
|
||||
> This is a caution.</code></pre>
|
||||
<p>There are currently no plans of supporting alternative admonition syntaxes
|
||||
such as <code>!!! note</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<p>There are currently no plans of supporting alternative admonition syntaxes
|
||||
such as <code>!!! note</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
134
src/public/app/doc_notes/en/User Guide/navigation.html
generated
@@ -9,6 +9,73 @@
|
||||
<ul>
|
||||
<li><a href="User%20Guide.html" target="detail">User Guide</a>
|
||||
<ul>
|
||||
<li>Advanced Usage
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Attributes.html" target="detail">Attributes</a>
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Attributes/Attribute%20Inheritance.html"
|
||||
target="detail">Attribute Inheritance</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Attributes/Promoted%20Attributes.html"
|
||||
target="detail">Promoted Attributes</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Attributes/Template.html" target="detail">Template</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Relation%20Map.html" target="detail">Relation Map</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Note%20Map.html" target="detail">Note Map</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Sharing.html" target="detail">Sharing</a>
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Sharing/Serving%20directly%20the%20content%20o.html"
|
||||
target="detail">Serving directly the content of a note</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Advanced%20Showcases.html" target="detail">Advanced Showcases</a>
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Advanced%20Showcases/Day%20Notes.html"
|
||||
target="detail">Day Notes</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Advanced%20Showcases/Weight%20Tracker.html"
|
||||
target="detail">Weight Tracker</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Advanced%20Showcases/Task%20Manager.html"
|
||||
target="detail">Task Manager</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Custom%20Request%20Handler.html"
|
||||
target="detail">Custom Request Handler</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Custom%20Resource%20Providers.html"
|
||||
target="detail">Custom Resource Providers</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/ETAPI%20(REST%20API).html" target="detail">ETAPI (REST API)</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Default%20Note%20Title.html" target="detail">Default Note Title</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Database.html" target="detail">Database</a>
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Database/Manually%20altering%20the%20database.html"
|
||||
target="detail">Manually altering the database</a>
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Database/Manually%20altering%20the%20database/SQL%20Console.html"
|
||||
target="detail">SQL Console</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Configuration%20(config.ini%20or%20e.html"
|
||||
target="detail">Configuration (config.ini or environment variables)</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Bulk%20actions.html" target="detail">Bulk actions</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Installation & Setup
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Installation%20%26%20Setup/Desktop%20Installation.html"
|
||||
@@ -235,73 +302,6 @@
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Advanced Usage
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Attributes.html" target="detail">Attributes</a>
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Attributes/Attribute%20Inheritance.html"
|
||||
target="detail">Attribute Inheritance</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Attributes/Promoted%20Attributes.html"
|
||||
target="detail">Promoted Attributes</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Attributes/Template.html" target="detail">Template</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Relation%20Map.html" target="detail">Relation Map</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Note%20Map.html" target="detail">Note Map</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Sharing.html" target="detail">Sharing</a>
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Sharing/Serving%20directly%20the%20content%20o.html"
|
||||
target="detail">Serving directly the content of a note</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Advanced%20Showcases.html" target="detail">Advanced Showcases</a>
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Advanced%20Showcases/Day%20Notes.html"
|
||||
target="detail">Day Notes</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Advanced%20Showcases/Weight%20Tracker.html"
|
||||
target="detail">Weight Tracker</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Advanced%20Showcases/Task%20Manager.html"
|
||||
target="detail">Task Manager</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Custom%20Request%20Handler.html"
|
||||
target="detail">Custom Request Handler</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Custom%20Resource%20Providers.html"
|
||||
target="detail">Custom Resource Providers</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/ETAPI%20(REST%20API).html" target="detail">ETAPI (REST API)</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Default%20Note%20Title.html" target="detail">Default Note Title</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Database.html" target="detail">Database</a>
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Database/Manually%20altering%20the%20database.html"
|
||||
target="detail">Manually altering the database</a>
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Database/Manually%20altering%20the%20database/SQL%20Console.html"
|
||||
target="detail">SQL Console</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Configuration%20(config.ini%20or%20e.html"
|
||||
target="detail">Configuration (config.ini or environment variables)</a>
|
||||
</li>
|
||||
<li><a href="User%20Guide/Advanced%20Usage/Bulk%20actions.html" target="detail">Bulk actions</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Theme development
|
||||
<ul>
|
||||
<li><a href="User%20Guide/Theme%20development/Creating%20a%20custom%20theme.html"
|
||||
|
||||
1
src/public/app/doc_notes/en/User Guide/style.css
generated
@@ -25,7 +25,6 @@
|
||||
border-radius: 0.5em;
|
||||
padding: 1em;
|
||||
margin: 1.25em 0;
|
||||
margin-right: 14px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -22,13 +22,19 @@ function getItems(): MenuItem<CommandNames>[] {
|
||||
|
||||
function handleLinkContextMenuItem(command: string | undefined, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) {
|
||||
if (!hoistedNoteId) {
|
||||
hoistedNoteId = appContext.tabManager.getActiveContext().hoistedNoteId;
|
||||
hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId ?? null;
|
||||
}
|
||||
|
||||
if (command === "openNoteInNewTab") {
|
||||
appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope });
|
||||
} else if (command === "openNoteInNewSplit") {
|
||||
const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
|
||||
const subContexts = appContext.tabManager.getActiveContext()?.getSubContexts();
|
||||
|
||||
if (!subContexts) {
|
||||
logError("subContexts is null");
|
||||
return;
|
||||
}
|
||||
|
||||
const { ntxId } = subContexts[subContexts.length - 1];
|
||||
|
||||
appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope });
|
||||
|
||||
@@ -288,11 +288,15 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
|
||||
|
||||
const noteContext = ntxId ? appContext.tabManager.getNoteContextById(ntxId) : appContext.tabManager.getActiveContext();
|
||||
|
||||
noteContext.setNote(notePath, { viewScope }).then(() => {
|
||||
if (noteContext !== appContext.tabManager.getActiveContext()) {
|
||||
appContext.tabManager.activateNoteContext(noteContext.ntxId);
|
||||
}
|
||||
});
|
||||
if (noteContext) {
|
||||
noteContext.setNote(notePath, { viewScope }).then(() => {
|
||||
if (noteContext !== appContext.tabManager.getActiveContext()) {
|
||||
appContext.tabManager.activateNoteContext(noteContext.ntxId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
appContext.tabManager.openContextWithNote(notePath, { viewScope, activate: true });
|
||||
}
|
||||
}
|
||||
} else if (hrefLink) {
|
||||
const withinEditLink = $link?.hasClass("ck-link-actions__preview");
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import options from "./options.js";
|
||||
import Split from "split.js"
|
||||
|
||||
export const DEFAULT_GUTTER_SIZE = 5;
|
||||
|
||||
let leftInstance: ReturnType<typeof Split> | null;
|
||||
let rightInstance: ReturnType<typeof Split> | null;
|
||||
|
||||
@@ -26,7 +28,7 @@ function setupLeftPaneResizer(leftPaneVisible: boolean) {
|
||||
if (leftPaneVisible) {
|
||||
leftInstance = Split(["#left-pane", "#rest-pane"], {
|
||||
sizes: [leftPaneWidth, 100 - leftPaneWidth],
|
||||
gutterSize: 5,
|
||||
gutterSize: DEFAULT_GUTTER_SIZE,
|
||||
onDragEnd: (sizes) => options.save("leftPaneWidth", Math.round(sizes[0]))
|
||||
});
|
||||
}
|
||||
@@ -54,7 +56,7 @@ function setupRightPaneResizer() {
|
||||
if (rightPaneVisible) {
|
||||
rightInstance = Split(["#center-pane", "#right-pane"], {
|
||||
sizes: [100 - rightPaneWidth, rightPaneWidth],
|
||||
gutterSize: 5,
|
||||
gutterSize: DEFAULT_GUTTER_SIZE,
|
||||
minSize: [ 300, 180 ],
|
||||
onDragEnd: (sizes) => options.save("rightPaneWidth", Math.round(sizes[1]))
|
||||
});
|
||||
|
||||
@@ -138,7 +138,7 @@ function getParentProtectedStatus(node: Fancytree.FancytreeNode) {
|
||||
return hoistedNoteService.isHoistedNode(node) ? false : node.getParent().data.isProtected;
|
||||
}
|
||||
|
||||
function getNoteIdFromUrl(urlOrNotePath: string | undefined) {
|
||||
function getNoteIdFromUrl(urlOrNotePath: string | null | undefined) {
|
||||
if (!urlOrNotePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -411,7 +411,11 @@ async function openInAppHelp($button: JQuery<HTMLElement>) {
|
||||
if (inAppHelpPage) {
|
||||
// Dynamic import to avoid import issues in tests.
|
||||
const appContext = (await import("../components/app_context.js")).default;
|
||||
const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (!activeContext) {
|
||||
return;
|
||||
}
|
||||
const subContexts = activeContext.getSubContexts();
|
||||
const targetNote = `_help_${inAppHelpPage}`;
|
||||
const helpSubcontext = subContexts.find((s) => s.viewScope?.viewMode === "contextual-help");
|
||||
const viewScope: ViewScope = {
|
||||
|
||||
@@ -16,7 +16,7 @@ export default class Mutex {
|
||||
return newPromise;
|
||||
}
|
||||
|
||||
async runExclusively(cb: () => Promise<void>) {
|
||||
async runExclusively<T>(cb: () => Promise<T>) {
|
||||
const unlock = await this.lock();
|
||||
|
||||
try {
|
||||
|
||||
@@ -12,7 +12,7 @@ export default class ClosePaneButton extends OnClickButtonWidget {
|
||||
);
|
||||
}
|
||||
|
||||
async noteContextReorderEvent({ ntxIdsInOrder }: EventData<"noteContextReorderEvent">) {
|
||||
async noteContextReorderEvent({ ntxIdsInOrder }: EventData<"noteContextReorder">) {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export default class ScrollingContainer extends Container<BasicWidget> {
|
||||
this.$widget.scrollTop(0);
|
||||
}
|
||||
|
||||
async noteSwitchedAndActivatedEvent({ noteContext, notePath }: EventData<"noteSwitchedAndActivatedEvent">) {
|
||||
async noteSwitchedAndActivatedEvent({ noteContext, notePath }: EventData<"noteSwitchedAndActivated">) {
|
||||
this.noteContext = noteContext;
|
||||
|
||||
this.$widget.scrollTop(0);
|
||||
|
||||
@@ -63,7 +63,7 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
||||
hoistedNoteId?: string;
|
||||
viewScope?: any;
|
||||
}) {
|
||||
const mainNtxId = appContext.tabManager.getActiveMainContext().ntxId;
|
||||
const mainNtxId = appContext.tabManager.getActiveMainContext()?.ntxId;
|
||||
|
||||
if (!mainNtxId) {
|
||||
logError("empty mainNtxId!");
|
||||
@@ -76,7 +76,7 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
||||
ntxId = mainNtxId;
|
||||
}
|
||||
|
||||
hoistedNoteId = hoistedNoteId || appContext.tabManager.getActiveContext().hoistedNoteId;
|
||||
hoistedNoteId = hoistedNoteId || appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||
|
||||
const noteContext = await appContext.tabManager.openEmptyTab(null, hoistedNoteId, mainNtxId);
|
||||
|
||||
@@ -199,7 +199,7 @@ export default class SplitNoteContainer extends FlexContainer<SplitNoteWidget> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (widget.hasBeenAlreadyShown || name === "noteSwitchedAndActivatedEvent" || appContext.tabManager.getActiveMainContext() === noteContext.getMainContext()) {
|
||||
if (widget.hasBeenAlreadyShown || name === "noteSwitchedAndActivated" || appContext.tabManager.getActiveMainContext() === noteContext.getMainContext()) {
|
||||
widget.hasBeenAlreadyShown = true;
|
||||
|
||||
return Promise.all([
|
||||
|
||||
@@ -8,8 +8,6 @@ import appContext from "../../components/app_context.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { Modal } from "bootstrap";
|
||||
|
||||
let branchId;
|
||||
|
||||
const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<form class="branch-prefix-form">
|
||||
@@ -25,7 +23,7 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
|
||||
|
||||
<div class="input-group">
|
||||
<input class="branch-prefix-input form-control">
|
||||
<div class="branch-prefix-note-title input-group-text"></div>
|
||||
<div class="branch-prefix-note-title input-group-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -38,61 +36,70 @@ const TPL = `<div class="branch-prefix-dialog modal fade mx-auto" tabindex="-1"
|
||||
</div>`;
|
||||
|
||||
export default class BranchPrefixDialog extends BasicWidget {
|
||||
private modal!: Modal;
|
||||
private $form!: JQuery<HTMLElement>;
|
||||
private $treePrefixInput!: JQuery<HTMLElement>;
|
||||
private $noteTitle!: JQuery<HTMLElement>;
|
||||
private branchId: string | null = null;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.modal = Modal.getOrCreateInstance(this.$widget);
|
||||
this.modal = Modal.getOrCreateInstance(this.$widget[0]);
|
||||
this.$form = this.$widget.find(".branch-prefix-form");
|
||||
this.$treePrefixInput = this.$widget.find(".branch-prefix-input");
|
||||
this.$noteTitle = this.$widget.find(".branch-prefix-note-title");
|
||||
|
||||
this.$form.on("submit", () => {
|
||||
this.savePrefix();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
this.$widget.on("shown.bs.modal", () => this.$treePrefixInput.trigger("focus"));
|
||||
}
|
||||
|
||||
async refresh(notePath) {
|
||||
async refresh(notePath: string) {
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
|
||||
if (!noteId || !parentNoteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
branchId = await froca.getBranchId(parentNoteId, noteId);
|
||||
const branch = froca.getBranch(branchId);
|
||||
const newBranchId = await froca.getBranchId(parentNoteId, noteId);
|
||||
if (!newBranchId) {
|
||||
return;
|
||||
}
|
||||
this.branchId = newBranchId;
|
||||
|
||||
const branch = froca.getBranch(this.branchId);
|
||||
if (!branch || branch.noteId === "root") {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentNote = await froca.getNote(branch.parentNoteId);
|
||||
|
||||
if (parentNote.type === "search") {
|
||||
if (!parentNote || parentNote.type === "search") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$treePrefixInput.val(branch.prefix);
|
||||
this.$treePrefixInput.val(branch.prefix || "");
|
||||
|
||||
const noteTitle = await treeService.getNoteTitle(noteId);
|
||||
|
||||
this.$noteTitle.text(` - ${noteTitle}`);
|
||||
}
|
||||
|
||||
async editBranchPrefixEvent() {
|
||||
const notePath = appContext.tabManager.getActiveContextNotePath();
|
||||
if (!notePath) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.refresh(notePath);
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
}
|
||||
|
||||
async savePrefix() {
|
||||
const prefix = this.$treePrefixInput.val();
|
||||
|
||||
await server.put(`branches/${branchId}/set-prefix`, { prefix: prefix });
|
||||
await server.put(`branches/${this.branchId}/set-prefix`, { prefix: prefix });
|
||||
|
||||
this.modal.hide();
|
||||
|
||||
@@ -5,6 +5,8 @@ import utils from "../../services/utils.js";
|
||||
import server from "../../services/server.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
|
||||
|
||||
const TPL = `
|
||||
<div class="bulk-actions-dialog modal mx-auto" tabindex="-1" role="dialog">
|
||||
@@ -67,6 +69,13 @@ const TPL = `
|
||||
</div>`;
|
||||
|
||||
export default class BulkActionsDialog extends BasicWidget {
|
||||
private $includeDescendants!: JQuery<HTMLElement>;
|
||||
private $affectedNoteCount!: JQuery<HTMLElement>;
|
||||
private $availableActionList!: JQuery<HTMLElement>;
|
||||
private $existingActionList!: JQuery<HTMLElement>;
|
||||
private $executeButton!: JQuery<HTMLElement>;
|
||||
private selectedOrActiveNoteIds: string[] | null = null;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$includeDescendants = this.$widget.find(".include-descendants");
|
||||
@@ -79,9 +88,11 @@ export default class BulkActionsDialog extends BasicWidget {
|
||||
|
||||
this.$widget.on("click", "[data-action-add]", async (event) => {
|
||||
const actionName = $(event.target).attr("data-action-add");
|
||||
if (!actionName) {
|
||||
return;
|
||||
}
|
||||
|
||||
await bulkActionService.addAction("_bulkAction", actionName);
|
||||
|
||||
await this.refresh();
|
||||
});
|
||||
|
||||
@@ -93,7 +104,6 @@ export default class BulkActionsDialog extends BasicWidget {
|
||||
});
|
||||
|
||||
toastService.showMessage(t("bulk_actions.bulk_actions_executed"), 3000);
|
||||
|
||||
utils.closeActiveDialog();
|
||||
});
|
||||
}
|
||||
@@ -101,21 +111,28 @@ export default class BulkActionsDialog extends BasicWidget {
|
||||
async refresh() {
|
||||
this.renderAvailableActions();
|
||||
|
||||
if (!this.selectedOrActiveNoteIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { affectedNoteCount } = await server.post("bulk-action/affected-notes", {
|
||||
noteIds: this.selectedOrActiveNoteIds,
|
||||
includeDescendants: this.$includeDescendants.is(":checked")
|
||||
});
|
||||
}) as { affectedNoteCount: number };
|
||||
|
||||
this.$affectedNoteCount.text(affectedNoteCount);
|
||||
|
||||
const bulkActionNote = await froca.getNote("_bulkAction");
|
||||
if (!bulkActionNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions = bulkActionService.parseActions(bulkActionNote);
|
||||
|
||||
this.$existingActionList.empty();
|
||||
|
||||
if (actions.length > 0) {
|
||||
this.$existingActionList.append(...actions.map((action) => action.render()));
|
||||
this.$existingActionList.append(...actions.map((action) => action.render()).filter((action) => action !== null));
|
||||
} else {
|
||||
this.$existingActionList.append($("<p>").text(t("bulk_actions.none_yet")));
|
||||
}
|
||||
@@ -138,7 +155,7 @@ export default class BulkActionsDialog extends BasicWidget {
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }) {
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
// only refreshing deleted attrs, otherwise components update themselves
|
||||
if (loadResults.getAttributeRows().find((row) => row.type === "label" && row.name === "action" && row.noteId === "_bulkAction" && row.isDeleted)) {
|
||||
// this may be triggered from e.g., sync without open widget, then no need to refresh the widget
|
||||
@@ -148,12 +165,11 @@ export default class BulkActionsDialog extends BasicWidget {
|
||||
}
|
||||
}
|
||||
|
||||
async openBulkActionsDialogEvent({ selectedOrActiveNoteIds }) {
|
||||
async openBulkActionsDialogEvent({ selectedOrActiveNoteIds }: EventData<"openBulkActionsDialog">) {
|
||||
this.selectedOrActiveNoteIds = selectedOrActiveNoteIds;
|
||||
this.$includeDescendants.prop("checked", false);
|
||||
|
||||
await this.refresh();
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import branchService from "../../services/branches.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import BasicWidget from "../basic_widget.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
|
||||
|
||||
const TPL = `
|
||||
<div class="clone-to-dialog modal mx-auto" tabindex="-1" role="dialog">
|
||||
@@ -48,10 +50,14 @@ const TPL = `
|
||||
</div>`;
|
||||
|
||||
export default class CloneToDialog extends BasicWidget {
|
||||
private $form!: JQuery<HTMLElement>;
|
||||
private $noteAutoComplete!: JQuery<HTMLElement>;
|
||||
private $clonePrefix!: JQuery<HTMLElement>;
|
||||
private $noteList!: JQuery<HTMLElement>;
|
||||
private clonedNoteIds: string[] | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.clonedNoteIds = null;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
@@ -66,7 +72,6 @@ export default class CloneToDialog extends BasicWidget {
|
||||
|
||||
if (notePath) {
|
||||
this.$widget.modal("hide");
|
||||
|
||||
this.cloneNotesTo(notePath);
|
||||
} else {
|
||||
logError(t("clone_to.no_path_to_clone_to"));
|
||||
@@ -76,9 +81,9 @@ export default class CloneToDialog extends BasicWidget {
|
||||
});
|
||||
}
|
||||
|
||||
async cloneNoteIdsToEvent({ noteIds }) {
|
||||
async cloneNoteIdsToEvent({ noteIds }: EventData<"cloneNoteIdsTo">) {
|
||||
if (!noteIds || noteIds.length === 0) {
|
||||
noteIds = [appContext.tabManager.getActiveContextNoteId()];
|
||||
noteIds = [appContext.tabManager.getActiveContextNoteId() ?? ""];
|
||||
}
|
||||
|
||||
this.clonedNoteIds = [];
|
||||
@@ -90,14 +95,14 @@ export default class CloneToDialog extends BasicWidget {
|
||||
}
|
||||
|
||||
utils.openDialog(this.$widget);
|
||||
|
||||
this.$noteAutoComplete.val("").trigger("focus");
|
||||
|
||||
this.$noteList.empty();
|
||||
|
||||
for (const noteId of this.clonedNoteIds) {
|
||||
const note = await froca.getNote(noteId);
|
||||
|
||||
if (!note) {
|
||||
continue;
|
||||
}
|
||||
this.$noteList.append($("<li>").text(note.title));
|
||||
}
|
||||
|
||||
@@ -105,15 +110,29 @@ export default class CloneToDialog extends BasicWidget {
|
||||
noteAutocompleteService.showRecentNotes(this.$noteAutoComplete);
|
||||
}
|
||||
|
||||
async cloneNotesTo(notePath) {
|
||||
async cloneNotesTo(notePath: string) {
|
||||
const { noteId, parentNoteId } = treeService.getNoteIdAndParentIdFromUrl(notePath);
|
||||
if (!noteId || !parentNoteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetBranchId = await froca.getBranchId(parentNoteId, noteId);
|
||||
if (!targetBranchId || !this.clonedNoteIds) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const cloneNoteId of this.clonedNoteIds) {
|
||||
await branchService.cloneNoteToBranch(cloneNoteId, targetBranchId, this.$clonePrefix.val());
|
||||
await branchService.cloneNoteToBranch(cloneNoteId, targetBranchId, this.$clonePrefix.val() as string);
|
||||
|
||||
const clonedNote = await froca.getNote(cloneNoteId);
|
||||
const targetNote = await froca.getBranch(targetBranchId).getNote();
|
||||
const targetBranch = froca.getBranch(targetBranchId);
|
||||
if (!clonedNote || !targetBranch) {
|
||||
continue;
|
||||
}
|
||||
const targetNote = await targetBranch.getNote();
|
||||
if (!targetNote) {
|
||||
continue;
|
||||
}
|
||||
|
||||
toastService.showMessage(t("clone_to.note_cloned", { clonedTitle: clonedNote.title, targetTitle: targetNote.title }));
|
||||
}
|
||||
@@ -115,7 +115,10 @@ export default class RecentChangesDialog extends BasicWidget {
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
appContext.tabManager.getActiveContext().setNote(change.noteId);
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (activeContext) {
|
||||
activeContext.setNote(change.noteId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -141,7 +144,10 @@ export default class RecentChangesDialog extends BasicWidget {
|
||||
// Skip clicks on the link or deleted notes
|
||||
if (e.target?.nodeName !== "A" && !change.current_isDeleted) {
|
||||
// Open the current note
|
||||
appContext.tabManager.getActiveContext().setNote(change.noteId);
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (activeContext) {
|
||||
activeContext.setNote(change.noteId);
|
||||
}
|
||||
}
|
||||
})
|
||||
.toggleClass("deleted-note", !!change.current_isDeleted)
|
||||
|
||||
@@ -85,7 +85,7 @@ export default class CodeButtonsWidget extends NoteContextAwareWidget {
|
||||
this.$openTriliumApiDocsButton.toggle(note.mime.startsWith("application/javascript;env="));
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent({ noteId }: EventData<"noteTypeMimeChangedEvent">) {
|
||||
async noteTypeMimeChangedEvent({ noteId }: EventData<"noteTypeMimeChanged">) {
|
||||
if (this.isNote(noteId)) {
|
||||
await this.refresh();
|
||||
}
|
||||
|
||||
37
src/public/app/widgets/floating_buttons/help_button.spec.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { byBookType, byNoteType } from "./help_button.js";
|
||||
import fs from "fs";
|
||||
import type { NoteMetaFile } from "../../../../services/meta/note_meta.js";
|
||||
import type NoteMeta from "../../../../services/meta/note_meta.js";
|
||||
|
||||
describe("Help button", () => {
|
||||
it("All help notes are accessible", () => {
|
||||
function getNoteIds(item: NoteMeta | NoteMetaFile): string[] {
|
||||
const items = [];
|
||||
|
||||
if ("noteId" in item && item.noteId) {
|
||||
items.push(item.noteId);
|
||||
}
|
||||
|
||||
const children = "files" in item ? item.files : item.children;
|
||||
for (const child of children ?? []) {
|
||||
items.push(getNoteIds(child));
|
||||
}
|
||||
return items.flat();
|
||||
}
|
||||
|
||||
const allHelpNotes = [
|
||||
...Object.values(byNoteType),
|
||||
...Object.values(byBookType)
|
||||
].filter((noteId) => noteId) as string[];
|
||||
|
||||
const meta: NoteMetaFile = JSON.parse(fs.readFileSync("src/public/app/doc_notes/en/User Guide/!!!meta.json", "utf-8"));
|
||||
const allNoteIds = new Set(getNoteIds(meta));
|
||||
|
||||
for (const helpNote of allHelpNotes) {
|
||||
if (!allNoteIds.has(helpNote)) {
|
||||
expect.fail(`Help note with ID ${helpNote} does not exist in the in-app help.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -12,13 +12,13 @@ const TPL = `
|
||||
</button>
|
||||
`;
|
||||
|
||||
const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
|
||||
export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
|
||||
canvas: null,
|
||||
code: null,
|
||||
contentWidget: null,
|
||||
doc: null,
|
||||
file: null,
|
||||
geoMap: "foPEtsL51pD2",
|
||||
geoMap: "81SGnPGMk7Xc",
|
||||
image: null,
|
||||
launcher: null,
|
||||
mermaid: null,
|
||||
@@ -31,10 +31,10 @@ const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
|
||||
webView: null
|
||||
};
|
||||
|
||||
const byBookType: Record<ViewTypeOptions, string | null> = {
|
||||
export const byBookType: Record<ViewTypeOptions, string | null> = {
|
||||
list: null,
|
||||
grid: null,
|
||||
calendar: "fDGg7QcJg3Xm"
|
||||
calendar: "xWbu3jpNWapp"
|
||||
};
|
||||
|
||||
export default class ContextualHelpButton extends NoteContextAwareWidget {
|
||||
|
||||
@@ -6,17 +6,26 @@ import utils from "../services/utils.js";
|
||||
import { loadElkIfNeeded, postprocessMermaidSvg } from "../services/mermaid.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
import ScrollingContainer from "./containers/scrolling_container.js";
|
||||
import Split from "split.js";
|
||||
import { DEFAULT_GUTTER_SIZE } from "../services/resizer.js";
|
||||
|
||||
const TPL = `<div class="mermaid-widget">
|
||||
<style>
|
||||
.mermaid-widget {
|
||||
flex-grow: 2;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
body.mobile .mermaid-widget {
|
||||
min-height: 200px;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
padding: 20px;
|
||||
margin-bottom: 10px;
|
||||
flex-grow: 2;
|
||||
flex-basis: 0;
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
body.desktop .mermaid-widget + .gutter {
|
||||
border-bottom: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.mermaid-render {
|
||||
@@ -24,10 +33,6 @@ const TPL = `<div class="mermaid-widget">
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mermaid-render svg {
|
||||
width: 95%; /* https://github.com/zadam/trilium/issues/4340 */
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="mermaid-error alert alert-warning">
|
||||
@@ -46,6 +51,10 @@ export default class MermaidWidget extends NoteContextAwareWidget {
|
||||
private $errorContainer!: JQuery<HTMLElement>;
|
||||
private $errorMessage!: JQuery<HTMLElement>;
|
||||
private dirtyAttachment?: boolean;
|
||||
private zoomHandler?: () => void;
|
||||
private zoomInstance?: SvgPanZoom.Instance;
|
||||
private splitInstance?: Split.Instance;
|
||||
private lastNote?: FNote;
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && this.note?.type === "mermaid" && this.note.isContentAvailable() && this.noteContext?.viewScope?.viewMode === "default";
|
||||
@@ -60,6 +69,9 @@ export default class MermaidWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
const isSameNote = (this.lastNote === note);
|
||||
|
||||
this.cleanup();
|
||||
this.$errorContainer.hide();
|
||||
|
||||
await libraryLoader.requireLibrary(libraryLoader.MERMAID);
|
||||
@@ -69,9 +81,9 @@ export default class MermaidWidget extends NoteContextAwareWidget {
|
||||
...(getMermaidConfig() as any)
|
||||
});
|
||||
|
||||
this.$display.empty();
|
||||
|
||||
const wheelZoomLoaded = libraryLoader.requireLibrary(libraryLoader.WHEEL_ZOOM);
|
||||
if (!isSameNote) {
|
||||
this.$display.empty();
|
||||
}
|
||||
|
||||
this.$errorContainer.hide();
|
||||
|
||||
@@ -93,21 +105,47 @@ export default class MermaidWidget extends NoteContextAwareWidget {
|
||||
}
|
||||
|
||||
this.$display.html(svg);
|
||||
|
||||
await wheelZoomLoaded;
|
||||
|
||||
this.$display.attr("id", `mermaid-render-${idCounter}`);
|
||||
|
||||
WZoom.create(`#mermaid-render-${idCounter}`, {
|
||||
maxScale: 50,
|
||||
speed: 1.3,
|
||||
zoomOnClick: false
|
||||
});
|
||||
// Fit the image to bounds.
|
||||
const $svg = this.$display.find("svg");
|
||||
$svg.attr("width", "100%").attr("height", "100%");
|
||||
|
||||
// Enable pan to zoom.
|
||||
this.#setupPanZoom($svg[0], isSameNote);
|
||||
} catch (e: any) {
|
||||
console.warn(e);
|
||||
this.#cleanUpZoom();
|
||||
this.$display.empty();
|
||||
this.$errorMessage.text(e.message);
|
||||
this.$errorContainer.show();
|
||||
}
|
||||
|
||||
this.#setupResizer();
|
||||
this.lastNote = note;
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
super.cleanup();
|
||||
if (this.zoomHandler) {
|
||||
$(window).off("resize", this.zoomHandler);
|
||||
this.zoomHandler = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
#cleanUpZoom() {
|
||||
if (this.zoomInstance) {
|
||||
this.zoomInstance.destroy();
|
||||
this.zoomInstance = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
toggleInt(show: boolean | null | undefined): void {
|
||||
super.toggleInt(show);
|
||||
|
||||
if (!show) {
|
||||
this.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
async renderSvg() {
|
||||
@@ -125,6 +163,66 @@ export default class MermaidWidget extends NoteContextAwareWidget {
|
||||
return postprocessMermaidSvg(svg);
|
||||
}
|
||||
|
||||
async #setupPanZoom(svgEl: SVGElement, isSameNote: boolean) {
|
||||
// Clean up
|
||||
let pan = null;
|
||||
let zoom = null;
|
||||
if (this.zoomInstance) {
|
||||
// Store pan and zoom for same note, when the user is editing the note.
|
||||
if (isSameNote && this.zoomInstance) {
|
||||
pan = this.zoomInstance.getPan();
|
||||
zoom = this.zoomInstance.getZoom();
|
||||
}
|
||||
this.#cleanUpZoom();
|
||||
}
|
||||
|
||||
const svgPanZoom = (await import("svg-pan-zoom")).default;
|
||||
const zoomInstance = svgPanZoom(svgEl, {
|
||||
zoomEnabled: true,
|
||||
controlIconsEnabled: true
|
||||
});
|
||||
|
||||
if (pan && zoom) {
|
||||
// Restore the pan and zoom.
|
||||
zoomInstance.zoom(zoom);
|
||||
zoomInstance.pan(pan);
|
||||
} else {
|
||||
// New instance, reposition properly.
|
||||
zoomInstance.center();
|
||||
zoomInstance.fit();
|
||||
}
|
||||
|
||||
this.zoomHandler = () => {
|
||||
zoomInstance.resize();
|
||||
zoomInstance.fit();
|
||||
zoomInstance.center();
|
||||
};
|
||||
this.zoomInstance = zoomInstance;
|
||||
$(window).on("resize", this.zoomHandler);
|
||||
}
|
||||
|
||||
#setupResizer() {
|
||||
if (!utils.isDesktop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selfEl = this.$widget;
|
||||
const scrollingContainer = this.parent?.children.find((ch) => ch instanceof ScrollingContainer)?.$widget;
|
||||
|
||||
if (!selfEl.length || !scrollingContainer?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.splitInstance) {
|
||||
this.splitInstance = Split([ selfEl[0], scrollingContainer[0] ], {
|
||||
sizes: [ 50, 50 ],
|
||||
direction: "vertical",
|
||||
gutterSize: DEFAULT_GUTTER_SIZE,
|
||||
onDragEnd: () => this.zoomHandler?.()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (this.noteId && loadResults.isNoteContentReloaded(this.noteId)) {
|
||||
this.dirtyAttachment = true;
|
||||
|
||||
@@ -105,7 +105,7 @@ class NoteContextAwareWidget extends BasicWidget {
|
||||
}
|
||||
|
||||
// when note is both switched and activated, this should not produce a double refresh
|
||||
async noteSwitchedAndActivatedEvent({ noteContext, notePath }: EventData<"noteSwitchedAndActivatedEvent">) {
|
||||
async noteSwitchedAndActivatedEvent({ noteContext, notePath }: EventData<"noteSwitchedAndActivated">) {
|
||||
this.noteContext = noteContext;
|
||||
|
||||
// if notePath does not match, then the noteContext has been switched to another note in the meantime
|
||||
@@ -119,7 +119,7 @@ class NoteContextAwareWidget extends BasicWidget {
|
||||
this.noteContext = noteContext;
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent({ noteId }: EventData<"noteTypeMimeChangedEvent">) {
|
||||
async noteTypeMimeChangedEvent({ noteId }: EventData<"noteTypeMimeChanged">) {
|
||||
if (this.isNote(noteId)) {
|
||||
await this.refresh();
|
||||
}
|
||||
|
||||
@@ -140,13 +140,19 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
|
||||
if (!e.target || e.target.nodeName !== "A") {
|
||||
// click on the link is handled by link handling, but we want the whole item clickable
|
||||
appContext.tabManager.getActiveContext().setNote(note.noteId);
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (activeContext) {
|
||||
activeContext.setNote(note.noteId);
|
||||
}
|
||||
}
|
||||
});
|
||||
shortcutService.bindElShortcut($link, "return", () => {
|
||||
this.dropdown.hide();
|
||||
|
||||
appContext.tabManager.getActiveContext().setNote(note.noteId);
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (activeContext) {
|
||||
activeContext.setNote(note.noteId);
|
||||
}
|
||||
});
|
||||
|
||||
this.$dropdownMenu.append($link);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import Draggabilly, { type DraggabillyCallback, type MoveVector } from "draggabilly";
|
||||
import Draggabilly, { type MoveVector } from "draggabilly";
|
||||
import { t } from "../services/i18n.js";
|
||||
import BasicWidget from "./basic_widget.js";
|
||||
import contextMenu from "../menus/context_menu.js";
|
||||
import utils from "../services/utils.js";
|
||||
import keyboardActionService from "../services/keyboard_actions.js";
|
||||
import appContext, { type CommandData, type CommandListenerData, type EventData } from "../components/app_context.js";
|
||||
import appContext, { type CommandListenerData, type EventData } from "../components/app_context.js";
|
||||
import froca from "../services/froca.js";
|
||||
import attributeService from "../services/attributes.js";
|
||||
import type NoteContext from "../components/note_context.js";
|
||||
@@ -419,13 +419,13 @@ export default class TabRowWidget extends BasicWidget {
|
||||
closeActiveTabCommand({ $el }: CommandListenerData<"closeActiveTab">) {
|
||||
const ntxId = $el.closest(".note-tab").attr("data-ntx-id");
|
||||
|
||||
appContext.tabManager.removeNoteContext(ntxId);
|
||||
appContext.tabManager.removeNoteContext(ntxId ?? null);
|
||||
}
|
||||
|
||||
setTabCloseEvent($tab: JQuery<HTMLElement>) {
|
||||
$tab.on("mousedown", (e) => {
|
||||
if (e.which === 2) {
|
||||
appContext.tabManager.removeNoteContext($tab.attr("data-ntx-id"));
|
||||
appContext.tabManager.removeNoteContext($tab.attr("data-ntx-id") ?? null);
|
||||
|
||||
return true; // event has been handled
|
||||
}
|
||||
@@ -494,7 +494,7 @@ export default class TabRowWidget extends BasicWidget {
|
||||
return $tab.attr("data-ntx-id");
|
||||
}
|
||||
|
||||
noteContextRemovedEvent({ ntxIds }: EventData<"noteContextRemovedEvent">) {
|
||||
noteContextRemovedEvent({ ntxIds }: EventData<"noteContextRemoved">) {
|
||||
for (const ntxId of ntxIds) {
|
||||
this.removeTab(ntxId);
|
||||
}
|
||||
@@ -516,7 +516,7 @@ export default class TabRowWidget extends BasicWidget {
|
||||
this.draggabillyDragging.element.style.transform = "";
|
||||
this.draggabillyDragging.dragEnd();
|
||||
this.draggabillyDragging.isDragging = false;
|
||||
this.draggabillyDragging.positionDrag = () => {}; // Prevent Draggabilly from updating tabEl.style.transform in later frames
|
||||
this.draggabillyDragging.positionDrag = () => { }; // Prevent Draggabilly from updating tabEl.style.transform in later frames
|
||||
this.draggabillyDragging.destroy();
|
||||
this.draggabillyDragging = null;
|
||||
}
|
||||
@@ -628,7 +628,7 @@ export default class TabRowWidget extends BasicWidget {
|
||||
return closestIndex;
|
||||
}
|
||||
|
||||
noteSwitchedAndActivatedEvent({ noteContext }: EventData<"noteSwitchedAndActivatedEvent">) {
|
||||
noteSwitchedAndActivatedEvent({ noteContext }: EventData<"noteSwitchedAndActivated">) {
|
||||
this.activeContextChangedEvent();
|
||||
|
||||
this.updateTabById(noteContext.mainNtxId || noteContext.ntxId);
|
||||
@@ -638,7 +638,7 @@ export default class TabRowWidget extends BasicWidget {
|
||||
this.updateTabById(noteContext.mainNtxId || noteContext.ntxId);
|
||||
}
|
||||
|
||||
noteContextReorderEvent({ oldMainNtxId, newMainNtxId }: EventData<"noteContextReorderEvent">) {
|
||||
noteContextReorderEvent({ oldMainNtxId, newMainNtxId }: EventData<"noteContextReorder">) {
|
||||
if (!oldMainNtxId || !newMainNtxId) {
|
||||
// no need to update tab row
|
||||
return;
|
||||
@@ -649,8 +649,8 @@ export default class TabRowWidget extends BasicWidget {
|
||||
this.updateTabById(newMainNtxId);
|
||||
}
|
||||
|
||||
contextsReopenedEvent({ mainNtxId, tabPosition }: EventData<"contextsReopenedEvent">) {
|
||||
if (mainNtxId === undefined || tabPosition === undefined) {
|
||||
contextsReopenedEvent({ mainNtxId, tabPosition }: EventData<"contextsReopened">) {
|
||||
if (!mainNtxId || !tabPosition) {
|
||||
// no tab reopened
|
||||
return;
|
||||
}
|
||||
@@ -748,7 +748,7 @@ export default class TabRowWidget extends BasicWidget {
|
||||
hoistedNoteChangedEvent({ ntxId }: EventData<"hoistedNoteChanged">) {
|
||||
const $tab = this.getTabById(ntxId);
|
||||
|
||||
if ($tab) {
|
||||
if ($tab && ntxId) {
|
||||
const noteContext = appContext.tabManager.getNoteContextById(ntxId);
|
||||
|
||||
this.updateTab($tab, noteContext);
|
||||
|
||||
@@ -10,12 +10,17 @@ const TPL = `<div class="note-detail-doc note-detail-printable">
|
||||
}
|
||||
|
||||
.note-detail-doc-content pre {
|
||||
background-color: var(--accented-background-color);
|
||||
border: 1px solid var(--main-border-color);
|
||||
border: 0;
|
||||
box-shadow: var(--code-block-box-shadow);
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.note-detail-doc-content pre:not(.hljs) {
|
||||
background-color: var(--accented-background-color);
|
||||
border: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.note-detail-doc.contextual-help {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,10 @@ export default class EmptyTypeWidget extends TypeWidget {
|
||||
return false;
|
||||
}
|
||||
|
||||
appContext.tabManager.getActiveContext().setNote(suggestion.notePath);
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (activeContext) {
|
||||
activeContext.setNote(suggestion.notePath);
|
||||
}
|
||||
});
|
||||
|
||||
this.$workspaceNotes = this.$widget.find(".workspace-notes");
|
||||
|
||||
@@ -5,6 +5,9 @@ import nodeMenu from "@mind-elixir/node-menu";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
|
||||
// allow node-menu plugin css to be bundled by webpack
|
||||
import "@mind-elixir/node-menu/dist/style.css";
|
||||
|
||||
const NEW_TOPIC_NAME = "";
|
||||
|
||||
const TPL = `
|
||||
@@ -22,7 +25,7 @@ const TPL = `
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu {
|
||||
.map-container .node-menu {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
right: 20px;
|
||||
@@ -38,28 +41,28 @@ const TPL = `
|
||||
transition: .3s all
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu.close {
|
||||
.map-container .node-menu.close {
|
||||
height: 29px;
|
||||
width: 46px;
|
||||
overflow: hidden
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .button-container {
|
||||
.map-container .node-menu .button-container {
|
||||
padding: 3px 0;
|
||||
direction: rtl
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu #nm-tag {
|
||||
.map-container .node-menu #nm-tag {
|
||||
margin-top: 20px
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .nm-fontsize-container {
|
||||
.map-container .node-menu .nm-fontsize-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-bottom: 20px
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .nm-fontsize-container div {
|
||||
.map-container .node-menu .nm-fontsize-container div {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
display: flex;
|
||||
@@ -71,12 +74,12 @@ const TPL = `
|
||||
border-radius: 100%
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .nm-fontcolor-container {
|
||||
.map-container .node-menu .nm-fontcolor-container {
|
||||
margin-bottom: 10px
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu input,
|
||||
.mind-elixir .node-menu textarea {
|
||||
.map-container .node-menu input,
|
||||
.map-container .node-menu textarea {
|
||||
background: var(--input-background-color);
|
||||
border: 1px solid var(--panel-border-color);
|
||||
border-radius: var(--bs-border-radius);
|
||||
@@ -87,17 +90,17 @@ const TPL = `
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu textarea {
|
||||
.map-container .node-menu textarea {
|
||||
resize: none
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .split6 {
|
||||
.map-container .node-menu .split6 {
|
||||
display: inline-block;
|
||||
width: 16.66%;
|
||||
margin-bottom: 5px
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .palette {
|
||||
.map-container .node-menu .palette {
|
||||
border-radius: 100%;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
@@ -105,35 +108,35 @@ const TPL = `
|
||||
margin: auto
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .nmenu-selected,
|
||||
.mind-elixir .node-menu .palette:hover {
|
||||
.map-container .node-menu .nmenu-selected,
|
||||
.map-container .node-menu .palette:hover {
|
||||
box-shadow: tomato 0 0 0 2px;
|
||||
background-color: #c7e9fa
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .size-selected {
|
||||
.map-container .node-menu .size-selected {
|
||||
background-color: tomato !important;
|
||||
border-color: tomato;
|
||||
fill: #fff;
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .size-selected svg {
|
||||
.map-container .node-menu .size-selected svg {
|
||||
color: #fff
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .bof {
|
||||
.map-container .node-menu .bof {
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .bof span {
|
||||
.map-container .node-menu .bof span {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
border-radius: 4px;
|
||||
padding: 2px 5px
|
||||
}
|
||||
|
||||
.mind-elixir .node-menu .bof .selected {
|
||||
.map-container .node-menu .bof .selected {
|
||||
background-color: tomato;
|
||||
color: #fff
|
||||
}
|
||||
|
||||
@@ -65,10 +65,10 @@ const TPL = `
|
||||
<div class="col-6">
|
||||
<label for="main-font-size">${t("fonts.size")}</label>
|
||||
|
||||
<div class="input-group main-font-size-input-group">
|
||||
<label class="input-group tn-number-unit-pair main-font-size-input-group">
|
||||
<input id="main-font-size" type="number" class="main-font-size form-control options-number-input" min="50" max="200" step="10"/>
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -83,10 +83,10 @@ const TPL = `
|
||||
<div class="col-6">
|
||||
<label for="tree-font-size">${t("fonts.size")}</label>
|
||||
|
||||
<div class="input-group tree-font-size-input-group">
|
||||
<label class="input-group tn-number-unit-pair tree-font-size-input-group">
|
||||
<input id="tree-font-size" type="number" class="tree-font-size form-control options-number-input" min="50" max="200" step="10"/>
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -101,10 +101,10 @@ const TPL = `
|
||||
<div class="col-6">
|
||||
<label for="detail-font-size">${t("fonts.size")}</label>
|
||||
|
||||
<div class="input-group detail-font-size-input-group">
|
||||
<label class="input-group tn-number-unit-pair detail-font-size-input-group">
|
||||
<input id="detail-font-size" type="number" class="detail-font-size form-control options-number-input" min="50" max="200" step="10"/>
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -119,10 +119,10 @@ const TPL = `
|
||||
<div class="col-6">
|
||||
<label for="monospace-font-size">${t("fonts.size")}</label>
|
||||
|
||||
<div class="input-group monospace-font-size-input-group">
|
||||
<label class="input-group tn-number-unit-pair monospace-font-size-input-group">
|
||||
<input id="monospace-font-size" type="number" class="monospace-font-size form-control options-number-input" min="50" max="200" step="10"/>
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,7 +14,10 @@ const TPL = `
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<label for="max-content-width">${t("max_content_width.max_width_label")}</label>
|
||||
<input id="max-content-width" type="number" min="${MIN_VALUE}" step="10" class="max-content-width form-control options-number-input">
|
||||
<label class="input-group tn-number-unit-pair">
|
||||
<input id="max-content-width" type="number" min="${MIN_VALUE}" step="10" class="max-content-width form-control options-number-input">
|
||||
<span class="input-group-text">${t("max_content_width.max_width_unit")}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ const TPL = `
|
||||
|
||||
<div class="form-group">
|
||||
<label for="auto-readonly-size-code">${t("code_auto_read_only_size.label")}</label>
|
||||
<input id="auto-readonly-size-code" class="auto-readonly-size-code form-control options-number-input" type="number" min="0">
|
||||
<label class="input-group tn-number-unit-pair">
|
||||
<input id="auto-readonly-size-code" class="auto-readonly-size-code form-control options-number-input" type="number" min="0">
|
||||
<span class="input-group-text">${t("code_auto_read_only_size.unit")}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
||||
@@ -30,12 +30,18 @@ const TPL = `
|
||||
<div class="image-compression-enabled-wraper">
|
||||
<div class="form-group">
|
||||
<label>${t("images.max_image_dimensions")}</label>
|
||||
<input class="image-max-width-height form-control options-number-input" type="number" min="1">
|
||||
<label class="input-group tn-number-unit-pair">
|
||||
<input class="image-max-width-height form-control options-number-input" type="number" min="1">
|
||||
<span class="input-group-text">${t("images.max_image_dimensions_unit")}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>${t("images.jpeg_quality_description")}</label>
|
||||
<input class="image-jpeg-quality form-control options-number-input" min="10" max="100" type="number">
|
||||
<label class="input-group tn-number-unit-pair">
|
||||
<input class="image-jpeg-quality form-control options-number-input" min="10" max="100" type="number">
|
||||
<span class="input-group-text">%</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,10 @@ const TPL = `
|
||||
|
||||
<div class="form-group">
|
||||
<label>${t("revisions_snapshot_limit.snapshot_number_limit_label")}</label>
|
||||
<input class="revision-snapshot-number-limit form-control options-number-input" type="number" min="-1">
|
||||
<label class="input-group tn-number-unit-pair">
|
||||
<input class="revision-snapshot-number-limit form-control options-number-input" type="number" min="-1">
|
||||
<span class="input-group-text">${t("revisions_snapshot_limit.snapshot_number_limit_unit")}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button class="erase-excess-revision-snapshots-now-button btn btn-sm">
|
||||
|
||||
@@ -14,11 +14,6 @@ const TPL = `
|
||||
<input id="sync-server-host" class="sync-server-host form-control" placeholder="https://<host>:<port>">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sync-server-timeout" >${t("sync_2.timeout")}</label>
|
||||
<input id="sync-server-timeout" class="sync-server-timeout form-control" min="1" max="10000000" type="number" style="text-align: left;">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sync-proxy form-control" >${t("sync_2.proxy_label")}</label>
|
||||
<input id="sync-proxy form-control" class="sync-proxy form-control" placeholder="https://<host>:<port>">
|
||||
@@ -27,6 +22,14 @@ const TPL = `
|
||||
<p class="form-text">${t("sync_2.special_value_description")}</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="sync-server-timeout">${t("sync_2.timeout")}</label>
|
||||
<label class="input-group tn-number-unit-pair">
|
||||
<input id="sync-server-timeout" class="sync-server-timeout form-control" min="1" max="10000000" type="number" style="text-align: left;">
|
||||
<span class="input-group-text">${t("sync_2.timeout_unit")}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<button class="btn btn-primary">${t("sync_2.save")}</button>
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@ const TPL = `
|
||||
${t("table_of_contents.description")}
|
||||
|
||||
<div class="form-group">
|
||||
<input type="number" class="min-toc-headings form-control options-number-input options-number-input" min="0" max="9999999999999999" step="1" />
|
||||
<label class="input-group tn-number-unit-pair">
|
||||
<input type="number" class="min-toc-headings form-control options-number-input options-number-input" min="0" max="9999999999999999" step="1" />
|
||||
<span class="input-group-text">${t("table_of_contents.unit")}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p class="form-text">${t("table_of_contents.disable_info")}</p>
|
||||
|
||||
@@ -10,7 +10,10 @@ const TPL = `
|
||||
|
||||
<div class="form-group">
|
||||
<label for="auto-readonly-size-text">${t("text_auto_read_only_size.label")}</label>
|
||||
<input id="auto-readonly-size-text" class="auto-readonly-size-text form-control options-number-input" type="number" min="0">
|
||||
<label class="input-group tn-number-unit-pair">
|
||||
<input id="auto-readonly-size-text" class="auto-readonly-size-text form-control options-number-input" type="number" min="0">
|
||||
<span class="input-group-text">${t("text_auto_read_only_size.unit")}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
||||
@@ -1730,7 +1730,6 @@ footer.file-footer button {
|
||||
border-radius: 0.5em;
|
||||
padding: 1em;
|
||||
margin: 1.25em 0;
|
||||
margin-right: 14px;
|
||||
position: relative;
|
||||
padding-left: 2.5em;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -127,13 +127,24 @@
|
||||
--left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, 0.15);
|
||||
--left-pane-item-selected-action-button-hover-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25);
|
||||
|
||||
--launcher-pane-background-color: #1a1a1a;
|
||||
--launcher-pane-horizontal-background-color: #282828;
|
||||
--launcher-pane-horizontal-border-color: rgb(22, 22, 22);
|
||||
--launcher-pane-text-color: #909090;
|
||||
--launcher-pane-button-hover-color: #ffffff;
|
||||
--launcher-pane-button-hover-background: #ffffff1c;
|
||||
--launcher-pane-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
|
||||
/* Deprecated: now local variables in #launcher, with the values dependent on the current layout. */
|
||||
--launcher-pane-background-color: unset;
|
||||
--launcher-pane-text-color: unset;
|
||||
|
||||
--launcher-pane-vert-background-color: #1a1a1a;
|
||||
--launcher-pane-vert-text-color: #909090;
|
||||
--launcher-pane-vert-button-hover-color: #ffffff;
|
||||
--launcher-pane-vert-button-hover-background: #ffffff1c;
|
||||
--launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
|
||||
--launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color);
|
||||
|
||||
--launcher-pane-horiz-border-color: rgb(22, 22, 22);
|
||||
--launcher-pane-horiz-background-color: #282828;
|
||||
--launcher-pane-horiz-text-color: #909090;
|
||||
--launcher-pane-horiz-button-hover-color: #ffffff;
|
||||
--launcher-pane-horiz-button-hover-background: #ffffff1c;
|
||||
--launcher-pane-horiz-button-hover-shadow: unset;
|
||||
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
|
||||
|
||||
--protected-session-active-icon-color: #8edd8e;
|
||||
--sync-status-error-pulse-color: #f47871;
|
||||
@@ -163,6 +174,14 @@
|
||||
--promoted-attribute-card-background-color: var(--card-background-color);
|
||||
--promoted-attribute-card-shadow-color: #000000b3;
|
||||
|
||||
--floating-button-shadow-color: #00000080;
|
||||
--floating-button-background-color: #494949d2;
|
||||
--floating-button-color: var(--button-text-color);
|
||||
--floating-button-hover-background: #ffffff20;
|
||||
--floating-button-hover-color: white;
|
||||
--floating-button-hide-button-background: gray;
|
||||
--floating-button-separator-color: #00000080;
|
||||
|
||||
--right-pane-item-hover-background: #ffffff26;
|
||||
--right-pane-item-hover-color: white;
|
||||
|
||||
|
||||
@@ -121,13 +121,23 @@
|
||||
--left-pane-item-action-button-hover-shadow: 2px 2px 3px rgba(0, 0, 0, 0.15);
|
||||
--left-pane-item-selected-action-button-hover-shadow: 2px 2px 10px rgba(0, 0, 0, 0.25);
|
||||
|
||||
--launcher-pane-background-color: #e8e8e8;
|
||||
--launcher-pane-horizontal-background-color: #fafafa;
|
||||
--launcher-pane-horizontal-border-color: rgba(0, 0, 0, 0.1);
|
||||
--launcher-pane-text-color: #000000bd;
|
||||
--launcher-pane-button-hover-color: black;
|
||||
--launcher-pane-button-hover-background: white;
|
||||
--launcher-pane-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.075);
|
||||
/* Deprecated: now local variables in #launcher, with the values dependent on the current layout. */
|
||||
--launcher-pane-background-color: unset;
|
||||
--launcher-pane-text-color: unset;
|
||||
|
||||
--launcher-pane-vert-background-color: #e8e8e8;
|
||||
--launcher-pane-vert-text-color: #000000bd;
|
||||
--launcher-pane-vert-button-hover-color: black;
|
||||
--launcher-pane-vert-button-hover-background: white;
|
||||
--launcher-pane-vert-button-hover-shadow: 4px 4px 4px rgba(0, 0, 0, 0.075);
|
||||
--launcher-pane-vert-button-focus-outline-color: var(--input-focus-outline-color);
|
||||
|
||||
--launcher-pane-horiz-border-color: rgba(0, 0, 0, 0.1);
|
||||
--launcher-pane-horiz-background-color: #fafafa;
|
||||
--launcher-pane-horiz-button-hover-color: black;
|
||||
--launcher-pane-horiz-button-hover-background: var(--icon-button-hover-background);
|
||||
--launcher-pane-horiz-button-hover-shadow: unset;
|
||||
--launcher-pane-horiz-button-focus-outline-color: var(--input-focus-outline-color);
|
||||
|
||||
--protected-session-active-icon-color: #16b516;
|
||||
--sync-status-error-pulse-color: #ff5528;
|
||||
@@ -157,6 +167,14 @@
|
||||
--promoted-attribute-card-background-color: var(--card-background-color);
|
||||
--promoted-attribute-card-shadow-color: #00000033;
|
||||
|
||||
--floating-button-shadow-color: #0000001f;
|
||||
--floating-button-background-color: #e4e4e4cc;
|
||||
--floating-button-color: var(--button-text-color);
|
||||
--floating-button-hover-background: #00000017;
|
||||
--floating-button-hover-color: black;
|
||||
--floating-button-hide-button-background: gray;
|
||||
--floating-button-separator-color: #c0c0c0d1;
|
||||
|
||||
--new-tab-button-background: #d8d8d8;
|
||||
--new-tab-button-color: #3a3a3a;
|
||||
--new-tab-button-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@import url(./forms.css);
|
||||
@import url(./shell.css);
|
||||
@import url(./dialogs.css);
|
||||
@import url(./pages.css);
|
||||
@import url(./ribbon.css);
|
||||
@import url(./settings.css);
|
||||
@import url(./notes/empty.css);
|
||||
@import url(./notes/text.css);
|
||||
|
||||
@font-face {
|
||||
@@ -10,6 +10,10 @@
|
||||
src: url(../../fonts/Inter/Inter-VariableFont_opsz\,wght.ttf);
|
||||
}
|
||||
|
||||
/*
|
||||
* GLOBAL VARIABLES
|
||||
*/
|
||||
|
||||
:root {
|
||||
--main-font-family: "Inter", sans-serif;
|
||||
|
||||
@@ -26,11 +30,15 @@
|
||||
|
||||
--left-pane-item-selected-shadow-size: 2px;
|
||||
|
||||
--launcher-pane-size: 58px;
|
||||
--launcher-pane-horizontal-size: 54px;
|
||||
--launcher-pane-horizontal-icon-size: 20px;
|
||||
--launcher-pane-button-margin: 6px;
|
||||
--launcher-pane-button-gap: 3px;
|
||||
--launcher-pane-vert-size: 58px;
|
||||
--launcher-pane-vert-icon-size: 150%;
|
||||
--launcher-pane-vert-button-margin: 6px;
|
||||
--launcher-pane-vert-button-gap: 3px;
|
||||
|
||||
--launcher-pane-horiz-size: 54px;
|
||||
--launcher-pane-horiz-icon-size: 20px;
|
||||
--launcher-pane-horiz-button-margin: 8px;
|
||||
--launcher-pane-horiz-button-gap: 3px;
|
||||
|
||||
--tree-actions-toolbar-horizontal-margin: 8px;
|
||||
--tree-actions-toolbar-vertical-margin: 8px;
|
||||
@@ -45,6 +53,11 @@
|
||||
|
||||
--center-pane-border-radius: 10px;
|
||||
|
||||
--floating-button-height: 34px;
|
||||
--floating-button-width: 40px;
|
||||
--floating-button-icon-size: 20px;
|
||||
--floating-button-show-hide-button-size: 26px;
|
||||
|
||||
--menu-padding-size: 8px;
|
||||
--menu-item-icon-vert-offset: -2px;
|
||||
|
||||
@@ -69,45 +82,196 @@
|
||||
--tab-note-icons: true;
|
||||
}
|
||||
|
||||
/* Tool dialogs - small dialogs without a backdrop */
|
||||
div.tn-tool-dialog {
|
||||
border-radius: 10px;
|
||||
background: var(--tool-dialog-background-color) !important;
|
||||
user-select: none;
|
||||
box-shadow: 10px 10px 93px -25px var(--tool-dialog-shadow-color);
|
||||
}
|
||||
|
||||
/*
|
||||
* Note search suggestions
|
||||
* MENUS
|
||||
*
|
||||
* Note: apply the "tn-dropdown-list" class for scrollable dropdown menus. Submenus are not
|
||||
* supported when this class is used.
|
||||
*/
|
||||
|
||||
/* List body */
|
||||
.jump-to-note-dialog .jump-to-note-results .aa-suggestions,
|
||||
.note-detail-empty .aa-suggestions {
|
||||
padding: 0;
|
||||
.dropdown-menu:not(.static) {
|
||||
border-radius: var(--dropdown-border-radius);
|
||||
padding: var(--menu-padding-size) !important;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
/* List item */
|
||||
.jump-to-note-dialog .aa-suggestions div,
|
||||
.note-detail-empty .aa-suggestions div {
|
||||
.dropdown-menu::-webkit-scrollbar-track {
|
||||
background: var(--menu-background-color);
|
||||
}
|
||||
|
||||
body.mobile .dropdown-menu {
|
||||
backdrop-filter: var(--dropdown-backdrop-filter);
|
||||
border-radius: var(--dropdown-border-radius);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body.mobile .dropdown-menu .dropdown-menu {
|
||||
backdrop-filter: unset !important;
|
||||
border-radius: unset !important;
|
||||
}
|
||||
|
||||
body.desktop .dropdown-menu::before {
|
||||
content: "";
|
||||
backdrop-filter: var(--dropdown-backdrop-filter);
|
||||
border-radius: var(--dropdown-border-radius);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
body.desktop .dropdown-menu.tn-dropdown-list {
|
||||
backdrop-filter: var(--dropdown-backdrop-filter);
|
||||
}
|
||||
|
||||
body.desktop .dropdown-menu.tn-dropdown-list::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.desktop .dropdown-submenu .dropdown-menu::before {
|
||||
content: unset;
|
||||
}
|
||||
|
||||
body.mobile .dropdown-submenu .dropdown-menu {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
body.desktop .dropdown-submenu .dropdown-menu {
|
||||
backdrop-filter: var(--dropdown-backdrop-filter);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.dropdown-item,
|
||||
body.mobile .dropdown-submenu .dropdown-toggle {
|
||||
padding: 2px 2px 2px 8px !important;
|
||||
padding-inline-end: 16px !important;
|
||||
/* Note: the right padding should also accommodate the submenu arrow. */
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
color: var(--menu-text-color);
|
||||
cursor: default;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
/* Selected list item */
|
||||
.jump-to-note-dialog .aa-suggestions div.aa-cursor,
|
||||
.note-detail-empty .aa-suggestions div.aa-cursor {
|
||||
background: var(--hover-item-background-color);
|
||||
color: var(--hover-item-text-color);
|
||||
body.mobile .dropdown-submenu {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
body.mobile .dropdown-item:not(:last-of-type) {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
body.mobile .dropdown-submenu:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
html body .dropdown-item.disabled,
|
||||
html body .dropdown-item[disabled] {
|
||||
color: var(--menu-text-color) !important;
|
||||
opacity: var(--menu-item-disabled-opacity);
|
||||
}
|
||||
|
||||
/* Menu item icon */
|
||||
.dropdown-item .bx {
|
||||
transform: translateY(var(--menu-item-icon-vert-offset));
|
||||
color: var(--menu-item-icon-color) !important;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
/* Menu item keyboard shortcut */
|
||||
.dropdown-item kbd {
|
||||
margin-left: 16px;
|
||||
font-family: unset !important;
|
||||
font-size: unset !important;
|
||||
color: var(--menu-item-keyboard-shortcut-color) !important;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
position: relative;
|
||||
border-color: transparent !important;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.dropdown-divider::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: -1px;
|
||||
left: calc(0px - var(--menu-padding-size));
|
||||
right: calc(0px - var(--menu-padding-size));
|
||||
border-top: 1px solid var(--menu-item-delimiter-color);
|
||||
}
|
||||
|
||||
/* Menu item arrow */
|
||||
.dropdown-menu .dropdown-toggle::after {
|
||||
content: "\ed3b" !important;
|
||||
position: absolute;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: unset !important;
|
||||
border: unset !important;
|
||||
padding: 0 4px;
|
||||
font-family: boxicons;
|
||||
font-size: 1.2em;
|
||||
color: var(--menu-item-arrow-color) !important;
|
||||
}
|
||||
|
||||
/* Menu item group heading */
|
||||
|
||||
/* The heading body */
|
||||
.dropdown-menu h6 {
|
||||
position: relative;
|
||||
background: transparent;
|
||||
padding: 1em 8px 14px 8px;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8em;
|
||||
letter-spacing: 1pt;
|
||||
color: var(--menu-item-group-header-color) !important;
|
||||
}
|
||||
|
||||
/* The delimiter line */
|
||||
.dropdown-menu h6::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: calc(0px - var(--menu-padding-size));
|
||||
right: calc(0px - var(--menu-padding-size));
|
||||
border-top: 1px solid var(--menu-item-delimiter-color);
|
||||
}
|
||||
|
||||
/* Static menus (used as a list, such as on the note revisions dialog) */
|
||||
body.desktop .dropdown-menu.static {
|
||||
box-shadow: unset;
|
||||
border-radius: 4px;
|
||||
border: unset;
|
||||
background-color: var(--card-background-color) !important;
|
||||
padding: var(--menu-padding-size) !important;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body.desktop .dropdown-menu.static .dropdown-item.active {
|
||||
--active-item-text-color: var(--menu-text-color);
|
||||
}
|
||||
|
||||
body.desktop .dropdown-menu .dropdown-toggle::after {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body.mobile .dropdown-menu .dropdown-toggle::after {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
body.mobile .dropdown-menu .dropdown-item.submenu-open .dropdown-toggle::after {
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Toasts
|
||||
* TOASTS
|
||||
*/
|
||||
|
||||
#toast-container {
|
||||
#toast-container {
|
||||
/* The vertical gap between toasts */
|
||||
gap: 10px;
|
||||
}
|
||||
@@ -143,4 +307,223 @@ div.tn-tool-dialog {
|
||||
|
||||
#toast-container .toast .toast-body {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE TOOLTIPS
|
||||
*/
|
||||
|
||||
.tooltip .tooltip-inner:has(.note-tooltip-content) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.note-tooltip-content {
|
||||
padding: 8px;
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
.note-tooltip-title .note-title-with-path {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
.note-tooltip-title a {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.note-tooltip-title.note-no-content {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.note-tooltip-title:not(.note-no-content) .note-title-with-path {
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 2px solid currentColor;
|
||||
}
|
||||
|
||||
.note-tooltip-content .note-path {
|
||||
display: block;
|
||||
color: var(--muted-text-color);
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.note-tooltip-content .note-tooltip-attributes {
|
||||
margin-top: -4px;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.note-tooltip-content .rendered-content {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
/* NOTE PATHS */
|
||||
|
||||
.note-path .path-bracket {
|
||||
/* Hide the leading and trailing bracket from the path */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.note-path .path-delimiter {
|
||||
/* Hide the path delimiters (slashes) */
|
||||
display: none;
|
||||
}
|
||||
|
||||
.note-path .path-delimiter + span::before {
|
||||
/* Replace the path delimiters with arrows */
|
||||
display: inline-block;
|
||||
content: "\ed3b";
|
||||
padding: 0 0.25em;
|
||||
font-family: boxicons;
|
||||
opacity: 0.75;
|
||||
transform: translateY(4%);
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE LIST
|
||||
*/
|
||||
|
||||
.note-list .note-book-card {
|
||||
--note-list-horizontal-padding: 22px;
|
||||
--note-list-vertical-padding: 15px;
|
||||
background-color: var(--card-background-color);
|
||||
border: 1px solid var(--card-border-color) !important;
|
||||
box-shadow: 2px 3px 4px var(--card-shadow-color);
|
||||
border-radius: 12px;
|
||||
user-select: none;
|
||||
padding: 0;
|
||||
margin: 5px 10px 5px 0;
|
||||
}
|
||||
|
||||
.note-list.list-view .note-book-card {
|
||||
box-shadow: 0 0 3px var(--card-shadow-color);
|
||||
}
|
||||
|
||||
.note-list.list-view .note-book-card .note-book-header .note-icon {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card:active {
|
||||
background-color: var(--card-background-press-color);
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card a {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-header {
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
padding: 0.5em 1rem;
|
||||
border-bottom-color: var(--card-border-color);
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-header .note-icon {
|
||||
font-size: 17px;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-header .note-book-title {
|
||||
font-size: 1em;
|
||||
color: var(--active-item-text-color);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-header .rendered-note-attributes {
|
||||
font-size: 0.7em;
|
||||
font-weight: normal;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-header:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-content {
|
||||
padding: 0 !important;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-content .rendered-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-content .rendered-content.text-with-ellipsis {
|
||||
padding: 1rem !important;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-content h1,
|
||||
.note-list-wrapper .note-book-card .note-book-content h2,
|
||||
.note-list-wrapper .note-book-card .note-book-content h3,
|
||||
.note-list-wrapper .note-book-card .note-book-content h4,
|
||||
.note-list-wrapper .note-book-card .note-book-content h5,
|
||||
.note-list-wrapper .note-book-card .note-book-content h6 {
|
||||
font-size: 1rem;
|
||||
color: var(--active-item-text-color);
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-content.type-canvas .rendered-content,
|
||||
.note-list-wrapper .note-book-card .note-book-content.type-mindMap .rendered-content,
|
||||
.note-list-wrapper .note-book-card .note-book-content.type-code .rendered-content,
|
||||
.note-list-wrapper .note-book-card .note-book-content.type-video .rendered-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .note-book-content.type-code pre {
|
||||
height: 100%;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.note-list-wrapper .note-book-card .bx {
|
||||
color: var(--left-pane-icon-color) !important;
|
||||
}
|
||||
|
||||
.note-list.grid-view .note-book-card:hover {
|
||||
background: var(--card-background-color) !important;
|
||||
filter: contrast(105%);
|
||||
}
|
||||
|
||||
.note-list.grid-view .note-book-card img {
|
||||
object-fit: cover !important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.note-list.grid-view .ck-content {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.note-list.grid-view .ck-content p {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.note-list.grid-view .ck-content figure.image {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE SEARCH SUGGESTIONS
|
||||
*/
|
||||
|
||||
/* List body */
|
||||
.jump-to-note-dialog .jump-to-note-results .aa-suggestions,
|
||||
.note-detail-empty .aa-suggestions {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* List item */
|
||||
.jump-to-note-dialog .aa-suggestions div,
|
||||
.note-detail-empty .aa-suggestions div {
|
||||
border-radius: 6px;
|
||||
padding: 6px 12px;
|
||||
color: var(--menu-text-color);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Selected list item */
|
||||
.jump-to-note-dialog .aa-suggestions div.aa-cursor,
|
||||
.note-detail-empty .aa-suggestions div.aa-cursor {
|
||||
background: var(--hover-item-background-color);
|
||||
color: var(--hover-item-text-color);
|
||||
}
|
||||
398
src/public/stylesheets/theme-next/dialogs.css
Normal file
@@ -0,0 +1,398 @@
|
||||
/*
|
||||
* MODALS
|
||||
*/
|
||||
|
||||
/* Modal body */
|
||||
.modal .modal-content {
|
||||
box-shadow: 0 .5em 3em .5em var(--modal-shadow-color);
|
||||
border: 1px solid var(--modal-border-color);
|
||||
background: var(--modal-background-color);
|
||||
}
|
||||
|
||||
/* Modal header */
|
||||
.modal .modal-header {
|
||||
border-bottom: unset;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.modal .modal-header .modal-title {
|
||||
flex-grow: 1;
|
||||
font-size: 1.2em;
|
||||
color: var(--modal-title-color);
|
||||
}
|
||||
|
||||
/* Modal and toast control buttons (close and help) */
|
||||
|
||||
.modal .modal-header .btn-close,
|
||||
.modal .modal-header .help-button,
|
||||
#toast-container .toast .toast-header .btn-close {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 8px;
|
||||
border: 0;
|
||||
border-radius: 50%;
|
||||
padding: 0;
|
||||
width: var(--modal-control-button-size);
|
||||
height: var(--modal-control-button-size);
|
||||
background: var(--modal-control-button-background);
|
||||
font-size: var(--modal-control-button-size);
|
||||
line-height: normal;
|
||||
font-weight: normal;
|
||||
color: var(--modal-control-button-color);
|
||||
opacity: 1;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.modal .modal-header .btn-close,
|
||||
#toast-container .toast .toast-header .btn-close {
|
||||
--modal-control-button-hover-background: var(--modal-close-button-hover-background);
|
||||
}
|
||||
|
||||
.modal .modal-header .btn-close::after,
|
||||
#toast-container .toast .toast-header .btn-close::after {
|
||||
content: "\ec8d";
|
||||
font-family: boxicons;
|
||||
}
|
||||
|
||||
.modal .modal-header .help-button {
|
||||
margin-right: 0;
|
||||
font-size: calc(var(--modal-control-button-size) * .75);
|
||||
font-family: unset;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal .modal-header .btn-close:hover,
|
||||
.modal .modal-header .help-button:hover,
|
||||
#toast-container .toast .toast-header .btn-close:hover {
|
||||
background: var(--modal-control-button-hover-background);
|
||||
color: var(--modal-control-button-hover-color);
|
||||
}
|
||||
|
||||
.modal .modal-header .btn-close:active,
|
||||
.modal .modal-header .help-button:active,
|
||||
#toast-container .toast .toast-header .btn-close:active {
|
||||
transform: scale(.85);
|
||||
}
|
||||
|
||||
.modal .modal-header .btn-close:focus,
|
||||
.modal .modal-header .help-button:focus,
|
||||
#toast-container .toast .toast-header .btn-close:focus {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.modal .modal-header .btn-close:focus-visible,
|
||||
.modal .modal-header .help-button:focus-visible,
|
||||
#toast-container .toast .toast-header .btn-close:focus-visible {
|
||||
outline: 2px solid var(--input-focus-outline-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Modal footer */
|
||||
.modal .modal-footer {
|
||||
background: var(--modal-footer-background);
|
||||
color: var(--modal-footer-color);
|
||||
border-top: unset;
|
||||
}
|
||||
|
||||
/* Tool dialogs - small dialogs without a backdrop */
|
||||
div.tn-tool-dialog {
|
||||
border-radius: 10px;
|
||||
background: var(--tool-dialog-background-color) !important;
|
||||
user-select: none;
|
||||
box-shadow: 10px 10px 93px -25px var(--tool-dialog-shadow-color);
|
||||
}
|
||||
|
||||
/*
|
||||
* JUMP TO NOTE DIALOG
|
||||
*/
|
||||
|
||||
.jump-to-note-dialog .modal-dialog {
|
||||
--modal-background-color: var(--menu-background-color);
|
||||
--modal-footer-background: transparent;
|
||||
--bs-modal-header-border-width: 0;
|
||||
--bs-modal-footer-border-width: 0;
|
||||
--bs-modal-footer-gap: 0;
|
||||
|
||||
backdrop-filter: var(--dropdown-backdrop-filter);
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .modal-content {
|
||||
--bs-modal-header-padding-x: 0;
|
||||
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
|
||||
border: 1px solid var(--dropdown-border-color);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .modal-header {
|
||||
padding: unset !important;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .modal-body {
|
||||
padding: 26px 0 !important;
|
||||
}
|
||||
|
||||
/* Search box wrapper */
|
||||
.jump-to-note-dialog .input-group {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .input-group:hover {
|
||||
background: var(--quick-search-hover-background);
|
||||
}
|
||||
|
||||
/* Focused search box */
|
||||
.jump-to-note-dialog .input-group:focus-within {
|
||||
border-color: var(--quick-search-focus-border);
|
||||
background: var(--quick-search-focus-background);
|
||||
color: var(--quick-search-focus-color);
|
||||
}
|
||||
|
||||
.jump-to-note-dialog .input-clearer-button {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* RECENT CHANGES DIALOG
|
||||
*/
|
||||
|
||||
.recent-changes-dialog .modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.recent-changes-content {
|
||||
margin: var(--bs-modal-padding);
|
||||
}
|
||||
|
||||
/* Date headings */
|
||||
.recent-changes-content > div > b {
|
||||
position: sticky;
|
||||
display: block;
|
||||
top: 0;
|
||||
background: var(--modal-background-color);
|
||||
padding: 10px 0 10px calc(var(--timeline-left-gap) + var(--timeline-right-gap));
|
||||
font-size: 1.25em;
|
||||
font-weight: 300;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.recent-changes-content ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Timeline items */
|
||||
.recent-changes-content ul li {
|
||||
display: flex;
|
||||
position: relative;
|
||||
margin: 0;
|
||||
border: unset;
|
||||
padding-top: var(--timeline-item-top-padding);
|
||||
padding-bottom: var(--timeline-item-bottom-padding);
|
||||
padding-left: calc(var(--timeline-left-gap) + var(--timeline-right-gap));
|
||||
padding-right: var(--timeline-left-gap);
|
||||
color: var(--active-item-text-color);
|
||||
}
|
||||
|
||||
.recent-changes-content li > span:first-child::after {
|
||||
/* Remove the dash between time and note title */
|
||||
content: "" !important;
|
||||
}
|
||||
|
||||
.recent-changes-content ul li:not(.deleted-note):hover {
|
||||
border-radius: 8px;
|
||||
background: var(--hover-item-background-color);
|
||||
color: var(--hover-item-text-color);
|
||||
}
|
||||
|
||||
.recent-changes-content ul li .note-path {
|
||||
color: var(--muted-text-color);
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
/* Item time */
|
||||
.recent-changes-content ul li > span:first-child {
|
||||
display: inline-block;
|
||||
min-width: 80px;
|
||||
vertical-align: top;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
/* Item title & path container */
|
||||
.recent-changes-content ul li > span:nth-child(2) {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Item title link */
|
||||
|
||||
.recent-changes-content ul li .note-title a {
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
.recent-changes-content ul li .note-title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Item title for deleted notes */
|
||||
.recent-changes-content ul li.deleted-note .note-title > .note-title {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* Item path */
|
||||
.recent-changes-content ul li > span:nth-child(2) small {
|
||||
display: block;
|
||||
line-height: 1;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
/* Timeline connector */
|
||||
.recent-changes-content ul li::before,
|
||||
.recent-changes-content > div > b::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: var(--connector-top, 0);
|
||||
left: calc(var(--timeline-left-gap) + ((var(--timeline-bullet-size) - var(--timeline-connector-size)) / 2));
|
||||
bottom: var(--connector-bottom, 0);
|
||||
width: var(--timeline-connector-size);
|
||||
border-radius: var(--connector-radius, 0) var(--connector-radius, 0) 0 0;
|
||||
background: var(--timeline-connector-color);
|
||||
transition: background-color 400ms ease-in-out;
|
||||
}
|
||||
|
||||
.recent-changes-content ul li:hover:before {
|
||||
mix-blend-mode: var(--timeline-connector-hover-blend-mode);
|
||||
}
|
||||
|
||||
.recent-changes-content > div:hover {
|
||||
--timeline-connector-color: var(--timeline-connector-active-color);
|
||||
}
|
||||
|
||||
/* The first item of the timeline */
|
||||
.recent-changes-content > div:first-child > *:first-child {
|
||||
--connector-top: 50%;
|
||||
--connector-radius: calc(var(--timeline-connector-size) / 2);
|
||||
}
|
||||
|
||||
/* The last item of the timeline */
|
||||
.recent-changes-content > div:last-child li:last-child {
|
||||
--connector-bottom: 50%;
|
||||
}
|
||||
|
||||
/* Timeline bullet */
|
||||
.recent-changes-content ul li::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
top: calc(var(--timeline-item-top-padding) + var(--timeline-bullet-vertical-pos));
|
||||
left: var(--timeline-left-gap);
|
||||
width: var(--timeline-bullet-size);
|
||||
height: var(--timeline-bullet-size);
|
||||
border-radius: 50%;
|
||||
background: var(--timeline-bullet-color);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
/* Hovered timeline bullet */
|
||||
.recent-changes-content ul li:hover::after {
|
||||
background: var(--timeline-bullet-hover-color);
|
||||
}
|
||||
|
||||
/*
|
||||
* CHEATSHEET DIALOG
|
||||
*/
|
||||
|
||||
.help-dialog .modal-content {
|
||||
--modal-background-color: var(--help-background-color);
|
||||
backdrop-filter: blur(var(--help-backdrop-blur));
|
||||
}
|
||||
|
||||
.help-dialog .help-cards {
|
||||
display: block;
|
||||
columns: 3;
|
||||
column-gap: 20px;
|
||||
}
|
||||
|
||||
.help-dialog .card {
|
||||
margin: 0;
|
||||
width: auto;
|
||||
border: none;
|
||||
background: unset;
|
||||
padding: 16px 8px;
|
||||
break-inside: avoid-column;
|
||||
}
|
||||
|
||||
.help-dialog .card-body {
|
||||
box-shadow: var(--help-card-shadow);
|
||||
border-radius: 6px;
|
||||
background: var(--help-card-background);
|
||||
}
|
||||
|
||||
.help-dialog .card-body h5,
|
||||
.help-dialog .card-body h6 {
|
||||
color: var(--help-card-heading-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.help-dialog .card-body h5 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.help-dialog .card-body h6 {
|
||||
font-size: 15px;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
/* Help card item */
|
||||
.help-dialog .help-cards ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.help-dialog .help-cards li + li {
|
||||
margin-top: 0.4em;
|
||||
}
|
||||
|
||||
/* Keyboard shortcut */
|
||||
.help-dialog .help-cards kbd,
|
||||
.ck-content kbd {
|
||||
box-shadow: var(--help-kbd-shortcut-shadow);
|
||||
margin: 0 4px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 2px 10px;
|
||||
background: var(--help-kbd-shortcut-background);
|
||||
color: var(--help-kbd-shortcut-color);
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.5pt;
|
||||
}
|
||||
|
||||
.help-dialog .help-cards kbd:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* Inline code - used for Markdown samples */
|
||||
.help-dialog .help-cards code {
|
||||
border-radius: 4px;
|
||||
background: var(--help-code-background);
|
||||
padding: 0 8px;
|
||||
color: var(--help-code-color);
|
||||
}
|
||||
|
||||
/* DELETE NOTE PREVIEW DIALOG */
|
||||
|
||||
.delete-notes-list .note-path {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
/*
|
||||
* ATTRIBUTE DETAIL DIALOG
|
||||
*/
|
||||
|
||||
/* Labels */
|
||||
.attr-edit-table th {
|
||||
padding-right: 12px;
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -220,6 +220,7 @@ input::selection,
|
||||
border-radius: 6px;
|
||||
padding-right: 8px;
|
||||
color: var(--quick-search-color);
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.input-group:hover {
|
||||
@@ -302,6 +303,24 @@ input::selection,
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/*
|
||||
Numeric input with measurement unit as a sufix
|
||||
|
||||
<label class="input-group tn-number-unit-pair">
|
||||
<input ... >
|
||||
<span class="input-group-text">meters</span>
|
||||
</label>
|
||||
*/
|
||||
|
||||
label.input-group.tn-number-unit-pair {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
label.input-group.tn-number-unit-pair input {
|
||||
width: 120px !important;
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
/* Combo box-like dropdown buttons */
|
||||
|
||||
.select-button.dropdown-toggle::after {
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/* The container */
|
||||
div.note-detail-empty {
|
||||
max-width: 70%;
|
||||
margin: 50px auto;
|
||||
}
|
||||
|
||||
/* The search results list */
|
||||
.note-detail-empty span.aa-dropdown-menu {
|
||||
margin-top: 1em;
|
||||
border: unset;
|
||||
}
|
||||
@@ -132,105 +132,4 @@ html .note-detail-editable-text :not(figure, .include-note, hr):first-child {
|
||||
.ck-content .table > figcaption {
|
||||
background: var(--accented-background-color);
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
/*
|
||||
* Search in text panel
|
||||
*/
|
||||
|
||||
.find-replace-widget {
|
||||
container-type: inline-size;
|
||||
border-top: 3px solid var(--root-background) !important;
|
||||
}
|
||||
|
||||
.find-replace-widget > div {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.find-replace-widget > div + div {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
div.find-replace-widget div.find-widget-found-wrapper > span {
|
||||
min-width: 50px;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* The up / down buttons of the "Find in text" input */
|
||||
.find-replace-widget .input-group button {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.find-replace-widget .form-check {
|
||||
padding-left: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.find-replace-widget .form-check .form-check-input {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* Narrow version */
|
||||
@container (max-width: 600px) {
|
||||
.find-replace-widget > *,
|
||||
.find-replace-widget input,
|
||||
.find-replace-widget button.btn.btn-sm {
|
||||
font-size: .9em;
|
||||
}
|
||||
|
||||
.find-widget-box {
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.find-widget-box,
|
||||
.replace-widget-box {
|
||||
padding-right: 3em !important;
|
||||
}
|
||||
|
||||
.find-widget-close-button {
|
||||
position: absolute;
|
||||
top: .85em;
|
||||
right: .5em;
|
||||
}
|
||||
|
||||
.find-widget-box > * {
|
||||
margin: unset !important;
|
||||
}
|
||||
|
||||
div.find-widget-search-term-input-group {
|
||||
margin-bottom: 8px;
|
||||
max-width: unset;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.find-widget-found-wrapper,
|
||||
.find-widget-found-wrapper > span {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
|
||||
.find-widget-spacer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-check {
|
||||
min-height: unset;
|
||||
margin-bottom: unset;
|
||||
}
|
||||
|
||||
.replace-widget-box {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.replace-widget-box > * {
|
||||
margin-right: unset !important;
|
||||
}
|
||||
|
||||
div.replace-widget-box button.btn.btn-sm {
|
||||
min-width: unset;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,127 @@
|
||||
/*
|
||||
* Settings
|
||||
* LOG IN PAGE
|
||||
*/
|
||||
:root {
|
||||
|
||||
.login-page {
|
||||
display: flex; /* Note: the login page contents is hidden before this property is applied */
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-page > div {
|
||||
padding-top: 0 !important;
|
||||
padding-bottom: 20vh;
|
||||
}
|
||||
|
||||
.login-page h1 {
|
||||
margin-bottom: .5em;
|
||||
font-weight: 300;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
.login-page .form-group {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.login-page .alert {
|
||||
margin: 0;
|
||||
border: unset;
|
||||
padding: 8px 0 0 0;
|
||||
font-size: .85em;
|
||||
color: var(--dropdown-item-icon-destructive-color) !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* SEARCH PAGE
|
||||
*/
|
||||
|
||||
/* Button bar */
|
||||
.search-definition-widget .search-setting-table tbody:last-child div {
|
||||
justify-content: flex-end !important;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-result-widget-content .note-path .path-bracket {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.search-result-widget-content .note-path {
|
||||
opacity: 0.75;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/*
|
||||
* SQL CONSOLE
|
||||
*/
|
||||
|
||||
/* Table buttons */
|
||||
|
||||
.sql-table-schemas-widget .sql-table-schemas button {
|
||||
--color: var(--main-text-color);
|
||||
--background: var(--card-background-color);
|
||||
|
||||
display: inline-block;
|
||||
box-shadow: 2px 2px 2px var(--card-shadow-color);
|
||||
margin-top: 4px;
|
||||
vertical-align: baseline;
|
||||
border: unset;
|
||||
border-radius: 12px;
|
||||
padding: 2px 12px;
|
||||
background: var(--background) !important;
|
||||
color: var(--color) !important;
|
||||
line-height: unset;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.sql-table-schemas-widget .sql-table-schemas button:hover,
|
||||
.sql-table-schemas-widget .sql-table-schemas button:active,
|
||||
.sql-table-schemas-widget .sql-table-schemas button:focus-visible {
|
||||
--background: var(--card-background-press-color);
|
||||
--color: var(--main-text-color);
|
||||
}
|
||||
|
||||
/* Tooltip */
|
||||
|
||||
.tooltip .table-schema {
|
||||
font-family: var(--monospace-font-family);
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
/* Data type */
|
||||
.tooltip .table-schema td:nth-child(2) {
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE MAP
|
||||
*/
|
||||
|
||||
.note-detail-note-map .fixnodes-type-switcher .tn-tool-button.toggled {
|
||||
color: var(--tab-close-button-hover-background);
|
||||
}
|
||||
|
||||
/*
|
||||
* EMPTY NOTE PAGE
|
||||
*/
|
||||
|
||||
/* The container */
|
||||
div.note-detail-empty {
|
||||
max-width: 70%;
|
||||
margin: 50px auto;
|
||||
}
|
||||
|
||||
/* The search results list */
|
||||
.note-detail-empty span.aa-dropdown-menu {
|
||||
margin-top: 1em;
|
||||
border: unset;
|
||||
}
|
||||
|
||||
/*
|
||||
* OPTIONS PAGES
|
||||
*/
|
||||
|
||||
:root {
|
||||
--options-card-min-width: 500px;
|
||||
--options-card-max-width: 900px;
|
||||
--options-card-padding: 17px;
|
||||
@@ -20,6 +140,11 @@
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
/* Add a gap between consecutive buttons */
|
||||
.note-detail-content-widget-content.options button.btn + button.btn {
|
||||
margin-inline-start: 8px;
|
||||
}
|
||||
|
||||
.note-detail-content-widget-content.options:has(.shortcuts-options-section)::after {
|
||||
height: 0;
|
||||
}
|
||||
@@ -107,15 +232,6 @@
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
/* Appeareance */
|
||||
|
||||
.main-font-size-input-group,
|
||||
.tree-font-size-input-group,
|
||||
.detail-font-size-input-group,
|
||||
.monospace-font-size-input-group {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
/* Shortcuts */
|
||||
|
||||
.note-detail-content-widget-content:has(.shortcuts-options-section) {
|
||||
@@ -153,15 +153,4 @@ div.editability-dropdown a.dropdown-item {
|
||||
/* Narrow width layout */
|
||||
.note-info-widget {
|
||||
container: info-section / inline-size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attribute detail dialog
|
||||
*/
|
||||
|
||||
/* Labels */
|
||||
.attr-edit-table th {
|
||||
padding-right: 12px;
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
@@ -1090,7 +1090,8 @@
|
||||
"max_content_width": {
|
||||
"title": "Content Width",
|
||||
"default_description": "Trilium by default limits max content width to improve readability for maximized screens on wide screens.",
|
||||
"max_width_label": "Max content width in pixels",
|
||||
"max_width_label": "Max content width",
|
||||
"max_width_unit": "pixels",
|
||||
"apply_changes_description": "To apply content width changes, click on",
|
||||
"reload_button": "reload frontend",
|
||||
"reload_description": "changes from appearance options"
|
||||
@@ -1128,7 +1129,8 @@
|
||||
"code_auto_read_only_size": {
|
||||
"title": "Automatic Read-Only Size",
|
||||
"description": "Automatic read-only note size is the size after which notes will be displayed in a read-only mode (for performance reasons).",
|
||||
"label": "Automatic read-only size (code notes)"
|
||||
"label": "Automatic read-only size (code notes)",
|
||||
"unit": "characters"
|
||||
},
|
||||
"code-editor-options": {
|
||||
"title": "Editor"
|
||||
@@ -1149,7 +1151,8 @@
|
||||
"download_images_automatically": "Download images automatically for offline use.",
|
||||
"download_images_description": "Pasted HTML can contain references to online images, Trilium will find those references and download the images so that they are available offline.",
|
||||
"enable_image_compression": "Enable image compression",
|
||||
"max_image_dimensions": "Max width / height of an image in pixels (image will be resized if it exceeds this setting).",
|
||||
"max_image_dimensions": "Max width / height of an image (image will be resized if it exceeds this setting).",
|
||||
"max_image_dimensions_unit": "pixels",
|
||||
"jpeg_quality_description": "JPEG quality (10 - worst quality, 100 - best quality, 50 - 85 is recommended)"
|
||||
},
|
||||
"attachment_erasure_timeout": {
|
||||
@@ -1181,6 +1184,7 @@
|
||||
"note_revisions_snapshot_limit_title": "Note Revision Snapshot Limit",
|
||||
"note_revisions_snapshot_limit_description": "The note revision snapshot number limit refers to the maximum number of revisions that can be saved for each note. Where -1 means no limit, 0 means delete all revisions. You can set the maximum revisions for a single note through the #versioningLimit label.",
|
||||
"snapshot_number_limit_label": "Note revision snapshot number limit:",
|
||||
"snapshot_number_limit_unit": "snapshots",
|
||||
"erase_excess_revision_snapshots": "Erase excess revision snapshots now",
|
||||
"erase_excess_revision_snapshots_prompt": "Excess revision snapshots have been erased."
|
||||
},
|
||||
@@ -1223,13 +1227,15 @@
|
||||
"table_of_contents": {
|
||||
"title": "Table of Contents",
|
||||
"description": "Table of contents will appear in text notes when the note has more than a defined number of headings. You can customize this number:",
|
||||
"unit": "headings",
|
||||
"disable_info": "You can also use this option to effectively disable TOC by setting a very high number.",
|
||||
"shortcut_info": "You can configure a keyboard shortcut for quickly toggling the right pane (including TOC) in the Options -> Shortcuts (name 'toggleRightPane')."
|
||||
},
|
||||
"text_auto_read_only_size": {
|
||||
"title": "Automatic Read-Only Size",
|
||||
"description": "Automatic read-only note size is the size after which notes will be displayed in a read-only mode (for performance reasons).",
|
||||
"label": "Automatic read-only size (text notes)"
|
||||
"label": "Automatic read-only size (text notes)",
|
||||
"unit": "characters"
|
||||
},
|
||||
"i18n": {
|
||||
"title": "Localization",
|
||||
@@ -1329,7 +1335,8 @@
|
||||
"sync_2": {
|
||||
"config_title": "Sync Configuration",
|
||||
"server_address": "Server instance address",
|
||||
"timeout": "Sync timeout (milliseconds)",
|
||||
"timeout": "Sync timeout",
|
||||
"timeout_unit": "milliseconds",
|
||||
"proxy_label": "Sync proxy server (optional)",
|
||||
"note": "Note",
|
||||
"note_description": "If you leave the proxy setting blank, the system proxy will be used (applies to desktop/electron build only).",
|
||||
|
||||
@@ -353,7 +353,8 @@
|
||||
"code_auto_read_only_size": {
|
||||
"description": "Marchează pragul în care o notiță de o anumită dimensiune va fi afișată în mod de citire (pentru motive de performanță).",
|
||||
"label": "Pragul de dimensiune pentru setarea modului de citire automat (la notițe de cod)",
|
||||
"title": "Pragul de mod de citire automat"
|
||||
"title": "Pragul de mod de citire automat",
|
||||
"unit": "caractere"
|
||||
},
|
||||
"code_buttons": {
|
||||
"execute_button_title": "Execută scriptul",
|
||||
@@ -697,7 +698,8 @@
|
||||
"enable_image_compression": "Activează compresia imaginilor",
|
||||
"images_section_title": "Imagini",
|
||||
"jpeg_quality_description": "Calitatea JPEG (10 - cea mai slabă calitate, 100 - cea mai bună calitate, se recomandă între 50 și 85)",
|
||||
"max_image_dimensions": "Lungimea/lățimea maximă a unei imagini în pixeli (imaginea va fi redimensionată dacă depășește acest prag)."
|
||||
"max_image_dimensions": "Lungimea/lățimea maximă a unei imagini (imaginea va fi redimensionată dacă depășește acest prag).",
|
||||
"max_image_dimensions_unit": "pixeli"
|
||||
},
|
||||
"import": {
|
||||
"chooseImportFile": "Selectați fișierul de importat",
|
||||
@@ -772,7 +774,8 @@
|
||||
"max_content_width": {
|
||||
"apply_changes_description": "Pentru a aplica schimbările de lățime a conținutului, dați click pe",
|
||||
"default_description": "În mod implicit Trilium limitează lățimea conținutului pentru a îmbunătăți lizibilitatea pentru ferestrele maximizate pe ecrane late.",
|
||||
"max_width_label": "Lungimea maximă a conținutului în pixeli",
|
||||
"max_width_label": "Lungimea maximă a conținutului",
|
||||
"max_width_unit": "pixeli",
|
||||
"reload_button": "reîncarcă interfața",
|
||||
"reload_description": "schimbări din opțiunile de afișare",
|
||||
"title": "Lățime conținut"
|
||||
@@ -1215,10 +1218,12 @@
|
||||
"test_button": "Probează sincronizarea",
|
||||
"test_description": "Această opțiune va testa conexiunea și comunicarea cu serverul de sincronizare. Dacă serverul de sincronizare nu este inițializat, acest lucru va rula și o sincronizare cu documentul local.",
|
||||
"test_title": "Probează sincronizarea",
|
||||
"timeout": "Timp limită de sincronizare (millisecunde)"
|
||||
"timeout": "Timp limită de sincronizare",
|
||||
"timeout_unit": "milisecunde"
|
||||
},
|
||||
"table_of_contents": {
|
||||
"description": "Tabela de conținut va apărea în notițele de tip text atunci când notița are un număr de titluri mai mare decât cel definit. Acest număr se poate personaliza:",
|
||||
"unit": "titluri",
|
||||
"disable_info": "De asemenea se poate dezactiva tabela de conținut setând o valoare foarte mare.",
|
||||
"shortcut_info": "Se poate configura și o scurtatură pentru a comuta rapid vizibilitatea panoului din dreapta (inclusiv tabela de conținut) în Opțiuni -> Scurtături (denumirea „toggleRightPane”).",
|
||||
"title": "Tabelă de conținut"
|
||||
@@ -1226,7 +1231,8 @@
|
||||
"text_auto_read_only_size": {
|
||||
"description": "Marchează pragul în care o notiță de o anumită dimensiune va fi afișată în mod de citire (pentru motive de performanță).",
|
||||
"label": "Pragul de dimensiune pentru setarea modului de citire automat (la notițe text)",
|
||||
"title": "Pragul de mod de citire automat"
|
||||
"title": "Pragul de mod de citire automat",
|
||||
"unit": "caractere"
|
||||
},
|
||||
"theme": {
|
||||
"auto_theme": "Temă auto (se adaptează la schema de culori a sistemului)",
|
||||
@@ -1481,7 +1487,8 @@
|
||||
"erase_excess_revision_snapshots_prompt": "Reviziile excesive au fost șterse.",
|
||||
"note_revisions_snapshot_limit_description": "Limita numărului de revizii se referă la numărul maxim de revizii pentru fiecare notiță. -1 reprezintă nicio limită, 0 înseamnă ștergerea tuturor reviziilor. Se poate seta valoarea individual pentru o notiță prin eticheta #versioningLimit.",
|
||||
"note_revisions_snapshot_limit_title": "Limita de revizii a notițelor",
|
||||
"snapshot_number_limit_label": "Numărul maxim de revizii pentru notițe:"
|
||||
"snapshot_number_limit_label": "Numărul maxim de revizii pentru notițe:",
|
||||
"snapshot_number_limit_unit": "revizii"
|
||||
},
|
||||
"search_result": {
|
||||
"no_notes_found": "Nu au fost găsite notițe pentru parametrii de căutare dați.",
|
||||
|
||||
@@ -96,8 +96,6 @@ async function register(app: express.Application) {
|
||||
app.use(`/${assetPath}/node_modules/codemirror/mode/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/codemirror/mode/")));
|
||||
app.use(`/${assetPath}/node_modules/codemirror/keymap/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/codemirror/keymap/")));
|
||||
|
||||
app.use(`/${assetPath}/node_modules/mind-elixir/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/mind-elixir/dist/")));
|
||||
app.use(`/${assetPath}/node_modules/@mind-elixir/node-menu/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/@mind-elixir/node-menu/dist/")));
|
||||
app.use(`/${assetPath}/node_modules/@highlightjs/cdn-assets/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/@highlightjs/cdn-assets/")));
|
||||
|
||||
app.use(`/${assetPath}/node_modules/leaflet/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/leaflet/dist/")));
|
||||
|
||||
@@ -193,4 +193,37 @@ describe("Markdown export", () => {
|
||||
expect(markdownExportService.toMarkdown(html)).toBe(expected);
|
||||
});
|
||||
|
||||
it("exports code in tables properly", () => {
|
||||
const html = trimIndentation`\
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
Row 1
|
||||
</td>
|
||||
<td>
|
||||
<p>Allows displaying the value of one or more attributes in the calendar
|
||||
like this: </p>
|
||||
<p>
|
||||
<img src="13_Calendar View_image.png" alt="">
|
||||
</p>
|
||||
|
||||
<pre><code class="language-text-x-trilium-auto">#weight="70"
|
||||
#Mood="Good"
|
||||
#calendar:displayedAttributes="weight,Mood"</code></pre>
|
||||
<p>It can also be used with relations, case in which it will display the
|
||||
title of the target note:</p><pre><code class="language-text-x-trilium-auto">~assignee=@My assignee
|
||||
#calendar:displayedAttributes="assignee"</code></pre>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
`;
|
||||
|
||||
const expected = trimIndentation`\
|
||||
<table><tbody><tr><td>Row 1</td><td><p>Allows displaying the value of one or more attributes in the calendar like this: </p><p><img src="13_Calendar View_image.png" alt=""></p><pre><code class="language-text-x-trilium-auto">#weight="70"
|
||||
#Mood="Good"
|
||||
#calendar:displayedAttributes="weight,Mood"</code></pre><p>It can also be used with relations, case in which it will display the title of the target note:</p><pre><code class="language-text-x-trilium-auto">~assignee=@My assignee
|
||||
#calendar:displayedAttributes="assignee"</code></pre></td></tr></tbody></table>`;
|
||||
expect(markdownExportService.toMarkdown(html)).toBe(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
import TurndownService from "turndown";
|
||||
import turndownPluginGfm from "@joplin/turndown-plugin-gfm";
|
||||
import { gfm } from "../../../packages/turndown-plugin-gfm/src/gfm.js";
|
||||
|
||||
let instance: TurndownService | null = null;
|
||||
|
||||
@@ -43,7 +43,7 @@ function toMarkdown(content: string) {
|
||||
instance.addRule("fencedCodeBlock", fencedCodeBlockFilter);
|
||||
instance.addRule("img", buildImageFilter());
|
||||
instance.addRule("admonition", buildAdmonitionFilter());
|
||||
instance.use(turndownPluginGfm.gfm);
|
||||
instance.use(gfm);
|
||||
instance.keep([ "kbd" ]);
|
||||
}
|
||||
|
||||
|
||||