mirror of
https://github.com/zadam/trilium.git
synced 2026-06-26 22:11:07 +02:00
Merge remote-tracking branch 'origin/main' into standalone
This commit is contained in:
@@ -1,20 +1,9 @@
|
||||
:root {
|
||||
/* Default values to be overridden by themes */
|
||||
--calendar-coll-event-background-lightness: 95%;
|
||||
--calendar-coll-event-background-saturation: 80%;
|
||||
--calendar-coll-event-background-color: var(--accented-background-color);
|
||||
--calendar-coll-event-text-color: var(--main-text-color);
|
||||
--calendar-coll-event-hover-filter: none;
|
||||
--callendar-coll-event-archived-sripe-color: #00000013;
|
||||
--calendar-coll-today-background-color: var(--more-accented-background-color);
|
||||
}
|
||||
|
||||
.calendar-view {
|
||||
--fc-event-border-color: var(--calendar-coll-event-text-color);
|
||||
--fc-event-bg-color: var(--calendar-coll-event-background-color);
|
||||
--fc-event-text-color: var(--calendar-coll-event-text-color);
|
||||
--fc-event-border-color: var(--calendar-coll-event-text-color, var(--main-text-color));
|
||||
--fc-event-bg-color: var(--calendar-coll-event-background-color, var(--accented-background-color));
|
||||
--fc-event-text-color: var(--calendar-coll-event-text-color, var(--main-text-color));
|
||||
--fc-event-selected-overlay-color: transparent;
|
||||
--fc-today-bg-color: var(--calendar-coll-today-background-color);
|
||||
--fc-today-bg-color: var(--calendar-coll-today-background-color, var(--more-accented-background-color));
|
||||
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@@ -123,7 +112,7 @@
|
||||
z-index: -1;
|
||||
|
||||
--c1: transparent;
|
||||
--c2: var(--callendar-coll-event-archived-sripe-color);
|
||||
--c2: var(--callendar-coll-event-archived-sripe-color, #00000013);
|
||||
|
||||
background: repeating-linear-gradient(45deg, var(--c1), var(--c1) 8px,
|
||||
var(--c2) 8px, var(--c2) 16px);
|
||||
@@ -153,8 +142,8 @@
|
||||
--fc-event-text-color: var(--custom-color);
|
||||
|
||||
--fc-event-bg-color: hsl(var(--custom-color-hue),
|
||||
var(--calendar-coll-event-background-saturation),
|
||||
var(--calendar-coll-event-background-lightness)) !important;
|
||||
var(--calendar-coll-event-background-saturation, 80%),
|
||||
var(--calendar-coll-event-background-lightness, 95%)) !important;
|
||||
}
|
||||
|
||||
.calendar-view a.fc-timegrid-event:focus-visible,
|
||||
@@ -171,7 +160,7 @@
|
||||
|
||||
.calendar-view a.fc-timegrid-event:hover,
|
||||
.calendar-view a.fc-daygrid-event:hover {
|
||||
filter: var(--calendar-coll-event-hover-filter);
|
||||
filter: var(--calendar-coll-event-hover-filter, none);
|
||||
border-color: var(--fc-event-text-color);
|
||||
text-decoration: none;
|
||||
color: currentColor;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import BasicWidget from "./basic_widget.js";
|
||||
import server from "../services/server.js";
|
||||
import linkService from "../services/link.js";
|
||||
import froca from "../services/froca.js";
|
||||
import utils, { handleRightToLeftPlacement } from "../services/utils.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import shortcutService, { isIMEComposing } from "../services/shortcuts.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import { Dropdown, Tooltip } from "bootstrap";
|
||||
|
||||
import appContext from "../components/app_context.js";
|
||||
import froca from "../services/froca.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import linkService from "../services/link.js";
|
||||
import server from "../services/server.js";
|
||||
import shortcutService, { isIMEComposing } from "../services/shortcuts.js";
|
||||
import utils, { handleRightToLeftPlacement } from "../services/utils.js";
|
||||
import BasicWidget from "./basic_widget.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="quick-search input-group input-group-sm">
|
||||
<style>
|
||||
@@ -245,7 +246,7 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
const { searchResultNoteIds, searchResults, error } = await server.get<QuickSearchResponse>(`quick-search/${encodeURIComponent(searchString)}`);
|
||||
|
||||
if (error) {
|
||||
let tooltip = new Tooltip(this.$searchString[0], {
|
||||
const tooltip = new Tooltip(this.$searchString[0], {
|
||||
trigger: "manual",
|
||||
title: `Search error: ${error}`,
|
||||
placement: handleRightToLeftPlacement("right")
|
||||
@@ -289,10 +290,9 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
const resultsToDisplay = this.allSearchResults.slice(startIndex, endIndex);
|
||||
|
||||
for (const result of resultsToDisplay) {
|
||||
const noteId = result.notePath.split("/").pop();
|
||||
if (!noteId) continue;
|
||||
if (!result.notePath) continue;
|
||||
|
||||
const $item = $('<a class="dropdown-item" tabindex="0" href="javascript:">');
|
||||
const $item = $(`<a class="dropdown-item" tabindex="0" href="#${result.notePath}">`);
|
||||
|
||||
// Build the display HTML with content snippet below the title
|
||||
let itemHtml = `<div class="quick-search-item">
|
||||
@@ -317,23 +317,13 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
|
||||
$item.html(itemHtml);
|
||||
|
||||
$item.on("click", (e) => {
|
||||
$item.on("click auxclick", () => {
|
||||
this.dropdown.hide();
|
||||
e.preventDefault();
|
||||
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (activeContext) {
|
||||
activeContext.setNote(noteId);
|
||||
}
|
||||
});
|
||||
|
||||
shortcutService.bindElShortcut($item, "return", () => {
|
||||
this.dropdown.hide();
|
||||
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (activeContext) {
|
||||
activeContext.setNote(noteId);
|
||||
}
|
||||
$item[0].click();
|
||||
});
|
||||
|
||||
this.$dropdownMenu.append($item);
|
||||
@@ -350,24 +340,18 @@ export default class QuickSearchWidget extends BasicWidget {
|
||||
const $link = await linkService.createLink(note.noteId, { showNotePath: true, showNoteIcon: true });
|
||||
$link.addClass("dropdown-item");
|
||||
$link.attr("tabIndex", "0");
|
||||
$link.on("click", (e) => {
|
||||
$link.on("click auxclick", (e) => {
|
||||
this.dropdown.hide();
|
||||
|
||||
if (!e.target || e.target.nodeName !== "A") {
|
||||
// click on the link is handled by link handling, but we want the whole item clickable
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (activeContext) {
|
||||
activeContext.setNote(note.noteId);
|
||||
}
|
||||
if (!e.target || (e.target as HTMLElement).nodeName !== "A") {
|
||||
// click on the <a> is handled by the global goToLink handler,
|
||||
// but we want the whole item clickable
|
||||
$link.find("a")[0]?.dispatchEvent(new MouseEvent(e.type, e.originalEvent as MouseEventInit));
|
||||
}
|
||||
});
|
||||
shortcutService.bindElShortcut($link, "return", () => {
|
||||
this.dropdown.hide();
|
||||
|
||||
const activeContext = appContext.tabManager.getActiveContext();
|
||||
if (activeContext) {
|
||||
activeContext.setNote(note.noteId);
|
||||
}
|
||||
$link.find("a")[0]?.click();
|
||||
});
|
||||
|
||||
this.$dropdownMenu.append($link);
|
||||
|
||||
@@ -903,7 +903,7 @@ export default class TabRowWidget extends BasicWidget {
|
||||
loadResults.isNoteReloaded(noteContext.noteId) ||
|
||||
loadResults
|
||||
.getAttributeRows()
|
||||
.find((attr) => ["workspace", "workspaceIconClass", "workspaceTabBackgroundColor"].includes(attr.name || "") && attributeService.isAffecting(attr, noteContext.note))
|
||||
.find((attr) => ["workspace", "iconClass", "workspaceIconClass", "workspaceTabBackgroundColor"].includes(attr.name || "") && attributeService.isAffecting(attr, noteContext.note))
|
||||
) {
|
||||
const $tab = this.getTabById(noteContext.ntxId);
|
||||
|
||||
|
||||
@@ -8,13 +8,11 @@ import { HTMLProps } from "preact/compat";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
import FNote from "../../../entities/fnote";
|
||||
import attribute_autocomplete from "../../../services/attribute_autocomplete";
|
||||
import dialog from "../../../services/dialog";
|
||||
import { isExperimentalFeatureEnabled } from "../../../services/experimental_features";
|
||||
import { t } from "../../../services/i18n";
|
||||
import server from "../../../services/server";
|
||||
import toast from "../../../services/toast";
|
||||
import utils from "../../../services/utils";
|
||||
import ActionButton from "../../react/ActionButton";
|
||||
import { useEditorSpacedUpdate, useTriliumEvent, useTriliumEvents } from "../../react/hooks";
|
||||
import { TypeWidgetProps } from "../type_widget";
|
||||
@@ -23,7 +21,7 @@ import { buildRelationContextMenuHandler } from "./context_menu";
|
||||
import { JsPlumb } from "./jsplumb";
|
||||
import { NoteBox } from "./NoteBox";
|
||||
import setupOverlays, { uniDirectionalOverlays } from "./overlays";
|
||||
import { getMousePosition, getZoom, idToNoteId, noteIdToId } from "./utils";
|
||||
import { getMousePosition, getZoom, idToNoteId, noteIdToId, promptForRelationName } from "./utils";
|
||||
|
||||
const isNewLayout = isExperimentalFeatureEnabled("new-layout");
|
||||
|
||||
@@ -415,27 +413,7 @@ function useRelationCreation({ mapApiRef, jsPlumbApiRef }: { mapApiRef: RefObjec
|
||||
// if there's no event, then this has been triggered programmatically
|
||||
if (!originalEvent || !mapApiRef.current) return;
|
||||
|
||||
const name = await dialog.prompt({
|
||||
message: t("relation_map.specify_new_relation_name"),
|
||||
shown: ({ $answer }) => {
|
||||
if (!$answer) {
|
||||
return;
|
||||
}
|
||||
|
||||
$answer.on("keyup", () => {
|
||||
// invalid characters are simply ignored (from user perspective they are not even entered)
|
||||
const attrName = utils.filterAttributeName($answer.val() as string);
|
||||
|
||||
$answer.val(attrName);
|
||||
});
|
||||
|
||||
attribute_autocomplete.initAttributeNameAutocomplete({
|
||||
$el: $answer,
|
||||
attributeType: "relation",
|
||||
open: true
|
||||
});
|
||||
}
|
||||
});
|
||||
const name = await promptForRelationName();
|
||||
|
||||
// Delete the newly created connection if the dialog was dismissed.
|
||||
if (!name || !name.trim()) {
|
||||
|
||||
@@ -75,6 +75,29 @@ export default class RelationMapApi {
|
||||
this.onDataChange(true);
|
||||
}
|
||||
|
||||
async renameRelation(connection: Connection, newName: string) {
|
||||
newName = utils.filterAttributeName(newName);
|
||||
const relation = this.relations.find((rel) => rel.attributeId === connection.id);
|
||||
|
||||
if (!relation) return false;
|
||||
|
||||
// Check if a relation with the new name already exists between these notes.
|
||||
const exists = this.relations.some(
|
||||
(rel) => rel.sourceNoteId === relation.sourceNoteId && rel.targetNoteId === relation.targetNoteId && rel.name === newName
|
||||
);
|
||||
if (exists) return false;
|
||||
|
||||
await server.put(`notes/${relation.sourceNoteId}/relations/${newName}/to/${relation.targetNoteId}`);
|
||||
await server.remove(`notes/${relation.sourceNoteId}/relations/${relation.name}/to/${relation.targetNoteId}`);
|
||||
this.onDataChange(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
getRelationName(connection: Connection): string | undefined {
|
||||
const relation = this.relations.find((rel) => rel.attributeId === connection.id);
|
||||
return relation?.name;
|
||||
}
|
||||
|
||||
cleanupOtherNotes(noteIds: string[]) {
|
||||
const filteredNotes = this.data.notes.filter((note) => noteIds.includes(note.noteId));
|
||||
if (filteredNotes.length === this.data.notes.length) return;
|
||||
|
||||
@@ -9,6 +9,7 @@ import dialog from "../../../services/dialog";
|
||||
import { t } from "../../../services/i18n";
|
||||
import server from "../../../services/server";
|
||||
import RelationMapApi from "./api";
|
||||
import { promptForRelationName } from "./utils";
|
||||
|
||||
export function buildNoteContextMenuHandler(note: FNote | null | undefined, mapApiRef: RefObject<RelationMapApi>) {
|
||||
return (e: MouseEvent) => {
|
||||
@@ -73,9 +74,25 @@ export function buildRelationContextMenuHandler(connection: Connection, mapApiRe
|
||||
contextMenu.show({
|
||||
x: event.pageX,
|
||||
y: event.pageY,
|
||||
items: [{ title: t("relation_map.remove_relation"), command: "remove", uiIcon: "bx bx-trash" }],
|
||||
items: [
|
||||
{ title: t("relation_map.rename_relation"), command: "rename", uiIcon: "bx bx-pencil" },
|
||||
{ kind: "separator" },
|
||||
{ title: t("relation_map.remove_relation"), command: "remove", uiIcon: "bx bx-trash" }
|
||||
],
|
||||
selectMenuItemHandler: async ({ command }) => {
|
||||
if (command === "remove") {
|
||||
if (command === "rename") {
|
||||
const currentName = mapApiRef.current?.getRelationName(connection) ?? "";
|
||||
const newName = await promptForRelationName(currentName);
|
||||
|
||||
if (!newName?.trim() || newName === currentName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await mapApiRef.current?.renameRelation(connection, newName);
|
||||
if (!result) {
|
||||
await dialog.info(t("relation_map.connection_exists", { name: newName }));
|
||||
}
|
||||
} else if (command === "remove") {
|
||||
if (!(await dialog.confirm(t("relation_map.confirm_remove_relation")))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import attribute_autocomplete from "../../../services/attribute_autocomplete";
|
||||
import dialog from "../../../services/dialog";
|
||||
import { t } from "../../../services/i18n";
|
||||
import utils from "../../../services/utils";
|
||||
|
||||
export function noteIdToId(noteId: string) {
|
||||
return `rel-map-note-${noteId}`;
|
||||
@@ -32,3 +35,26 @@ export function getMousePosition(evt: MouseEvent, container: HTMLDivElement, zoo
|
||||
y: ((evt.clientY ?? 0) - rect.top) / zoom
|
||||
};
|
||||
}
|
||||
|
||||
export function promptForRelationName(defaultValue?: string): Promise<string | null> {
|
||||
return dialog.prompt({
|
||||
message: t("relation_map.specify_new_relation_name"),
|
||||
defaultValue,
|
||||
shown: ({ $answer }) => {
|
||||
if (!$answer) {
|
||||
return;
|
||||
}
|
||||
|
||||
$answer.on("keyup", () => {
|
||||
const attrName = utils.filterAttributeName($answer.val() as string);
|
||||
$answer.val(attrName);
|
||||
});
|
||||
|
||||
attribute_autocomplete.initAttributeNameAutocomplete({
|
||||
$el: $answer,
|
||||
attributeType: "relation",
|
||||
open: true
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user