mirror of
https://github.com/zadam/trilium.git
synced 2025-11-16 02:05:53 +01:00
chore(nx): move all monorepo-style in subfolder for processing
This commit is contained in:
@@ -1,93 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import server from "../../services/server.js";
|
||||
import ws from "../../services/ws.js";
|
||||
import appContext, { type EventData } from "../../components/app_context.js";
|
||||
import toastService from "../../services/toast.js";
|
||||
import treeService from "../../services/tree.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import keyboardActionService from "../../services/keyboard_actions.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="code-buttons-widget">
|
||||
<style>
|
||||
.code-buttons-widget {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button data-trigger-command="runActiveNote" class="execute-button floating-button btn" title="${t("code_buttons.execute_button_title")}">
|
||||
<span class="bx bx-play"></span>
|
||||
</button>
|
||||
|
||||
<button class="trilium-api-docs-button floating-button btn" title="${t("code_buttons.trilium_api_docs_button_title")}">
|
||||
<span class="bx bx-help-circle"></span>
|
||||
</button>
|
||||
|
||||
<button class="save-to-note-button floating-button btn" title="${t("code_buttons.save_to_note_button_title")}">
|
||||
<span class="bx bx-save"></span>
|
||||
</button>
|
||||
</div>`;
|
||||
|
||||
// TODO: Deduplicate with server.
|
||||
interface SaveSqlConsoleResponse {
|
||||
notePath: string;
|
||||
}
|
||||
|
||||
export default class CodeButtonsWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $openTriliumApiDocsButton!: JQuery<HTMLElement>;
|
||||
private $executeButton!: JQuery<HTMLElement>;
|
||||
private $saveToNoteButton!: JQuery<HTMLElement>;
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && this.note && (this.note.mime.startsWith("application/javascript") || this.note.mime === "text/x-sqlite;schema=trilium");
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$openTriliumApiDocsButton = this.$widget.find(".trilium-api-docs-button");
|
||||
this.$openTriliumApiDocsButton.on("click", () => {
|
||||
toastService.showMessage(t("code_buttons.opening_api_docs_message"));
|
||||
|
||||
if (this.note?.mime.endsWith("frontend")) {
|
||||
window.open("https://zadam.github.io/trilium/frontend_api/FrontendScriptApi.html", "_blank");
|
||||
} else {
|
||||
window.open("https://zadam.github.io/trilium/backend_api/BackendScriptApi.html", "_blank");
|
||||
}
|
||||
});
|
||||
|
||||
this.$executeButton = this.$widget.find(".execute-button");
|
||||
this.$saveToNoteButton = this.$widget.find(".save-to-note-button");
|
||||
this.$saveToNoteButton.on("click", async () => {
|
||||
const { notePath } = await server.post<SaveSqlConsoleResponse>("special-notes/save-sql-console", { sqlConsoleNoteId: this.noteId });
|
||||
|
||||
await ws.waitForMaxKnownEntityChangeId();
|
||||
|
||||
await appContext.tabManager.getActiveContext()?.setNote(notePath);
|
||||
|
||||
toastService.showMessage(t("code_buttons.sql_console_saved_message", { notePath: await treeService.getNotePathTitle(notePath) }));
|
||||
});
|
||||
|
||||
keyboardActionService.updateDisplayedShortcuts(this.$widget);
|
||||
|
||||
this.contentSized();
|
||||
|
||||
super.doRender();
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
this.$executeButton.toggle(note.mime.startsWith("application/javascript") || note.mime === "text/x-sqlite;schema=trilium");
|
||||
|
||||
this.$saveToNoteButton.toggle(note.mime === "text/x-sqlite;schema=trilium" && note.isHiddenCompletely());
|
||||
|
||||
this.$openTriliumApiDocsButton.toggle(note.mime.startsWith("application/javascript;env="));
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent({ noteId }: EventData<"noteTypeMimeChanged">) {
|
||||
if (this.isNote(noteId)) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import imageService from "../../services/image.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<button type="button"
|
||||
class="copy-image-reference-button"
|
||||
title="${t("copy_image_reference_button.button_title")}">
|
||||
<span class="bx bx-copy"></span>
|
||||
|
||||
<div class="hidden-image-copy"></div>
|
||||
</button>`;
|
||||
|
||||
export default class CopyImageReferenceButton extends NoteContextAwareWidget {
|
||||
|
||||
private $hiddenImageCopy!: JQuery<HTMLElement>;
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && ["mermaid", "canvas", "mindMap"].includes(this.note?.type ?? "") && this.note?.isContentAvailable() && this.noteContext?.viewScope?.viewMode === "default";
|
||||
}
|
||||
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
this.$widget = $(TPL);
|
||||
this.$hiddenImageCopy = this.$widget.find(".hidden-image-copy");
|
||||
|
||||
this.$widget.on("click", () => {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$hiddenImageCopy.empty().append($("<img>").attr("src", utils.createImageSrcUrl(this.note)));
|
||||
|
||||
imageService.copyImageReferenceToClipboard(this.$hiddenImageCopy);
|
||||
|
||||
this.$hiddenImageCopy.empty();
|
||||
});
|
||||
this.contentSized();
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import OnClickButtonWidget from "../buttons/onclick_button.js";
|
||||
import appContext from "../../components/app_context.js";
|
||||
import attributeService from "../../services/attributes.js";
|
||||
import protectedSessionHolder from "../../services/protected_session_holder.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import LoadResults from "../../services/load_results.js";
|
||||
import type { AttributeRow } from "../../services/load_results.js";
|
||||
import FNote from "../../entities/fnote.js";
|
||||
|
||||
export default class EditButton extends OnClickButtonWidget {
|
||||
isEnabled(): boolean {
|
||||
return Boolean(super.isEnabled() && this.note && this.noteContext?.viewScope?.viewMode === "default");
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.icon("bx-pencil")
|
||||
.title(t("edit_button.edit_this_note"))
|
||||
.titlePlacement("bottom")
|
||||
.onClick((widget) => {
|
||||
if (this.noteContext?.viewScope) {
|
||||
this.noteContext.viewScope.readOnlyTemporarilyDisabled = true;
|
||||
appContext.triggerEvent("readOnlyTemporarilyDisabled", { noteContext: this.noteContext });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote): Promise<void> {
|
||||
if (note.isProtected && !protectedSessionHolder.isProtectedSessionAvailable()) {
|
||||
this.toggleInt(false);
|
||||
} else {
|
||||
// prevent flickering by assuming hidden before async operation
|
||||
this.toggleInt(false);
|
||||
|
||||
const wasVisible = this.isVisible();
|
||||
|
||||
// can't do this in isEnabled() since isReadOnly is async
|
||||
const isReadOnly = await this.noteContext?.isReadOnly();
|
||||
this.toggleInt(Boolean(isReadOnly));
|
||||
|
||||
// make the edit button stand out on the first display, otherwise
|
||||
// it's difficult to notice that the note is readonly
|
||||
if (this.isVisible() && !wasVisible && this.$widget) {
|
||||
this.$widget.addClass("bx-tada bx-lg");
|
||||
|
||||
setTimeout(() => {
|
||||
this.$widget?.removeClass("bx-tada bx-lg");
|
||||
}, 1700);
|
||||
}
|
||||
}
|
||||
|
||||
await super.refreshWithNote(note);
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: { loadResults: LoadResults }): void {
|
||||
if (loadResults.getAttributeRows().find((attr: AttributeRow) =>
|
||||
attr.type === "label" &&
|
||||
attr.name?.toLowerCase().includes("readonly") &&
|
||||
this.note &&
|
||||
attributeService.isAffecting(attr, this.note)
|
||||
)) {
|
||||
if (this.noteContext?.viewScope) {
|
||||
this.noteContext.viewScope.readOnlyTemporarilyDisabled = false;
|
||||
}
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
readOnlyTemporarilyDisabledEvent() {
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
async noteTypeMimeChangedEvent({ noteId }: { noteId: string }): Promise<void> {
|
||||
if (this.isNote(noteId)) {
|
||||
await this.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type BasicWidget from "../basic_widget.js";
|
||||
|
||||
/*
|
||||
* Note:
|
||||
*
|
||||
* For floating button widgets that require content to overflow, the has-overflow CSS class should
|
||||
* be applied to the root element of the widget. Additionally, this root element may need to
|
||||
* properly handle rounded corners, as defined by the --border-radius CSS variable.
|
||||
*/
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="floating-buttons no-print">
|
||||
<style>
|
||||
.floating-buttons {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.floating-buttons-children,
|
||||
.show-floating-buttons {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.note-split.rtl .floating-buttons-children,
|
||||
.note-split.rtl .show-floating-buttons {
|
||||
right: unset;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.note-split.rtl .close-floating-buttons {
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.note-split.rtl .close-floating-buttons,
|
||||
.note-split.rtl .show-floating-buttons {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.type-canvas .floating-buttons-children {
|
||||
top: 70px;
|
||||
}
|
||||
|
||||
.type-canvas .floating-buttons-children > * {
|
||||
--border-radius: 0; /* Overridden by themes */
|
||||
}
|
||||
|
||||
.floating-buttons-children > *:not(.hidden-int):not(.no-content-hidden) {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.floating-buttons-children > *:not(.has-overflow) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.floating-buttons-children > button, .floating-buttons-children .floating-button {
|
||||
font-size: 150%;
|
||||
padding: 5px 10px 4px 10px;
|
||||
width: 40px;
|
||||
cursor: pointer;
|
||||
color: var(--button-text-color);
|
||||
background: var(--button-background-color);
|
||||
border-radius: var(--button-border-radius);
|
||||
border: 1px solid transparent;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.floating-buttons-children > button:hover, .floating-buttons-children .floating-button:hover {
|
||||
text-decoration: none;
|
||||
border-color: var(--button-border-color);
|
||||
}
|
||||
|
||||
.floating-buttons .floating-buttons-children.temporarily-hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="floating-buttons-children"></div>
|
||||
|
||||
<!-- Show button that displays floating button after click on close button -->
|
||||
<div class="show-floating-buttons">
|
||||
<style>
|
||||
.floating-buttons-children.temporarily-hidden+.show-floating-buttons {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.floating-buttons-children:not(.temporarily-hidden)+.show-floating-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.show-floating-buttons {
|
||||
/* display: none;*/
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
|
||||
.show-floating-buttons-button {
|
||||
border: 1px solid transparent;
|
||||
color: var(--button-text-color);
|
||||
padding: 6px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.show-floating-buttons-button:hover {
|
||||
border: 1px solid var(--button-border-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" class="show-floating-buttons-button btn bx bx-chevrons-left"
|
||||
title="${t("show_floating_buttons_button.button_title")}"></button>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class FloatingButtons extends NoteContextAwareWidget {
|
||||
|
||||
private $children!: JQuery<HTMLElement>;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$children = this.$widget.find(".floating-buttons-children");
|
||||
|
||||
for (const widget of this.children) {
|
||||
if ("render" in widget) {
|
||||
this.$children.append((widget as BasicWidget).render());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
this.toggle(true);
|
||||
this.$widget.find(".show-floating-buttons-button").on("click", () => this.toggle(true));
|
||||
}
|
||||
|
||||
toggle(show: boolean) {
|
||||
this.$widget.find(".floating-buttons-children").toggleClass("temporarily-hidden", !show);
|
||||
}
|
||||
|
||||
hideFloatingButtonsCommand() {
|
||||
this.toggle(false);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = /*html*/`\
|
||||
<div class="geo-map-buttons">
|
||||
<style>
|
||||
.geo-map-buttons {
|
||||
contain: none;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.leaflet-pane {
|
||||
z-index: 50;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button"
|
||||
class="geo-map-create-child-note floating-button btn bx bx-plus-circle"
|
||||
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 }));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { byBookType, byNoteType } from "./help_button.js";
|
||||
import fs from "fs";
|
||||
import type { HiddenSubtreeItem } from "@triliumnext/commons";
|
||||
|
||||
describe("Help button", () => {
|
||||
it("All help notes are accessible", () => {
|
||||
function getNoteIds(item: HiddenSubtreeItem | HiddenSubtreeItem[]): string[] {
|
||||
const items: (string | string[])[] = [];
|
||||
|
||||
if ("id" in item && item.id) {
|
||||
items.push(item.id);
|
||||
}
|
||||
|
||||
const subitems = (Array.isArray(item) ? item : item.children);
|
||||
for (const child of subitems ?? []) {
|
||||
items.push(getNoteIds(child as (HiddenSubtreeItem | HiddenSubtreeItem[])));
|
||||
}
|
||||
return items.flat();
|
||||
}
|
||||
|
||||
const allHelpNotes = [
|
||||
...Object.values(byNoteType),
|
||||
...Object.values(byBookType)
|
||||
].filter((noteId) => noteId) as string[];
|
||||
|
||||
const meta: HiddenSubtreeItem[] = JSON.parse(fs.readFileSync("src/public/app/doc_notes/en/User Guide/!!!meta.json", "utf-8"));
|
||||
const allNoteIds = new Set(getNoteIds(meta));
|
||||
|
||||
for (const helpNote of allHelpNotes) {
|
||||
if (!allNoteIds.has(`_help_${helpNote}`)) {
|
||||
expect.fail(`Help note with ID ${helpNote} does not exist in the in-app help.`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,75 +0,0 @@
|
||||
import appContext, { type EventData } from "../../components/app_context.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import type { NoteType } from "../../entities/fnote.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import type { ViewScope } from "../../services/link.js";
|
||||
import type { ViewTypeOptions } from "../../services/note_list_renderer.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<button class="open-contextual-help-button" title="${t("help-button.title")}">
|
||||
<span class="bx bx-help-circle"></span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
export const byNoteType: Record<Exclude<NoteType, "book">, string | null> = {
|
||||
canvas: null,
|
||||
code: null,
|
||||
contentWidget: null,
|
||||
doc: null,
|
||||
file: null,
|
||||
geoMap: "81SGnPGMk7Xc",
|
||||
image: null,
|
||||
launcher: null,
|
||||
mermaid: null,
|
||||
mindMap: null,
|
||||
noteMap: null,
|
||||
relationMap: null,
|
||||
render: null,
|
||||
search: null,
|
||||
text: null,
|
||||
webView: null,
|
||||
aiChat: null
|
||||
};
|
||||
|
||||
export const byBookType: Record<ViewTypeOptions, string | null> = {
|
||||
list: null,
|
||||
grid: null,
|
||||
calendar: "xWbu3jpNWapp"
|
||||
};
|
||||
|
||||
export default class ContextualHelpButton extends NoteContextAwareWidget {
|
||||
|
||||
isEnabled() {
|
||||
if (!super.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!ContextualHelpButton.#getUrlToOpen(this.note);
|
||||
}
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
}
|
||||
|
||||
static #getUrlToOpen(note: FNote | null | undefined) {
|
||||
if (note && note.type !== "book" && byNoteType[note.type]) {
|
||||
return byNoteType[note.type];
|
||||
} else if (note?.hasLabel("calendarRoot")) {
|
||||
return "l0tKav7yLHGF";
|
||||
} else if (note && note.type === "book") {
|
||||
return byBookType[note.getAttributeValue("label", "viewType") as ViewTypeOptions ?? ""]
|
||||
}
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote | null | undefined): Promise<void> {
|
||||
this.$widget.attr("data-in-app-help", ContextualHelpButton.#getUrlToOpen(this.note) ?? "");
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (this.note?.type === "book" && loadResults.getAttributeRows().find((attr) => attr.noteId === this.noteId && attr.name === "viewType")) {
|
||||
this.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="close-floating-buttons">
|
||||
<style>
|
||||
.close-floating-buttons {
|
||||
display: none;
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
|
||||
/* conditionally display close button if there's some other button visible */
|
||||
.floating-buttons *:not(.hidden-int):not(.hidden-no-content) ~ .close-floating-buttons {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.close-floating-buttons-button {
|
||||
border: 1px solid transparent;
|
||||
color: var(--button-text-color);
|
||||
padding: 6px;
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
.close-floating-buttons-button:hover {
|
||||
border: 1px solid var(--button-border-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button"
|
||||
class="close-floating-buttons-button btn bx bx-chevrons-right"
|
||||
title="${t("hide_floating_buttons_button.button_title")}"></button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
export default class HideFloatingButtonsButton extends NoteContextAwareWidget {
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on("click", () => this.triggerCommand("hideFloatingButtons"));
|
||||
this.contentSized();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<button type="button"
|
||||
class="export-svg-button"
|
||||
title="${t("png_export_button.button_title")}">
|
||||
<span class="bx bxs-file-png"></span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
export default class PngExportButton extends NoteContextAwareWidget {
|
||||
isEnabled() {
|
||||
return super.isEnabled() && ["mermaid", "mindMap"].includes(this.note?.type ?? "") && this.note?.isContentAvailable() && this.noteContext?.viewScope?.viewMode === "default";
|
||||
}
|
||||
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on("click", () => this.triggerEvent("exportPng", { ntxId: this.ntxId }));
|
||||
this.contentSized();
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import appContext from "../../components/app_context.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import OnClickButtonWidget from "../buttons/onclick_button.js";
|
||||
|
||||
export default class RefreshButton extends OnClickButtonWidget {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this
|
||||
.title(t("backend_log.refresh"))
|
||||
.icon("bx-refresh")
|
||||
.onClick(() => this.triggerEvent("refreshData", { ntxId: this.noteContext?.ntxId }))
|
||||
}
|
||||
|
||||
isEnabled(): boolean | null | undefined {
|
||||
return super.isEnabled()
|
||||
&& this.note?.noteId === "_backendLog"
|
||||
&& this.noteContext?.viewScope?.viewMode === "default";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="relation-map-buttons">
|
||||
<style>
|
||||
.relation-map-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
export default class RelationMapButtons extends NoteContextAwareWidget {
|
||||
|
||||
private $createChildNote!: JQuery<HTMLElement>;
|
||||
private $zoomInButton!: JQuery<HTMLElement>;
|
||||
private $zoomOutButton!: JQuery<HTMLElement>;
|
||||
private $resetPanZoomButton!: JQuery<HTMLElement>;
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled() && this.note?.type === "relationMap";
|
||||
}
|
||||
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
this.$widget = $(TPL);
|
||||
this.$createChildNote = this.$widget.find(".relation-map-create-child-note");
|
||||
this.$zoomInButton = this.$widget.find(".relation-map-zoom-in");
|
||||
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 }));
|
||||
|
||||
this.$zoomInButton.on("click", () => this.triggerEvent("relationMapResetZoomIn", { ntxId: this.ntxId }));
|
||||
this.$zoomOutButton.on("click", () => this.triggerEvent("relationMapResetZoomOut", { ntxId: this.ntxId }));
|
||||
this.contentSized();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { t } from "../../services/i18n.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<button type="button"
|
||||
class="export-svg-button"
|
||||
title="${t("svg_export_button.button_title")}">
|
||||
<span class="bx bxs-file-image"></span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
export default class SvgExportButton extends NoteContextAwareWidget {
|
||||
isEnabled() {
|
||||
return super.isEnabled() && ["mermaid", "mindMap"].includes(this.note?.type ?? "") && this.note?.isContentAvailable() && this.noteContext?.viewScope?.viewMode === "default";
|
||||
}
|
||||
|
||||
doRender() {
|
||||
super.doRender();
|
||||
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on("click", () => this.triggerEvent("exportSvg", { ntxId: this.ntxId }));
|
||||
this.contentSized();
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import type { EventData } from "../../components/app_context.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import options from "../../services/options.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<button type="button"
|
||||
class="switch-layout-button">
|
||||
<span class="bx"></span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
export default class SwitchSplitOrientationButton extends NoteContextAwareWidget {
|
||||
isEnabled() {
|
||||
return super.isEnabled()
|
||||
&& ["mermaid"].includes(this.note?.type ?? "")
|
||||
&& this.note?.isContentAvailable()
|
||||
&& !this.note?.hasLabel("readOnly")
|
||||
&& this.noteContext?.viewScope?.viewMode === "default";
|
||||
}
|
||||
|
||||
doRender(): void {
|
||||
super.doRender();
|
||||
this.$widget = $(TPL);
|
||||
this.$widget.on("click", () => {
|
||||
const currentOrientation = options.get("splitEditorOrientation");
|
||||
options.save("splitEditorOrientation", toggleOrientation(currentOrientation));
|
||||
});
|
||||
this.#adjustIcon();
|
||||
this.contentSized();
|
||||
}
|
||||
|
||||
#adjustIcon() {
|
||||
const currentOrientation = options.get("splitEditorOrientation");
|
||||
const upcomingOrientation = toggleOrientation(currentOrientation);
|
||||
const $icon = this.$widget.find("span.bx");
|
||||
$icon
|
||||
.toggleClass("bxs-dock-bottom", upcomingOrientation === "vertical")
|
||||
.toggleClass("bxs-dock-left", upcomingOrientation === "horizontal");
|
||||
|
||||
if (upcomingOrientation === "vertical") {
|
||||
this.$widget.attr("title", t("switch_layout_button.title_vertical"));
|
||||
} else {
|
||||
this.$widget.attr("title", t("switch_layout_button.title_horizontal"));
|
||||
}
|
||||
}
|
||||
|
||||
entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
|
||||
if (loadResults.isOptionReloaded("splitEditorOrientation")) {
|
||||
this.#adjustIcon();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function toggleOrientation(orientation: string) {
|
||||
if (orientation === "horizontal") {
|
||||
return "vertical";
|
||||
} else {
|
||||
return "horizontal";
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
import attributes from "../../services/attributes.js";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import OnClickButtonWidget from "../buttons/onclick_button.js";
|
||||
|
||||
export default class ToggleReadOnlyButton extends OnClickButtonWidget {
|
||||
|
||||
private isReadOnly?: boolean;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this
|
||||
.title(() => this.isReadOnly ? t("toggle_read_only_button.unlock-editing") : t("toggle_read_only_button.lock-editing"))
|
||||
.titlePlacement("bottom")
|
||||
.icon(() => this.isReadOnly ? "bx-lock-open-alt" : "bx-lock-alt")
|
||||
.onClick(() => this.#toggleReadOnly());
|
||||
}
|
||||
|
||||
#toggleReadOnly() {
|
||||
if (!this.noteId || !this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isReadOnly) {
|
||||
attributes.removeOwnedLabelByName(this.note, "readOnly");
|
||||
} else {
|
||||
attributes.setLabel(this.noteId, "readOnly");
|
||||
}
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote | null | undefined) {
|
||||
const isReadOnly = !!note?.hasLabel("readOnly");
|
||||
|
||||
if (isReadOnly !== this.isReadOnly) {
|
||||
this.isReadOnly = isReadOnly;
|
||||
this.refreshIcon();
|
||||
}
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return super.isEnabled()
|
||||
&& this.note?.type === "mermaid"
|
||||
&& this.note?.isContentAvailable()
|
||||
&& this.noteContext?.viewScope?.viewMode === "default";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
/**
|
||||
* !!! Filename is intentionally mangled, because some adblockers don't like the word "backlinks".
|
||||
*/
|
||||
|
||||
import { t } from "../../services/i18n.js";
|
||||
import NoteContextAwareWidget from "../note_context_aware_widget.js";
|
||||
import linkService from "../../services/link.js";
|
||||
import server from "../../services/server.js";
|
||||
import froca from "../../services/froca.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
|
||||
const TPL = /*html*/`
|
||||
<div class="backlinks-widget has-overflow">
|
||||
<style>
|
||||
.backlinks-widget {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.backlinks-ticker {
|
||||
border-radius: 10px;
|
||||
border-color: var(--main-border-color);
|
||||
background-color: var(--more-accented-background-color);
|
||||
padding: 4px 10px 4px 10px;
|
||||
opacity: 90%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.backlinks-count {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.backlinks-items {
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
right: 10px;
|
||||
width: 400px;
|
||||
border-radius: 10px;
|
||||
background-color: var(--accented-background-color);
|
||||
color: var(--main-text-color);
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.backlink-excerpt {
|
||||
border-left: 2px solid var(--main-border-color);
|
||||
padding-left: 10px;
|
||||
opacity: 80%;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.backlink-excerpt .backlink-link { /* the actual backlink */
|
||||
font-weight: bold;
|
||||
background-color: yellow;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="backlinks-ticker">
|
||||
<span class="backlinks-count"></span>
|
||||
</div>
|
||||
|
||||
<div class="backlinks-items dropdown-menu" style="display: none;"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// TODO: Deduplicate with server
|
||||
interface Backlink {
|
||||
noteId: string;
|
||||
relationName?: string;
|
||||
excerpts?: string[];
|
||||
}
|
||||
|
||||
export default class BacklinksWidget extends NoteContextAwareWidget {
|
||||
|
||||
private $count!: JQuery<HTMLElement>;
|
||||
private $items!: JQuery<HTMLElement>;
|
||||
private $ticker!: JQuery<HTMLElement>;
|
||||
|
||||
doRender() {
|
||||
this.$widget = $(TPL);
|
||||
this.$count = this.$widget.find(".backlinks-count");
|
||||
this.$items = this.$widget.find(".backlinks-items");
|
||||
this.$ticker = this.$widget.find(".backlinks-ticker");
|
||||
|
||||
this.$count.on("click", () => {
|
||||
this.$items.toggle();
|
||||
this.$items.css("max-height", ($(window).height() ?? 0) - (this.$items.offset()?.top ?? 0) - 10);
|
||||
|
||||
if (this.$items.is(":visible")) {
|
||||
this.renderBacklinks();
|
||||
}
|
||||
});
|
||||
|
||||
this.contentSized();
|
||||
}
|
||||
|
||||
async refreshWithNote(note: FNote) {
|
||||
this.clearItems();
|
||||
|
||||
if (this.noteContext?.viewScope?.viewMode !== "default") {
|
||||
this.toggle(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// can't use froca since that would count only relations from loaded notes
|
||||
// TODO: Deduplicate response type
|
||||
const resp = await server.get<{ count: number }>(`note-map/${this.noteId}/backlink-count`);
|
||||
|
||||
if (!resp || !resp.count) {
|
||||
this.toggle(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggle(true);
|
||||
this.$count.text(
|
||||
// i18next plural
|
||||
`${t("zpetne_odkazy.backlink", { count: resp.count })}`
|
||||
);
|
||||
}
|
||||
|
||||
toggle(show: boolean) {
|
||||
this.$widget.toggleClass("hidden-no-content", !show)
|
||||
.toggleClass("visible", !!show);
|
||||
}
|
||||
|
||||
clearItems() {
|
||||
this.$items.empty().hide();
|
||||
}
|
||||
|
||||
async renderBacklinks() {
|
||||
if (!this.note) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$items.empty();
|
||||
|
||||
const backlinks = await server.get<Backlink[]>(`note-map/${this.noteId}/backlinks`);
|
||||
|
||||
if (!backlinks.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
await froca.getNotes(backlinks.map((bl) => bl.noteId)); // prefetch all
|
||||
|
||||
for (const backlink of backlinks) {
|
||||
const $item = $("<div>");
|
||||
|
||||
$item.append(
|
||||
await linkService.createLink(backlink.noteId, {
|
||||
showNoteIcon: true,
|
||||
showNotePath: true,
|
||||
showTooltip: false
|
||||
})
|
||||
);
|
||||
|
||||
if (backlink.relationName) {
|
||||
$item.append($("<p>").text(`${t("zpetne_odkazy.relation")}: ${backlink.relationName}`));
|
||||
} else {
|
||||
$item.append(...(backlink.excerpts ?? []));
|
||||
}
|
||||
|
||||
this.$items.append($item);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user