Merge remote-tracking branch 'origin/main' into renovate/mind-elixir-5.x
@ -261,7 +261,6 @@ export type CommandMappings = {
|
||||
|
||||
// Geomap
|
||||
deleteFromMap: { noteId: string };
|
||||
openGeoLocation: { noteId: string; event: JQuery.MouseDownEvent };
|
||||
|
||||
toggleZenMode: CommandData;
|
||||
|
||||
|
@ -326,7 +326,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
|
||||
}
|
||||
|
||||
// Some book types must always display a note list, even if no children.
|
||||
if (["calendar", "table"].includes(note.getLabelValue("viewType") ?? "")) {
|
||||
if (["calendar", "table", "geoMap"].includes(note.getLabelValue("viewType") ?? "")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,6 @@ const NOTE_TYPE_ICONS = {
|
||||
doc: "bx bxs-file-doc",
|
||||
contentWidget: "bx bxs-widget",
|
||||
mindMap: "bx bx-sitemap",
|
||||
geoMap: "bx bx-map-alt",
|
||||
aiChat: "bx bx-bot"
|
||||
};
|
||||
|
||||
@ -36,7 +35,7 @@ const NOTE_TYPE_ICONS = {
|
||||
* end user. Those types should be used only for checking against, they are
|
||||
* not for direct use.
|
||||
*/
|
||||
export type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "geoMap" | "aiChat";
|
||||
export type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "aiChat";
|
||||
|
||||
export interface NotePathRecord {
|
||||
isArchived: boolean;
|
||||
|
@ -17,11 +17,17 @@ interface MenuSeparatorItem {
|
||||
title: "----";
|
||||
}
|
||||
|
||||
export interface MenuItemBadge {
|
||||
title: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface MenuCommandItem<T> {
|
||||
title: string;
|
||||
command?: T;
|
||||
type?: string;
|
||||
uiIcon?: string;
|
||||
badges?: MenuItemBadge[];
|
||||
templateNoteId?: string;
|
||||
enabled?: boolean;
|
||||
handler?: MenuHandler<T>;
|
||||
@ -161,6 +167,18 @@ class ContextMenu {
|
||||
.append(" ") // some space between icon and text
|
||||
.append(item.title);
|
||||
|
||||
if ("badges" in item && item.badges) {
|
||||
for (let badge of item.badges) {
|
||||
const badgeElement = $(`<span class="badge">`).text(badge.title);
|
||||
|
||||
if (badge.className) {
|
||||
badgeElement.addClass(badge.className);
|
||||
}
|
||||
|
||||
$link.append(badgeElement);
|
||||
}
|
||||
}
|
||||
|
||||
if ("shortcut" in item && item.shortcut) {
|
||||
$link.append($("<kbd>").text(item.shortcut));
|
||||
}
|
||||
|
@ -277,13 +277,21 @@ function goToLink(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent) {
|
||||
return goToLinkExt(evt, hrefLink, $link);
|
||||
}
|
||||
|
||||
function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement>, hrefLink: string | undefined, $link?: JQuery<HTMLElement> | null) {
|
||||
/**
|
||||
* Handles navigation to a link, which can be an internal note path (e.g., `#root/1234`) or an external URL (e.g., `https://example.com`).
|
||||
*
|
||||
* @param evt the event that triggered the link navigation, or `null` if the link was clicked programmatically. Used to determine if the link should be opened in a new tab/window, based on the button presses.
|
||||
* @param hrefLink the link to navigate to, which can be a note path (e.g., `#root/1234`) or an external URL with any supported protocol (e.g., `https://example.com`).
|
||||
* @param $link the jQuery element of the link that was clicked, used to determine if the link is an anchor link (e.g., `#fn1` or `#fnref1`) and to handle it accordingly.
|
||||
* @returns `true` if the link was handled (i.e., the element was found and scrolled to), or a falsy value otherwise.
|
||||
*/
|
||||
function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement> | null, hrefLink: string | undefined, $link?: JQuery<HTMLElement> | null) {
|
||||
if (hrefLink?.startsWith("data:")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
evt?.preventDefault();
|
||||
evt?.stopPropagation();
|
||||
|
||||
if (hrefLink && hrefLink.startsWith("#") && !hrefLink.startsWith("#root/") && $link) {
|
||||
if (handleAnchor(hrefLink, $link)) {
|
||||
@ -293,14 +301,14 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
|
||||
|
||||
const { notePath, viewScope } = parseNavigationStateFromUrl(hrefLink);
|
||||
|
||||
const ctrlKey = utils.isCtrlKey(evt);
|
||||
const shiftKey = evt.shiftKey;
|
||||
const isLeftClick = "which" in evt && evt.which === 1;
|
||||
const isMiddleClick = "which" in evt && evt.which === 2;
|
||||
const ctrlKey = evt && utils.isCtrlKey(evt);
|
||||
const shiftKey = evt?.shiftKey;
|
||||
const isLeftClick = !evt || ("which" in evt && evt.which === 1);
|
||||
const isMiddleClick = evt && "which" in evt && evt.which === 2;
|
||||
const targetIsBlank = ($link?.attr("target") === "_blank");
|
||||
const openInNewTab = (isLeftClick && ctrlKey) || isMiddleClick || targetIsBlank;
|
||||
const activate = (isLeftClick && ctrlKey && shiftKey) || (isMiddleClick && shiftKey);
|
||||
const openInNewWindow = isLeftClick && evt.shiftKey && !ctrlKey;
|
||||
const openInNewWindow = isLeftClick && evt?.shiftKey && !ctrlKey;
|
||||
|
||||
if (notePath) {
|
||||
if (openInNewWindow) {
|
||||
@ -311,7 +319,7 @@ function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent
|
||||
viewScope
|
||||
});
|
||||
} else if (isLeftClick) {
|
||||
const ntxId = $(evt.target as any)
|
||||
const ntxId = $(evt?.target as any)
|
||||
.closest("[data-ntx-id]")
|
||||
.attr("data-ntx-id");
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import CalendarView from "../widgets/view_widgets/calendar_view.js";
|
||||
import GeoView from "../widgets/view_widgets/geo_view/index.js";
|
||||
import ListOrGridView from "../widgets/view_widgets/list_or_grid_view.js";
|
||||
import TableView from "../widgets/view_widgets/table_view/index.js";
|
||||
import type { ViewModeArgs } from "../widgets/view_widgets/view_mode.js";
|
||||
import type ViewMode from "../widgets/view_widgets/view_mode.js";
|
||||
|
||||
export type ViewTypeOptions = "list" | "grid" | "calendar" | "table";
|
||||
export type ViewTypeOptions = "list" | "grid" | "calendar" | "table" | "geoMap";
|
||||
|
||||
export default class NoteListRenderer {
|
||||
|
||||
@ -26,6 +27,9 @@ export default class NoteListRenderer {
|
||||
case "table":
|
||||
this.viewMode = new TableView(args);
|
||||
break;
|
||||
case "geoMap":
|
||||
this.viewMode = new GeoView(args);
|
||||
break;
|
||||
default:
|
||||
this.viewMode = null;
|
||||
}
|
||||
@ -34,7 +38,7 @@ export default class NoteListRenderer {
|
||||
#getViewType(parentNote: FNote): ViewTypeOptions {
|
||||
const viewType = parentNote.getLabelValue("viewType");
|
||||
|
||||
if (!["list", "grid", "calendar", "table"].includes(viewType || "")) {
|
||||
if (!["list", "grid", "calendar", "table", "geoMap"].includes(viewType || "")) {
|
||||
// when not explicitly set, decide based on the note type
|
||||
return parentNote.type === "search" ? "list" : "grid";
|
||||
} else {
|
||||
|
@ -1,42 +1,86 @@
|
||||
import server from "./server.js";
|
||||
import froca from "./froca.js";
|
||||
import { t } from "./i18n.js";
|
||||
import type { MenuItem } from "../menus/context_menu.js";
|
||||
import froca from "./froca.js";
|
||||
import server from "./server.js";
|
||||
import type { MenuCommandItem, MenuItem, MenuItemBadge } from "../menus/context_menu.js";
|
||||
import type { NoteType } from "../entities/fnote.js";
|
||||
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
|
||||
|
||||
export interface NoteTypeMapping {
|
||||
type: NoteType;
|
||||
mime?: string;
|
||||
title: string;
|
||||
icon?: string;
|
||||
/** Indicates whether this type should be marked as a newly introduced feature. */
|
||||
isNew?: boolean;
|
||||
/** Indicates that this note type is part of a beta feature. */
|
||||
isBeta?: boolean;
|
||||
/** Indicates that this note type cannot be created by the user. */
|
||||
reserved?: boolean;
|
||||
/** Indicates that once a note of this type is created, its type can no longer be changed. */
|
||||
static?: boolean;
|
||||
}
|
||||
|
||||
export const NOTE_TYPES: NoteTypeMapping[] = [
|
||||
// The suggested note type ordering method: insert the item into the corresponding group,
|
||||
// then ensure the items within the group are ordered alphabetically.
|
||||
|
||||
// The default note type (always the first item)
|
||||
{ type: "text", mime: "text/html", title: t("note_types.text"), icon: "bx-note" },
|
||||
|
||||
// Text notes group
|
||||
{ type: "book", mime: "", title: t("note_types.book"), icon: "bx-book" },
|
||||
|
||||
// Graphic notes
|
||||
{ type: "canvas", mime: "application/json", title: t("note_types.canvas"), icon: "bx-pen" },
|
||||
{ type: "mermaid", mime: "text/mermaid", title: t("note_types.mermaid-diagram"), icon: "bx-selection" },
|
||||
|
||||
// Map notes
|
||||
{ type: "mindMap", mime: "application/json", title: t("note_types.mind-map"), icon: "bx-sitemap" },
|
||||
{ type: "noteMap", mime: "", title: t("note_types.note-map"), icon: "bxs-network-chart", static: true },
|
||||
{ type: "relationMap", mime: "application/json", title: t("note_types.relation-map"), icon: "bxs-network-chart" },
|
||||
|
||||
// Misc note types
|
||||
{ type: "render", mime: "", title: t("note_types.render-note"), icon: "bx-extension" },
|
||||
{ type: "search", title: t("note_types.saved-search"), icon: "bx-file-find", static: true },
|
||||
{ type: "webView", mime: "", title: t("note_types.web-view"), icon: "bx-globe-alt" },
|
||||
|
||||
// Code notes
|
||||
{ type: "code", mime: "text/plain", title: t("note_types.code"), icon: "bx-code" },
|
||||
|
||||
// Reserved types (cannot be created by the user)
|
||||
{ type: "contentWidget", mime: "", title: t("note_types.widget"), reserved: true },
|
||||
{ type: "doc", mime: "", title: t("note_types.doc"), reserved: true },
|
||||
{ type: "file", title: t("note_types.file"), reserved: true },
|
||||
{ type: "image", title: t("note_types.image"), reserved: true },
|
||||
{ type: "launcher", mime: "", title: t("note_types.launcher"), reserved: true },
|
||||
{ type: "aiChat", mime: "application/json", title: t("note_types.ai-chat"), reserved: true }
|
||||
];
|
||||
|
||||
/** The maximum age in days for a template to be marked with the "New" badge */
|
||||
const NEW_TEMPLATE_MAX_AGE = 3;
|
||||
|
||||
/** The length of a day in milliseconds. */
|
||||
const DAY_LENGTH = 1000 * 60 * 60 * 24;
|
||||
|
||||
/** The menu item badge used to mark new note types and templates */
|
||||
const NEW_BADGE: MenuItemBadge = {
|
||||
title: t("note_types.new-feature"),
|
||||
className: "new-note-type-badge"
|
||||
};
|
||||
|
||||
/** The menu item badge used to mark note types that are part of a beta feature */
|
||||
const BETA_BADGE = {
|
||||
title: t("note_types.beta-feature")
|
||||
};
|
||||
|
||||
const SEPARATOR = { title: "----" };
|
||||
|
||||
const creationDateCache = new Map<string, Date>();
|
||||
let rootCreationDate: Date | undefined;
|
||||
|
||||
async function getNoteTypeItems(command?: TreeCommandNames) {
|
||||
const items: MenuItem<TreeCommandNames>[] = [
|
||||
// The suggested note type ordering method: insert the item into the corresponding group,
|
||||
// then ensure the items within the group are ordered alphabetically.
|
||||
// Please keep the order synced with the listing found also in aps/client/src/widgets/note_types.ts.
|
||||
|
||||
// The default note type (always the first item)
|
||||
{ title: t("note_types.text"), command, type: "text", uiIcon: "bx bx-note" },
|
||||
|
||||
// Text notes group
|
||||
{ title: t("note_types.book"), command, type: "book", uiIcon: "bx bx-book" },
|
||||
|
||||
// Graphic notes
|
||||
{ title: t("note_types.canvas"), command, type: "canvas", uiIcon: "bx bx-pen" },
|
||||
{ title: t("note_types.mermaid-diagram"), command, type: "mermaid", uiIcon: "bx bx-selection" },
|
||||
|
||||
// Map notes
|
||||
{ title: t("note_types.geo-map"), command, type: "geoMap", uiIcon: "bx bx-map-alt" },
|
||||
{ title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" },
|
||||
{ title: t("note_types.note-map"), command, type: "noteMap", uiIcon: "bx bxs-network-chart" },
|
||||
{ title: t("note_types.relation-map"), command, type: "relationMap", uiIcon: "bx bxs-network-chart" },
|
||||
|
||||
// Misc note types
|
||||
{ title: t("note_types.render-note"), command, type: "render", uiIcon: "bx bx-extension" },
|
||||
{ title: t("note_types.saved-search"), command, type: "search", uiIcon: "bx bx-file-find" },
|
||||
{ title: t("note_types.web-view"), command, type: "webView", uiIcon: "bx bx-globe-alt" },
|
||||
|
||||
// Code notes
|
||||
{ title: t("note_types.code"), command, type: "code", uiIcon: "bx bx-code" },
|
||||
|
||||
// Templates
|
||||
...getBlankNoteTypes(command),
|
||||
...await getBuiltInTemplates(command),
|
||||
...await getUserTemplates(command)
|
||||
];
|
||||
@ -44,6 +88,28 @@ async function getNoteTypeItems(command?: TreeCommandNames) {
|
||||
return items;
|
||||
}
|
||||
|
||||
function getBlankNoteTypes(command): MenuItem<TreeCommandNames>[] {
|
||||
return NOTE_TYPES.filter((nt) => !nt.reserved).map((nt) => {
|
||||
const menuItem: MenuCommandItem<TreeCommandNames> = {
|
||||
title: nt.title,
|
||||
command,
|
||||
type: nt.type,
|
||||
uiIcon: "bx " + nt.icon,
|
||||
badges: []
|
||||
}
|
||||
|
||||
if (nt.isNew) {
|
||||
menuItem.badges?.push(NEW_BADGE);
|
||||
}
|
||||
|
||||
if (nt.isBeta) {
|
||||
menuItem.badges?.push(BETA_BADGE);
|
||||
}
|
||||
|
||||
return menuItem;
|
||||
});
|
||||
}
|
||||
|
||||
async function getUserTemplates(command?: TreeCommandNames) {
|
||||
const templateNoteIds = await server.get<string[]>("search-templates");
|
||||
const templateNotes = await froca.getNotes(templateNoteIds);
|
||||
@ -54,14 +120,21 @@ async function getUserTemplates(command?: TreeCommandNames) {
|
||||
const items: MenuItem<TreeCommandNames>[] = [
|
||||
SEPARATOR
|
||||
];
|
||||
|
||||
for (const templateNote of templateNotes) {
|
||||
items.push({
|
||||
const item: MenuItem<TreeCommandNames> = {
|
||||
title: templateNote.title,
|
||||
uiIcon: templateNote.getIcon(),
|
||||
command: command,
|
||||
type: templateNote.type,
|
||||
templateNoteId: templateNote.noteId
|
||||
});
|
||||
};
|
||||
|
||||
if (await isNewTemplate(templateNote.noteId)) {
|
||||
item.badges = [NEW_BADGE];
|
||||
}
|
||||
|
||||
items.push(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
@ -81,18 +154,71 @@ async function getBuiltInTemplates(command?: TreeCommandNames) {
|
||||
const items: MenuItem<TreeCommandNames>[] = [
|
||||
SEPARATOR
|
||||
];
|
||||
|
||||
for (const templateNote of childNotes) {
|
||||
items.push({
|
||||
const item: MenuItem<TreeCommandNames> = {
|
||||
title: templateNote.title,
|
||||
uiIcon: templateNote.getIcon(),
|
||||
command: command,
|
||||
type: templateNote.type,
|
||||
templateNoteId: templateNote.noteId
|
||||
});
|
||||
};
|
||||
|
||||
if (await isNewTemplate(templateNote.noteId)) {
|
||||
item.badges = [NEW_BADGE];
|
||||
}
|
||||
|
||||
items.push(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
async function isNewTemplate(templateNoteId) {
|
||||
if (rootCreationDate === undefined) {
|
||||
// Retrieve the root note creation date
|
||||
try {
|
||||
let rootNoteInfo: any = await server.get("notes/root");
|
||||
if ("dateCreated" in rootNoteInfo) {
|
||||
rootCreationDate = new Date(rootNoteInfo.dateCreated);
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to retrieve the template's creation date from the cache
|
||||
let creationDate: Date | undefined = creationDateCache.get(templateNoteId);
|
||||
|
||||
if (creationDate === undefined) {
|
||||
// The creation date isn't available in the cache, try to retrieve it from the server
|
||||
try {
|
||||
const noteInfo: any = await server.get("notes/" + templateNoteId);
|
||||
if ("dateCreated" in noteInfo) {
|
||||
creationDate = new Date(noteInfo.dateCreated);
|
||||
creationDateCache.set(templateNoteId, creationDate);
|
||||
}
|
||||
} catch (ex) {
|
||||
console.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (creationDate) {
|
||||
if (rootCreationDate && creationDate.getTime() - rootCreationDate.getTime() < 30000) {
|
||||
// Ignore templates created within 30 seconds after the root note is created.
|
||||
// This is useful to prevent predefined templates from being marked
|
||||
// as 'New' after setting up a new database.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine the difference in days between now and the template's creation date
|
||||
const age = (new Date().getTime() - creationDate.getTime()) / DAY_LENGTH;
|
||||
// Return true if the template is at most NEW_TEMPLATE_MAX_AGE days old
|
||||
return (age <= NEW_TEMPLATE_MAX_AGE);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getNoteTypeItems
|
||||
};
|
||||
|
@ -192,6 +192,13 @@ samp {
|
||||
font-family: var(--monospace-font-family) !important;
|
||||
}
|
||||
|
||||
.badge {
|
||||
--bs-badge-color: var(--muted-text-color);
|
||||
|
||||
margin-left: 8px;
|
||||
background: var(--accented-background-color);
|
||||
}
|
||||
|
||||
.input-group-text {
|
||||
background-color: var(--accented-background-color) !important;
|
||||
color: var(--muted-text-color) !important;
|
||||
|
@ -178,6 +178,9 @@
|
||||
|
||||
--alert-bar-background: #6b6b6b3b;
|
||||
|
||||
--badge-background-color: #ffffff1a;
|
||||
--badge-text-color: var(--muted-text-color);
|
||||
|
||||
--promoted-attribute-card-background-color: var(--card-background-color);
|
||||
--promoted-attribute-card-shadow-color: #000000b3;
|
||||
|
||||
|
@ -171,6 +171,9 @@
|
||||
|
||||
--alert-bar-background: #32637b29;
|
||||
|
||||
--badge-background-color: #00000011;
|
||||
--badge-text-color: var(--muted-text-color);
|
||||
|
||||
--promoted-attribute-card-background-color: var(--card-background-color);
|
||||
--promoted-attribute-card-shadow-color: #00000033;
|
||||
|
||||
|
@ -171,6 +171,16 @@ html body .dropdown-item[disabled] {
|
||||
opacity: var(--menu-item-disabled-opacity);
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
:root .badge {
|
||||
--bs-badge-color: var(--badge-text-color);
|
||||
--bs-badge-font-weight: 500;
|
||||
|
||||
background: var(--badge-background-color);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .2pt;
|
||||
}
|
||||
|
||||
/* Menu item icon */
|
||||
.dropdown-item .bx {
|
||||
transform: translateY(var(--menu-item-icon-vert-offset));
|
||||
|
@ -761,7 +761,8 @@
|
||||
"book_properties": "Book Properties",
|
||||
"invalid_view_type": "Invalid view type '{{type}}'",
|
||||
"calendar": "Calendar",
|
||||
"table": "Table"
|
||||
"table": "Table",
|
||||
"geo-map": "Geo Map"
|
||||
},
|
||||
"edited_notes": {
|
||||
"no_edited_notes_found": "No edited notes on this day yet...",
|
||||
@ -1627,7 +1628,8 @@
|
||||
"geo-map": "Geo Map",
|
||||
"beta-feature": "Beta",
|
||||
"ai-chat": "AI Chat",
|
||||
"task-list": "Task List"
|
||||
"task-list": "Task List",
|
||||
"new-feature": "New"
|
||||
},
|
||||
"protect_note": {
|
||||
"toggle-on": "Protect the note",
|
||||
@ -1858,7 +1860,8 @@
|
||||
},
|
||||
"geo-map-context": {
|
||||
"open-location": "Open location",
|
||||
"remove-from-map": "Remove from map"
|
||||
"remove-from-map": "Remove from map",
|
||||
"add-note": "Add a marker at this location"
|
||||
},
|
||||
"help-button": {
|
||||
"title": "Open the relevant help page"
|
||||
|
@ -189,7 +189,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
|
||||
this.toggleDisabled(this.$findInTextButton, ["text", "code", "book", "mindMap"].includes(note.type));
|
||||
|
||||
this.toggleDisabled(this.$showAttachmentsButton, !isInOptions);
|
||||
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "geoMap"].includes(note.type));
|
||||
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type));
|
||||
|
||||
const canPrint = ["text", "code"].includes(note.type);
|
||||
this.toggleDisabled(this.$printActiveNoteButton, canPrint);
|
||||
|
@ -154,13 +154,21 @@ export default class NoteTypeChooserDialog extends BasicWidget {
|
||||
this.$noteTypeDropdown.append($('<h6 class="dropdown-header">').append(t("note_type_chooser.templates")));
|
||||
} else {
|
||||
const commandItem = noteType as MenuCommandItem<CommandNames>;
|
||||
this.$noteTypeDropdown.append(
|
||||
$('<a class="dropdown-item" tabindex="0">')
|
||||
const listItem = $('<a class="dropdown-item" tabindex="0">')
|
||||
.attr("data-note-type", commandItem.type || "")
|
||||
.attr("data-template-note-id", commandItem.templateNoteId || "")
|
||||
.append($("<span>").addClass(commandItem.uiIcon || ""))
|
||||
.append(` ${noteType.title}`)
|
||||
);
|
||||
.append(` ${noteType.title}`);
|
||||
|
||||
if (commandItem.badges) {
|
||||
for (let badge of commandItem.badges) {
|
||||
listItem.append($(`<span class="badge">`)
|
||||
.addClass(badge.className || "")
|
||||
.text(badge.title));
|
||||
}
|
||||
}
|
||||
|
||||
this.$noteTypeDropdown.append(listItem);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,9 @@ const TPL = /*html*/`\
|
||||
export default class GeoMapButtons extends NoteContextAwareWidget {
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && this.note?.type === "geoMap";
|
||||
return super.isEnabled()
|
||||
&& this.note?.getLabelValue("viewType") === "geoMap"
|
||||
&& !this.note.hasLabel("readOnly");
|
||||
}
|
||||
|
||||
doRender() {
|
||||
|
@ -17,7 +17,6 @@ export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
|
||||
contentWidget: null,
|
||||
doc: null,
|
||||
file: null,
|
||||
geoMap: "81SGnPGMk7Xc",
|
||||
image: null,
|
||||
launcher: null,
|
||||
mermaid: null,
|
||||
@ -35,7 +34,8 @@ export const byBookType: Record<ViewTypeOptions, string | null> = {
|
||||
list: null,
|
||||
grid: null,
|
||||
calendar: "xWbu3jpNWapp",
|
||||
table: "2FvYrpmOXm29"
|
||||
table: "2FvYrpmOXm29",
|
||||
geoMap: "81SGnPGMk7Xc"
|
||||
};
|
||||
|
||||
export default class ContextualHelpButton extends NoteContextAwareWidget {
|
||||
|
@ -39,10 +39,20 @@ export default class ToggleReadOnlyButton extends OnClickButtonWidget {
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled()
|
||||
&& this.note?.type === "mermaid"
|
||||
&& this.note?.isContentAvailable()
|
||||
&& this.noteContext?.viewScope?.viewMode === "default";
|
||||
if (!super.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this?.note?.isContentAvailable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.noteContext?.viewScope?.viewMode !== "default") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.note.type === "mermaid" ||
|
||||
(this.note.getLabelValue("viewType") === "geoMap");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
import type { Map } from "leaflet";
|
||||
import L from "leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
|
||||
const TPL = /*html*/`\
|
||||
<div class="geo-map-widget">
|
||||
<style>
|
||||
.note-detail-geo-map,
|
||||
.geo-map-widget,
|
||||
.geo-map-container {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
z-index: 900;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="geo-map-container"></div>
|
||||
</div>`;
|
||||
|
||||
export type Leaflet = typeof L;
|
||||
export type InitCallback = (L: Leaflet) => void;
|
||||
|
||||
export default class GeoMapWidget extends NoteContextAwareWidget {
|
||||
|
||||
map?: Map;
|
||||
$container!: JQuery<HTMLElement>;
|
||||
private initCallback?: InitCallback;
|
||||
|
||||
constructor(widgetMode: "type", initCallback?: InitCallback) {
|
||||
super();
|
||||
this.initCallback = initCallback;
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
|
||||
this.$container = this.$widget.find(".geo-map-container");
|
||||
|
||||
const map = L.map(this.$container[0], {
|
||||
worldCopyJump: true
|
||||
});
|
||||
|
||||
this.map = map;
|
||||
if (this.initCallback) {
|
||||
this.initCallback(L);
|
||||
}
|
||||
|
||||
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
detectRetina: true
|
||||
}).addTo(map);
|
||||
}
|
||||
}
|
@ -28,7 +28,6 @@ import ContentWidgetTypeWidget from "./type_widgets/content_widget.js";
|
||||
import AttachmentListTypeWidget from "./type_widgets/attachment_list.js";
|
||||
import AttachmentDetailTypeWidget from "./type_widgets/attachment_detail.js";
|
||||
import MindMapWidget from "./type_widgets/mind_map.js";
|
||||
import GeoMapTypeWidget from "./type_widgets/geo_map.js";
|
||||
import utils from "../services/utils.js";
|
||||
import type { NoteType } from "../entities/fnote.js";
|
||||
import type TypeWidget from "./type_widgets/type_widget.js";
|
||||
@ -71,7 +70,6 @@ const typeWidgetClasses = {
|
||||
attachmentDetail: AttachmentDetailTypeWidget,
|
||||
attachmentList: AttachmentListTypeWidget,
|
||||
mindMap: MindMapWidget,
|
||||
geoMap: GeoMapTypeWidget,
|
||||
aiChat: AiChatTypeWidget,
|
||||
|
||||
// Split type editors
|
||||
@ -197,7 +195,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
|
||||
// https://github.com/zadam/trilium/issues/2522
|
||||
const isBackendNote = this.noteContext?.noteId === "_backendLog";
|
||||
const isSqlNote = this.mime === "text/x-sqlite;schema=trilium";
|
||||
const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "geoMap", "mermaid"].includes(this.type ?? "");
|
||||
const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "mermaid"].includes(this.type ?? "");
|
||||
const isFullHeight = (!this.noteContext?.hasNoteList() && isFullHeightNoteType && !isSqlNote)
|
||||
|| this.noteContext?.viewScope?.viewMode === "attachments"
|
||||
|| isBackendNote;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import NoteListRenderer from "../services/note_list_renderer.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { CommandListener, CommandListenerData, CommandMappings, CommandNames, EventData } from "../components/app_context.js";
|
||||
import type { CommandListener, CommandListenerData, CommandMappings, CommandNames, EventData, EventNames } from "../components/app_context.js";
|
||||
import type ViewMode from "./view_widgets/view_mode.js";
|
||||
import AttributeDetailWidget from "./attribute_widgets/attribute_detail.js";
|
||||
import { Attribute } from "../services/attribute_parser.js";
|
||||
@ -176,4 +176,17 @@ export default class NoteListWidget extends NoteContextAwareWidget {
|
||||
return super.triggerCommand(name, data);
|
||||
}
|
||||
|
||||
handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown[] | unknown> | null {
|
||||
super.handleEventInChildren(name, data);
|
||||
|
||||
if (this.viewMode) {
|
||||
const ret = this.viewMode.handleEvent(name, data);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -186,6 +186,15 @@ interface RefreshContext {
|
||||
noteIdsToReload: Set<string>;
|
||||
}
|
||||
|
||||
/**
|
||||
* The information contained within a drag event.
|
||||
*/
|
||||
export interface DragData {
|
||||
noteId: string;
|
||||
branchId: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default class NoteTreeWidget extends NoteContextAwareWidget {
|
||||
private $tree!: JQuery<HTMLElement>;
|
||||
private $treeActions!: JQuery<HTMLElement>;
|
||||
|
@ -1,60 +1,15 @@
|
||||
import server from "../services/server.js";
|
||||
import { Dropdown } from "bootstrap";
|
||||
import { NOTE_TYPES } from "../services/note_types.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import mimeTypesService from "../services/mime_types.js";
|
||||
import NoteContextAwareWidget from "./note_context_aware_widget.js";
|
||||
import dialogService from "../services/dialog.js";
|
||||
import { t } from "../services/i18n.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
import type { NoteType } from "../entities/fnote.js";
|
||||
import server from "../services/server.js";
|
||||
import type { EventData } from "../components/app_context.js";
|
||||
import { Dropdown } from "bootstrap";
|
||||
import type { NoteType } from "../entities/fnote.js";
|
||||
import type FNote from "../entities/fnote.js";
|
||||
|
||||
interface NoteTypeMapping {
|
||||
type: NoteType;
|
||||
mime?: string;
|
||||
title: string;
|
||||
isBeta?: boolean;
|
||||
selectable: boolean;
|
||||
}
|
||||
|
||||
const NOTE_TYPES: NoteTypeMapping[] = [
|
||||
// The suggested note type ordering method: insert the item into the corresponding group,
|
||||
// then ensure the items within the group are ordered alphabetically.
|
||||
// Please keep the order synced with the listing found also in apps/client/src/services/note_types.ts.
|
||||
|
||||
// The default note type (always the first item)
|
||||
{ type: "text", mime: "text/html", title: t("note_types.text"), selectable: true },
|
||||
|
||||
// Text notes group
|
||||
{ type: "book", mime: "", title: t("note_types.book"), selectable: true },
|
||||
|
||||
// Graphic notes
|
||||
{ type: "canvas", mime: "application/json", title: t("note_types.canvas"), selectable: true },
|
||||
{ type: "mermaid", mime: "text/mermaid", title: t("note_types.mermaid-diagram"), selectable: true },
|
||||
|
||||
// Map notes
|
||||
{ type: "geoMap", mime: "application/json", title: t("note_types.geo-map"), isBeta: true, selectable: true },
|
||||
{ type: "mindMap", mime: "application/json", title: t("note_types.mind-map"), selectable: true },
|
||||
{ type: "relationMap", mime: "application/json", title: t("note_types.relation-map"), selectable: true },
|
||||
|
||||
// Misc note types
|
||||
{ type: "render", mime: "", title: t("note_types.render-note"), selectable: true },
|
||||
{ type: "webView", mime: "", title: t("note_types.web-view"), selectable: true },
|
||||
|
||||
// Code notes
|
||||
{ type: "code", mime: "text/plain", title: t("note_types.code"), selectable: true },
|
||||
|
||||
// Reserved types (cannot be created by the user)
|
||||
{ type: "contentWidget", mime: "", title: t("note_types.widget"), selectable: false },
|
||||
{ type: "doc", mime: "", title: t("note_types.doc"), selectable: false },
|
||||
{ type: "file", title: t("note_types.file"), selectable: false },
|
||||
{ type: "image", title: t("note_types.image"), selectable: false },
|
||||
{ type: "launcher", mime: "", title: t("note_types.launcher"), selectable: false },
|
||||
{ type: "noteMap", mime: "", title: t("note_types.note-map"), selectable: false },
|
||||
{ type: "search", title: t("note_types.saved-search"), selectable: false },
|
||||
{ type: "aiChat", mime: "application/json", title: t("note_types.ai-chat"), selectable: false }
|
||||
];
|
||||
|
||||
const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => !nt.selectable).map((nt) => nt.type);
|
||||
const NOT_SELECTABLE_NOTE_TYPES = NOTE_TYPES.filter((nt) => nt.reserved || nt.static).map((nt) => nt.type);
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="dropdown note-type-widget">
|
||||
@ -64,13 +19,6 @@ const TPL = /*html*/`
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.note-type-dropdown .badge {
|
||||
margin-left: 8px;
|
||||
background: var(--accented-background-color);
|
||||
font-weight: normal;
|
||||
color: var(--menu-text-color);
|
||||
}
|
||||
</style>
|
||||
<button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" class="btn btn-sm dropdown-toggle select-button note-type-button">
|
||||
<span class="note-type-desc"></span>
|
||||
@ -117,10 +65,15 @@ export default class NoteTypeWidget extends NoteContextAwareWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const noteType of NOTE_TYPES.filter((nt) => nt.selectable)) {
|
||||
for (const noteType of NOTE_TYPES.filter((nt) => !nt.reserved && !nt.static)) {
|
||||
let $typeLink: JQuery<HTMLElement>;
|
||||
|
||||
const $title = $("<span>").text(noteType.title);
|
||||
|
||||
if (noteType.isNew) {
|
||||
$title.append($(`<span class="badge new-note-type-badge">`).text(t("note_types.new-feature")));
|
||||
}
|
||||
|
||||
if (noteType.isBeta) {
|
||||
$title.append($(`<span class="badge">`).text(t("note_types.beta-feature")));
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ export default class NoteWrapperWidget extends FlexContainer<BasicWidget> {
|
||||
}
|
||||
|
||||
#isFullWidthNote(note: FNote) {
|
||||
if (["image", "mermaid", "book", "render", "canvas", "webView", "mindMap", "geoMap"].includes(note.type)) {
|
||||
if (["image", "mermaid", "book", "render", "canvas", "webView", "mindMap"].includes(note.type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ const TPL = /*html*/`
|
||||
<option value="list">${t("book_properties.list")}</option>
|
||||
<option value="calendar">${t("book_properties.calendar")}</option>
|
||||
<option value="table">${t("book_properties.table")}</option>
|
||||
<option value="geoMap">${t("book_properties.geo-map")}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@ -126,7 +127,7 @@ export default class BookPropertiesWidget extends NoteContextAwareWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!["list", "grid", "calendar", "table"].includes(type)) {
|
||||
if (!["list", "grid", "calendar", "table", "geoMap"].includes(type)) {
|
||||
throw new Error(t("book_properties.invalid_view_type", { type }));
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,7 @@ export default class BookTypeWidget extends TypeWidget {
|
||||
switch (this.note?.getAttributeValue("label", "viewType")) {
|
||||
case "calendar":
|
||||
case "table":
|
||||
case "geoMap":
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
|
@ -1,447 +0,0 @@
|
||||
import { GPX, Marker, type LatLng, type LeafletMouseEvent } from "leaflet";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import GeoMapWidget, { type InitCallback, type Leaflet } from "../geo_map.js";
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import server from "../../services/server.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import dialogService from "../../services/dialog.js";
|
||||
import type { CommandListenerData, EventData } from "../../components/app_context.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import attributes from "../../services/attributes.js";
|
||||
import openContextMenu from "./geo_map_context_menu.js";
|
||||
import link from "../../services/link.js";
|
||||
import note_tooltip from "../../services/note_tooltip.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
|
||||
import markerIcon from "leaflet/dist/images/marker-icon.png";
|
||||
import markerIconShadow from "leaflet/dist/images/marker-shadow.png";
|
||||
import { hasTouchBar } from "../../services/utils.js";
|
||||
|
||||
const TPL = /*html*/`\
|
||||
<div class="note-detail-geo-map note-detail-printable">
|
||||
<style>
|
||||
.leaflet-pane {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.geo-map-container.placing-note {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.geo-map-container .marker-pin {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.geo-map-container .leaflet-div-icon {
|
||||
position: relative;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.geo-map-container .leaflet-div-icon .icon-shadow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.geo-map-container .leaflet-div-icon .bx {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 2px;
|
||||
background-color: white;
|
||||
color: black;
|
||||
padding: 2px;
|
||||
border-radius: 50%;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.geo-map-container .leaflet-div-icon .title-label {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 0.75rem;
|
||||
height: 1rem;
|
||||
color: black;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
|
||||
white-space: no-wrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</div>`;
|
||||
|
||||
const LOCATION_ATTRIBUTE = "geolocation";
|
||||
const CHILD_NOTE_ICON = "bx bx-pin";
|
||||
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
|
||||
const DEFAULT_ZOOM = 2;
|
||||
|
||||
interface MapData {
|
||||
view?: {
|
||||
center?: LatLng | [number, number];
|
||||
zoom?: number;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Deduplicate
|
||||
interface CreateChildResponse {
|
||||
note: {
|
||||
noteId: string;
|
||||
};
|
||||
}
|
||||
|
||||
enum State {
|
||||
Normal,
|
||||
NewNote
|
||||
}
|
||||
|
||||
export default class GeoMapTypeWidget extends TypeWidget {
|
||||
|
||||
private geoMapWidget: GeoMapWidget;
|
||||
private _state: State;
|
||||
private L!: Leaflet;
|
||||
private currentMarkerData: Record<string, Marker>;
|
||||
private currentTrackData: Record<string, GPX>;
|
||||
private gpxLoaded?: boolean;
|
||||
private ignoreNextZoomEvent?: boolean;
|
||||
|
||||
static getType() {
|
||||
return "geoMap";
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.geoMapWidget = new GeoMapWidget("type", (L: Leaflet) => this.#onMapInitialized(L));
|
||||
this.currentMarkerData = {};
|
||||
this.currentTrackData = {};
|
||||
this._state = State.Normal;
|
||||
|
||||
this.child(this.geoMapWidget);
|
||||
}
|
||||
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.append(this.geoMapWidget.render());
|
||||
}
|
||||
|
||||
async #onMapInitialized(L: Leaflet) {
|
||||
this.L = L;
|
||||
const map = this.geoMapWidget.map;
|
||||
if (!map) {
|
||||
throw new Error(t("geo-map.unable-to-load-map"));
|
||||
}
|
||||
|
||||
this.#restoreViewportAndZoom();
|
||||
|
||||
// Restore markers.
|
||||
await this.#reloadMarkers();
|
||||
|
||||
// This fixes an issue with the map appearing cut off at the beginning, due to the container not being properly attached
|
||||
setTimeout(() => {
|
||||
map.invalidateSize();
|
||||
}, 100);
|
||||
|
||||
const updateFn = () => this.spacedUpdate.scheduleUpdate();
|
||||
map.on("moveend", updateFn);
|
||||
map.on("zoomend", updateFn);
|
||||
map.on("click", (e) => this.#onMapClicked(e));
|
||||
|
||||
if (hasTouchBar) {
|
||||
map.on("zoom", () => {
|
||||
if (!this.ignoreNextZoomEvent) {
|
||||
this.triggerCommand("refreshTouchBar");
|
||||
}
|
||||
|
||||
this.ignoreNextZoomEvent = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async #restoreViewportAndZoom() {
|
||||
const map = this.geoMapWidget.map;
|
||||
if (!map || !this.note) {
|
||||
return;
|
||||
}
|
||||
const blob = await this.note.getBlob();
|
||||
|
||||
let parsedContent: MapData = {};
|
||||
if (blob && blob.content) {
|
||||
parsedContent = JSON.parse(blob.content);
|
||||
}
|
||||
|
||||
// Restore viewport position & zoom
|
||||
const center = parsedContent.view?.center ?? DEFAULT_COORDINATES;
|
||||
const zoom = parsedContent.view?.zoom ?? DEFAULT_ZOOM;
|
||||
map.setView(center, zoom);
|
||||
}
|
||||
|
||||
async #reloadMarkers() {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete all existing markers
|
||||
for (const marker of Object.values(this.currentMarkerData)) {
|
||||
marker.remove();
|
||||
}
|
||||
|
||||
// Delete all existing tracks
|
||||
for (const track of Object.values(this.currentTrackData)) {
|
||||
track.remove();
|
||||
}
|
||||
|
||||
// Add the new markers.
|
||||
this.currentMarkerData = {};
|
||||
const childNotes = await this.note.getChildNotes();
|
||||
for (const childNote of childNotes) {
|
||||
if (childNote.mime === "application/gpx+xml") {
|
||||
this.#processNoteWithGpxTrack(childNote);
|
||||
continue;
|
||||
}
|
||||
|
||||
const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE);
|
||||
if (latLng) {
|
||||
this.#processNoteWithMarker(childNote, latLng);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #processNoteWithGpxTrack(note: FNote) {
|
||||
if (!this.L || !this.geoMapWidget.map) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.gpxLoaded) {
|
||||
await import("leaflet-gpx");
|
||||
this.gpxLoaded = true;
|
||||
}
|
||||
|
||||
const xmlResponse = await server.get<string | Uint8Array>(`notes/${note.noteId}/open`, undefined, true);
|
||||
let stringResponse: string;
|
||||
if (xmlResponse instanceof Uint8Array) {
|
||||
stringResponse = new TextDecoder().decode(xmlResponse);
|
||||
} else {
|
||||
stringResponse = xmlResponse;
|
||||
}
|
||||
|
||||
const track = new this.L.GPX(stringResponse, {
|
||||
markers: {
|
||||
startIcon: this.#buildIcon(note.getIcon(), note.getColorClass(), note.title),
|
||||
endIcon: this.#buildIcon("bxs-flag-checkered"),
|
||||
wptIcons: {
|
||||
"": this.#buildIcon("bx bx-pin")
|
||||
}
|
||||
},
|
||||
polyline_options: {
|
||||
color: note.getLabelValue("color") ?? "blue"
|
||||
}
|
||||
});
|
||||
track.addTo(this.geoMapWidget.map);
|
||||
this.currentTrackData[note.noteId] = track;
|
||||
}
|
||||
|
||||
#processNoteWithMarker(note: FNote, latLng: string) {
|
||||
const map = this.geoMapWidget.map;
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [lat, lng] = latLng.split(",", 2).map((el) => parseFloat(el));
|
||||
const L = this.L;
|
||||
const icon = this.#buildIcon(note.getIcon(), note.getColorClass(), note.title);
|
||||
|
||||
const marker = L.marker(L.latLng(lat, lng), {
|
||||
icon,
|
||||
draggable: true,
|
||||
autoPan: true,
|
||||
autoPanSpeed: 5
|
||||
})
|
||||
.addTo(map)
|
||||
.on("moveend", (e) => {
|
||||
this.moveMarker(note.noteId, (e.target as Marker).getLatLng());
|
||||
});
|
||||
marker.on("mousedown", ({ originalEvent }) => {
|
||||
// Middle click to open in new tab
|
||||
if (originalEvent.button === 1) {
|
||||
const hoistedNoteId = this.hoistedNoteId;
|
||||
//@ts-ignore, fix once tab manager is ported.
|
||||
appContext.tabManager.openInNewTab(note.noteId, hoistedNoteId);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
marker.on("contextmenu", (e) => {
|
||||
openContextMenu(note.noteId, e.originalEvent);
|
||||
});
|
||||
|
||||
const el = marker.getElement();
|
||||
if (el) {
|
||||
const $el = $(el);
|
||||
$el.attr("data-href", `#${note.noteId}`);
|
||||
note_tooltip.setupElementTooltip($($el));
|
||||
}
|
||||
|
||||
this.currentMarkerData[note.noteId] = marker;
|
||||
}
|
||||
|
||||
#buildIcon(bxIconClass: string, colorClass?: string, title?: string) {
|
||||
return this.L.divIcon({
|
||||
html: /*html*/`\
|
||||
<img class="icon" src="${markerIcon}" />
|
||||
<img class="icon-shadow" src="${markerIconShadow}" />
|
||||
<span class="bx ${bxIconClass} ${colorClass ?? ""}"></span>
|
||||
<span class="title-label">${title ?? ""}</span>`,
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41]
|
||||
});
|
||||
}
|
||||
|
||||
#changeState(newState: State) {
|
||||
this._state = newState;
|
||||
this.geoMapWidget.$container.toggleClass("placing-note", newState === State.NewNote);
|
||||
if (hasTouchBar) {
|
||||
this.triggerCommand("refreshTouchBar");
|
||||
}
|
||||
}
|
||||
|
||||
async #onMapClicked(e: LeafletMouseEvent) {
|
||||
if (this._state !== State.NewNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.closePersistent("geo-new-note");
|
||||
const title = await dialogService.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") });
|
||||
|
||||
if (title?.trim()) {
|
||||
const { note } = await server.post<CreateChildResponse>(`notes/${this.noteId}/children?target=into`, {
|
||||
title,
|
||||
content: "",
|
||||
type: "text"
|
||||
});
|
||||
attributes.setLabel(note.noteId, "iconClass", CHILD_NOTE_ICON);
|
||||
this.moveMarker(note.noteId, e.latlng);
|
||||
}
|
||||
|
||||
this.#changeState(State.Normal);
|
||||
}
|
||||
|
||||
async moveMarker(noteId: string, latLng: LatLng | null) {
|
||||
const value = latLng ? [latLng.lat, latLng.lng].join(",") : "";
|
||||
await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value);
|
||||
}
|
||||
|
||||
getData(): any {
|
||||
const map = this.geoMapWidget.map;
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data: MapData = {
|
||||
view: {
|
||||
center: map.getBounds().getCenter(),
|
||||
zoom: map.getZoom()
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
content: JSON.stringify(data)
|
||||
};
|
||||
}
|
||||
|
||||
async geoMapCreateChildNoteEvent({ ntxId }: EventData<"geoMapCreateChildNote">) {
|
||||
if (!this.isNoteContext(ntxId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.showPersistent({
|
||||
icon: "plus",
|
||||
id: "geo-new-note",
|
||||
title: "New note",
|
||||
message: t("geo-map.create-child-note-instruction")
|
||||
});
|
||||
|
||||
this.#changeState(State.NewNote);
|
||||
|
||||
const globalKeyListener: (this: Window, ev: KeyboardEvent) => any = (e) => {
|
||||
if (e.key !== "Escape") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#changeState(State.Normal);
|
||||
|
||||
window.removeEventListener("keydown", globalKeyListener);
|
||||
toastService.closePersistent("geo-new-note");
|
||||
};
|
||||
window.addEventListener("keydown", globalKeyListener);
|
||||
}
|
||||
|
||||
async doRefresh(note: FNote) {
|
||||
await this.geoMapWidget.refresh();
|
||||
this.#restoreViewportAndZoom();
|
||||
await this.#reloadMarkers();
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
// If any of the children branches are altered.
|
||||
if (loadResults.getBranchRows().find((branch) => branch.parentNoteId === this.noteId)) {
|
||||
this.#reloadMarkers();
|
||||
return;
|
||||
}
|
||||
|
||||
// If any of note has its location attribute changed.
|
||||
// TODO: Should probably filter by parent here as well.
|
||||
const attributeRows = loadResults.getAttributeRows();
|
||||
if (attributeRows.find((at) => [LOCATION_ATTRIBUTE, "color"].includes(at.name ?? ""))) {
|
||||
this.#reloadMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
openGeoLocationEvent({ noteId, event }: EventData<"openGeoLocation">) {
|
||||
const marker = this.currentMarkerData[noteId];
|
||||
if (!marker) {
|
||||
return;
|
||||
}
|
||||
|
||||
const latLng = this.currentMarkerData[noteId].getLatLng();
|
||||
const url = `geo:${latLng.lat},${latLng.lng}`;
|
||||
link.goToLinkExt(event, url);
|
||||
}
|
||||
|
||||
deleteFromMapEvent({ noteId }: EventData<"deleteFromMap">) {
|
||||
this.moveMarker(noteId, null);
|
||||
}
|
||||
|
||||
buildTouchBarCommand({ TouchBar }: CommandListenerData<"buildTouchBar">) {
|
||||
const map = this.geoMapWidget.map;
|
||||
const that = this;
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
|
||||
return [
|
||||
new TouchBar.TouchBarSlider({
|
||||
label: "Zoom",
|
||||
value: map.getZoom(),
|
||||
minValue: map.getMinZoom(),
|
||||
maxValue: map.getMaxZoom(),
|
||||
change(newValue) {
|
||||
that.ignoreNextZoomEvent = true;
|
||||
map.setZoom(newValue);
|
||||
},
|
||||
}),
|
||||
new TouchBar.TouchBarButton({
|
||||
label: "New geo note",
|
||||
click: () => this.triggerCommand("geoMapCreateChildNote", { ntxId: this.ntxId }),
|
||||
enabled: (this._state === State.Normal)
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import appContext from "../../components/app_context.js";
|
||||
import type { ContextMenuEvent } from "../../menus/context_menu.js";
|
||||
import contextMenu from "../../menus/context_menu.js";
|
||||
import linkContextMenu from "../../menus/link_context_menu.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
|
||||
export default function openContextMenu(noteId: string, e: ContextMenuEvent) {
|
||||
contextMenu.show({
|
||||
x: e.pageX,
|
||||
y: e.pageY,
|
||||
items: [
|
||||
...linkContextMenu.getItems(),
|
||||
{ title: t("geo-map-context.open-location"), command: "openGeoLocation", uiIcon: "bx bx-map-alt" },
|
||||
{ title: "----" },
|
||||
{ title: t("geo-map-context.remove-from-map"), command: "deleteFromMap", uiIcon: "bx bx-trash" }
|
||||
],
|
||||
selectMenuItemHandler: ({ command }, e) => {
|
||||
if (command === "deleteFromMap") {
|
||||
appContext.triggerCommand(command, { noteId });
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === "openGeoLocation") {
|
||||
appContext.triggerCommand(command, { noteId, event: e });
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass the events to the link context menu
|
||||
linkContextMenu.handleLinkContextMenuItem(command, noteId);
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import type { LatLng, LeafletMouseEvent } from "leaflet";
|
||||
import appContext, { type CommandMappings } from "../../../components/app_context.js";
|
||||
import contextMenu, { type MenuItem } from "../../../menus/context_menu.js";
|
||||
import linkContextMenu from "../../../menus/link_context_menu.js";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import { createNewNote } from "./editing.js";
|
||||
import { copyTextWithToast } from "../../../services/clipboard_ext.js";
|
||||
import link from "../../../services/link.js";
|
||||
|
||||
export default function openContextMenu(noteId: string, e: LeafletMouseEvent, isEditable: boolean) {
|
||||
let items: MenuItem<keyof CommandMappings>[] = [
|
||||
...buildGeoLocationItem(e),
|
||||
{ title: "----" },
|
||||
...linkContextMenu.getItems(),
|
||||
];
|
||||
|
||||
if (isEditable) {
|
||||
items = [
|
||||
...items,
|
||||
{ title: "----" },
|
||||
{ title: t("geo-map-context.remove-from-map"), command: "deleteFromMap", uiIcon: "bx bx-trash" }
|
||||
];
|
||||
}
|
||||
|
||||
contextMenu.show({
|
||||
x: e.originalEvent.pageX,
|
||||
y: e.originalEvent.pageY,
|
||||
items,
|
||||
selectMenuItemHandler: ({ command }, e) => {
|
||||
if (command === "deleteFromMap") {
|
||||
appContext.triggerCommand(command, { noteId });
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass the events to the link context menu
|
||||
linkContextMenu.handleLinkContextMenuItem(command, noteId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function openMapContextMenu(noteId: string, e: LeafletMouseEvent, isEditable: boolean) {
|
||||
let items: MenuItem<keyof CommandMappings>[] = [
|
||||
...buildGeoLocationItem(e)
|
||||
];
|
||||
|
||||
if (isEditable) {
|
||||
items = [
|
||||
...items,
|
||||
{ title: "----" },
|
||||
{
|
||||
title: t("geo-map-context.add-note"),
|
||||
handler: () => createNewNote(noteId, e),
|
||||
uiIcon: "bx bx-plus"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
contextMenu.show({
|
||||
x: e.originalEvent.pageX,
|
||||
y: e.originalEvent.pageY,
|
||||
items,
|
||||
selectMenuItemHandler: () => {
|
||||
// Nothing to do, as the commands handle themselves.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildGeoLocationItem(e: LeafletMouseEvent) {
|
||||
function formatGeoLocation(latlng: LatLng, precision: number = 6) {
|
||||
return `${latlng.lat.toFixed(precision)}, ${latlng.lng.toFixed(precision)}`;
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
title: formatGeoLocation(e.latlng),
|
||||
uiIcon: "bx bx-current-location",
|
||||
handler: () => copyTextWithToast(formatGeoLocation(e.latlng, 15))
|
||||
},
|
||||
{
|
||||
title: t("geo-map-context.open-location"),
|
||||
uiIcon: "bx bx-map-alt",
|
||||
handler: () => link.goToLinkExt(null, `geo:${e.latlng.lat},${e.latlng.lng}`)
|
||||
}
|
||||
];
|
||||
}
|
80
apps/client/src/widgets/view_widgets/geo_view/editing.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import { LatLng, LeafletMouseEvent } from "leaflet";
|
||||
import attributes from "../../../services/attributes";
|
||||
import { LOCATION_ATTRIBUTE } from "./index.js";
|
||||
import dialog from "../../../services/dialog";
|
||||
import server from "../../../services/server";
|
||||
import { t } from "../../../services/i18n";
|
||||
import type { Map } from "leaflet";
|
||||
import type { DragData } from "../../note_tree.js";
|
||||
import froca from "../../../services/froca.js";
|
||||
import branches from "../../../services/branches.js";
|
||||
|
||||
const CHILD_NOTE_ICON = "bx bx-pin";
|
||||
|
||||
// TODO: Deduplicate
|
||||
interface CreateChildResponse {
|
||||
note: {
|
||||
noteId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function moveMarker(noteId: string, latLng: LatLng | null) {
|
||||
const value = latLng ? [latLng.lat, latLng.lng].join(",") : "";
|
||||
await attributes.setLabel(noteId, LOCATION_ATTRIBUTE, value);
|
||||
}
|
||||
|
||||
export async function createNewNote(noteId: string, e: LeafletMouseEvent) {
|
||||
const title = await dialog.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") });
|
||||
|
||||
if (title?.trim()) {
|
||||
const { note } = await server.post<CreateChildResponse>(`notes/${noteId}/children?target=into`, {
|
||||
title,
|
||||
content: "",
|
||||
type: "text"
|
||||
});
|
||||
attributes.setLabel(note.noteId, "iconClass", CHILD_NOTE_ICON);
|
||||
moveMarker(note.noteId, e.latlng);
|
||||
}
|
||||
}
|
||||
|
||||
export function setupDragging($container: JQuery<HTMLElement>, map: Map, mapNoteId: string) {
|
||||
$container.on("dragover", (e) => {
|
||||
// Allow drag.
|
||||
e.preventDefault();
|
||||
});
|
||||
$container.on("drop", async (e) => {
|
||||
if (!e.originalEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = e.originalEvent.dataTransfer?.getData('text');
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsedData = JSON.parse(data) as DragData[];
|
||||
if (!parsedData.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { noteId } = parsedData[0];
|
||||
|
||||
const offset = $container.offset();
|
||||
const x = e.originalEvent.clientX - (offset?.left ?? 0);
|
||||
const y = e.originalEvent.clientY - (offset?.top ?? 0);
|
||||
const latlng = map.containerPointToLatLng([ x, y ]);
|
||||
|
||||
const note = await froca.getNote(noteId, true);
|
||||
const parents = note?.getParentNoteIds();
|
||||
if (parents?.includes(mapNoteId)) {
|
||||
await moveMarker(noteId, latlng);
|
||||
} else {
|
||||
await branches.cloneNoteToParentNote(noteId, mapNoteId);
|
||||
await moveMarker(noteId, latlng);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
});
|
||||
}
|
331
apps/client/src/widgets/view_widgets/geo_view/index.ts
Normal file
@ -0,0 +1,331 @@
|
||||
import ViewMode, { ViewModeArgs } from "../view_mode.js";
|
||||
import L from "leaflet";
|
||||
import type { GPX, LatLng, LeafletMouseEvent, Map, Marker } from "leaflet";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import SpacedUpdate from "../../../services/spaced_update.js";
|
||||
import { t } from "../../../services/i18n.js";
|
||||
import processNoteWithMarker, { processNoteWithGpxTrack } from "./markers.js";
|
||||
import { hasTouchBar } from "../../../services/utils.js";
|
||||
import toast from "../../../services/toast.js";
|
||||
import { CommandListenerData, EventData } from "../../../components/app_context.js";
|
||||
import { createNewNote, moveMarker, setupDragging } from "./editing.js";
|
||||
import { openMapContextMenu } from "./context_menu.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="geo-view">
|
||||
<style>
|
||||
.geo-view {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.geo-map-container {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.leaflet-pane {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.geo-map-container.placing-note {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.geo-map-container .marker-pin {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.geo-map-container .leaflet-div-icon {
|
||||
position: relative;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.geo-map-container .leaflet-div-icon .icon-shadow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.geo-map-container .leaflet-div-icon .bx {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 2px;
|
||||
background-color: white;
|
||||
color: black;
|
||||
padding: 2px;
|
||||
border-radius: 50%;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.geo-map-container .leaflet-div-icon .title-label {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 0.75rem;
|
||||
height: 1rem;
|
||||
color: black;
|
||||
width: 100px;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
|
||||
white-space: no-wrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="geo-map-container"></div>
|
||||
</div>`;
|
||||
|
||||
interface MapData {
|
||||
view?: {
|
||||
center?: LatLng | [number, number];
|
||||
zoom?: number;
|
||||
};
|
||||
}
|
||||
|
||||
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
|
||||
const DEFAULT_ZOOM = 2;
|
||||
export const LOCATION_ATTRIBUTE = "geolocation";
|
||||
|
||||
enum State {
|
||||
Normal,
|
||||
NewNote
|
||||
}
|
||||
|
||||
export default class GeoView extends ViewMode<MapData> {
|
||||
|
||||
private $root: JQuery<HTMLElement>;
|
||||
private $container!: JQuery<HTMLElement>;
|
||||
private map?: Map;
|
||||
private spacedUpdate: SpacedUpdate;
|
||||
private _state: State;
|
||||
private ignoreNextZoomEvent?: boolean;
|
||||
|
||||
private currentMarkerData: Record<string, Marker>;
|
||||
private currentTrackData: Record<string, GPX>;
|
||||
|
||||
constructor(args: ViewModeArgs) {
|
||||
super(args, "geoMap");
|
||||
this.$root = $(TPL);
|
||||
this.$container = this.$root.find(".geo-map-container");
|
||||
this.spacedUpdate = new SpacedUpdate(() => this.onSave(), 5_000);
|
||||
|
||||
this.currentMarkerData = {};
|
||||
this.currentTrackData = {};
|
||||
this._state = State.Normal;
|
||||
|
||||
args.$parent.append(this.$root);
|
||||
}
|
||||
|
||||
async renderList() {
|
||||
this.renderMap();
|
||||
return this.$root;
|
||||
}
|
||||
|
||||
async renderMap() {
|
||||
const map = L.map(this.$container[0], {
|
||||
worldCopyJump: true
|
||||
});
|
||||
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
detectRetina: true
|
||||
}).addTo(map);
|
||||
|
||||
this.map = map;
|
||||
|
||||
this.#onMapInitialized();
|
||||
}
|
||||
|
||||
async #onMapInitialized() {
|
||||
const map = this.map;
|
||||
if (!map) {
|
||||
throw new Error(t("geo-map.unable-to-load-map"));
|
||||
}
|
||||
|
||||
this.#restoreViewportAndZoom();
|
||||
|
||||
const isEditable = !this.isReadOnly;
|
||||
const updateFn = () => this.spacedUpdate.scheduleUpdate();
|
||||
map.on("moveend", updateFn);
|
||||
map.on("zoomend", updateFn);
|
||||
map.on("click", (e) => this.#onMapClicked(e))
|
||||
map.on("contextmenu", (e) => openMapContextMenu(this.parentNote.noteId, e, isEditable));
|
||||
|
||||
if (isEditable) {
|
||||
setupDragging(this.$container, map, this.parentNote.noteId);
|
||||
}
|
||||
|
||||
this.#reloadMarkers();
|
||||
|
||||
if (hasTouchBar) {
|
||||
map.on("zoom", () => {
|
||||
if (!this.ignoreNextZoomEvent) {
|
||||
this.triggerCommand("refreshTouchBar");
|
||||
}
|
||||
|
||||
this.ignoreNextZoomEvent = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async #restoreViewportAndZoom() {
|
||||
const map = this.map;
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedContent = await this.viewStorage.restore();
|
||||
|
||||
// Restore viewport position & zoom
|
||||
const center = parsedContent?.view?.center ?? DEFAULT_COORDINATES;
|
||||
const zoom = parsedContent?.view?.zoom ?? DEFAULT_ZOOM;
|
||||
map.setView(center, zoom);
|
||||
}
|
||||
|
||||
private onSave() {
|
||||
const map = this.map;
|
||||
let data: MapData = {};
|
||||
if (map) {
|
||||
data = {
|
||||
view: {
|
||||
center: map.getBounds().getCenter(),
|
||||
zoom: map.getZoom()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.viewStorage.store(data);
|
||||
}
|
||||
|
||||
async #reloadMarkers() {
|
||||
if (!this.map) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete all existing markers
|
||||
for (const marker of Object.values(this.currentMarkerData)) {
|
||||
marker.remove();
|
||||
}
|
||||
|
||||
// Delete all existing tracks
|
||||
for (const track of Object.values(this.currentTrackData)) {
|
||||
track.remove();
|
||||
}
|
||||
|
||||
// Add the new markers.
|
||||
this.currentMarkerData = {};
|
||||
const notes = await this.parentNote.getChildNotes();
|
||||
const draggable = !this.isReadOnly;
|
||||
for (const childNote of notes) {
|
||||
if (childNote.mime === "application/gpx+xml") {
|
||||
const track = await processNoteWithGpxTrack(this.map, childNote);
|
||||
this.currentTrackData[childNote.noteId] = track;
|
||||
continue;
|
||||
}
|
||||
|
||||
const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE);
|
||||
if (latLng) {
|
||||
const marker = processNoteWithMarker(this.map, childNote, latLng, draggable);
|
||||
this.currentMarkerData[childNote.noteId] = marker;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get isFullHeight(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
#changeState(newState: State) {
|
||||
this._state = newState;
|
||||
this.$container.toggleClass("placing-note", newState === State.NewNote);
|
||||
if (hasTouchBar) {
|
||||
this.triggerCommand("refreshTouchBar");
|
||||
}
|
||||
}
|
||||
|
||||
onEntitiesReloaded({ loadResults }: EventData<"entitiesReloaded">): boolean | void {
|
||||
// If any of the children branches are altered.
|
||||
if (loadResults.getBranchRows().find((branch) => branch.parentNoteId === this.parentNote.noteId)) {
|
||||
this.#reloadMarkers();
|
||||
return;
|
||||
}
|
||||
|
||||
// If any of note has its location attribute changed.
|
||||
// TODO: Should probably filter by parent here as well.
|
||||
const attributeRows = loadResults.getAttributeRows();
|
||||
if (attributeRows.find((at) => [LOCATION_ATTRIBUTE, "color"].includes(at.name ?? ""))) {
|
||||
this.#reloadMarkers();
|
||||
}
|
||||
}
|
||||
|
||||
async geoMapCreateChildNoteEvent({ ntxId }: EventData<"geoMapCreateChildNote">) {
|
||||
toast.showPersistent({
|
||||
icon: "plus",
|
||||
id: "geo-new-note",
|
||||
title: "New note",
|
||||
message: t("geo-map.create-child-note-instruction")
|
||||
});
|
||||
|
||||
this.#changeState(State.NewNote);
|
||||
|
||||
const globalKeyListener: (this: Window, ev: KeyboardEvent) => any = (e) => {
|
||||
if (e.key !== "Escape") {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#changeState(State.Normal);
|
||||
|
||||
window.removeEventListener("keydown", globalKeyListener);
|
||||
toast.closePersistent("geo-new-note");
|
||||
};
|
||||
window.addEventListener("keydown", globalKeyListener);
|
||||
}
|
||||
|
||||
async #onMapClicked(e: LeafletMouseEvent) {
|
||||
if (this._state !== State.NewNote) {
|
||||
return;
|
||||
}
|
||||
|
||||
toast.closePersistent("geo-new-note");
|
||||
await createNewNote(this.parentNote.noteId, e);
|
||||
this.#changeState(State.Normal);
|
||||
}
|
||||
|
||||
deleteFromMapEvent({ noteId }: EventData<"deleteFromMap">) {
|
||||
moveMarker(noteId, null);
|
||||
}
|
||||
|
||||
buildTouchBarCommand({ TouchBar }: CommandListenerData<"buildTouchBar">) {
|
||||
const map = this.map;
|
||||
const that = this;
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
|
||||
return [
|
||||
new TouchBar.TouchBarSlider({
|
||||
label: "Zoom",
|
||||
value: map.getZoom(),
|
||||
minValue: map.getMinZoom(),
|
||||
maxValue: map.getMaxZoom(),
|
||||
change(newValue) {
|
||||
that.ignoreNextZoomEvent = true;
|
||||
map.setZoom(newValue);
|
||||
},
|
||||
}),
|
||||
new TouchBar.TouchBarButton({
|
||||
label: "New geo note",
|
||||
click: () => this.triggerCommand("geoMapCreateChildNote"),
|
||||
enabled: (this._state === State.Normal)
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
}
|
92
apps/client/src/widgets/view_widgets/geo_view/markers.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import markerIcon from "leaflet/dist/images/marker-icon.png";
|
||||
import markerIconShadow from "leaflet/dist/images/marker-shadow.png";
|
||||
import { marker, latLng, divIcon, Map, type Marker } from "leaflet";
|
||||
import type FNote from "../../../entities/fnote.js";
|
||||
import openContextMenu from "./context_menu.js";
|
||||
import server from "../../../services/server.js";
|
||||
import { moveMarker } from "./editing.js";
|
||||
import appContext from "../../../components/app_context.js";
|
||||
import L from "leaflet";
|
||||
|
||||
let gpxLoaded = false;
|
||||
|
||||
export default function processNoteWithMarker(map: Map, note: FNote, location: string, isEditable: boolean) {
|
||||
const [lat, lng] = location.split(",", 2).map((el) => parseFloat(el));
|
||||
const icon = buildIcon(note.getIcon(), note.getColorClass(), note.title, note.noteId);
|
||||
|
||||
const newMarker = marker(latLng(lat, lng), {
|
||||
icon,
|
||||
draggable: isEditable,
|
||||
autoPan: true,
|
||||
autoPanSpeed: 5
|
||||
}).addTo(map);
|
||||
|
||||
if (isEditable) {
|
||||
newMarker.on("moveend", (e) => {
|
||||
moveMarker(note.noteId, (e.target as Marker).getLatLng());
|
||||
});
|
||||
}
|
||||
|
||||
newMarker.on("mousedown", ({ originalEvent }) => {
|
||||
// Middle click to open in new tab
|
||||
if (originalEvent.button === 1) {
|
||||
const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||
//@ts-ignore, fix once tab manager is ported.
|
||||
appContext.tabManager.openInNewTab(note.noteId, hoistedNoteId);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
newMarker.on("contextmenu", (e) => {
|
||||
openContextMenu(note.noteId, e, isEditable);
|
||||
});
|
||||
|
||||
return newMarker;
|
||||
}
|
||||
|
||||
export async function processNoteWithGpxTrack(map: Map, note: FNote) {
|
||||
if (!gpxLoaded) {
|
||||
const GPX = await import("leaflet-gpx");
|
||||
gpxLoaded = true;
|
||||
}
|
||||
|
||||
const xmlResponse = await server.get<string | Uint8Array>(`notes/${note.noteId}/open`, undefined, true);
|
||||
let stringResponse: string;
|
||||
if (xmlResponse instanceof Uint8Array) {
|
||||
stringResponse = new TextDecoder().decode(xmlResponse);
|
||||
} else {
|
||||
stringResponse = xmlResponse;
|
||||
}
|
||||
|
||||
const track = new L.GPX(stringResponse, {
|
||||
markers: {
|
||||
startIcon: buildIcon(note.getIcon(), note.getColorClass(), note.title),
|
||||
endIcon: buildIcon("bxs-flag-checkered"),
|
||||
wptIcons: {
|
||||
"": buildIcon("bx bx-pin")
|
||||
}
|
||||
},
|
||||
polyline_options: {
|
||||
color: note.getLabelValue("color") ?? "blue"
|
||||
}
|
||||
});
|
||||
track.addTo(map);
|
||||
return track;
|
||||
}
|
||||
|
||||
function buildIcon(bxIconClass: string, colorClass?: string, title?: string, noteIdLink?: string) {
|
||||
let html = /*html*/`\
|
||||
<img class="icon" src="${markerIcon}" />
|
||||
<img class="icon-shadow" src="${markerIconShadow}" />
|
||||
<span class="bx ${bxIconClass} ${colorClass ?? ""}"></span>
|
||||
<span class="title-label">${title ?? ""}</span>`;
|
||||
|
||||
if (noteIdLink) {
|
||||
html = `<div data-href="#root/${noteIdLink}">${html}</div>`;
|
||||
}
|
||||
|
||||
return divIcon({
|
||||
html,
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41]
|
||||
});
|
||||
}
|
@ -44,12 +44,16 @@ export default abstract class ViewMode<T extends object> extends Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
get isReadOnly() {
|
||||
return this.parentNote.hasLabel("readOnly");
|
||||
}
|
||||
|
||||
get viewStorage() {
|
||||
if (this._viewStorage) {
|
||||
return this._viewStorage;
|
||||
}
|
||||
|
||||
this._viewStorage = new ViewModeStorage(this.parentNote, this.viewType);
|
||||
this._viewStorage = new ViewModeStorage<T>(this.parentNote, this.viewType);
|
||||
return this._viewStorage;
|
||||
}
|
||||
|
||||
|
@ -26,18 +26,19 @@ export default class ViewModeStorage<T extends object> {
|
||||
}
|
||||
|
||||
async restore() {
|
||||
const existingAttachments = await this.note.getAttachmentsByRole(ATTACHMENT_ROLE);
|
||||
const existingAttachments = (await this.note.getAttachmentsByRole(ATTACHMENT_ROLE))
|
||||
.filter(a => a.title === this.attachmentName);
|
||||
if (existingAttachments.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const attachment = existingAttachments
|
||||
.find(a => a.title === this.attachmentName);
|
||||
if (!attachment) {
|
||||
return undefined;
|
||||
if (existingAttachments.length > 1) {
|
||||
// Clean up duplicates.
|
||||
await Promise.all(existingAttachments.slice(1).map(async a => await server.remove(`attachments/${a.attachmentId}`)));
|
||||
}
|
||||
|
||||
const attachment = existingAttachments[0];
|
||||
const attachmentData = await server.get<{ content: string } | null>(`attachments/${attachment.attachmentId}/blob`);
|
||||
return JSON.parse(attachmentData?.content ?? "{}");
|
||||
return JSON.parse(attachmentData?.content ?? "{}") as T;
|
||||
}
|
||||
}
|
||||
|
2
apps/server/src/assets/doc_notes/en/User Guide/!!!meta.json
generated
vendored
Before Width: | Height: | Size: 605 B After Width: | Height: | Size: 605 B |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/15_Geo Map View_image.png
generated
vendored
Normal file
After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 179 B After Width: | Height: | Size: 179 B |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 210 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 338 KiB |
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
@ -1,5 +1,11 @@
|
||||
<aside class="admonition important">
|
||||
<p>Starting with Trilium v0.97.0, the geo map has been converted from a standalone
|
||||
<a
|
||||
href="#root/pOsGYCXsbNQG/_help_KSZ04uQ2D1St">note type</a>to a type of view for the <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_0ESUbbAxVnoK">Note List</a>. </p>
|
||||
</aside>
|
||||
<figure class="image image-style-align-center">
|
||||
<img style="aspect-ratio:892/675;" src="10_Geo Map_image.png" width="892"
|
||||
<img style="aspect-ratio:892/675;" src="9_Geo Map View_image.png" width="892"
|
||||
height="675">
|
||||
</figure>
|
||||
<p>This note type displays the children notes on a geographical map, based
|
||||
@ -19,9 +25,9 @@
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<figure class="image image-style-align-center image_resized" style="width:51.25%;">
|
||||
<img style="aspect-ratio:1256/1044;" src="7_Geo Map_image.png" width="1256"
|
||||
height="1044">
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:483/413;" src="15_Geo Map View_image.png" width="483"
|
||||
height="413">
|
||||
</figure>
|
||||
</td>
|
||||
<td>Right click on any note on the note tree and select <em>Insert child note</em> → <em>Geo Map (beta)</em>.</td>
|
||||
@ -30,7 +36,7 @@
|
||||
<td>2</td>
|
||||
<td>
|
||||
<figure class="image image-style-align-center image_resized" style="width:53.44%;">
|
||||
<img style="aspect-ratio:1720/1396;" src="9_Geo Map_image.png" width="1720"
|
||||
<img style="aspect-ratio:1720/1396;" src="8_Geo Map View_image.png" width="1720"
|
||||
height="1396">
|
||||
</figure>
|
||||
</td>
|
||||
@ -39,7 +45,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
|
||||
<h2>Repositioning the map</h2>
|
||||
<ul>
|
||||
<li>Click and drag the map in order to move across the map.</li>
|
||||
@ -49,6 +54,7 @@
|
||||
<p>The position on the map and the zoom are saved inside the map note and
|
||||
restored when visiting again the note.</p>
|
||||
<h2>Adding a marker using the map</h2>
|
||||
<h3>Adding a new note using the plus button</h3>
|
||||
<figure class="table">
|
||||
<table>
|
||||
<thead>
|
||||
@ -63,18 +69,18 @@
|
||||
<td>1</td>
|
||||
<td>To create a marker, first navigate to the desired point on the map. Then
|
||||
press the
|
||||
<img src="11_Geo Map_image.png">button in the <a href="#root/_help_XpOYSgsLkTJy">Floating buttons</a> (top-right)
|
||||
<img src="10_Geo Map View_image.png">button in the <a href="#root/_help_XpOYSgsLkTJy">Floating buttons</a> (top-right)
|
||||
area.
|
||||
<br>
|
||||
<br>If the button is not visible, make sure the button section is visible
|
||||
by pressing the chevron button (
|
||||
<img src="17_Geo Map_image.png">) in the top-right of the map.</td>
|
||||
<img src="17_Geo Map View_image.png">) in the top-right of the map.</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>
|
||||
<img class="image_resized" style="aspect-ratio:1730/416;width:100%;" src="2_Geo Map_image.png"
|
||||
<img class="image_resized" style="aspect-ratio:1730/416;width:100%;" src="2_Geo Map View_image.png"
|
||||
width="1730" height="416">
|
||||
</td>
|
||||
<td>Once pressed, the map will enter in the insert mode, as illustrated by
|
||||
@ -86,7 +92,7 @@
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>
|
||||
<img class="image_resized" style="aspect-ratio:1586/404;width:100%;" src="8_Geo Map_image.png"
|
||||
<img class="image_resized" style="aspect-ratio:1586/404;width:100%;" src="7_Geo Map View_image.png"
|
||||
width="1586" height="404">
|
||||
</td>
|
||||
<td>Enter the name of the marker/note to be created.</td>
|
||||
@ -94,7 +100,7 @@
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>
|
||||
<img class="image_resized" style="aspect-ratio:1696/608;width:100%;" src="16_Geo Map_image.png"
|
||||
<img class="image_resized" style="aspect-ratio:1696/608;width:100%;" src="16_Geo Map View_image.png"
|
||||
width="1696" height="608">
|
||||
</td>
|
||||
<td>Once confirmed, the marker will show up on the map and it will also be
|
||||
@ -103,11 +109,34 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
|
||||
<h3>Adding a new note using the contextual menu</h3>
|
||||
<ol>
|
||||
<li>Right click anywhere on the map, where to place the newly created marker
|
||||
(and corresponding note).</li>
|
||||
<li>Select <em>Add a marker at this location</em>.</li>
|
||||
<li>Enter the name of the newly created note.</li>
|
||||
<li>The map should be updated with the new marker.</li>
|
||||
</ol>
|
||||
<h3>Adding an existing note on note from the note tree</h3>
|
||||
<ol>
|
||||
<li>Select the desired note in the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
|
||||
<li>Hold the mouse on the note and drag it to the map to the desired location.</li>
|
||||
<li>The map should be updated with the new marker.</li>
|
||||
</ol>
|
||||
<p>This works for:</p>
|
||||
<ul>
|
||||
<li>Notes that are not part of the geo map, case in which a <a href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_IakOLONlIfGI">clone</a> will
|
||||
be created.</li>
|
||||
<li>Notes that are a child of the geo map but not yet positioned on the map.</li>
|
||||
<li>Notes that are a child of the geo map and also positioned, case in which
|
||||
the marker will be relocated to the new position.</li>
|
||||
</ul>
|
||||
<h2>How the location of the markers is stored</h2>
|
||||
<p>The location of a marker is stored in the <code>#geolocation</code> attribute
|
||||
of the child notes:</p>
|
||||
<img src="18_Geo Map_image.png" width="1288" height="278">
|
||||
<p>
|
||||
<img src="18_Geo Map View_image.png" width="1288" height="278">
|
||||
</p>
|
||||
<p>This value can be added manually if needed. The value of the attribute
|
||||
is made up of the latitude and longitude separated by a comma.</p>
|
||||
<h2>Repositioning markers</h2>
|
||||
@ -129,18 +158,40 @@
|
||||
<li>Middle-clicking the marker will open the note in a new tab.</li>
|
||||
<li>Right-clicking the marker will open a contextual menu allowing:
|
||||
<ul>
|
||||
<li>Opening the note in a new tab, split or window.</li>
|
||||
<li>Opening the location using an external application (if the operating system
|
||||
supports it).</li>
|
||||
<li>Removing the marker from the map, which will remove the <code>#geolocation</code> attribute
|
||||
of the note. To add it back again, the coordinates have to be manually
|
||||
added back in.</li>
|
||||
<li> </li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2>Contextual menu</h2>
|
||||
<p>It's possible to press the right mouse button to display a contextual
|
||||
menu.</p>
|
||||
<ol>
|
||||
<li>If right-clicking an empty section of the map (not on a marker), it allows
|
||||
to:
|
||||
<ol>
|
||||
<li>Displays the latitude and longitude. Clicking this option will copy them
|
||||
to the clipboard.</li>
|
||||
<li>Open the location using an external application (if the operating system
|
||||
supports it).</li>
|
||||
<li>Adding a new marker at that location.</li>
|
||||
</ol>
|
||||
</li>
|
||||
<li>If right-clicking on a marker, it allows to:
|
||||
<ol>
|
||||
<li>Displays the latitude and longitude. Clicking this option will copy them
|
||||
to the clipboard.</li>
|
||||
<li>Open the location using an external application (if the operating system
|
||||
supports it).</li>
|
||||
<li>Open the note in a new tab, split or window.</li>
|
||||
<li>Remove the marker from the map, which will remove the <code>#geolocation</code> attribute
|
||||
of the note. To add it back again, the coordinates have to be manually
|
||||
added back in.</li>
|
||||
</ol>
|
||||
</li>
|
||||
</ol>
|
||||
<h2>Icon and color of the markers</h2>
|
||||
<figure class="image image-style-align-center">
|
||||
<img style="aspect-ratio:523/295;" src="Geo Map_image.jpg" alt="image"
|
||||
<img style="aspect-ratio:523/295;" src="Geo Map View_image.jpg" alt="image"
|
||||
width="523" height="295">
|
||||
</figure>
|
||||
<p>The markers will have the same icon as the note.</p>
|
||||
@ -171,7 +222,7 @@
|
||||
<td>1</td>
|
||||
<td>
|
||||
<figure class="image image-style-align-center image_resized" style="width:56.84%;">
|
||||
<img style="aspect-ratio:732/918;" src="13_Geo Map_image.png" width="732"
|
||||
<img style="aspect-ratio:732/918;" src="12_Geo Map View_image.png" width="732"
|
||||
height="918">
|
||||
</figure>
|
||||
</td>
|
||||
@ -188,7 +239,7 @@
|
||||
<td>2</td>
|
||||
<td>
|
||||
<figure class="image image-style-align-center image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:518/84;" src="4_Geo Map_image.png" width="518"
|
||||
<img style="aspect-ratio:518/84;" src="4_Geo Map View_image.png" width="518"
|
||||
height="84">
|
||||
</figure>
|
||||
</td>
|
||||
@ -198,7 +249,7 @@
|
||||
<td>3</td>
|
||||
<td>
|
||||
<figure class="image image-style-align-center image_resized" style="width:100%;">
|
||||
<img style="aspect-ratio:1074/276;" src="12_Geo Map_image.png" width="1074"
|
||||
<img style="aspect-ratio:1074/276;" src="11_Geo Map View_image.png" width="1074"
|
||||
height="276">
|
||||
</figure>
|
||||
</td>
|
||||
@ -210,7 +261,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
|
||||
<h3>Adding from OpenStreetMap</h3>
|
||||
<p>Similarly to the Google Maps approach:</p>
|
||||
<figure class="table" style="width:100%;">
|
||||
@ -231,7 +281,7 @@
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>
|
||||
<img class="image_resized" style="aspect-ratio:562/454;width:100%;" src="1_Geo Map_image.png"
|
||||
<img class="image_resized" style="aspect-ratio:562/454;width:100%;" src="1_Geo Map View_image.png"
|
||||
width="562" height="454">
|
||||
</td>
|
||||
<td>Go to any location on openstreetmap.org and right click to bring up the
|
||||
@ -240,7 +290,7 @@
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>
|
||||
<img class="image_resized" style="aspect-ratio:696/480;width:100%;" src="Geo Map_image.png"
|
||||
<img class="image_resized" style="aspect-ratio:696/480;width:100%;" src="Geo Map View_image.png"
|
||||
width="696" height="480">
|
||||
</td>
|
||||
<td>The address will be visible in the top-left of the screen, in the place
|
||||
@ -251,7 +301,7 @@
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>
|
||||
<img class="image_resized" style="aspect-ratio:640/276;width:100%;" src="5_Geo Map_image.png"
|
||||
<img class="image_resized" style="aspect-ratio:640/276;width:100%;" src="5_Geo Map View_image.png"
|
||||
width="640" height="276">
|
||||
</td>
|
||||
<td>Simply paste the value inside the text box into the <code>#geolocation</code> attribute
|
||||
@ -260,7 +310,6 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</figure>
|
||||
|
||||
<h2>Adding GPS tracks (.gpx)</h2>
|
||||
<p>Trilium has basic support for displaying GPS tracks on the geo map.</p>
|
||||
<figure
|
||||
@ -283,7 +332,7 @@ class="table" style="width:100%;">
|
||||
<td>1</td>
|
||||
<td>
|
||||
<figure class="image image-style-align-center">
|
||||
<img style="aspect-ratio:226/74;" src="3_Geo Map_image.png" width="226"
|
||||
<img style="aspect-ratio:226/74;" src="3_Geo Map View_image.png" width="226"
|
||||
height="74">
|
||||
</figure>
|
||||
</td>
|
||||
@ -294,7 +343,7 @@ class="table" style="width:100%;">
|
||||
<td>2</td>
|
||||
<td>
|
||||
<figure class="image image-style-align-center">
|
||||
<img style="aspect-ratio:322/222;" src="15_Geo Map_image.png" width="322"
|
||||
<img style="aspect-ratio:322/222;" src="14_Geo Map View_image.png" width="322"
|
||||
height="222">
|
||||
</figure>
|
||||
</td>
|
||||
@ -305,7 +354,7 @@ class="table" style="width:100%;">
|
||||
<td>3</td>
|
||||
<td>
|
||||
<figure class="image image-style-align-center">
|
||||
<img style="aspect-ratio:620/530;" src="6_Geo Map_image.png" width="620"
|
||||
<img style="aspect-ratio:620/530;" src="6_Geo Map View_image.png" width="620"
|
||||
height="530">
|
||||
</figure>
|
||||
</td>
|
||||
@ -324,12 +373,22 @@ class="table" style="width:100%;">
|
||||
<p>If the GPX contains waypoints, they will also be displayed. If they have
|
||||
a name, it is displayed when hovering over it with the mouse.</p>
|
||||
</aside>
|
||||
<h2>Read-only mode</h2>
|
||||
<p>When a map is in read-only all editing features will be disabled such
|
||||
as:</p>
|
||||
<ul>
|
||||
<li>The add button in the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_XpOYSgsLkTJy">Floating buttons</a>.</li>
|
||||
<li>Dragging markers.</li>
|
||||
<li>Editing from the contextual menu (removing locations or adding new items).</li>
|
||||
</ul>
|
||||
<p>To enable read-only mode simply press the <em>Lock</em> icon from the
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_XpOYSgsLkTJy">Floating buttons</a>. To disable it, press the button again.</p>
|
||||
<h2>Troubleshooting</h2>
|
||||
<figure class="image image-style-align-right image_resized" style="width:34.06%;">
|
||||
<img style="aspect-ratio:678/499;" src="14_Geo Map_image.png" width="678"
|
||||
<img style="aspect-ratio:678/499;" src="13_Geo Map View_image.png" width="678"
|
||||
height="499">
|
||||
</figure>
|
||||
|
||||
<h3>Grid-like artifacts on the map</h3>
|
||||
<p>This occurs if the application is not at 100% zoom which causes the pixels
|
||||
of the map to not render correctly due to fractional scaling. The only
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@ -1,16 +1,16 @@
|
||||
<figure class="image">
|
||||
<img style="aspect-ratio:1050/259;" src="Table_image.png" width="1050"
|
||||
<img style="aspect-ratio:1050/259;" src="Table View_image.png" width="1050"
|
||||
height="259">
|
||||
</figure>
|
||||
<p>The table view displays information in a grid, where the rows are individual
|
||||
notes and the columns are <a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a>.
|
||||
notes and the columns are <a class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a>.
|
||||
In addition, values are editable.</p>
|
||||
<h2>Interaction</h2>
|
||||
<h3>Creating a new table</h3>
|
||||
<p>Right click the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> and
|
||||
<p>Right click the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> and
|
||||
select <em>Insert child note</em> and look for the <em>Table item</em>.</p>
|
||||
<h3>Adding columns</h3>
|
||||
<p>Each column is a <a href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">promoted attribute</a> that
|
||||
<p>Each column is a <a href="#root/_help_OFXdgB2nNk1F">promoted attribute</a> that
|
||||
is defined on the Book note. Ideally, the promoted attributes need to be
|
||||
inheritable in order to show up in the child notes.</p>
|
||||
<p>To create a new column, simply press <em>Add new column</em> at the bottom
|
||||
@ -19,7 +19,7 @@
|
||||
<ul>
|
||||
<li>The current item number, identified by the <code>#</code> symbol. This simply
|
||||
counts the note and is affected by sorting.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/_help_m1lbrzyKDaRB">Note ID</a>,
|
||||
<li><a class="reference-link" href="#root/_help_m1lbrzyKDaRB">Note ID</a>,
|
||||
representing the unique ID used internally by Trilium</li>
|
||||
<li>The title of the note.</li>
|
||||
</ul>
|
||||
@ -28,9 +28,7 @@
|
||||
<p>To create a new note, press <em>Add new row</em> at the bottom of the table.
|
||||
By default it will try to edit the title of the newly created note.</p>
|
||||
<p>Alternatively, the note can be created from the<a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a> or
|
||||
<a
|
||||
href="#root/pOsGYCXsbNQG/_help_CdNpE2pqjmI6">scripting</a>.</p>
|
||||
href="#root/_help_oPVyFC7WL2Lp">Note Tree</a> or <a href="#root/_help_CdNpE2pqjmI6">scripting</a>.</p>
|
||||
<h3>Editing data</h3>
|
||||
<p>Simply click on a cell within a row to change its value. The change will
|
||||
not only reflect in the table, but also as an attribute of the corresponding
|
||||
@ -58,7 +56,7 @@
|
||||
</ul>
|
||||
<h3>Reordering rows</h3>
|
||||
<p>Notes can be dragged around to change their order. This will also change
|
||||
the order of the note in the <a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>.</p>
|
||||
the order of the note in the <a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</p>
|
||||
<p>Currently, it's possible to reorder notes even if sorting is used, but
|
||||
the result might be inconsistent.</p>
|
||||
<h2>Limitations</h2>
|
||||
@ -67,7 +65,7 @@
|
||||
<ol>
|
||||
<li>As mentioned previously, the columns of the table are defined as
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a>.
|
||||
class="reference-link" href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a>.
|
||||
<ol>
|
||||
<li>But only the promoted attributes that are defined at the level of the
|
||||
Book note are actually taken into consideration.</li>
|
||||
@ -77,22 +75,23 @@
|
||||
<li>Hierarchy is not yet supported, so the table will only show the items
|
||||
that are direct children of the <em>Book</em> note.</li>
|
||||
<li>Multiple labels and relations are not supported. If a <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> is
|
||||
defined with a <em>Multi value</em> specificity, they will be ignored.</li>
|
||||
href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a> is defined
|
||||
with a <em>Multi value</em> specificity, they will be ignored.</li>
|
||||
</ol>
|
||||
<h2>Use in search</h2>
|
||||
<p>The table view can be used in a <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_m523cpzocqaD">Saved Search</a> by
|
||||
<p>The table view can be used in a <a class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a> by
|
||||
adding the <code>#viewType=table</code> attribute.</p>
|
||||
<p>Unlike when used in a book, saved searches are not limited to the sub-hierarchy
|
||||
of a note and allows for advanced queries thanks to the power of the
|
||||
<a
|
||||
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_eIg8jdvaoNNd">Search</a>.</p>
|
||||
class="reference-link" href="#root/_help_eIg8jdvaoNNd">Search</a>.</p>
|
||||
<p>However, there are also some limitations:</p>
|
||||
<ul>
|
||||
<li>It's not possible to reorder notes.</li>
|
||||
<li>It's not possible to add a new row.</li>
|
||||
</ul>
|
||||
<p>Columns are supported, by being defined as <a class="reference-link"
|
||||
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a> to
|
||||
the <a class="reference-link" href="#root/pOsGYCXsbNQG/KSZ04uQ2D1St/_help_m523cpzocqaD">Saved Search</a> note.</p>
|
||||
href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a> to the
|
||||
<a
|
||||
class="reference-link" href="#root/_help_m523cpzocqaD">Saved Search</a> note.</p>
|
||||
<p>Editing is also supported.</p>
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@ -54,4 +54,7 @@
|
||||
hide the Mermaid source code and display the diagram preview in full-size.
|
||||
In this case, the read-only mode can be easily toggled on or off via a
|
||||
dedicated button in the <a class="reference-link" href="#root/_help_XpOYSgsLkTJy">Floating buttons</a> area.</li>
|
||||
<li><a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/0ESUbbAxVnoK/_help_81SGnPGMk7Xc">Geo Map View</a> will
|
||||
disallow all interaction that would otherwise change the map (dragging
|
||||
notes, adding new items).</li>
|
||||
</ul>
|
BIN
apps/server/src/assets/doc_notes/en/User Guide/User Guide/Note Types/7_Geo Map_image.png
generated
vendored
Before Width: | Height: | Size: 122 KiB |
@ -0,0 +1,46 @@
|
||||
import becca from "../becca/becca";
|
||||
import becca_loader from "../becca/becca_loader";
|
||||
import cls from "../services/cls.js";
|
||||
import hidden_subtree from "../services/hidden_subtree";
|
||||
|
||||
export default () => {
|
||||
cls.init(() => {
|
||||
becca_loader.load();
|
||||
|
||||
// Ensure the geomap template is generated.
|
||||
hidden_subtree.checkHiddenSubtree(true);
|
||||
|
||||
for (const note of Object.values(becca.notes)) {
|
||||
if (note.type as string !== "geoMap") {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`Migrating note '${note.noteId}' from geoMap to book type...`);
|
||||
|
||||
note.type = "book";
|
||||
note.mime = "";
|
||||
note.save();
|
||||
|
||||
const content = note.getContent();
|
||||
if (content) {
|
||||
const title = "geoMap.json";
|
||||
const existingAttachment = note.getAttachmentsByRole("viewConfig")
|
||||
.filter(a => a.title === title)[0];
|
||||
if (existingAttachment) {
|
||||
existingAttachment.setContent(content);
|
||||
} else {
|
||||
note.saveAttachment({
|
||||
role: "viewConfig",
|
||||
title,
|
||||
mime: "application/json",
|
||||
content,
|
||||
position: 0
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
note.setContent("");
|
||||
note.setRelation("template", "_template_geo_map");
|
||||
}
|
||||
});
|
||||
};
|
@ -6,6 +6,11 @@
|
||||
|
||||
// Migrations should be kept in descending order, so the latest migration is first.
|
||||
const MIGRATIONS: (SqlMigration | JsMigration)[] = [
|
||||
// Migrate geo map to collection
|
||||
{
|
||||
version: 233,
|
||||
module: async () => import("./0233__migrate_geo_map_to_collection.js")
|
||||
},
|
||||
// Remove embedding tables since LLM embedding functionality has been removed
|
||||
{
|
||||
version: 232,
|
||||
|
@ -248,7 +248,7 @@ function register(app: express.Application) {
|
||||
route(GET, "/api/setup/status", [], setupApiRoute.getStatus, apiResultHandler);
|
||||
asyncRoute(PST, "/api/setup/new-document", [auth.checkAppNotInitialized], setupApiRoute.setupNewDocument, apiResultHandler);
|
||||
asyncRoute(PST, "/api/setup/sync-from-server", [auth.checkAppNotInitialized], setupApiRoute.setupSyncFromServer, apiResultHandler);
|
||||
route(GET, "/api/setup/sync-seed", [auth.checkCredentials], setupApiRoute.getSyncSeed, apiResultHandler);
|
||||
route(GET, "/api/setup/sync-seed", [loginRateLimiter, auth.checkCredentials], setupApiRoute.getSyncSeed, apiResultHandler);
|
||||
asyncRoute(PST, "/api/setup/sync-seed", [auth.checkAppNotInitialized], setupApiRoute.saveSyncSeed, apiResultHandler);
|
||||
|
||||
apiRoute(GET, "/api/autocomplete", autocompleteApiRoute.getAutocomplete);
|
||||
@ -263,7 +263,7 @@ function register(app: express.Application) {
|
||||
apiRoute(PST, "/api/bulk-action/execute", bulkActionRoute.execute);
|
||||
apiRoute(PST, "/api/bulk-action/affected-notes", bulkActionRoute.getAffectedNoteCount);
|
||||
|
||||
route(PST, "/api/login/sync", [], loginApiRoute.loginSync, apiResultHandler);
|
||||
route(PST, "/api/login/sync", [loginRateLimiter], loginApiRoute.loginSync, apiResultHandler);
|
||||
// this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username)
|
||||
apiRoute(PST, "/api/login/protected", loginApiRoute.loginToProtectedSession);
|
||||
apiRoute(PST, "/api/login/protected/touch", loginApiRoute.touchProtectedSession);
|
||||
|
@ -3,7 +3,7 @@ import build from "./build.js";
|
||||
import packageJson from "../../package.json" with { type: "json" };
|
||||
import dataDir from "./data_dir.js";
|
||||
|
||||
const APP_DB_VERSION = 232;
|
||||
const APP_DB_VERSION = 233;
|
||||
const SYNC_VERSION = 36;
|
||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
|
||||
|
||||
|
@ -102,7 +102,7 @@ eventService.subscribe(eventService.ENTITY_CREATED, ({ entityName, entity }) =>
|
||||
const content = note.getContent();
|
||||
|
||||
if (
|
||||
["text", "code", "mermaid", "canvas", "relationMap", "mindMap", "geoMap"].includes(note.type) &&
|
||||
["text", "code", "mermaid", "canvas", "relationMap", "mindMap"].includes(note.type) &&
|
||||
typeof content === "string" &&
|
||||
// if the note has already content we're not going to overwrite it with template's one
|
||||
(!content || content.trim().length === 0) &&
|
||||
|
@ -380,7 +380,7 @@ function checkHiddenSubtreeRecursively(parentNoteId: string, item: HiddenSubtree
|
||||
type: attr.type,
|
||||
name: attr.name,
|
||||
value: attr.value,
|
||||
isInheritable: false
|
||||
isInheritable: attr.isInheritable
|
||||
}).save();
|
||||
} else if (attr.name === "docName" || (existingAttribute.noteId.startsWith("_help") && attr.name === "iconClass")) {
|
||||
if (existingAttribute.value !== attr.value) {
|
||||
|
@ -43,6 +43,33 @@ export default function buildHiddenSubtreeTemplates() {
|
||||
value: "table"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "_template_geo_map",
|
||||
type: "book",
|
||||
title: "Geo Map",
|
||||
icon: "bx bx-map-alt",
|
||||
attributes: [
|
||||
{
|
||||
name: "template",
|
||||
type: "label"
|
||||
},
|
||||
{
|
||||
name: "viewType",
|
||||
type: "label",
|
||||
value: "geoMap"
|
||||
},
|
||||
{
|
||||
name: "hidePromotedAttributes",
|
||||
type: "label"
|
||||
},
|
||||
{
|
||||
name: "label:geolocation",
|
||||
type: "label",
|
||||
value: "promoted,alias=Geolocation,single,text",
|
||||
isInheritable: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
BIN
apps/server/src/services/import/samples/geomap.zip
Normal file
@ -70,6 +70,19 @@ describe("processNoteContent", () => {
|
||||
expect(content).toContain(`<a class="reference-link" href="#root/${shopNote.noteId}`);
|
||||
expect(content).toContain(`<img src="api/images/${bananaNote!.noteId}/banana.jpeg`);
|
||||
});
|
||||
|
||||
it("can import old geomap notes", async () => {
|
||||
const { importedNote } = await testImport("geomap.zip");
|
||||
expect(importedNote.type).toBe("book");
|
||||
expect(importedNote.mime).toBe("");
|
||||
expect(importedNote.getRelationValue("template")).toBe("_template_geo_map");
|
||||
|
||||
const attachment = importedNote.getAttachmentsByRole("viewConfig")[0];
|
||||
expect(attachment.title).toBe("geoMap.json");
|
||||
expect(attachment.mime).toBe("application/json");
|
||||
const content = attachment.getContent();
|
||||
expect(content).toStrictEqual(`{"view":{"center":{"lat":49.19598332223546,"lng":-2.1414576506668808},"zoom":12}}`);
|
||||
});
|
||||
});
|
||||
|
||||
function getNoteByTitlePath(parentNote: BNote, ...titlePath: string[]) {
|
||||
|
@ -502,6 +502,28 @@ async function importZip(taskContext: TaskContext, fileBuffer: Buffer, importRoo
|
||||
firstNote = firstNote || note;
|
||||
}
|
||||
} else {
|
||||
if (detectedType as string === "geoMap") {
|
||||
attributes.push({
|
||||
noteId,
|
||||
type: "relation",
|
||||
name: "template",
|
||||
value: "_template_geo_map"
|
||||
});
|
||||
|
||||
const attachment = new BAttachment({
|
||||
attachmentId: getNewAttachmentId(newEntityId()),
|
||||
ownerId: noteId,
|
||||
title: "geoMap.json",
|
||||
role: "viewConfig",
|
||||
mime: "application/json",
|
||||
position: 0
|
||||
});
|
||||
|
||||
attachment.setContent(content, { forceSave: true });
|
||||
content = "";
|
||||
mime = "";
|
||||
}
|
||||
|
||||
({ note } = noteService.createNewNote({
|
||||
parentNoteId: parentNoteId,
|
||||
title: noteTitle || "",
|
||||
@ -656,12 +678,15 @@ export function readZipFile(buffer: Buffer, processEntryCallback: (zipfile: yauz
|
||||
|
||||
function resolveNoteType(type: string | undefined): NoteType {
|
||||
// BC for ZIPs created in Trilium 0.57 and older
|
||||
if (type === "relation-map") {
|
||||
switch (type) {
|
||||
case "relation-map":
|
||||
return "relationMap";
|
||||
} else if (type === "note-map") {
|
||||
case "note-map":
|
||||
return "noteMap";
|
||||
} else if (type === "web-view") {
|
||||
case "web-view":
|
||||
return "webView";
|
||||
case "geoMap":
|
||||
return "book";
|
||||
}
|
||||
|
||||
if (type && (ALLOWED_NOTE_TYPES as readonly string[]).includes(type)) {
|
||||
|
@ -15,7 +15,6 @@ const noteTypes = [
|
||||
{ type: "doc", defaultMime: "" },
|
||||
{ type: "contentWidget", defaultMime: "" },
|
||||
{ type: "mindMap", defaultMime: "application/json" },
|
||||
{ type: "geoMap", defaultMime: "application/json" },
|
||||
{ type: "aiChat", defaultMime: "application/json" }
|
||||
];
|
||||
|
||||
|
467
docs/User Guide/!!!meta.json
vendored
@ -3177,6 +3177,27 @@
|
||||
"value": "bx bx-edit-alt",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "_optionsTextNotes",
|
||||
"isInheritable": false,
|
||||
"position": 80
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "_optionsCodeNotes",
|
||||
"isInheritable": false,
|
||||
"position": 90
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "81SGnPGMk7Xc",
|
||||
"isInheritable": false,
|
||||
"position": 100
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
@ -3431,7 +3452,7 @@
|
||||
"0ESUbbAxVnoK",
|
||||
"2FvYrpmOXm29"
|
||||
],
|
||||
"title": "Table",
|
||||
"title": "Table View",
|
||||
"notePosition": 20,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
@ -3439,30 +3460,30 @@
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-table",
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "OFXdgB2nNk1F",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "OFXdgB2nNk1F",
|
||||
"value": "m1lbrzyKDaRB",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "oPVyFC7WL2Lp",
|
||||
"value": "eIg8jdvaoNNd",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "m1lbrzyKDaRB",
|
||||
"value": "oPVyFC7WL2Lp",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
@ -3481,15 +3502,15 @@
|
||||
"position": 60
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "eIg8jdvaoNNd",
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-table",
|
||||
"isInheritable": false,
|
||||
"position": 70
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Table.md",
|
||||
"dataFileName": "Table View.md",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "vJYUG9fLQ2Pd",
|
||||
@ -3497,7 +3518,232 @@
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Table_image.png"
|
||||
"dataFileName": "Table View_image.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "81SGnPGMk7Xc",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"gh7bpGYxajRS",
|
||||
"BFs8mudNFgCS",
|
||||
"0ESUbbAxVnoK",
|
||||
"81SGnPGMk7Xc"
|
||||
],
|
||||
"title": "Geo Map View",
|
||||
"notePosition": 40,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "XpOYSgsLkTJy",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-map-alt",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "oPVyFC7WL2Lp",
|
||||
"isInheritable": false,
|
||||
"position": 20
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "IakOLONlIfGI",
|
||||
"isInheritable": false,
|
||||
"position": 30
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "KSZ04uQ2D1St",
|
||||
"isInheritable": false,
|
||||
"position": 40
|
||||
},
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "0ESUbbAxVnoK",
|
||||
"isInheritable": false,
|
||||
"position": 50
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Geo Map View.md",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "1f07O0Z25ZRr",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "3oh61qhNLu7D",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "1_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "aCSNn9QlgHFi",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "2_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "aCuXZY7WV4li",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "3_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "agH6yREFgsoU",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "4_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "AHyDUM6R5HeG",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "5_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "CcjWLhE3KKfv",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "6_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "fQy8R1vxKhwN",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "7_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "gJ4Yz80jxcbn",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "8_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "I39BinT2gsN9",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "9_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "IeXU8SLZU7Oz",
|
||||
"title": "image.jpg",
|
||||
"role": "image",
|
||||
"mime": "image/jpg",
|
||||
"position": 10,
|
||||
"dataFileName": "Geo Map View_image.jpg"
|
||||
},
|
||||
{
|
||||
"attachmentId": "Mb9kRm63MxjE",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "10_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "Mx2xwNIk76ZS",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "11_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "oaahbsMRbqd2",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "12_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "pGf1p74KKGU4",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/jpg",
|
||||
"position": 10,
|
||||
"dataFileName": "13_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "tfa1TRUatWEh",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "14_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "tuNZ7Uk9WfX1",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "15_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "x6yBLIsY2LSv",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "16_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "yJMyBRYA3Kwi",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "17_Geo Map View_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "ZvTlu9WMd37z",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "18_Geo Map View_image.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -7937,201 +8183,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "81SGnPGMk7Xc",
|
||||
"notePath": [
|
||||
"pOsGYCXsbNQG",
|
||||
"KSZ04uQ2D1St",
|
||||
"81SGnPGMk7Xc"
|
||||
],
|
||||
"title": "Geo Map",
|
||||
"notePosition": 200,
|
||||
"prefix": null,
|
||||
"isExpanded": false,
|
||||
"type": "text",
|
||||
"mime": "text/html",
|
||||
"attributes": [
|
||||
{
|
||||
"type": "relation",
|
||||
"name": "internalLink",
|
||||
"value": "XpOYSgsLkTJy",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"name": "iconClass",
|
||||
"value": "bx bx-map-alt",
|
||||
"isInheritable": false,
|
||||
"position": 10
|
||||
}
|
||||
],
|
||||
"format": "markdown",
|
||||
"dataFileName": "Geo Map.md",
|
||||
"attachments": [
|
||||
{
|
||||
"attachmentId": "1f07O0Z25ZRr",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "3oh61qhNLu7D",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "1_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "aCSNn9QlgHFi",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "2_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "aCuXZY7WV4li",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "3_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "agH6yREFgsoU",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "4_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "AHyDUM6R5HeG",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "5_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "CcjWLhE3KKfv",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "6_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "DapDey8gMiFc",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "7_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "fQy8R1vxKhwN",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "8_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "gJ4Yz80jxcbn",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "9_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "I39BinT2gsN9",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "10_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "IeXU8SLZU7Oz",
|
||||
"title": "image.jpg",
|
||||
"role": "image",
|
||||
"mime": "image/jpg",
|
||||
"position": 10,
|
||||
"dataFileName": "Geo Map_image.jpg"
|
||||
},
|
||||
{
|
||||
"attachmentId": "Mb9kRm63MxjE",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "11_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "Mx2xwNIk76ZS",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "12_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "oaahbsMRbqd2",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "13_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "pGf1p74KKGU4",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/jpg",
|
||||
"position": 10,
|
||||
"dataFileName": "14_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "tfa1TRUatWEh",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "15_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "x6yBLIsY2LSv",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "16_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "yJMyBRYA3Kwi",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "17_Geo Map_image.png"
|
||||
},
|
||||
{
|
||||
"attachmentId": "ZvTlu9WMd37z",
|
||||
"title": "image.png",
|
||||
"role": "image",
|
||||
"mime": "image/png",
|
||||
"position": 10,
|
||||
"dataFileName": "18_Geo Map_image.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"isClone": false,
|
||||
"noteId": "W8vYD3Q1zjCR",
|
||||
|
@ -7,7 +7,7 @@ For example:
|
||||
|
||||
* <a class="reference-link" href="../Note%20Types/Text.md">Text</a> notes are represented internally as HTML, using the <a class="reference-link" href="Technologies%20used/CKEditor.md">CKEditor</a> representation. Note that due to the custom plugins, some HTML elements are specific to Trilium only, for example the admonitions.
|
||||
* <a class="reference-link" href="../Note%20Types/Code.md">Code</a> notes are plain text and are represented internally as-is.
|
||||
* <a class="reference-link" href="../Note%20Types/Geo%20Map.md">Geo Map</a> notes contain only minimal information (viewport, zoom) as a JSON.
|
||||
* <a class="reference-link" href="../Basic%20Concepts%20and%20Features/Notes/Note%20List/Geo%20Map%20View.md">Geo Map</a> notes contain only minimal information (viewport, zoom) as a JSON.
|
||||
* <a class="reference-link" href="../Note%20Types/Canvas.md">Canvas</a> notes are represented as JSON, with Trilium's own information alongside with <a class="reference-link" href="Technologies%20used/Excalidraw.md">Excalidraw</a>'s internal JSON representation format.
|
||||
* <a class="reference-link" href="../Note%20Types/Mind%20Map.md">Mind Map</a> notes are represented as JSON, with the internal format of <a class="reference-link" href="Technologies%20used/MindElixir.md">MindElixir</a>.
|
||||
|
||||
|
@ -16,7 +16,7 @@ Trilium allows you to share selected notes as **publicly accessible** read-only
|
||||
|
||||
### By note type
|
||||
|
||||
<figure class="table" style="width:100%;"><table class="ck-table-resized"><colgroup><col style="width:19.92%;"><col style="width:41.66%;"><col style="width:38.42%;"></colgroup><thead><tr><th> </th><th>Supported features</th><th>Limitations</th></tr></thead><tbody><tr><th><a class="reference-link" href="../Note%20Types/Text.md">Text</a></th><td><ul><li>Table of contents.</li><li>Syntax highlight of code blocks, provided a language is selected (does not work if “Auto-detected” is enabled).</li><li>Rendering for math equations.</li></ul></td><td><ul><li>Including notes is not supported.</li><li>Inline Mermaid diagrams are not rendered.</li></ul></td></tr><tr><th><a class="reference-link" href="../Note%20Types/Code.md">Code</a></th><td><ul><li>Basic support (displaying the contents of the note in a monospace font).</li></ul></td><td><ul><li>No syntax highlight.</li></ul></td></tr><tr><th><a class="reference-link" href="../Note%20Types/Saved%20Search.md">Saved Search</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/Relation%20Map.md">Relation Map</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/Note%20Map.md">Note Map</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/Render%20Note.md">Render Note</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/Book.md">Book</a></th><td><ul><li>The child notes are displayed in a fixed format. </li></ul></td><td><ul><li>More advanced view types such as the calendar view are not supported.</li></ul></td></tr><tr><th><a class="reference-link" href="../Note%20Types/Mermaid%20Diagrams.md">Mermaid Diagrams</a></th><td><ul><li>The diagram is displayed as a vector image.</li></ul></td><td><ul><li>No further interaction supported.</li></ul></td></tr><tr><th><a class="reference-link" href="../Note%20Types/Canvas.md">Canvas</a></th><td><ul><li>The diagram is displayed as a vector image.</li></ul></td><td><ul><li>No further interaction supported.</li></ul></td></tr><tr><th><a class="reference-link" href="../Note%20Types/Web%20View.md">Web View</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/Mind%20Map.md">Mind Map</a></th><td>The diagram is displayed as a vector image.</td><td><ul><li>No further interaction supported.</li></ul></td></tr><tr><th><a class="reference-link" href="../Note%20Types/Geo%20Map.md">Geo Map</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/File.md">File</a></th><td>Basic interaction (downloading the file).</td><td><ul><li>No further interaction supported.</li></ul></td></tr></tbody></table></figure>
|
||||
<figure class="table" style="width:100%;"><table class="ck-table-resized"><colgroup><col style="width:19.92%;"><col style="width:41.66%;"><col style="width:38.42%;"></colgroup><thead><tr><th> </th><th>Supported features</th><th>Limitations</th></tr></thead><tbody><tr><th><a class="reference-link" href="../Note%20Types/Text.md">Text</a></th><td><ul><li>Table of contents.</li><li>Syntax highlight of code blocks, provided a language is selected (does not work if “Auto-detected” is enabled).</li><li>Rendering for math equations.</li></ul></td><td><ul><li>Including notes is not supported.</li><li>Inline Mermaid diagrams are not rendered.</li></ul></td></tr><tr><th><a class="reference-link" href="../Note%20Types/Code.md">Code</a></th><td><ul><li>Basic support (displaying the contents of the note in a monospace font).</li></ul></td><td><ul><li>No syntax highlight.</li></ul></td></tr><tr><th><a class="reference-link" href="../Note%20Types/Saved%20Search.md">Saved Search</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/Relation%20Map.md">Relation Map</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/Note%20Map.md">Note Map</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/Render%20Note.md">Render Note</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/Book.md">Book</a></th><td><ul><li>The child notes are displayed in a fixed format. </li></ul></td><td><ul><li>More advanced view types such as the calendar view are not supported.</li></ul></td></tr><tr><th><a class="reference-link" href="../Note%20Types/Mermaid%20Diagrams.md">Mermaid Diagrams</a></th><td><ul><li>The diagram is displayed as a vector image.</li></ul></td><td><ul><li>No further interaction supported.</li></ul></td></tr><tr><th><a class="reference-link" href="../Note%20Types/Canvas.md">Canvas</a></th><td><ul><li>The diagram is displayed as a vector image.</li></ul></td><td><ul><li>No further interaction supported.</li></ul></td></tr><tr><th><a class="reference-link" href="../Note%20Types/Web%20View.md">Web View</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/Mind%20Map.md">Mind Map</a></th><td>The diagram is displayed as a vector image.</td><td><ul><li>No further interaction supported.</li></ul></td></tr><tr><th><a class="reference-link" href="../Basic%20Concepts%20and%20Features/Notes/Note%20List/Geo%20Map%20View.md">Geo Map</a></th><td>Not supported.</td></tr><tr><th><a class="reference-link" href="../Note%20Types/File.md">File</a></th><td>Basic interaction (downloading the file).</td><td><ul><li>No further interaction supported.</li></ul></td></tr></tbody></table></figure>
|
||||
|
||||
While the sharing feature is powerful, it has some limitations:
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Leaflet
|
||||
Leaflet is the library behind [Geo map](../../Note%20Types/Geo%20Map.md) notes.
|
||||
Leaflet is the library behind [Geo map](../../Basic%20Concepts%20and%20Features/Notes/Note%20List/Geo%20Map%20View.md) notes.
|
||||
|
||||
## Plugins
|
||||
|
||||
|
Before Width: | Height: | Size: 605 B After Width: | Height: | Size: 605 B |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
BIN
docs/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/15_Geo Map View_image.png
vendored
Normal file
After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 179 B After Width: | Height: | Size: 179 B |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB |
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 210 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 338 KiB |
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 108 KiB |
133
docs/User Guide/User Guide/Basic Concepts and Features/Notes/Note List/Geo Map View.md
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
# Geo Map View
|
||||
> [!IMPORTANT]
|
||||
> Starting with Trilium v0.97.0, the geo map has been converted from a standalone [note type](../../../Note%20Types.md) to a type of view for the <a class="reference-link" href="../Note%20List.md">Note List</a>.
|
||||
|
||||
<figure class="image image-style-align-center"><img style="aspect-ratio:892/675;" src="9_Geo Map View_image.png" width="892" height="675"></figure>
|
||||
|
||||
This note type displays the children notes on a geographical map, based on an attribute. It is also possible to add new notes at a specific location using the built-in interface.
|
||||
|
||||
## Creating a new geo map
|
||||
|
||||
<figure class="table"><table><thead><tr><th> </th><th> </th><th> </th></tr></thead><tbody><tr><td>1</td><td><figure class="image"><img style="aspect-ratio:483/413;" src="15_Geo Map View_image.png" width="483" height="413"></figure></td><td>Right click on any note on the note tree and select <em>Insert child note</em> → <em>Geo Map (beta)</em>.</td></tr><tr><td>2</td><td><figure class="image image-style-align-center image_resized" style="width:53.44%;"><img style="aspect-ratio:1720/1396;" src="8_Geo Map View_image.png" width="1720" height="1396"></figure></td><td>By default the map will be empty and will show the entire world.</td></tr></tbody></table></figure>
|
||||
|
||||
## Repositioning the map
|
||||
|
||||
* Click and drag the map in order to move across the map.
|
||||
* Use the mouse wheel, two-finger gesture on a touchpad or the +/- buttons on the top-left to adjust the zoom.
|
||||
|
||||
The position on the map and the zoom are saved inside the map note and restored when visiting again the note.
|
||||
|
||||
## Adding a marker using the map
|
||||
|
||||
### Adding a new note using the plus button
|
||||
|
||||
<figure class="table"><table><thead><tr><th> </th><th> </th><th> </th></tr></thead><tbody><tr><td>1</td><td>To create a marker, first navigate to the desired point on the map. Then press the <img src="10_Geo Map View_image.png"> button in the <a href="../../UI%20Elements/Floating%20buttons.md">Floating buttons</a> (top-right) area. <br><br>If the button is not visible, make sure the button section is visible by pressing the chevron button (<img src="17_Geo Map View_image.png">) in the top-right of the map.</td><td> </td></tr><tr><td>2</td><td><img class="image_resized" style="aspect-ratio:1730/416;width:100%;" src="2_Geo Map View_image.png" width="1730" height="416"></td><td>Once pressed, the map will enter in the insert mode, as illustrated by the notification. <br><br>Simply click the point on the map where to place the marker, or the Escape key to cancel.</td></tr><tr><td>3</td><td><img class="image_resized" style="aspect-ratio:1586/404;width:100%;" src="7_Geo Map View_image.png" width="1586" height="404"></td><td>Enter the name of the marker/note to be created.</td></tr><tr><td>4</td><td><img class="image_resized" style="aspect-ratio:1696/608;width:100%;" src="16_Geo Map View_image.png" width="1696" height="608"></td><td>Once confirmed, the marker will show up on the map and it will also be displayed as a child note of the map.</td></tr></tbody></table></figure>
|
||||
|
||||
### Adding a new note using the contextual menu
|
||||
|
||||
1. Right click anywhere on the map, where to place the newly created marker (and corresponding note).
|
||||
2. Select _Add a marker at this location_.
|
||||
3. Enter the name of the newly created note.
|
||||
4. The map should be updated with the new marker.
|
||||
|
||||
### Adding an existing note on note from the note tree
|
||||
|
||||
1. Select the desired note in the <a class="reference-link" href="../../UI%20Elements/Note%20Tree.md">Note Tree</a>.
|
||||
2. Hold the mouse on the note and drag it to the map to the desired location.
|
||||
3. The map should be updated with the new marker.
|
||||
|
||||
This works for:
|
||||
|
||||
* Notes that are not part of the geo map, case in which a [clone](../Cloning%20Notes.md) will be created.
|
||||
* Notes that are a child of the geo map but not yet positioned on the map.
|
||||
* Notes that are a child of the geo map and also positioned, case in which the marker will be relocated to the new position.
|
||||
|
||||
## How the location of the markers is stored
|
||||
|
||||
The location of a marker is stored in the `#geolocation` attribute of the child notes:
|
||||
|
||||
<img src="18_Geo Map View_image.png" width="1288" height="278">
|
||||
|
||||
This value can be added manually if needed. The value of the attribute is made up of the latitude and longitude separated by a comma.
|
||||
|
||||
## Repositioning markers
|
||||
|
||||
It's possible to reposition existing markers by simply drag and dropping them to the new destination.
|
||||
|
||||
As soon as the mouse is released, the new position is saved.
|
||||
|
||||
If moved by mistake, there is currently no way to undo the change. If the mouse was not yet released, it's possible to force a refresh of the page (<kbd>Ctrl</kbd>+<kbd>R</kbd> ) to cancel it.
|
||||
|
||||
## Interaction with the markers
|
||||
|
||||
* Hovering over a marker will display the content of the note it belongs to.
|
||||
* Clicking on the note title in the tooltip will navigate to the note in the current view.
|
||||
* Middle-clicking the marker will open the note in a new tab.
|
||||
* Right-clicking the marker will open a contextual menu allowing:
|
||||
|
||||
## Contextual menu
|
||||
|
||||
It's possible to press the right mouse button to display a contextual menu.
|
||||
|
||||
1. If right-clicking an empty section of the map (not on a marker), it allows to:
|
||||
1. Displays the latitude and longitude. Clicking this option will copy them to the clipboard.
|
||||
2. Open the location using an external application (if the operating system supports it).
|
||||
3. Adding a new marker at that location.
|
||||
2. If right-clicking on a marker, it allows to:
|
||||
1. Displays the latitude and longitude. Clicking this option will copy them to the clipboard.
|
||||
2. Open the location using an external application (if the operating system supports it).
|
||||
3. Open the note in a new tab, split or window.
|
||||
4. Remove the marker from the map, which will remove the `#geolocation` attribute of the note. To add it back again, the coordinates have to be manually added back in.
|
||||
|
||||
## Icon and color of the markers
|
||||
|
||||
<figure class="image image-style-align-center"><img style="aspect-ratio:523/295;" src="Geo Map View_image.jpg" alt="image" width="523" height="295"></figure>
|
||||
|
||||
The markers will have the same icon as the note.
|
||||
|
||||
It's possible to add a custom color to a marker by assigning them a `#color` attribute such as `#color=green`.
|
||||
|
||||
## Adding the coordinates manually
|
||||
|
||||
In a nutshell, create a child note and set the `#geolocation` attribute to the coordinates.
|
||||
|
||||
The value of the attribute is made up of the latitude and longitude separated by a comma.
|
||||
|
||||
### Adding from Google Maps
|
||||
|
||||
<figure class="table" style="width:100%;"><table class="ck-table-resized"><colgroup><col style="width:2.77%;"><col style="width:33.24%;"><col style="width:63.99%;"></colgroup><thead><tr><th> </th><th> </th><th> </th></tr></thead><tbody><tr><td>1</td><td><figure class="image image-style-align-center image_resized" style="width:56.84%;"><img style="aspect-ratio:732/918;" src="12_Geo Map View_image.png" width="732" height="918"></figure></td><td>Go to Google Maps on the web and look for a desired location, right click on it and a context menu will show up. <br><br>Simply click on the first item displaying the coordinates and they will be copied to clipboard. <br><br>Then paste the value inside the text box into the <code>#geolocation</code> attribute of a child note of the map (don't forget to surround the value with a <code>"</code> character).</td></tr><tr><td>2</td><td><figure class="image image-style-align-center image_resized" style="width:100%;"><img style="aspect-ratio:518/84;" src="4_Geo Map View_image.png" width="518" height="84"></figure></td><td>In Trilium, create a child note under the map.</td></tr><tr><td>3</td><td><figure class="image image-style-align-center image_resized" style="width:100%;"><img style="aspect-ratio:1074/276;" src="11_Geo Map View_image.png" width="1074" height="276"></figure></td><td>And then go to Owned Attributes and type <code>#geolocation="</code>, then paste from the clipboard as-is and then add the ending <code>"</code> character. Press Enter to confirm and the map should now be updated to contain the new note.</td></tr></tbody></table></figure>
|
||||
|
||||
### Adding from OpenStreetMap
|
||||
|
||||
Similarly to the Google Maps approach:
|
||||
|
||||
<figure class="table" style="width:100%;"><table class="ck-table-resized"><colgroup><col style="width:2.77%;"><col style="width:33.42%;"><col style="width:63.81%;"></colgroup><thead><tr><th> </th><th> </th><th> </th></tr></thead><tbody><tr><td>1</td><td><img class="image_resized" style="aspect-ratio:562/454;width:100%;" src="1_Geo Map View_image.png" width="562" height="454"></td><td>Go to any location on openstreetmap.org and right click to bring up the context menu. Select the “Show address” item.</td></tr><tr><td>2</td><td><img class="image_resized" style="aspect-ratio:696/480;width:100%;" src="Geo Map View_image.png" width="696" height="480"></td><td>The address will be visible in the top-left of the screen, in the place of the search bar. <br><br>Select the coordinates and copy them into the clipboard.</td></tr><tr><td>3</td><td><img class="image_resized" style="aspect-ratio:640/276;width:100%;" src="5_Geo Map View_image.png" width="640" height="276"></td><td>Simply paste the value inside the text box into the <code>#geolocation</code> attribute of a child note of the map and then it should be displayed on the map.</td></tr></tbody></table></figure>
|
||||
|
||||
## Adding GPS tracks (.gpx)
|
||||
|
||||
Trilium has basic support for displaying GPS tracks on the geo map.
|
||||
|
||||
<figure class="table" style="width:100%;"><table class="ck-table-resized"><colgroup><col style="width:2.77%;"><col style="width:30.22%;"><col style="width:67.01%;"></colgroup><thead><tr><th> </th><th> </th><th> </th></tr></thead><tbody><tr><td>1</td><td><figure class="image image-style-align-center"><img style="aspect-ratio:226/74;" src="3_Geo Map View_image.png" width="226" height="74"></figure></td><td>To add a track, simply drag & drop a .gpx file inside the geo map in the note tree.</td></tr><tr><td>2</td><td><figure class="image image-style-align-center"><img style="aspect-ratio:322/222;" src="14_Geo Map View_image.png" width="322" height="222"></figure></td><td>In order for the file to be recognized as a GPS track, it needs to show up as <code>application/gpx+xml</code> in the <em>File type</em> field.</td></tr><tr><td>3</td><td><figure class="image image-style-align-center"><img style="aspect-ratio:620/530;" src="6_Geo Map View_image.png" width="620" height="530"></figure></td><td>When going back to the map, the track should now be visible. <br><br>The start and end points of the track are indicated by the two blue markers.</td></tr></tbody></table></figure>
|
||||
|
||||
> [!NOTE]
|
||||
> The starting point of the track will be displayed as a marker, with the name of the note underneath. The start marker will also respect the icon and the `color` of the note. The end marker is displayed with a distinct icon.
|
||||
>
|
||||
> If the GPX contains waypoints, they will also be displayed. If they have a name, it is displayed when hovering over it with the mouse.
|
||||
|
||||
## Read-only mode
|
||||
|
||||
When a map is in read-only all editing features will be disabled such as:
|
||||
|
||||
* The add button in the <a class="reference-link" href="../../UI%20Elements/Floating%20buttons.md">Floating buttons</a>.
|
||||
* Dragging markers.
|
||||
* Editing from the contextual menu (removing locations or adding new items).
|
||||
|
||||
To enable read-only mode simply press the _Lock_ icon from the <a class="reference-link" href="../../UI%20Elements/Floating%20buttons.md">Floating buttons</a>. To disable it, press the button again.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<figure class="image image-style-align-right image_resized" style="width:34.06%;"><img style="aspect-ratio:678/499;" src="13_Geo Map View_image.png" width="678" height="499"></figure>
|
||||
|
||||
### Grid-like artifacts on the map
|
||||
|
||||
This occurs if the application is not at 100% zoom which causes the pixels of the map to not render correctly due to fractional scaling. The only possible solution is to set the UI zoom at 100% (default keyboard shortcut is <kbd>Ctrl</kbd>+<kbd>0</kbd>).
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@ -1,5 +1,5 @@
|
||||
# Table
|
||||
<figure class="image"><img style="aspect-ratio:1050/259;" src="Table_image.png" width="1050" height="259"></figure>
|
||||
# Table View
|
||||
<figure class="image"><img style="aspect-ratio:1050/259;" src="Table View_image.png" width="1050" height="259"></figure>
|
||||
|
||||
The table view displays information in a grid, where the rows are individual notes and the columns are <a class="reference-link" href="../../../Advanced%20Usage/Attributes/Promoted%20Attributes.md">Promoted Attributes</a>. In addition, values are editable.
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@ -40,3 +40,4 @@ When pressed, the note will become editable but will become read-only again afte
|
||||
Some note types have a special behavior based on whether the read-only mode is enabled:
|
||||
|
||||
* <a class="reference-link" href="../../Note%20Types/Mermaid%20Diagrams.md">Mermaid Diagrams</a> will hide the Mermaid source code and display the diagram preview in full-size. In this case, the read-only mode can be easily toggled on or off via a dedicated button in the <a class="reference-link" href="../UI%20Elements/Floating%20buttons.md">Floating buttons</a> area.
|
||||
* <a class="reference-link" href="Note%20List/Geo%20Map%20View.md">Geo Map View</a> will disallow all interaction that would otherwise change the map (dragging notes, adding new items).
|
2
docs/User Guide/User Guide/Note Types.md
vendored
@ -25,4 +25,4 @@ It is possible to change the type of a note after it has been created via the _B
|
||||
|
||||
The following note types are supported by Trilium:
|
||||
|
||||
<figure class="table" style="width:100%;"><table class="ck-table-resized"><colgroup><col style="width:29.42%;"><col style="width:70.58%;"></colgroup><thead><tr><th>Note Type</th><th>Description</th></tr></thead><tbody><tr><td><a class="reference-link" href="Note%20Types/Text.md">Text</a></td><td>The default note type, which allows for rich text formatting, images, admonitions and right-to-left support.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Code.md">Code</a></td><td>Uses a mono-space font and can be used to store larger chunks of code or plain text than a text note, and has better syntax highlighting.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Saved%20Search.md">Saved Search</a></td><td>Stores the information about a search (the search text, criteria, etc.) for later use. Can be used for quick filtering of a large amount of notes, for example. The search can easily be triggered.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Relation%20Map.md">Relation Map</a></td><td>Allows easy creation of notes and relations between them. Can be used for mainly relational data such as a family tree.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Note%20Map.md">Note Map</a></td><td>Displays the relationships between the notes, whether via relations or their hierarchical structure.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Render%20Note.md">Render Note</a></td><td>Used in <a class="reference-link" href="Scripting.md">Scripting</a>, it displays the HTML content of another note. This allows displaying any kind of content, provided there is a script behind it to generate it.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Book.md">Book</a></td><td><p>Displays the children of the note either as a grid, a list, or for a more specialized case: a calendar.</p><p>Generally useful for easy reading of short notes.</p></td></tr><tr><td><a class="reference-link" href="Note%20Types/Mermaid%20Diagrams.md">Mermaid Diagrams</a></td><td>Displays diagrams such as bar charts, flow charts, state diagrams, etc. Requires a bit of technical knowledge since the diagrams are written in a specialized format.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Canvas.md">Canvas</a></td><td>Allows easy drawing of sketches, diagrams, handwritten content. Uses the same technology behind <a href="https://excalidraw.com">excalidraw.com</a>.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Web%20View.md">Web View</a></td><td>Displays the content of an external web page, similar to a browser.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Mind%20Map.md">Mind Map</a></td><td>Easy for brainstorming ideas, by placing them in a hierarchical layout.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Geo%20Map.md">Geo Map</a></td><td>Displays the children of the note as a geographical map, one use-case would be to plan vacations. It even has basic support for tracks. Notes can also be created from it.</td></tr><tr><td><a class="reference-link" href="Note%20Types/File.md">File</a></td><td>Represents an uploaded file such as PDFs, images, video or audio files.</td></tr></tbody></table></figure>
|
||||
<figure class="table" style="width:100%;"><table class="ck-table-resized"><colgroup><col style="width:29.42%;"><col style="width:70.58%;"></colgroup><thead><tr><th>Note Type</th><th>Description</th></tr></thead><tbody><tr><td><a class="reference-link" href="Note%20Types/Text.md">Text</a></td><td>The default note type, which allows for rich text formatting, images, admonitions and right-to-left support.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Code.md">Code</a></td><td>Uses a mono-space font and can be used to store larger chunks of code or plain text than a text note, and has better syntax highlighting.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Saved%20Search.md">Saved Search</a></td><td>Stores the information about a search (the search text, criteria, etc.) for later use. Can be used for quick filtering of a large amount of notes, for example. The search can easily be triggered.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Relation%20Map.md">Relation Map</a></td><td>Allows easy creation of notes and relations between them. Can be used for mainly relational data such as a family tree.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Note%20Map.md">Note Map</a></td><td>Displays the relationships between the notes, whether via relations or their hierarchical structure.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Render%20Note.md">Render Note</a></td><td>Used in <a class="reference-link" href="Scripting.md">Scripting</a>, it displays the HTML content of another note. This allows displaying any kind of content, provided there is a script behind it to generate it.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Book.md">Book</a></td><td><p>Displays the children of the note either as a grid, a list, or for a more specialized case: a calendar.</p><p>Generally useful for easy reading of short notes.</p></td></tr><tr><td><a class="reference-link" href="Note%20Types/Mermaid%20Diagrams.md">Mermaid Diagrams</a></td><td>Displays diagrams such as bar charts, flow charts, state diagrams, etc. Requires a bit of technical knowledge since the diagrams are written in a specialized format.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Canvas.md">Canvas</a></td><td>Allows easy drawing of sketches, diagrams, handwritten content. Uses the same technology behind <a href="https://excalidraw.com">excalidraw.com</a>.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Web%20View.md">Web View</a></td><td>Displays the content of an external web page, similar to a browser.</td></tr><tr><td><a class="reference-link" href="Note%20Types/Mind%20Map.md">Mind Map</a></td><td>Easy for brainstorming ideas, by placing them in a hierarchical layout.</td></tr><tr><td><a class="reference-link" href="Basic%20Concepts%20and%20Features/Notes/Note%20List/Geo%20Map%20View.md">Geo Map</a></td><td>Displays the children of the note as a geographical map, one use-case would be to plan vacations. It even has basic support for tracks. Notes can also be created from it.</td></tr><tr><td><a class="reference-link" href="Note%20Types/File.md">File</a></td><td>Represents an uploaded file such as PDFs, images, video or audio files.</td></tr></tbody></table></figure>
|