mirror of
https://github.com/TriliumNext/Trilium.git
synced 2025-12-11 19:44:00 -06:00
Port toast to React (#7975)
This commit is contained in:
commit
f16441bba4
@ -27,6 +27,7 @@
|
|||||||
"@mermaid-js/layout-elk": "0.2.0",
|
"@mermaid-js/layout-elk": "0.2.0",
|
||||||
"@mind-elixir/node-menu": "5.0.1",
|
"@mind-elixir/node-menu": "5.0.1",
|
||||||
"@popperjs/core": "2.11.8",
|
"@popperjs/core": "2.11.8",
|
||||||
|
"@preact/signals": "2.5.1",
|
||||||
"@triliumnext/ckeditor5": "workspace:*",
|
"@triliumnext/ckeditor5": "workspace:*",
|
||||||
"@triliumnext/codemirror": "workspace:*",
|
"@triliumnext/codemirror": "workspace:*",
|
||||||
"@triliumnext/commons": "workspace:*",
|
"@triliumnext/commons": "workspace:*",
|
||||||
|
|||||||
@ -22,6 +22,7 @@ bundleService.getWidgetBundlesByParent().then(async (widgetBundles) => {
|
|||||||
appContext.setLayout(new DesktopLayout(widgetBundles));
|
appContext.setLayout(new DesktopLayout(widgetBundles));
|
||||||
appContext.start().catch((e) => {
|
appContext.start().catch((e) => {
|
||||||
toastService.showPersistent({
|
toastService.showPersistent({
|
||||||
|
id: "critical-error",
|
||||||
title: t("toast.critical-error.title"),
|
title: t("toast.critical-error.title"),
|
||||||
icon: "alert",
|
icon: "alert",
|
||||||
message: t("toast.critical-error.message", { message: e.message })
|
message: t("toast.critical-error.message", { message: e.message })
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import InfoDialog from "../widgets/dialogs/info.js";
|
|||||||
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
|
import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js";
|
||||||
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
|
import CallToActionDialog from "../widgets/dialogs/call_to_action.jsx";
|
||||||
import PopupEditorDialog from "../widgets/dialogs/PopupEditor.jsx";
|
import PopupEditorDialog from "../widgets/dialogs/PopupEditor.jsx";
|
||||||
|
import ToastContainer from "../widgets/Toast.jsx";
|
||||||
|
|
||||||
export function applyModals(rootContainer: RootContainer) {
|
export function applyModals(rootContainer: RootContainer) {
|
||||||
rootContainer
|
rootContainer
|
||||||
@ -50,5 +51,6 @@ export function applyModals(rootContainer: RootContainer) {
|
|||||||
.child(<PromptDialog />)
|
.child(<PromptDialog />)
|
||||||
.child(<IncorrectCpuArchDialog />)
|
.child(<IncorrectCpuArchDialog />)
|
||||||
.child(<PopupEditorDialog />)
|
.child(<PopupEditorDialog />)
|
||||||
.child(<CallToActionDialog />);
|
.child(<CallToActionDialog />)
|
||||||
|
.child(<ToastContainer />)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import toastService, { type ToastOptions } from "./toast.js";
|
import toastService, { type ToastOptionsWithRequiredId } from "./toast.js";
|
||||||
import froca from "./froca.js";
|
import froca from "./froca.js";
|
||||||
import hoistedNoteService from "./hoisted_note.js";
|
import hoistedNoteService from "./hoisted_note.js";
|
||||||
import ws from "./ws.js";
|
import ws from "./ws.js";
|
||||||
@ -195,11 +195,11 @@ function filterRootNote(branchIds: string[]) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeToast(id: string, message: string): ToastOptions {
|
function makeToast(id: string, message: string): ToastOptionsWithRequiredId {
|
||||||
return {
|
return {
|
||||||
id: id,
|
id,
|
||||||
title: t("branches.delete-status"),
|
title: t("branches.delete-status"),
|
||||||
message: message,
|
message,
|
||||||
icon: "trash"
|
icon: "trash"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -216,7 +216,7 @@ ws.subscribeToMessages(async (message) => {
|
|||||||
toastService.showPersistent(makeToast(message.taskId, t("branches.delete-notes-in-progress", { count: message.progressCount })));
|
toastService.showPersistent(makeToast(message.taskId, t("branches.delete-notes-in-progress", { count: message.progressCount })));
|
||||||
} else if (message.type === "taskSucceeded") {
|
} else if (message.type === "taskSucceeded") {
|
||||||
const toast = makeToast(message.taskId, t("branches.delete-finished-successfully"));
|
const toast = makeToast(message.taskId, t("branches.delete-finished-successfully"));
|
||||||
toast.closeAfter = 5000;
|
toast.timeout = 5000;
|
||||||
|
|
||||||
toastService.showPersistent(toast);
|
toastService.showPersistent(toast);
|
||||||
}
|
}
|
||||||
@ -234,7 +234,7 @@ ws.subscribeToMessages(async (message) => {
|
|||||||
toastService.showPersistent(makeToast(message.taskId, t("branches.undeleting-notes-in-progress", { count: message.progressCount })));
|
toastService.showPersistent(makeToast(message.taskId, t("branches.undeleting-notes-in-progress", { count: message.progressCount })));
|
||||||
} else if (message.type === "taskSucceeded") {
|
} else if (message.type === "taskSucceeded") {
|
||||||
const toast = makeToast(message.taskId, t("branches.undeleting-notes-finished-successfully"));
|
const toast = makeToast(message.taskId, t("branches.undeleting-notes-finished-successfully"));
|
||||||
toast.closeAfter = 5000;
|
toast.timeout = 5000;
|
||||||
|
|
||||||
toastService.showPersistent(toast);
|
toastService.showPersistent(toast);
|
||||||
}
|
}
|
||||||
@ -242,7 +242,7 @@ ws.subscribeToMessages(async (message) => {
|
|||||||
|
|
||||||
async function cloneNoteToBranch(childNoteId: string, parentBranchId: string, prefix?: string) {
|
async function cloneNoteToBranch(childNoteId: string, parentBranchId: string, prefix?: string) {
|
||||||
const resp = await server.put<Response>(`notes/${childNoteId}/clone-to-branch/${parentBranchId}`, {
|
const resp = await server.put<Response>(`notes/${childNoteId}/clone-to-branch/${parentBranchId}`, {
|
||||||
prefix: prefix
|
prefix
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
@ -252,7 +252,7 @@ async function cloneNoteToBranch(childNoteId: string, parentBranchId: string, pr
|
|||||||
|
|
||||||
async function cloneNoteToParentNote(childNoteId: string, parentNoteId: string, prefix?: string) {
|
async function cloneNoteToParentNote(childNoteId: string, parentNoteId: string, prefix?: string) {
|
||||||
const resp = await server.put<Response>(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, {
|
const resp = await server.put<Response>(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, {
|
||||||
prefix: prefix
|
prefix
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!resp.success) {
|
if (!resp.success) {
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export async function executeBundle(bundle: Bundle, originEntity?: Entity | null
|
|||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
const note = await froca.getNote(bundle.noteId);
|
const note = await froca.getNote(bundle.noteId);
|
||||||
toastService.showPersistent({
|
toastService.showPersistent({
|
||||||
|
id: `custom-script-failure-${note?.noteId}`,
|
||||||
title: t("toast.bundle-error.title"),
|
title: t("toast.bundle-error.title"),
|
||||||
icon: "bx bx-error-circle",
|
icon: "bx bx-error-circle",
|
||||||
message: t("toast.bundle-error.message", {
|
message: t("toast.bundle-error.message", {
|
||||||
@ -108,6 +109,7 @@ async function getWidgetBundlesByParent() {
|
|||||||
const noteId = bundle.noteId;
|
const noteId = bundle.noteId;
|
||||||
const note = await froca.getNote(noteId);
|
const note = await froca.getNote(noteId);
|
||||||
toastService.showPersistent({
|
toastService.showPersistent({
|
||||||
|
id: `custom-script-failure-${noteId}`,
|
||||||
title: t("toast.bundle-error.title"),
|
title: t("toast.bundle-error.title"),
|
||||||
icon: "bx bx-error-circle",
|
icon: "bx bx-error-circle",
|
||||||
message: t("toast.bundle-error.message", {
|
message: t("toast.bundle-error.message", {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import toastService, { type ToastOptions } from "./toast.js";
|
import toastService, { type ToastOptionsWithRequiredId } from "./toast.js";
|
||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import ws from "./ws.js";
|
import ws from "./ws.js";
|
||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
@ -57,11 +57,11 @@ export async function uploadFiles(entityType: string, parentNoteId: string, file
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeToast(id: string, message: string): ToastOptions {
|
function makeToast(id: string, message: string): ToastOptionsWithRequiredId {
|
||||||
return {
|
return {
|
||||||
id: id,
|
id,
|
||||||
title: t("import.import-status"),
|
title: t("import.import-status"),
|
||||||
message: message,
|
message,
|
||||||
icon: "plus"
|
icon: "plus"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -78,7 +78,7 @@ ws.subscribeToMessages(async (message) => {
|
|||||||
toastService.showPersistent(makeToast(message.taskId, t("import.in-progress", { progress: message.progressCount })));
|
toastService.showPersistent(makeToast(message.taskId, t("import.in-progress", { progress: message.progressCount })));
|
||||||
} else if (message.type === "taskSucceeded") {
|
} else if (message.type === "taskSucceeded") {
|
||||||
const toast = makeToast(message.taskId, t("import.successful"));
|
const toast = makeToast(message.taskId, t("import.successful"));
|
||||||
toast.closeAfter = 5000;
|
toast.timeout = 5000;
|
||||||
|
|
||||||
toastService.showPersistent(toast);
|
toastService.showPersistent(toast);
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ ws.subscribeToMessages(async (message: WebSocketMessage) => {
|
|||||||
toastService.showPersistent(makeToast(message.taskId, t("import.in-progress", { progress: message.progressCount })));
|
toastService.showPersistent(makeToast(message.taskId, t("import.in-progress", { progress: message.progressCount })));
|
||||||
} else if (message.type === "taskSucceeded") {
|
} else if (message.type === "taskSucceeded") {
|
||||||
const toast = makeToast(message.taskId, t("import.successful"));
|
const toast = makeToast(message.taskId, t("import.successful"));
|
||||||
toast.closeAfter = 5000;
|
toast.timeout = 5000;
|
||||||
|
|
||||||
toastService.showPersistent(toast);
|
toastService.showPersistent(toast);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import server from "./server.js";
|
import server from "./server.js";
|
||||||
import protectedSessionHolder from "./protected_session_holder.js";
|
import protectedSessionHolder from "./protected_session_holder.js";
|
||||||
import toastService from "./toast.js";
|
import toastService from "./toast.js";
|
||||||
import type { ToastOptions } from "./toast.js";
|
import type { ToastOptionsWithRequiredId } from "./toast.js";
|
||||||
import ws from "./ws.js";
|
import ws from "./ws.js";
|
||||||
import appContext from "../components/app_context.js";
|
import appContext from "../components/app_context.js";
|
||||||
import froca from "./froca.js";
|
import froca from "./froca.js";
|
||||||
@ -97,7 +97,7 @@ async function protectNote(noteId: string, protect: boolean, includingSubtree: b
|
|||||||
await server.put(`notes/${noteId}/protect/${protect ? 1 : 0}?subtree=${includingSubtree ? 1 : 0}`);
|
await server.put(`notes/${noteId}/protect/${protect ? 1 : 0}?subtree=${includingSubtree ? 1 : 0}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeToast(message: Message, title: string, text: string): ToastOptions {
|
function makeToast(message: Message, title: string, text: string): ToastOptionsWithRequiredId {
|
||||||
return {
|
return {
|
||||||
id: message.taskId,
|
id: message.taskId,
|
||||||
title,
|
title,
|
||||||
@ -124,7 +124,7 @@ ws.subscribeToMessages(async (message) => {
|
|||||||
} else if (message.type === "taskSucceeded") {
|
} else if (message.type === "taskSucceeded") {
|
||||||
const text = isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully");
|
const text = isProtecting ? t("protected_session.protecting-finished-successfully") : t("protected_session.unprotecting-finished-successfully");
|
||||||
const toast = makeToast(message, title, text);
|
const toast = makeToast(message, title, text);
|
||||||
toast.closeAfter = 3000;
|
toast.timeout = 3000;
|
||||||
|
|
||||||
toastService.showPersistent(toast);
|
toastService.showPersistent(toast);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { signal } from "@preact/signals";
|
||||||
|
|
||||||
import utils from "./utils.js";
|
import utils from "./utils.js";
|
||||||
|
|
||||||
export interface ToastOptions {
|
export interface ToastOptions {
|
||||||
@ -5,117 +7,80 @@ export interface ToastOptions {
|
|||||||
icon: string;
|
icon: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
message: string;
|
message: string;
|
||||||
delay?: number;
|
timeout?: number;
|
||||||
autohide?: boolean;
|
|
||||||
closeAfter?: number;
|
|
||||||
progress?: number;
|
progress?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toast({ title, icon, message, id, delay, autohide, progress }: ToastOptions) {
|
export type ToastOptionsWithRequiredId = Omit<ToastOptions, "id"> & Required<Pick<ToastOptions, "id">>;
|
||||||
const $toast = $(title
|
|
||||||
? `\
|
|
||||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
|
||||||
<div class="toast-header">
|
|
||||||
<strong class="me-auto">
|
|
||||||
<span class="bx bx-${icon}"></span>
|
|
||||||
<span class="toast-title"></span>
|
|
||||||
</strong>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="toast-body"></div>
|
|
||||||
<div class="toast-progress"></div>
|
|
||||||
</div>`
|
|
||||||
: `
|
|
||||||
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
|
||||||
<div class="toast-icon">
|
|
||||||
<span class="bx bx-${icon}"></span>
|
|
||||||
</div>
|
|
||||||
<div class="toast-body"></div>
|
|
||||||
<div class="toast-header">
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="toast-progress"></div>
|
|
||||||
</div>`
|
|
||||||
);
|
|
||||||
|
|
||||||
$toast.toggleClass("no-title", !title);
|
function showPersistent(options: ToastOptionsWithRequiredId) {
|
||||||
$toast.find(".toast-title").text(title ?? "");
|
const existingToast = toasts.value.find(toast => toast.id === options.id);
|
||||||
$toast.find(".toast-body").html(message);
|
if (existingToast) {
|
||||||
$toast.find(".toast-progress").css("width", `${(progress ?? 0) * 100}%`);
|
updateToast(options.id, options);
|
||||||
|
|
||||||
if (id) {
|
|
||||||
$toast.attr("id", `toast-${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#toast-container").append($toast);
|
|
||||||
|
|
||||||
$toast.toast({
|
|
||||||
delay: delay || 3000,
|
|
||||||
autohide: !!autohide
|
|
||||||
});
|
|
||||||
|
|
||||||
$toast.on("hidden.bs.toast", (e) => e.target.remove());
|
|
||||||
|
|
||||||
$toast.toast("show");
|
|
||||||
|
|
||||||
return $toast;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showPersistent(options: ToastOptions) {
|
|
||||||
let $toast = $(`#toast-${options.id}`);
|
|
||||||
|
|
||||||
if ($toast.length > 0) {
|
|
||||||
$toast.find(".toast-body").html(options.message);
|
|
||||||
$toast.find(".toast-progress").css("width", `${(options.progress ?? 0) * 100}%`);
|
|
||||||
} else {
|
} else {
|
||||||
options.autohide = false;
|
addToast(options);
|
||||||
|
|
||||||
$toast = toast(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.closeAfter) {
|
|
||||||
setTimeout(() => $toast.remove(), options.closeAfter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function closePersistent(id: string) {
|
function closePersistent(id: string) {
|
||||||
$(`#toast-${id}`).remove();
|
removeToastFromStore(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMessage(message: string, delay = 2000, icon = "check") {
|
function showMessage(message: string, timeout = 2000, icon = "bx bx-check") {
|
||||||
console.debug(utils.now(), "message:", message);
|
console.debug(utils.now(), "message:", message);
|
||||||
|
|
||||||
toast({
|
addToast({
|
||||||
icon,
|
icon,
|
||||||
message: message,
|
message,
|
||||||
autohide: true,
|
timeout
|
||||||
delay
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showError(message: string, delay = 10000) {
|
export function showError(message: string, timeout = 10000) {
|
||||||
console.log(utils.now(), "error: ", message);
|
console.log(utils.now(), "error: ", message);
|
||||||
|
|
||||||
toast({
|
addToast({
|
||||||
icon: "bx bx-error-circle",
|
icon: "bx bx-error-circle",
|
||||||
message: message,
|
message,
|
||||||
autohide: true,
|
timeout
|
||||||
delay
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showErrorTitleAndMessage(title: string, message: string, delay = 10000) {
|
function showErrorTitleAndMessage(title: string, message: string, timeout = 10000) {
|
||||||
console.log(utils.now(), "error: ", message);
|
console.log(utils.now(), "error: ", message);
|
||||||
|
|
||||||
toast({
|
addToast({
|
||||||
title: title,
|
title,
|
||||||
icon: "bx bx-error-circle",
|
icon: "bx bx-error-circle",
|
||||||
message: message,
|
message,
|
||||||
autohide: true,
|
timeout
|
||||||
delay
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//#region Toast store
|
||||||
|
export const toasts = signal<ToastOptionsWithRequiredId[]>([]);
|
||||||
|
|
||||||
|
function addToast(opts: ToastOptions) {
|
||||||
|
const id = opts.id ?? crypto.randomUUID();
|
||||||
|
const toast = { ...opts, id };
|
||||||
|
toasts.value = [ ...toasts.value, toast ];
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateToast(id: string, partial: Partial<ToastOptions>) {
|
||||||
|
toasts.value = toasts.value.map(toast => {
|
||||||
|
if (toast.id === id) {
|
||||||
|
return { ...toast, ...partial }
|
||||||
|
}
|
||||||
|
return toast;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeToastFromStore(id: string) {
|
||||||
|
toasts.value = toasts.value.filter(toast => toast.id !== id);
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
showMessage,
|
showMessage,
|
||||||
showError,
|
showError,
|
||||||
|
|||||||
@ -1135,61 +1135,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
|
|||||||
margin: 0 12px;
|
margin: 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#toast-container {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
top: 20px;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast {
|
|
||||||
--bs-toast-bg: var(--accented-background-color);
|
|
||||||
--bs-toast-color: var(--main-text-color);
|
|
||||||
z-index: 9999999999 !important;
|
|
||||||
pointer-events: all;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-header {
|
|
||||||
background-color: var(--more-accented-background-color) !important;
|
|
||||||
color: var(--main-text-color) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast-body {
|
|
||||||
white-space: preserve-breaks;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast.no-title {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast.no-title .toast-icon {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x);
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast.no-title .toast-body {
|
|
||||||
padding-inline-start: 0;
|
|
||||||
padding-inline-end: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast.no-title .toast-header {
|
|
||||||
background-color: unset !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toast .toast-progress {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
inset-inline-start: 0;
|
|
||||||
inset-inline-end: 0;
|
|
||||||
background-color: var(--toast-text-color) !important;
|
|
||||||
height: 4px;
|
|
||||||
transition: width 0.1s linear;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck-mentions .ck-button {
|
.ck-mentions .ck-button {
|
||||||
font-size: var(--detail-font-size) !important;
|
font-size: var(--detail-font-size) !important;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
|
|||||||
59
apps/client/src/widgets/Toast.css
Normal file
59
apps/client/src/widgets/Toast.css
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#toast-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
top: 20px;
|
||||||
|
pointer-events: none;
|
||||||
|
contain: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
--bs-toast-bg: var(--accented-background-color);
|
||||||
|
--bs-toast-color: var(--main-text-color);
|
||||||
|
z-index: 9999999999 !important;
|
||||||
|
pointer-events: all;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-header {
|
||||||
|
background-color: var(--more-accented-background-color) !important;
|
||||||
|
color: var(--main-text-color) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-body {
|
||||||
|
white-space: preserve-breaks;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.no-title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.no-title .toast-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.no-title .toast-body {
|
||||||
|
padding-inline-start: 0;
|
||||||
|
padding-inline-end: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.no-title .toast-header {
|
||||||
|
background-color: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast .toast-progress {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
inset-inline-end: 0;
|
||||||
|
background-color: var(--toast-text-color) !important;
|
||||||
|
height: 4px;
|
||||||
|
transition: width 0.1s linear;
|
||||||
|
}
|
||||||
61
apps/client/src/widgets/Toast.tsx
Normal file
61
apps/client/src/widgets/Toast.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import "./Toast.css";
|
||||||
|
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { useEffect } from "preact/hooks";
|
||||||
|
|
||||||
|
import { removeToastFromStore, ToastOptionsWithRequiredId, toasts } from "../services/toast";
|
||||||
|
import Icon from "./react/Icon";
|
||||||
|
import { RawHtmlBlock } from "./react/RawHtml";
|
||||||
|
|
||||||
|
export default function ToastContainer() {
|
||||||
|
return (
|
||||||
|
<div id="toast-container">
|
||||||
|
{toasts.value.map(toast => <Toast key={toast.id} {...toast} />)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Toast({ id, title, timeout, progress, message, icon }: ToastOptionsWithRequiredId) {
|
||||||
|
// Autohide.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!timeout || timeout <= 0) return;
|
||||||
|
const timerId = setTimeout(() => removeToastFromStore(id), timeout);
|
||||||
|
return () => clearTimeout(timerId);
|
||||||
|
}, [ id, timeout ]);
|
||||||
|
|
||||||
|
const closeButton = (
|
||||||
|
<button
|
||||||
|
type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"
|
||||||
|
onClick={() => removeToastFromStore(id)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const toastIcon = <Icon icon={icon.startsWith("bx ") ? icon : `bx bx-${icon}`} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={clsx("toast", !title && "no-title")}
|
||||||
|
role="alert" aria-live="assertive" aria-atomic="true"
|
||||||
|
id={`toast-${id}`}
|
||||||
|
>
|
||||||
|
{title ? (
|
||||||
|
<div class="toast-header">
|
||||||
|
<strong class="me-auto">
|
||||||
|
{toastIcon}
|
||||||
|
<span class="toast-title">{title}</span>
|
||||||
|
</strong>
|
||||||
|
{closeButton}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div class="toast-icon">{toastIcon}</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<RawHtmlBlock className="toast-body" html={message} />
|
||||||
|
|
||||||
|
{!title && <div class="toast-header">{closeButton}</div>}
|
||||||
|
<div
|
||||||
|
class="toast-progress"
|
||||||
|
style={{ width: `${(progress ?? 0) * 100}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -169,10 +169,11 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
|||||||
console.log("Got issue in widget ", this);
|
console.log("Got issue in widget ", this);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
||||||
let noteId = this._noteId;
|
const noteId = this._noteId;
|
||||||
if (this._noteId) {
|
if (this._noteId) {
|
||||||
froca.getNote(noteId, true).then((note) => {
|
froca.getNote(noteId, true).then((note) => {
|
||||||
toastService.showPersistent({
|
toastService.showPersistent({
|
||||||
|
id: `custom-widget-failure-${noteId}`,
|
||||||
title: t("toast.widget-error.title"),
|
title: t("toast.widget-error.title"),
|
||||||
icon: "bx bx-error-circle",
|
icon: "bx bx-error-circle",
|
||||||
message: t("toast.widget-error.message-custom", {
|
message: t("toast.widget-error.message-custom", {
|
||||||
@ -182,16 +183,16 @@ export class TypedBasicWidget<T extends TypedComponent<any>> extends TypedCompon
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return;
|
} else {
|
||||||
|
toastService.showPersistent({
|
||||||
|
id: `custom-widget-failure-unknown-${crypto.randomUUID()}`,
|
||||||
|
title: t("toast.widget-error.title"),
|
||||||
|
icon: "bx bx-error-circle",
|
||||||
|
message: t("toast.widget-error.message-unknown", {
|
||||||
|
message: e.message || e.toString()
|
||||||
|
})
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
toastService.showPersistent({
|
|
||||||
title: t("toast.widget-error.title"),
|
|
||||||
icon: "bx bx-error-circle",
|
|
||||||
message: t("toast.widget-error.message-unknown", {
|
|
||||||
message: e.message || e.toString()
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import FormRadioGroup from "../react/FormRadioGroup";
|
|||||||
import Modal from "../react/Modal";
|
import Modal from "../react/Modal";
|
||||||
import "./export.css";
|
import "./export.css";
|
||||||
import ws from "../../services/ws";
|
import ws from "../../services/ws";
|
||||||
import toastService, { ToastOptions } from "../../services/toast";
|
import toastService, { type ToastOptionsWithRequiredId } from "../../services/toast";
|
||||||
import utils from "../../services/utils";
|
import utils from "../../services/utils";
|
||||||
import open from "../../services/open";
|
import open from "../../services/open";
|
||||||
import froca from "../../services/froca";
|
import froca from "../../services/froca";
|
||||||
@ -132,11 +132,11 @@ function exportBranch(branchId: string, type: string, format: string, version: s
|
|||||||
}
|
}
|
||||||
|
|
||||||
ws.subscribeToMessages(async (message) => {
|
ws.subscribeToMessages(async (message) => {
|
||||||
function makeToast(id: string, message: string): ToastOptions {
|
function makeToast(id: string, message: string): ToastOptionsWithRequiredId {
|
||||||
return {
|
return {
|
||||||
id: id,
|
id,
|
||||||
title: t("export.export_status"),
|
title: t("export.export_status"),
|
||||||
message: message,
|
message,
|
||||||
icon: "export"
|
icon: "export"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -152,7 +152,7 @@ ws.subscribeToMessages(async (message) => {
|
|||||||
toastService.showPersistent(makeToast(message.taskId, t("export.export_in_progress", { progressCount: message.progressCount })));
|
toastService.showPersistent(makeToast(message.taskId, t("export.export_in_progress", { progressCount: message.progressCount })));
|
||||||
} else if (message.type === "taskSucceeded") {
|
} else if (message.type === "taskSucceeded") {
|
||||||
const toast = makeToast(message.taskId, t("export.export_finished_successfully"));
|
const toast = makeToast(message.taskId, t("export.export_finished_successfully"));
|
||||||
toast.closeAfter = 5000;
|
toast.timeout = 5000;
|
||||||
|
|
||||||
toastService.showPersistent(toast);
|
toastService.showPersistent(toast);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -725,7 +725,7 @@ export function useImperativeSearchHighlighlighting(highlightedTokens: string[]
|
|||||||
|
|
||||||
export function useNoteTreeDrag(containerRef: MutableRef<HTMLElement | null | undefined>, { dragEnabled, dragNotEnabledMessage, callback }: {
|
export function useNoteTreeDrag(containerRef: MutableRef<HTMLElement | null | undefined>, { dragEnabled, dragNotEnabledMessage, callback }: {
|
||||||
dragEnabled: boolean,
|
dragEnabled: boolean,
|
||||||
dragNotEnabledMessage: Omit<ToastOptions, "id" | "closeAfter">;
|
dragNotEnabledMessage: Omit<ToastOptions, "id">;
|
||||||
callback: (data: DragData[], e: DragEvent) => void
|
callback: (data: DragData[], e: DragEvent) => void
|
||||||
}) {
|
}) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -737,7 +737,7 @@ export function useNoteTreeDrag(containerRef: MutableRef<HTMLElement | null | un
|
|||||||
toast.showPersistent({
|
toast.showPersistent({
|
||||||
...dragNotEnabledMessage,
|
...dragNotEnabledMessage,
|
||||||
id: "drag-not-enabled",
|
id: "drag-not-enabled",
|
||||||
closeAfter: 5000
|
timeout: 5000
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,6 @@
|
|||||||
document.getElementsByTagName("body")[0].style.display = "none";
|
document.getElementsByTagName("body")[0].style.display = "none";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>
|
|
||||||
|
|
||||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container" style="display: none"></div>
|
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container" style="display: none"></div>
|
||||||
|
|
||||||
<%- include("./partials/windowGlobal.ejs", locals) %>
|
<%- include("./partials/windowGlobal.ejs", locals) %>
|
||||||
|
|||||||
@ -105,8 +105,6 @@
|
|||||||
>
|
>
|
||||||
<noscript><%= t("javascript-required") %></noscript>
|
<noscript><%= t("javascript-required") %></noscript>
|
||||||
|
|
||||||
<div id="toast-container" class="d-flex flex-column justify-content-center align-items-center"></div>
|
|
||||||
|
|
||||||
<div id="context-menu-cover"></div>
|
<div id="context-menu-cover"></div>
|
||||||
|
|
||||||
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div>
|
<div class="dropdown-menu dropdown-menu-sm" id="context-menu-container"></div>
|
||||||
|
|||||||
24
pnpm-lock.yaml
generated
24
pnpm-lock.yaml
generated
@ -190,6 +190,9 @@ importers:
|
|||||||
'@popperjs/core':
|
'@popperjs/core':
|
||||||
specifier: 2.11.8
|
specifier: 2.11.8
|
||||||
version: 2.11.8
|
version: 2.11.8
|
||||||
|
'@preact/signals':
|
||||||
|
specifier: 2.5.1
|
||||||
|
version: 2.5.1(preact@10.28.0)
|
||||||
'@triliumnext/ckeditor5':
|
'@triliumnext/ckeditor5':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/ckeditor5
|
version: link:../../packages/ckeditor5
|
||||||
@ -3897,6 +3900,14 @@ packages:
|
|||||||
'@babel/core': 7.x
|
'@babel/core': 7.x
|
||||||
vite: 2.x || 3.x || 4.x || 5.x || 6.x || 7.x
|
vite: 2.x || 3.x || 4.x || 5.x || 6.x || 7.x
|
||||||
|
|
||||||
|
'@preact/signals-core@1.12.1':
|
||||||
|
resolution: {integrity: sha512-BwbTXpj+9QutoZLQvbttRg5x3l5468qaV2kufh+51yha1c53ep5dY4kTuZR35+3pAZxpfQerGJiQqg34ZNZ6uA==}
|
||||||
|
|
||||||
|
'@preact/signals@2.5.1':
|
||||||
|
resolution: {integrity: sha512-VPjk5YFt7i11Fi4UK0tzaEe5xLwfhUxXL3l89ocxQ5aPz7bRo8M5+N73LjBMPklyXKYKz6YsNo4Smp8n6nplng==}
|
||||||
|
peerDependencies:
|
||||||
|
preact: 10.28.0
|
||||||
|
|
||||||
'@prefresh/babel-plugin@0.5.2':
|
'@prefresh/babel-plugin@0.5.2':
|
||||||
resolution: {integrity: sha512-AOl4HG6dAxWkJ5ndPHBgBa49oo/9bOiJuRDKHLSTyH+Fd9x00shTXpdiTj1W41l6oQIwUOAgJeHMn4QwIDpHkA==}
|
resolution: {integrity: sha512-AOl4HG6dAxWkJ5ndPHBgBa49oo/9bOiJuRDKHLSTyH+Fd9x00shTXpdiTj1W41l6oQIwUOAgJeHMn4QwIDpHkA==}
|
||||||
|
|
||||||
@ -15268,8 +15279,6 @@ snapshots:
|
|||||||
'@ckeditor/ckeditor5-core': 47.3.0
|
'@ckeditor/ckeditor5-core': 47.3.0
|
||||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||||
ckeditor5: 47.3.0
|
ckeditor5: 47.3.0
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
|
'@ckeditor/ckeditor5-code-block@47.3.0(patch_hash=2361d8caad7d6b5bddacc3a3b4aa37dbfba260b1c1b22a450413a79c1bb1ce95)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -15488,8 +15497,6 @@ snapshots:
|
|||||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||||
ckeditor5: 47.3.0
|
ckeditor5: 47.3.0
|
||||||
es-toolkit: 1.39.5
|
es-toolkit: 1.39.5
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@ckeditor/ckeditor5-editor-multi-root@47.3.0':
|
'@ckeditor/ckeditor5-editor-multi-root@47.3.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -15512,8 +15519,6 @@ snapshots:
|
|||||||
'@ckeditor/ckeditor5-table': 47.3.0
|
'@ckeditor/ckeditor5-table': 47.3.0
|
||||||
'@ckeditor/ckeditor5-utils': 47.3.0
|
'@ckeditor/ckeditor5-utils': 47.3.0
|
||||||
ckeditor5: 47.3.0
|
ckeditor5: 47.3.0
|
||||||
transitivePeerDependencies:
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
'@ckeditor/ckeditor5-emoji@47.3.0':
|
'@ckeditor/ckeditor5-emoji@47.3.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -18466,6 +18471,13 @@ snapshots:
|
|||||||
- preact
|
- preact
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
'@preact/signals-core@1.12.1': {}
|
||||||
|
|
||||||
|
'@preact/signals@2.5.1(preact@10.28.0)':
|
||||||
|
dependencies:
|
||||||
|
'@preact/signals-core': 1.12.1
|
||||||
|
preact: 10.28.0
|
||||||
|
|
||||||
'@prefresh/babel-plugin@0.5.2': {}
|
'@prefresh/babel-plugin@0.5.2': {}
|
||||||
|
|
||||||
'@prefresh/core@1.5.5(preact@10.28.0)':
|
'@prefresh/core@1.5.5(preact@10.28.0)':
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user