Merge branch 'develop' of https://github.com/TriliumNext/Notes into style/next/forms

This commit is contained in:
Adorian Doran
2025-01-22 23:48:29 +02:00
32 changed files with 889 additions and 123 deletions
+2 -1
View File
@@ -116,7 +116,8 @@ export const ALLOWED_NOTE_TYPES = [
"book",
"webView",
"code",
"mindMap"
"mindMap",
"geoMap"
] as const;
export type NoteType = (typeof ALLOWED_NOTE_TYPES)[number];
+22 -3
View File
@@ -23,6 +23,7 @@ import type LoadResults from "../services/load_results.js";
import type { Attribute } from "../services/attribute_parser.js";
import type NoteTreeWidget from "../widgets/note_tree.js";
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
import type { ContextMenuEvent } from "../menus/context_menu.js";
interface Layout {
getRootWidget: (appContext: AppContext) => RootWidget;
@@ -69,6 +70,7 @@ export interface ExecuteCommandData extends CommandData {
*/
export type CommandMappings = {
"api-log-messages": CommandData;
focusTree: CommandData,
focusOnDetail: Required<CommandData>;
focusOnSearchDefinition: Required<CommandData>;
searchNotes: CommandData & {
@@ -193,6 +195,10 @@ export type CommandMappings = {
setZoomFactorAndSave: {
zoomFactor: string;
}
// Geomap
deleteFromMap: { noteId: string },
openGeoLocation: { noteId: string, event: JQuery.MouseDownEvent }
};
type EventMappings = {
@@ -227,9 +233,12 @@ type EventMappings = {
activeContextChanged: {
noteContext: NoteContext;
};
beforeNoteSwitch: {
noteContext: NoteContext;
};
noteSwitched: {
noteContext: NoteContext;
notePath: string;
notePath: string | null;
};
noteSwitchedAndActivatedEvent: {
noteContext: NoteContext;
@@ -248,12 +257,16 @@ type EventMappings = {
noteId: string;
};
hoistedNoteChanged: {
ntxId: string;
noteId: string;
ntxId: string | null;
};
contextsReopenedEvent: {
mainNtxId: string;
tabPosition: number;
};
noteDetailRefreshed: {
ntxId?: string | null;
};
noteContextReorderEvent: {
oldMainNtxId: string;
newMainNtxId: string;
@@ -266,7 +279,13 @@ type EventMappings = {
};
exportSvg: {
ntxId: string;
}
};
geoMapCreateChildNote: {
ntxId: string | null | undefined; // TODO: deduplicate ntxId
};
tabReorder: {
ntxIdsInOrder: string[]
};
};
export type EventListener<T extends EventNames> = {
+1 -1
View File
@@ -61,7 +61,7 @@ export class TypedComponent<ChildT extends TypedComponent<ChildT>> {
}
}
triggerEvent(name: string, data = {}): Promise<unknown> | undefined | null {
triggerEvent<T extends EventNames>(name: T, data: EventData<T>): Promise<unknown> | undefined | null {
return this.parent?.triggerEvent(name, data);
}
+3 -2
View File
@@ -27,7 +27,8 @@ const NOTE_TYPE_ICONS = {
launcher: "bx bx-link",
doc: "bx bxs-file-doc",
contentWidget: "bx bxs-widget",
mindMap: "bx bx-sitemap"
mindMap: "bx bx-sitemap",
geoMap: "bx bx-map-alt"
};
/**
@@ -35,7 +36,7 @@ const NOTE_TYPE_ICONS = {
* end user. Those types should be used only for checking against, they are
* not for direct use.
*/
type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap";
type NoteType = "file" | "image" | "search" | "noteMap" | "launcher" | "doc" | "contentWidget" | "text" | "relationMap" | "render" | "canvas" | "mermaid" | "book" | "webView" | "code" | "mindMap" | "geoMap";
interface NotePathRecord {
isArchived: boolean;
+2
View File
@@ -85,6 +85,7 @@ import ScrollPaddingWidget from "../widgets/scroll_padding.js";
import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js";
import options from "../services/options.js";
import utils from "../services/utils.js";
import GeoMapButtons from "../widgets/floating_buttons/geo_map_button.js";
export default class DesktopLayout {
constructor(customWidgets) {
@@ -200,6 +201,7 @@ export default class DesktopLayout {
.child(new ShowHighlightsListWidgetButton())
.child(new CodeButtonsWidget())
.child(new RelationMapButtons())
.child(new GeoMapButtons())
.child(new CopyImageReferenceButton())
.child(new SvgExportButton())
.child(new BacklinksWidget())
+4
View File
@@ -1,5 +1,6 @@
import type { CommandNames } from "../components/app_context.js";
import keyboardActionService from "../services/keyboard_actions.js";
import note_tooltip from "../services/note_tooltip.js";
import utils from "../services/utils.js";
interface ContextMenuOptions<T extends CommandNames> {
@@ -31,6 +32,7 @@ export interface MenuCommandItem<T extends CommandNames> {
export type MenuItem<T extends CommandNames> = MenuCommandItem<T> | MenuSeparatorItem;
export type MenuHandler<T extends CommandNames> = (item: MenuCommandItem<T>, e: JQuery.MouseDownEvent<HTMLElement, undefined, HTMLElement, HTMLElement>) => void;
export type ContextMenuEvent = PointerEvent | MouseEvent | JQuery.ContextMenuEvent;
class ContextMenu {
private $widget: JQuery<HTMLElement>;
@@ -56,6 +58,8 @@ class ContextMenu {
async show<T extends CommandNames>(options: ContextMenuOptions<T>) {
this.options = options;
note_tooltip.dismissAllTooltips();
if (this.$widget.hasClass("show")) {
// The menu is already visible. Hide the menu then open it again
// at the new location to re-trigger the opening animation.
+32 -24
View File
@@ -1,36 +1,44 @@
import { t } from "../services/i18n.js";
import contextMenu from "./context_menu.js";
import appContext from "../components/app_context.js";
import contextMenu, { type ContextMenuEvent, type MenuItem } from "./context_menu.js";
import appContext, { type CommandNames } from "../components/app_context.js";
import type { ViewScope } from "../services/link.js";
function openContextMenu(notePath: string, e: PointerEvent | MouseEvent | JQuery.ContextMenuEvent, viewScope: ViewScope = {}, hoistedNoteId: string | null = null) {
function openContextMenu(notePath: string, e: ContextMenuEvent, viewScope: ViewScope = {}, hoistedNoteId: string | null = null) {
contextMenu.show({
x: e.pageX,
y: e.pageY,
items: [
{ title: t("link_context_menu.open_note_in_new_tab"), command: "openNoteInNewTab", uiIcon: "bx bx-link-external" },
{ title: t("link_context_menu.open_note_in_new_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right" },
{ title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open" }
],
selectMenuItemHandler: ({ command }) => {
if (!hoistedNoteId) {
hoistedNoteId = appContext.tabManager.getActiveContext().hoistedNoteId;
}
if (command === "openNoteInNewTab") {
appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope });
} else if (command === "openNoteInNewSplit") {
const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
const { ntxId } = subContexts[subContexts.length - 1];
appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope });
} else if (command === "openNoteInNewWindow") {
appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
}
}
items: getItems(),
selectMenuItemHandler: ({ command }) => handleLinkContextMenuItem(command, notePath, viewScope, hoistedNoteId)
});
}
function getItems(): MenuItem<CommandNames>[] {
return [
{ title: t("link_context_menu.open_note_in_new_tab"), command: "openNoteInNewTab", uiIcon: "bx bx-link-external" },
{ title: t("link_context_menu.open_note_in_new_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right" },
{ title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open" }
];
}
function handleLinkContextMenuItem(command: string | undefined, notePath: string, viewScope = {}, hoistedNoteId: string | null = null) {
if (!hoistedNoteId) {
hoistedNoteId = appContext.tabManager.getActiveContext().hoistedNoteId;
}
if (command === "openNoteInNewTab") {
appContext.tabManager.openContextWithNote(notePath, { hoistedNoteId, viewScope });
} else if (command === "openNoteInNewSplit") {
const subContexts = appContext.tabManager.getActiveContext().getSubContexts();
const { ntxId } = subContexts[subContexts.length - 1];
appContext.triggerCommand("openNewNoteSplit", { ntxId, notePath, hoistedNoteId, viewScope });
} else if (command === "openNoteInNewWindow") {
appContext.triggerCommand("openInWindow", { notePath, hoistedNoteId, viewScope });
}
}
export default {
getItems,
handleLinkContextMenuItem,
openContextMenu
};
+6 -1
View File
@@ -106,6 +106,10 @@ const HIGHLIGHT_JS: Library = {
}
};
const LEAFLET: Library = {
css: [ "node_modules/leaflet/dist/leaflet.css" ],
}
async function requireLibrary(library: Library) {
if (library.css) {
library.css.map((cssUrl) => requireCss(cssUrl));
@@ -196,5 +200,6 @@ export default {
MERMAID,
MARKJS,
I18NEXT,
HIGHLIGHT_JS
HIGHLIGHT_JS,
LEAFLET
};
+1 -1
View File
@@ -234,7 +234,7 @@ function goToLink(evt: MouseEvent | JQuery.ClickEvent) {
return goToLinkExt(evt, hrefLink, $link);
}
function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | React.PointerEvent<HTMLCanvasElement>, hrefLink: string | undefined, $link: JQuery<HTMLElement> | null) {
function goToLinkExt(evt: MouseEvent | JQuery.ClickEvent | JQuery.MouseDownEvent | React.PointerEvent<HTMLCanvasElement>, hrefLink: string | undefined, $link?: JQuery<HTMLElement> | null) {
if (hrefLink?.startsWith("data:")) {
return true;
}
@@ -1,3 +1,5 @@
// TODO: deduplicate with /src/services/import/mime_type_definitions.ts
/**
* A pseudo-MIME type which is used in the editor to automatically determine the language used in code blocks via heuristics.
*/
+7 -6
View File
@@ -18,11 +18,11 @@ function setupGlobalTooltip() {
return;
}
cleanUpTooltips();
dismissAllTooltips();
});
}
function cleanUpTooltips() {
function dismissAllTooltips() {
$(".note-tooltip").remove();
}
@@ -102,12 +102,12 @@ async function mouseEnterHandler(this: HTMLElement) {
customClass: linkId
});
cleanUpTooltips();
dismissAllTooltips();
$(this).tooltip("show");
// Dismiss the tooltip immediately if a link was clicked inside the tooltip.
$(`.${tooltipClass} a`).on("click", (e) => {
cleanUpTooltips();
dismissAllTooltips();
});
// the purpose of the code below is to:
@@ -117,7 +117,7 @@ async function mouseEnterHandler(this: HTMLElement) {
const checkTooltip = () => {
if (!$(this).filter(":hover").length && !$(`.${linkId}:hover`).length) {
// cursor is neither over the link nor over the tooltip, user likely is not interested
cleanUpTooltips();
dismissAllTooltips();
} else {
setTimeout(checkTooltip, 1000);
}
@@ -172,5 +172,6 @@ function renderFootnote($link: JQuery<HTMLElement>, url: string) {
export default {
setupGlobalTooltip,
setupElementTooltip
setupElementTooltip,
dismissAllTooltips
};
+2 -1
View File
@@ -18,7 +18,8 @@ async function getNoteTypeItems(command?: NoteTypeCommandNames) {
{ title: t("note_types.mermaid-diagram"), command, type: "mermaid", uiIcon: "bx bx-selection" },
{ title: t("note_types.canvas"), command, type: "canvas", uiIcon: "bx bx-pen" },
{ title: t("note_types.web-view"), command, type: "webView", uiIcon: "bx bx-globe-alt" },
{ title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" }
{ title: t("note_types.mind-map"), command, type: "mindMap", uiIcon: "bx bx-sitemap" },
{ title: t("note_types.geo-map"), command, type: "geoMap", uiIcon: "bx bx-map-alt" },
];
const templateNoteIds = await server.get<string[]>("search-templates");
@@ -42,7 +42,7 @@ const TPL = `
<li data-trigger-command="convertNoteIntoAttachment" class="dropdown-item">
<span class="bx bx-paperclip"></span> ${t("note_actions.convert_into_attachment")}
</li>
<li data-trigger-command="renderActiveNote" class="dropdown-item render-note-button">
<span class="bx bx-extension"></span> ${t("note_actions.re_render_note")}<kbd data-command="renderActiveNote"></kbd>
</li>
@@ -54,15 +54,15 @@ const TPL = `
<li data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button">
<span class="bx bx-printer"></span> ${t("note_actions.print_note")}<kbd data-command="printActiveNote"></kbd></li>
<div class="dropdown-divider"></div>
<li class="dropdown-item import-files-button"><span class="bx bx-import"></span> ${t("note_actions.import_files")}</li>
<li class="dropdown-item export-note-button"><span class="bx bx-export"></span> ${t("note_actions.export_note")}</li>
<div class="dropdown-divider"></div>
@@ -79,7 +79,7 @@ const TPL = `
<span class="bx bx-code"></span> ${t("note_actions.note_source")}<kbd data-command="showNoteSource"></kbd>
</li>
<div class="dropdown-divider"></div>
@@ -89,10 +89,10 @@ const TPL = `
<li class="dropdown-item delete-note-button"><span class="bx bx-trash destructive-action-icon"></span> ${t("note_actions.delete_note")}</li>
<div class="dropdown-divider"></div>
<li data-trigger-command="showAttachments" class="dropdown-item show-attachments-button">
<span class="bx bx-paperclip"></span> ${t("note_actions.note_attachments")}<kbd data-command="showAttachments"></kbd>
</li>
@@ -154,7 +154,7 @@ export default class NoteActionsWidget extends NoteContextAwareWidget {
this.toggleDisabled(this.$findInTextButton, ["text", "code", "book"].includes(note.type));
this.toggleDisabled(this.$showAttachmentsButton, !isInOptions);
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap"].includes(note.type));
this.toggleDisabled(this.$showSourceButton, ["text", "code", "relationMap", "mermaid", "canvas", "mindMap", "geoMap"].includes(note.type));
this.toggleDisabled(this.$printActiveNoteButton, ["text", "code"].includes(note.type));
@@ -22,7 +22,7 @@ export default class LeftPaneContainer extends FlexContainer<Component> {
this.toggleInt(visible);
if (visible) {
this.triggerEvent("focusTree");
this.triggerEvent("focusTree", {});
} else {
const activeNoteContext = appContext.tabManager.getActiveContext();
this.triggerEvent("focusOnDetail", { ntxId: activeNoteContext.ntxId });
@@ -0,0 +1,42 @@
import { t } from "../../services/i18n.js";
import NoteContextAwareWidget from "../note_context_aware_widget.js"
const TPL = `\
<div class="geo-map-buttons">
<style>
.geo-map-buttons {
display: flex;
gap: 10px;
}
.leaflet-pane {
z-index: 50;
}
.geo-map-buttons {
contain: none;
background: var(--main-background-color);
box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity));
border-radius: 4px;
}
</style>
<button type="button"
class="geo-map-create-child-note floating-button btn bx bx-folder-plus"
title="${t("geo-map.create-child-note-title")}" />
</div>`;
export default class GeoMapButtons extends NoteContextAwareWidget {
isEnabled() {
return super.isEnabled() && this.note?.type === "geoMap";
}
doRender() {
super.doRender();
this.$widget = $(TPL);
this.$widget.find(".geo-map-create-child-note").on("click", () => this.triggerEvent("geoMapCreateChildNote", { ntxId: this.ntxId }));
}
}
@@ -13,16 +13,16 @@ const TPL = `
<button type="button"
class="relation-map-create-child-note floating-button btn bx bx-folder-plus"
title="${t("relation_map_buttons.create_child_note_title")}"></button>
<button type="button"
class="relation-map-reset-pan-zoom floating-button btn bx bx-crop"
title="${t("relation_map_buttons.reset_pan_zoom_title")}"></button>
<div class="btn-group">
<button type="button"
class="relation-map-zoom-in floating-button btn bx bx-zoom-in"
title="${t("relation_map_buttons.zoom_in_title")}"></button>
<button type="button"
class="relation-map-zoom-out floating-button btn bx bx-zoom-out"
title="${t("relation_map_buttons.zoom_out_title")}"></button>
@@ -43,6 +43,7 @@ export default class RelationMapButtons extends NoteContextAwareWidget {
this.$zoomOutButton = this.$widget.find(".relation-map-zoom-out");
this.$resetPanZoomButton = this.$widget.find(".relation-map-reset-pan-zoom");
// TODO: Deduplicate object creation here.
this.$createChildNote.on("click", () => this.triggerEvent("relationMapCreateChildNote", { ntxId: this.ntxId }));
this.$resetPanZoomButton.on("click", () => this.triggerEvent("relationMapResetPanZoom", { ntxId: this.ntxId }));
+57
View File
@@ -0,0 +1,57 @@
import type { Map } from "leaflet";
import library_loader from "../services/library_loader.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
const TPL = `\
<div class="geo-map-widget">
<style>
.note-detail-geo-map,
.geo-map-widget,
.geo-map-container {
height: 100%;
overflow: hidden;
}
</style>
<div class="geo-map-container"></div>
</div>`
export type Leaflet = typeof import("leaflet");
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");
library_loader.requireLibrary(library_loader.LEAFLET)
.then(async () => {
const L = (await import("leaflet")).default;
const map = L.map(this.$container[0], {
});
this.map = map;
if (this.initCallback) {
this.initCallback(L);
}
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
});
}
}
+6 -4
View File
@@ -31,6 +31,7 @@ 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 { getStylesheetUrl, isSyntaxHighlightEnabled } from "../services/syntax_highlight.js";
import GeoMapTypeWidget from "./type_widgets/geo_map.js";
const TPL = `
<div class="note-detail">
@@ -39,7 +40,7 @@ const TPL = `
font-family: var(--detail-font-family);
font-size: var(--detail-font-size);
}
.note-detail.full-height {
height: 100%;
}
@@ -67,7 +68,8 @@ const typeWidgetClasses = {
contentWidget: ContentWidgetTypeWidget,
attachmentDetail: AttachmentDetailTypeWidget,
attachmentList: AttachmentListTypeWidget,
mindMap: MindMapWidget
mindMap: MindMapWidget,
geoMap: GeoMapTypeWidget
};
export default class NoteDetailWidget extends NoteContextAwareWidget {
@@ -147,7 +149,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
// https://github.com/zadam/trilium/issues/2522
this.$widget.toggleClass(
"full-height",
(!this.noteContext.hasNoteList() && ["canvas", "webView", "noteMap", "mindMap"].includes(this.type) && this.mime !== "text/x-sqlite;schema=trilium") ||
(!this.noteContext.hasNoteList() && ["canvas", "webView", "noteMap", "mindMap", "geoMap"].includes(this.type) && this.mime !== "text/x-sqlite;schema=trilium") ||
this.noteContext.viewScope.viewMode === "attachments"
);
}
@@ -276,7 +278,7 @@ export default class NoteDetailWidget extends NoteContextAwareWidget {
<script src="${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>
<script>
document.body.className += ' ck-content printed-content';
renderMathInElement(document.body, {trust: true});
</script>
`,
+1 -1
View File
@@ -41,7 +41,7 @@ export default class NoteWrapperWidget extends FlexContainer {
return;
}
this.$widget.toggleClass("full-content-width", ["image", "mermaid", "book", "render", "canvas", "webView", "mindMap"].includes(note.type) || !!note?.isLabelTruthy("fullContentWidth"));
this.$widget.toggleClass("full-content-width", ["image", "mermaid", "book", "render", "canvas", "webView", "mindMap", "geoMap"].includes(note.type) || !!note?.isLabelTruthy("fullContentWidth"));
this.$widget.addClass(note.getCssClass());
@@ -0,0 +1,323 @@
import { 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 { EventData } from "../../components/app_context.js";
import { t } from "../../services/i18n.js";
import attributes from "../../services/attributes.js";
import asset_path from "../../../../services/asset_path.js";
import openContextMenu from "./geo_map_context_menu.js";
import link from "../../services/link.js";
import note_tooltip from "../../services/note_tooltip.js";
const TPL = `\
<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;
}
}
type MarkerData = Record<string, Marker>;
enum State {
Normal,
NewNote
}
export default class GeoMapTypeWidget extends TypeWidget {
private geoMapWidget: GeoMapWidget;
private _state: State;
private L!: Leaflet;
private currentMarkerData: MarkerData;
static getType() {
return "geoMap";
}
constructor() {
super();
this.geoMapWidget = new GeoMapWidget("type", (L: Leaflet) => this.#onMapInitialized(L));
this.currentMarkerData = {};
this._state = State.Normal;
this.child(this.geoMapWidget);
}
doRender() {
this.$widget = $(TPL);
this.$widget.append(this.geoMapWidget.render());
super.doRender();
}
async #onMapInitialized(L: Leaflet) {
this.L = L;
const map = this.geoMapWidget.map;
if (!map) {
throw new Error(t("geo-map.unable-to-load-map"));
}
if (!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);
// Restore markers.
await this.#reloadMarkers();
const updateFn = () => this.spacedUpdate.scheduleUpdate();
map.on("moveend", updateFn);
map.on("zoomend", updateFn);
map.on("click", (e) => this.#onMapClicked(e));
}
async #reloadMarkers() {
const map = this.geoMapWidget.map;
if (!this.note || !map) {
return;
}
// Delete all existing markers
for (const marker of Object.values(this.currentMarkerData)) {
marker.remove();
}
// Add the new markers.
this.currentMarkerData = {};
const childNotes = await this.note.getChildNotes();
const L = this.L;
for (const childNote of childNotes) {
const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE);
if (!latLng) {
continue;
}
const [ lat, lng ] = latLng.split(",", 2).map((el) => parseFloat(el));
const icon = L.divIcon({
html: `\
<img class="icon" src="${asset_path}/node_modules/leaflet/dist/images/marker-icon.png" />
<img class="icon-shadow" src="${asset_path}/node_modules/leaflet/dist/images/marker-shadow.png" />
<span class="bx ${childNote.getIcon()}"></span>
<span class="title-label">${childNote.title}</span>`,
iconSize: [ 25, 41 ],
iconAnchor: [ 12, 41 ]
})
const marker = L.marker(L.latLng(lat, lng), {
icon,
draggable: true,
autoPan: true,
autoPanSpeed: 5,
})
.addTo(map)
.on("moveend", e => {
this.moveMarker(childNote.noteId, (e.target as Marker).getLatLng());
});
marker.on("contextmenu", (e) => {
openContextMenu(childNote.noteId, e.originalEvent);
});
const el = marker.getElement();
if (el) {
const $el = $(el);
$el.attr("data-href", `#${childNote.noteId}`);
note_tooltip.setupElementTooltip($($el));
}
this.currentMarkerData[childNote.noteId] = marker;
}
}
#changeState(newState: State) {
this._state = newState;
this.geoMapWidget.$container.toggleClass("placing-note", newState === State.NewNote);
}
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();
await this.#reloadMarkers();
}
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
const attributeRows = loadResults.getAttributeRows();
if (attributeRows.find((at) => at.name === LOCATION_ATTRIBUTE)) {
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);
}
}
@@ -0,0 +1,32 @@
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);
}
});
}
+11 -1
View File
@@ -1409,7 +1409,8 @@
"launcher": "Launcher",
"doc": "Doc",
"widget": "Widget",
"confirm-change": "It is not recommended to change note type when note content is not empty. Do you want to continue anyway?"
"confirm-change": "It is not recommended to change note type when note content is not empty. Do you want to continue anyway?",
"geo-map": "Geo Map (beta)"
},
"protect_note": {
"toggle-on": "Protect the note",
@@ -1629,5 +1630,14 @@
},
"note_tooltip": {
"note-has-been-deleted": "Note has been deleted."
},
"geo-map": {
"create-child-note-title": "Create a new child note and add it to the map",
"create-child-note-instruction": "Click on the map to create a new note at that location or press Escape to dismiss.",
"unable-to-load-map": "Unable to load map."
},
"geo-map-context": {
"open-location": "Open location",
"remove-from-map": "Remove from map"
}
}
+10 -1
View File
@@ -1379,7 +1379,8 @@
"image": "Imagine",
"launcher": "Scurtătură",
"widget": "Widget",
"confirm-change": "Nu se recomandă schimbarea tipului notiței atunci când ea are un conținut. Procedați oricum?"
"confirm-change": "Nu se recomandă schimbarea tipului notiței atunci când ea are un conținut. Procedați oricum?",
"geo-map": "Hartă geografică (beta)"
},
"protect_note": {
"toggle-off": "Deprotejează notița",
@@ -1633,5 +1634,13 @@
"notes": {
"duplicate-note-suffix": "(dupl.)",
"duplicate-note-title": "{{ noteTitle }} {{ duplicateNoteSuffix }}"
},
"geo-map-context": {
"open-location": "Deschide locația",
"remove-from-map": "Înlătură de pe hartă"
},
"geo-map": {
"create-child-note-title": "Crează o notiță nouă și adaug-o pe hartă",
"unable-to-load-map": "Nu s-a putut încărca harta."
}
}
+2
View File
@@ -105,6 +105,8 @@ async function register(app: express.Application) {
app.use(`/${assetPath}/node_modules/mind-elixir/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/mind-elixir/dist/")));
app.use(`/${assetPath}/node_modules/@mind-elixir/node-menu/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/@mind-elixir/node-menu/dist/")));
app.use(`/${assetPath}/node_modules/@highlightjs/cdn-assets/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/@highlightjs/cdn-assets/")));
app.use(`/${assetPath}/node_modules/leaflet/dist/`, persistentCacheStatic(path.join(srcRoot, "..", "node_modules/leaflet/dist/")));
}
export default {
+1 -1
View File
@@ -14,7 +14,7 @@ renderer.code = ({text, lang, escaped}: Tokens.Code) => {
import htmlSanitizer from "../html_sanitizer.js";
import importUtils from "./utils.js";
import { getMimeTypeFromHighlightJs, MIME_TYPE_AUTO, normalizeMimeTypeForCKEditor } from "../../public/app/services/mime_type_definitions.js";
import { getMimeTypeFromHighlightJs, MIME_TYPE_AUTO, normalizeMimeTypeForCKEditor } from "./mime_type_definitions.js";
function renderToHtml(content: string, title: string) {
const html = parse(content, {
@@ -0,0 +1,218 @@
// TODO: deduplicate with /src/public/app/services/mime_type_definitions.ts
/**
* A pseudo-MIME type which is used in the editor to automatically determine the language used in code blocks via heuristics.
*/
export const MIME_TYPE_AUTO = "text-x-trilium-auto";
export interface MimeTypeDefinition {
default?: boolean;
title: string;
mime: string;
/** The name of the language/mime type as defined by highlight.js (or one of the aliases), in order to be used for syntax highlighting such as inside code blocks. */
highlightJs?: string;
/** If specified, will load the corresponding highlight.js file from the `libraries/highlightjs/${id}.js` instead of `node_modules/@highlightjs/cdn-assets/languages/${id}.min.js`. */
highlightJsSource?: "libraries";
/** If specified, will load the corresponding highlight file from the given path instead of `node_modules`. */
codeMirrorSource?: string;
}
/**
* For highlight.js-supported languages, see https://github.com/highlightjs/highlight.js/blob/main/SUPPORTED_LANGUAGES.md.
*/
export const MIME_TYPES_DICT: readonly MimeTypeDefinition[] = Object.freeze([
{ default: true, title: "Plain text", mime: "text/plain", highlightJs: "plaintext" },
{ title: "APL", mime: "text/apl" },
{ title: "ASN.1", mime: "text/x-ttcn-asn" },
{ title: "ASP.NET", mime: "application/x-aspx" },
{ title: "Asterisk", mime: "text/x-asterisk" },
{ title: "Batch file (DOS)", mime: "application/x-bat", highlightJs: "dos", codeMirrorSource: "libraries/codemirror/batch.js" },
{ title: "Brainfuck", mime: "text/x-brainfuck", highlightJs: "brainfuck" },
{ default: true, title: "C", mime: "text/x-csrc", highlightJs: "c" },
{ default: true, title: "C#", mime: "text/x-csharp", highlightJs: "csharp" },
{ default: true, title: "C++", mime: "text/x-c++src", highlightJs: "cpp" },
{ title: "Clojure", mime: "text/x-clojure", highlightJs: "clojure" },
{ title: "ClojureScript", mime: "text/x-clojurescript" },
{ title: "Closure Stylesheets (GSS)", mime: "text/x-gss" },
{ title: "CMake", mime: "text/x-cmake", highlightJs: "cmake" },
{ title: "Cobol", mime: "text/x-cobol" },
{ title: "CoffeeScript", mime: "text/coffeescript", highlightJs: "coffeescript" },
{ title: "Common Lisp", mime: "text/x-common-lisp", highlightJs: "lisp" },
{ title: "CQL", mime: "text/x-cassandra" },
{ title: "Crystal", mime: "text/x-crystal", highlightJs: "crystal" },
{ default: true, title: "CSS", mime: "text/css", highlightJs: "css" },
{ title: "Cypher", mime: "application/x-cypher-query" },
{ title: "Cython", mime: "text/x-cython" },
{ title: "D", mime: "text/x-d", highlightJs: "d" },
{ title: "Dart", mime: "application/dart", highlightJs: "dart" },
{ title: "diff", mime: "text/x-diff", highlightJs: "diff" },
{ title: "Django", mime: "text/x-django", highlightJs: "django" },
{ title: "Dockerfile", mime: "text/x-dockerfile", highlightJs: "dockerfile" },
{ title: "DTD", mime: "application/xml-dtd" },
{ title: "Dylan", mime: "text/x-dylan" },
{ title: "EBNF", mime: "text/x-ebnf", highlightJs: "ebnf" },
{ title: "ECL", mime: "text/x-ecl" },
{ title: "edn", mime: "application/edn" },
{ title: "Eiffel", mime: "text/x-eiffel" },
{ title: "Elm", mime: "text/x-elm", highlightJs: "elm" },
{ title: "Embedded Javascript", mime: "application/x-ejs" },
{ title: "Embedded Ruby", mime: "application/x-erb", highlightJs: "erb" },
{ title: "Erlang", mime: "text/x-erlang", highlightJs: "erlang" },
{ title: "Esper", mime: "text/x-esper" },
{ title: "F#", mime: "text/x-fsharp", highlightJs: "fsharp" },
{ title: "Factor", mime: "text/x-factor" },
{ title: "FCL", mime: "text/x-fcl" },
{ title: "Forth", mime: "text/x-forth" },
{ title: "Fortran", mime: "text/x-fortran", highlightJs: "fortran" },
{ title: "Gas", mime: "text/x-gas" },
{ title: "Gherkin", mime: "text/x-feature", highlightJs: "gherkin" },
{ title: "GitHub Flavored Markdown", mime: "text/x-gfm", highlightJs: "markdown" },
{ default: true, title: "Go", mime: "text/x-go", highlightJs: "go" },
{ default: true, title: "Groovy", mime: "text/x-groovy", highlightJs: "groovy" },
{ title: "HAML", mime: "text/x-haml", highlightJs: "haml" },
{ default: true, title: "Haskell", mime: "text/x-haskell", highlightJs: "haskell" },
{ title: "Haskell (Literate)", mime: "text/x-literate-haskell" },
{ title: "Haxe", mime: "text/x-haxe", highlightJs: "haxe" },
{ default: true, title: "HTML", mime: "text/html", highlightJs: "xml" },
{ default: true, title: "HTTP", mime: "message/http", highlightJs: "http" },
{ title: "HXML", mime: "text/x-hxml" },
{ title: "IDL", mime: "text/x-idl" },
{ default: true, title: "Java", mime: "text/x-java", highlightJs: "java" },
{ title: "Java Server Pages", mime: "application/x-jsp", highlightJs: "java" },
{ title: "Jinja2", mime: "text/jinja2" },
{ default: true, title: "JS backend", mime: "application/javascript;env=backend", highlightJs: "javascript" },
{ default: true, title: "JS frontend", mime: "application/javascript;env=frontend", highlightJs: "javascript" },
{ default: true, title: "JSON", mime: "application/json", highlightJs: "json" },
{ title: "JSON-LD", mime: "application/ld+json", highlightJs: "json" },
{ title: "JSX", mime: "text/jsx", highlightJs: "javascript" },
{ title: "Julia", mime: "text/x-julia", highlightJs: "julia" },
{ default: true, title: "Kotlin", mime: "text/x-kotlin", highlightJs: "kotlin" },
{ title: "LaTeX", mime: "text/x-latex", highlightJs: "latex" },
{ title: "LESS", mime: "text/x-less", highlightJs: "less" },
{ title: "LiveScript", mime: "text/x-livescript", highlightJs: "livescript" },
{ title: "Lua", mime: "text/x-lua", highlightJs: "lua" },
{ title: "MariaDB SQL", mime: "text/x-mariadb", highlightJs: "sql" },
{ default: true, title: "Markdown", mime: "text/x-markdown", highlightJs: "markdown" },
{ title: "Mathematica", mime: "text/x-mathematica", highlightJs: "mathematica" },
{ title: "mbox", mime: "application/mbox" },
{ title: "mIRC", mime: "text/mirc" },
{ title: "Modelica", mime: "text/x-modelica" },
{ title: "MS SQL", mime: "text/x-mssql", highlightJs: "sql" },
{ title: "mscgen", mime: "text/x-mscgen" },
{ title: "msgenny", mime: "text/x-msgenny" },
{ title: "MUMPS", mime: "text/x-mumps" },
{ title: "MySQL", mime: "text/x-mysql", highlightJs: "sql" },
{ title: "Nginx", mime: "text/x-nginx-conf", highlightJs: "nginx" },
{ title: "NSIS", mime: "text/x-nsis", highlightJs: "nsis" },
{ title: "NTriples", mime: "application/n-triples" },
{ title: "Objective-C", mime: "text/x-objectivec", highlightJs: "objectivec" },
{ title: "OCaml", mime: "text/x-ocaml", highlightJs: "ocaml" },
{ title: "Octave", mime: "text/x-octave" },
{ title: "Oz", mime: "text/x-oz" },
{ title: "Pascal", mime: "text/x-pascal", highlightJs: "delphi" },
{ title: "PEG.js", mime: "null" },
{ default: true, title: "Perl", mime: "text/x-perl" },
{ title: "PGP", mime: "application/pgp" },
{ default: true, title: "PHP", mime: "text/x-php" },
{ title: "Pig", mime: "text/x-pig" },
{ title: "PLSQL", mime: "text/x-plsql", highlightJs: "sql" },
{ title: "PostgreSQL", mime: "text/x-pgsql", highlightJs: "pgsql" },
{ title: "PowerShell", mime: "application/x-powershell", highlightJs: "powershell" },
{ title: "Properties files", mime: "text/x-properties", highlightJs: "properties" },
{ title: "ProtoBuf", mime: "text/x-protobuf", highlightJs: "protobuf" },
{ title: "Pug", mime: "text/x-pug" },
{ title: "Puppet", mime: "text/x-puppet", highlightJs: "puppet" },
{ default: true, title: "Python", mime: "text/x-python", highlightJs: "python" },
{ title: "Q", mime: "text/x-q", highlightJs: "q" },
{ title: "R", mime: "text/x-rsrc", highlightJs: "r" },
{ title: "reStructuredText", mime: "text/x-rst" },
{ title: "RPM Changes", mime: "text/x-rpm-changes" },
{ title: "RPM Spec", mime: "text/x-rpm-spec" },
{ default: true, title: "Ruby", mime: "text/x-ruby", highlightJs: "ruby" },
{ title: "Rust", mime: "text/x-rustsrc", highlightJs: "rust" },
{ title: "SAS", mime: "text/x-sas", highlightJs: "sas" },
{ title: "Sass", mime: "text/x-sass" },
{ title: "Scala", mime: "text/x-scala" },
{ title: "Scheme", mime: "text/x-scheme" },
{ title: "SCSS", mime: "text/x-scss", highlightJs: "scss" },
{ default: true, title: "Shell (bash)", mime: "text/x-sh", highlightJs: "bash" },
{ title: "Sieve", mime: "application/sieve" },
{ title: "Slim", mime: "text/x-slim" },
{ title: "Smalltalk", mime: "text/x-stsrc", highlightJs: "smalltalk" },
{ title: "Smarty", mime: "text/x-smarty" },
{ title: "SML", mime: "text/x-sml", highlightJs: "sml" },
{ title: "Solr", mime: "text/x-solr" },
{ title: "Soy", mime: "text/x-soy" },
{ title: "SPARQL", mime: "application/sparql-query" },
{ title: "Spreadsheet", mime: "text/x-spreadsheet" },
{ default: true, title: "SQL", mime: "text/x-sql", highlightJs: "sql" },
{ title: "SQLite", mime: "text/x-sqlite", highlightJs: "sql" },
{ default: true, title: "SQLite (Trilium)", mime: "text/x-sqlite;schema=trilium", highlightJs: "sql" },
{ title: "Squirrel", mime: "text/x-squirrel" },
{ title: "sTeX", mime: "text/x-stex" },
{ title: "Stylus", mime: "text/x-styl", highlightJs: "stylus" },
{ default: true, title: "Swift", mime: "text/x-swift" },
{ title: "SystemVerilog", mime: "text/x-systemverilog" },
{ title: "Tcl", mime: "text/x-tcl", highlightJs: "tcl" },
{ title: "Terraform (HCL)", mime: "text/x-hcl", highlightJs: "terraform", highlightJsSource: "libraries", codeMirrorSource: "libraries/codemirror/hcl.js" },
{ title: "Textile", mime: "text/x-textile" },
{ title: "TiddlyWiki ", mime: "text/x-tiddlywiki" },
{ title: "Tiki wiki", mime: "text/tiki" },
{ title: "TOML", mime: "text/x-toml", highlightJs: "ini" },
{ title: "Tornado", mime: "text/x-tornado" },
{ title: "troff", mime: "text/troff" },
{ title: "TTCN", mime: "text/x-ttcn" },
{ title: "TTCN_CFG", mime: "text/x-ttcn-cfg" },
{ title: "Turtle", mime: "text/turtle" },
{ title: "Twig", mime: "text/x-twig", highlightJs: "twig" },
{ title: "TypeScript", mime: "application/typescript", highlightJs: "typescript" },
{ title: "TypeScript-JSX", mime: "text/typescript-jsx" },
{ title: "VB.NET", mime: "text/x-vb", highlightJs: "vbnet" },
{ title: "VBScript", mime: "text/vbscript", highlightJs: "vbscript" },
{ title: "Velocity", mime: "text/velocity" },
{ title: "Verilog", mime: "text/x-verilog", highlightJs: "verilog" },
{ title: "VHDL", mime: "text/x-vhdl", highlightJs: "vhdl" },
{ title: "Vue.js Component", mime: "text/x-vue" },
{ title: "Web IDL", mime: "text/x-webidl" },
{ default: true, title: "XML", mime: "text/xml", highlightJs: "xml" },
{ title: "XQuery", mime: "application/xquery", highlightJs: "xquery" },
{ title: "xu", mime: "text/x-xu" },
{ title: "Yacas", mime: "text/x-yacas" },
{ default: true, title: "YAML", mime: "text/x-yaml", highlightJs: "yaml" },
{ title: "Z80", mime: "text/x-z80" }
]);
/**
* Given a MIME type in the usual format (e.g. `text/csrc`), it returns a MIME type that can be passed down to the CKEditor
* code plugin.
*
* @param mimeType The MIME type to normalize, in the usual format (e.g. `text/c-src`).
* @returns the normalized MIME type (e.g. `text-c-src`).
*/
export function normalizeMimeTypeForCKEditor(mimeType: string) {
return mimeType.toLowerCase().replace(/[\W_]+/g, "-");
}
let byHighlightJsNameMappings: Record<string, MimeTypeDefinition> | null = null;
/**
* Given a Highlight.js language tag (e.g. `css`), it returns a corresponding {@link MimeTypeDefinition} if found.
*
* If there are multiple {@link MimeTypeDefinition}s for the language tag, then only the first one is retrieved. For example for `javascript`, the "JS frontend" mime type is returned.
*
* @param highlightJsName a language tag.
* @returns the corresponding {@link MimeTypeDefinition} if found, or `undefined` otherwise.
*/
export function getMimeTypeFromHighlightJs(highlightJsName: string) {
if (!byHighlightJsNameMappings) {
byHighlightJsNameMappings = {};
for (const mimeType of MIME_TYPES_DICT) {
if (mimeType.highlightJs && !byHighlightJsNameMappings[mimeType.highlightJs]) {
byHighlightJsNameMappings[mimeType.highlightJs] = mimeType;
}
}
}
return byHighlightJsNameMappings[highlightJsName];
}
+2 -1
View File
@@ -14,7 +14,8 @@ const noteTypes = [
{ type: "launcher", defaultMime: "" },
{ type: "doc", defaultMime: "" },
{ type: "contentWidget", defaultMime: "" },
{ type: "mindMap", defaultMime: "application/json" }
{ type: "mindMap", defaultMime: "application/json" },
{ type: "geoMap", defaultMime: "application/json" }
];
function getDefaultMimeForNoteType(typeName: string) {
-93
View File
@@ -1,93 +0,0 @@
/**
* Usage: node src/tools/generate_document.js 1000
* will create 1000 new notes and some clones into the current document.db
*/
import sqlInit from "../services/sql_init.js";
import noteService from "../services/notes.js";
import attributeService from "../services/attributes.js";
import cls from "../services/cls.js";
import cloningService from "../services/cloning.js";
import loremIpsum from "lorem-ipsum";
import "../becca/entity_constructor.js";
const noteCount = parseInt(process.argv[2]);
if (!noteCount) {
console.error(`Please enter number of notes as program parameter.`);
process.exit(1);
}
const notes = ["root"];
function getRandomNoteId() {
const index = Math.floor(Math.random() * notes.length);
return notes[index];
}
async function start() {
for (let i = 0; i < noteCount; i++) {
const title = loremIpsum.loremIpsum({
count: 1,
units: "sentences",
sentenceLowerBound: 1,
sentenceUpperBound: 10
});
const paragraphCount = Math.floor(Math.random() * Math.random() * 100);
const content = loremIpsum.loremIpsum({
count: paragraphCount,
units: "paragraphs",
sentenceLowerBound: 1,
sentenceUpperBound: 15,
paragraphLowerBound: 3,
paragraphUpperBound: 10,
format: "html"
});
const { note } = noteService.createNewNote({
parentNoteId: getRandomNoteId(),
title,
content,
type: "text"
});
console.log(`Created note ${i}: ${title}`);
if (Math.random() < 0.04) {
const noteIdToClone = note.noteId;
const parentNoteId = getRandomNoteId();
const prefix = Math.random() > 0.8 ? "prefix" : "";
const result = await cloningService.cloneNoteToBranch(noteIdToClone, parentNoteId, prefix);
console.log(`Cloning ${i}:`, result.success ? "succeeded" : "FAILED");
}
// does not have to be for the current note
await attributeService.createAttribute({
noteId: getRandomNoteId(),
type: "label",
name: "label",
value: "value",
isInheritable: Math.random() > 0.1 // 10% are inheritable
});
await attributeService.createAttribute({
noteId: getRandomNoteId(),
type: "relation",
name: "relation",
value: getRandomNoteId(),
isInheritable: Math.random() > 0.1 // 10% are inheritable
});
note.saveRevision();
notes.push(note.noteId);
}
process.exit(0);
}
sqlInit.dbReady.then(cls.wrap(start));