Compare commits

...

125 Commits

Author SHA1 Message Date
SiriusXT
254145f0e5 chore(window): handle potential JSON parsing failures 2025-12-29 16:26:50 +08:00
SiriusXT
c28f11336e chore(window_db): fix potential migration error 2025-12-29 16:11:49 +08:00
SiriusXT
2e30683b7b chore(window): avoid reduce error when no candidates 2025-12-29 16:08:29 +08:00
SiriusXT
0af7b8b145 chore(window): use MAX_SAVED_WINDOWS constant 2025-12-29 15:56:04 +08:00
SiriusXT
5d39b84886 fix(window): Fix incorrect noteContents error 2025-12-29 15:28:27 +08:00
SiriusXT
537c4051cc feat(window): add class to extra windows 2025-12-29 15:27:35 +08:00
SiriusXT
d0a22bc517 fix(window): Fix empty array issue during openNoteContents data migration 2025-12-29 15:27:11 +08:00
SiriusXT
19a75acf3f Merge branch 'main' into feat/extra-window 2025-12-29 14:44:25 +08:00
SiriusXT
3f0abce874 feat(window_db): migrate openNoteContexts to structured format with window metadata 2025-12-29 14:43:49 +08:00
SiriusXT
36dd29f919 feat(window): add class to extra windows 2025-12-29 14:37:40 +08:00
SiriusXT
d7838f0b67 feat(window): restore recently closed windows from tray 2025-12-29 14:37:35 +08:00
SiriusXT
3353d4f436 feat(window): record openNoteContents of recently closed windows 2025-12-29 14:33:34 +08:00
SiriusXT
7740154bdc feat(window): add windowId for extra windows 2025-12-29 14:32:53 +08:00
Elian Doran
a14eed81f6 Translations update from Hosted Weblate (#8197) 2025-12-28 23:25:34 +02:00
Hosted Weblate
54f51b365a Update translation files
Updated by "Cleanup translation files" add-on in Weblate.

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/
2025-12-28 22:19:17 +01:00
Marcelo Nolasco
c0e0a712ad Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (152 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/pt_BR/
2025-12-28 22:19:16 +01:00
Marcelo Nolasco
3ab5bbae4d Translated using Weblate (Portuguese (Brazil))
Currently translated at 11.2% (13 of 116 strings)

Translation: Trilium Notes/README
Translate-URL: https://hosted.weblate.org/projects/trilium/readme/pt_BR/
2025-12-28 22:19:15 +01:00
Marcelo Nolasco
cafeb3920a Translated using Weblate (Portuguese (Brazil))
Currently translated at 12.5% (19 of 152 strings)

Translation: Trilium Notes/Website
Translate-URL: https://hosted.weblate.org/projects/trilium/website/pt_BR/
2025-12-28 22:19:14 +01:00
MarcelWie
fb465b442c Translated using Weblate (German)
Currently translated at 94.3% (1632 of 1730 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/de/
2025-12-28 22:19:13 +01:00
Francis C.
d3a559a700 Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 99.5% (1722 of 1730 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/zh_Hant/
2025-12-28 22:19:13 +01:00
green
7768003735 Translated using Weblate (Japanese)
Currently translated at 100.0% (1730 of 1730 strings)

Translation: Trilium Notes/Client
Translate-URL: https://hosted.weblate.org/projects/trilium/client/ja/
2025-12-28 22:19:12 +01:00
Elian Doran
f02b3b48e8 docs(user): add missing share aliases 2025-12-28 23:18:51 +02:00
Elian Doran
ba273bb9f4 Custom icon pack (#8190) 2025-12-28 23:09:43 +02:00
Elian Doran
490c539d63 Merge branch 'main' into feature/icon_packs 2025-12-28 23:09:34 +02:00
Elian Doran
ebd60519dd fix(note_icon): empty slots appearing when reducing list 2025-12-28 22:59:01 +02:00
Elian Doran
56304a4d71 chore(icon-pack-builder): improve output dir 2025-12-28 22:54:29 +02:00
Elian Doran
32f0f98522 feat(icon-pack-builder): integrate boxicons 3 with brands 2025-12-28 22:53:03 +02:00
Elian Doran
b18dd22341 fix(icon-pack-builder): add missing deps 2025-12-28 22:52:54 +02:00
Elian Doran
8eebae0955 chore(scripts): add script to compare the two boxicons 2025-12-28 22:29:51 +02:00
Elian Doran
ed229e0578 chore(scripts): update boxicons script to use packs instead of weights 2025-12-28 22:29:42 +02:00
Elian Doran
dbfaad6c06 test(server): fix broken test after changes to CSS generation 2025-12-28 21:46:19 +02:00
Elian Doran
6e5176b088 chore(deps): fix dependency 2025-12-28 21:31:10 +02:00
Elian Doran
becf4d7426 fix(note_icon): crash when reducing number of items 2025-12-28 21:10:22 +02:00
Elian Doran
082040c6e1 feat(share): display an icon for attachment download 2025-12-28 21:03:30 +02:00
Elian Doran
1ae11ce3a5 fix(export/share): .zip attachment marked as html 2025-12-28 20:52:47 +02:00
Elian Doran
cf968b3590 fix(export/share): attachment download links not working 2025-12-28 20:45:33 +02:00
Adorian Doran
a3db1ab156 UI fixes (#8200) 2025-12-28 20:36:19 +02:00
Adorian Doran
7440110a44 Merge branch 'feat/ui/fixes' of https://github.com/TriliumNext/Trilium into feat/ui/fixes 2025-12-28 20:31:43 +02:00
Adorian Doran
3638e6b12c style/note title actions: fix an issue identified by gemini-code-assist 2025-12-28 20:31:32 +02:00
Adorian Doran
621ed5b9de Update apps/client/src/widgets/type_widgets/text/EditableText.css
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2025-12-28 20:24:24 +02:00
Adorian Doran
1e3135dea0 style/note title actions: fix width for full-width content notes 2025-12-28 20:19:51 +02:00
Elian Doran
8f21c0b34a feat(note_icon): use grid virtualization for listing high performance 2025-12-28 19:46:57 +02:00
Adorian Doran
b3feb38369 style/options: properly align the title with the option cards when the content is centered 2025-12-28 19:45:48 +02:00
Adorian Doran
2bf862d5b9 style/note scrolling container: make the alignment of children more consistent 2025-12-28 19:20:46 +02:00
Elian Doran
cbb7b4ffea refactor(note_icon): split off into two hooks 2025-12-28 19:20:44 +02:00
Elian Doran
b2f496048f feat(icon-pack-builder): add icons to packs 2025-12-28 18:57:32 +02:00
Elian Doran
e084bc4c07 feat(icon-pack-builder): add phosphor fill 2025-12-28 18:52:08 +02:00
Elian Doran
d9b0660def fix(icon-pack-builder): some phosphor icons not working due to alias 2025-12-28 18:41:54 +02:00
Elian Doran
b997452733 feat(icon-pack-builder): integrate phosphor 2025-12-28 18:33:01 +02:00
Elian Doran
e699566e62 chore(note_icon): remove ellipsis in placeholder 2025-12-28 18:13:46 +02:00
Elian Doran
2bd83e6285 feat(note_icon): display count and filter in search placeholder 2025-12-28 17:41:11 +02:00
Elian Doran
46e5090445 fix(icon-pack): non-BMP icons not rendering 2025-12-28 17:33:12 +02:00
Elian Doran
035a311e4d feat(icon-pack-builder): save attachment 2025-12-28 17:17:08 +02:00
Elian Doran
850528750c feat(icon-pack-builder): add manifest to zip 2025-12-28 16:56:41 +02:00
Elian Doran
645720a725 feat(icon-pack-builder): build zip without content yet 2025-12-28 16:50:16 +02:00
Elian Doran
a6c74449aa feat(icon-pack-builder): generate CSS for mdi 2025-12-28 16:25:21 +02:00
Elian Doran
7f05d9cdff test(client): broken tests after change in icon definition 2025-12-28 15:51:48 +02:00
Elian Doran
02d42dc5ff chore(icon_packs): address requested changes 2025-12-28 15:50:01 +02:00
Elian Doran
e730378b27 fix(icon_packs): references to .bx for icon selection 2025-12-28 13:42:45 +02:00
Elian Doran
c14d95f561 docs(user): mention icon pack prefix constraints 2025-12-28 12:36:18 +02:00
Elian Doran
13b700e0e5 chore(icon_packs): address requested changes 2025-12-28 12:30:26 +02:00
Elian Doran
f849c4b315 chore: fix typecheck 2025-12-28 12:15:43 +02:00
Elian Doran
c2c19e8ecd chore(server): address self-review 2025-12-28 12:01:10 +02:00
Elian Doran
12875ec308 chore(deps): update package lock 2025-12-28 11:56:50 +02:00
Elian Doran
5d12d57a22 test(server): fix broken tests after changes 2025-12-28 11:52:49 +02:00
Elian Doran
5cc2296768 chore(server): fix typecheck 2025-12-28 11:46:25 +02:00
Elian Doran
7c1175995f chore(icon_packs): remove prefix from phosphor script 2025-12-28 11:43:37 +02:00
Elian Doran
d834cd78a7 docs(user): document icon packs 2025-12-28 11:43:25 +02:00
Adorian Doran
79d2010bfa style/note title actions: properly align when the content is centered 2025-12-28 10:18:50 +02:00
Adorian Doran
3f86c809ce style/note title actions: properly align when the content is centered 2025-12-28 10:12:01 +02:00
Elian Doran
1570ea77d8 chore(icon_packs): integrate prefix as part of the attribute instead of manifest 2025-12-28 09:56:08 +02:00
Elian Doran
99bdd2e433 feat(icon_packs): skip duplicate icon packs 2025-12-28 09:43:33 +02:00
Elian Doran
7646061215 refactor(client): move a bx style to dedicated CSS 2025-12-28 09:43:17 +02:00
Elian Doran
505a985755 fix(mobile): icons missing 2025-12-28 09:28:24 +02:00
Elian Doran
e895ea406a chore(client): reintegrate boxicons special class names (e.g. flip) 2025-12-28 09:25:10 +02:00
Elian Doran
8b8a78e949 chore(server): get rid of boxicons CSS import 2025-12-28 02:27:02 +02:00
Elian Doran
1c940ff8a2 fix(icon_packs): integrate boxicons back into share export 2025-12-28 02:24:18 +02:00
Elian Doran
841cb32835 fix(icon_packs): integrate boxicons back into share theme 2025-12-28 01:58:22 +02:00
Elian Doran
61e96f91d0 fix(icon_packs): use right name for boxicons for compatibility 2025-12-28 01:20:01 +02:00
Elian Doran
9f6c07f5cc chore(icon_packs): use builtin boxicons for client 2025-12-28 01:00:45 +02:00
Elian Doran
1efb21c627 feat(export/share): render custom icons 2025-12-27 23:39:27 +02:00
Elian Doran
d5b04864c8 chore(export/share): inject font 2025-12-27 23:31:56 +02:00
Elian Doran
da28f4505a chore(export/share): inject pack CSS 2025-12-27 23:21:59 +02:00
Elian Doran
e2a628fa2f feat(icon_packs): ignore protected notes 2025-12-27 22:50:31 +02:00
Elian Doran
290f488c78 feat(share): ignore unsupported icon packs 2025-12-27 22:09:16 +02:00
Elian Doran
b00cb52da5 feat(share): basic support for custom icon packs 2025-12-27 21:58:18 +02:00
Elian Doran
c7bb5ff119 feat(attachments): display MIME type 2025-12-27 20:54:14 +02:00
Elian Doran
faa069b8a1 feat(note_icon): add message if no results 2025-12-27 20:47:56 +02:00
Elian Doran
e57f1e6f23 feat(note_icon): add placeholder for search 2025-12-27 20:45:08 +02:00
Elian Doran
73975ab521 feat(note_icon): use bootstrap tooltip 2025-12-27 20:43:05 +02:00
Elian Doran
761a67f238 feat(note_icon): display icon pack in note title 2025-12-27 20:37:59 +02:00
Elian Doran
736c69816d feat(note_icon): change design for icon reset button 2025-12-27 20:21:10 +02:00
Elian Doran
270339da11 style(next): selector interfering with grouped buttons 2025-12-27 20:16:13 +02:00
Elian Doran
aa93bc5492 fix(note_icon): one column short 2025-12-27 19:52:11 +02:00
Elian Doran
0c9c36ea7e fix(note_icon): missing tooltip for filter 2025-12-27 18:14:53 +02:00
Elian Doran
af67967502 fix(note_icon): modal not dismissing 2025-12-27 18:11:07 +02:00
Elian Doran
78bec0c782 feat(icon_packs): integrate boxicons JSON 2025-12-27 18:04:16 +02:00
Elian Doran
0c77563672 feat(icon_packs): mark icon packs as unsafe 2025-12-27 18:02:59 +02:00
Elian Doran
241a9e2e7f chore(icon_packs): process boxicons v2 2025-12-27 17:56:12 +02:00
Elian Doran
59b691d670 chore(scripts): process boxicons v3 icons 2025-12-27 00:26:33 +02:00
Elian Doran
ecec661b72 chore(scripts): add icon to process phosphor meta 2025-12-26 22:43:03 +02:00
Elian Doran
fb629f7693 feat(note_icon): display note pack icon 2025-12-26 21:14:13 +02:00
Elian Doran
13fff33aa4 feat(icon_packs): use note title isntead of manifest 2025-12-26 21:08:37 +02:00
Elian Doran
8053221b12 chore(note_icon): hide filter if no custom icon packs 2025-12-26 21:01:48 +02:00
Elian Doran
ba699f9842 refactor(note_icon): split filter content into a component 2025-12-26 21:01:19 +02:00
Elian Doran
eb5ebb53cb fix(icon_list): border-right icon missing 2025-12-26 20:57:10 +02:00
Elian Doran
c26357be40 feat(note_icon): allow filtering default icons 2025-12-26 20:49:20 +02:00
Elian Doran
db4af96040 feat(note_icon): filter by icon pack 2025-12-26 20:42:19 +02:00
Elian Doran
5cb3983fe0 chore(note_icon): get rid of categories 2025-12-26 20:03:34 +02:00
Elian Doran
92292de0ff chore(client): basic integration of icon packs in icon selector 2025-12-26 19:52:54 +02:00
Elian Doran
a26923cc6d fix(icon_pack): listing definitions even if parsing fails 2025-12-26 19:42:23 +02:00
Elian Doran
2c4ac4ba30 fix(server): crashing due to bad icon pack 2025-12-26 19:37:10 +02:00
Elian Doran
254511bfbf chore(icon_pack): switch schema to support multiple terms per icon 2025-12-26 19:25:31 +02:00
Elian Doran
e2f6f8a4e4 feat(icon_pack): generate icon registry for client 2025-12-26 19:10:28 +02:00
Elian Doran
e346963e76 feat(icon_pack): inject the icon pack into the client 2025-12-26 18:36:36 +02:00
Elian Doran
5f1bdf7264 chore(icon_pack): generate icon declarations 2025-12-26 18:16:33 +02:00
Elian Doran
93a3b29677 chore(icon_pack): generate root declaration 2025-12-26 18:08:26 +02:00
Elian Doran
b157cd909c chore(icon_pack): generate src declaration 2025-12-26 18:04:39 +02:00
Elian Doran
2f24703690 chore(icon_pack): generate font face declaration without source 2025-12-26 17:42:44 +02:00
Elian Doran
27efa8844e refactor(server): mark ownerId in AttachmentRow as mandatory 2025-12-26 17:32:28 +02:00
Elian Doran
98de4b6dc3 chore(icon_pack): map ttf 2025-12-26 17:31:35 +02:00
Elian Doran
d121de5152 chore(icon_pack): map woff attachment 2025-12-26 17:30:19 +02:00
Elian Doran
5ad7323d03 chore(icon_pack): map woff2 attachment 2025-12-26 17:28:57 +02:00
Elian Doran
183020a4e3 chore(icon_pack): return icon mappings 2025-12-26 16:04:56 +02:00
Elian Doran
a56a5fe1f5 feat(icon_pack): check if JSON is parsable 2025-12-26 16:00:21 +02:00
149 changed files with 58869 additions and 10775 deletions

View File

@@ -61,6 +61,7 @@
"panzoom": "9.4.3",
"preact": "10.28.1",
"react-i18next": "16.5.0",
"react-window": "2.2.3",
"reveal.js": "5.2.1",
"svg-pan-zoom": "3.6.2",
"tabulator-tables": "6.3.1",

View File

@@ -535,6 +535,7 @@ export type FilteredCommandNames<T extends CommandData> = keyof Pick<CommandMapp
export class AppContext extends Component {
isMainWindow: boolean;
windowId: string;
components: Component[];
beforeUnloadListeners: (WeakRef<BeforeUploadListener> | (() => boolean))[];
tabManager!: TabManager;
@@ -543,10 +544,11 @@ export class AppContext extends Component {
lastSearchString?: string;
constructor(isMainWindow: boolean) {
constructor(isMainWindow: boolean, windowId: string) {
super();
this.isMainWindow = isMainWindow;
this.windowId = windowId;
// non-widget/layout components needed for the application
this.components = [];
this.beforeUnloadListeners = [];
@@ -676,8 +678,7 @@ export class AppContext extends Component {
this.beforeUnloadListeners = this.beforeUnloadListeners.filter(l => l !== listener);
}
}
const appContext = new AppContext(window.glob.isMainWindow);
const appContext = new AppContext(window.glob.isMainWindow, window.glob.windowId);
// we should save all outstanding changes before the page/app is closed
$(window).on("beforeunload", () => {

View File

@@ -142,14 +142,15 @@ export default class Entrypoints extends Component {
}
async openInWindowCommand({ notePath, hoistedNoteId, viewScope }: NoteCommandData) {
const extraWindowId = utils.randomString(4);
const extraWindowHash = linkService.calculateHash({ notePath, hoistedNoteId, viewScope });
if (utils.isElectron()) {
const { ipcRenderer } = utils.dynamicRequire("electron");
ipcRenderer.send("create-extra-window", { extraWindowHash });
ipcRenderer.send("create-extra-window", { extraWindowId, extraWindowHash });
} else {
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=1${extraWindowHash}`;
const url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?extraWindow=${extraWindowId}${extraWindowHash}`;
window.open(url, "", "width=1000,height=800");
}

View File

@@ -11,6 +11,8 @@ import linkService from "../services/link.js";
import type { EventData } from "./app_context.js";
import type FNote from "../entities/fnote.js";
const MAX_SAVED_WINDOWS = 10;
interface TabState {
contexts: NoteContext[];
position: number;
@@ -41,9 +43,6 @@ export default class TabManager extends Component {
this.recentlyClosedTabs = [];
this.tabsUpdate = new SpacedUpdate(async () => {
if (!appContext.isMainWindow) {
return;
}
if (options.is("databaseReadonly")) {
return;
}
@@ -52,9 +51,21 @@ export default class TabManager extends Component {
.map((nc) => nc.getPojoState())
.filter((t) => !!t);
await server.put("options", {
openNoteContexts: JSON.stringify(openNoteContexts)
});
// Update the current windows openNoteContexts in options
const savedWindows = options.getJson("openNoteContexts");
const win = savedWindows.find(w => w.windowId === appContext.windowId);
if (win) {
win.contexts = openNoteContexts;
} else {
savedWindows.push({
windowId: appContext.windowId,
createdAt: Date.now(),
closedAt: null,
contexts: openNoteContexts
});
}
await options.save("openNoteContexts", JSON.stringify(savedWindows));
});
appContext.addBeforeUnloadListener(this);
@@ -69,8 +80,13 @@ export default class TabManager extends Component {
}
async loadTabs() {
// Get the current windows openNoteContexts
const savedWindows = options.getJson("openNoteContexts");
const currentWin = savedWindows.find(w => w.windowId === appContext.windowId);
const openNoteContexts = currentWin ? currentWin.contexts : undefined;
try {
const noteContextsToOpen = (appContext.isMainWindow && options.getJson("openNoteContexts")) || [];
const noteContextsToOpen = openNoteContexts || [];
// preload all notes at once
await froca.getNotes([...noteContextsToOpen.flatMap((tab: NoteContextState) =>
@@ -119,6 +135,32 @@ export default class TabManager extends Component {
}
});
// Save window contents
if (currentWin) {
currentWin.createdAt = Date.now();
currentWin.closedAt = null;
currentWin.contexts = filteredNoteContexts;
} else {
// Filter out the oldest entry (excluding the main window)
if (savedWindows?.length >= MAX_SAVED_WINDOWS) {
const candidates = savedWindows.filter(w => w.windowId !== "main");
if (candidates.length > 0) {
const oldest = candidates.reduce((a, b) =>
a.createdAt < b.createdAt ? a : b
);
savedWindows.splice(savedWindows.indexOf(oldest), 1);
}
}
savedWindows.push({
windowId: appContext.windowId,
createdAt: Date.now(),
closedAt: null,
contexts: filteredNoteContexts
});
}
await options.save("openNoteContexts", JSON.stringify(savedWindows));
// if there's a notePath in the URL, make sure it's open and active
// (useful, for e.g., opening clipped notes from clipper or opening link in an extra window)
if (parsedFromUrl.notePath) {

View File

@@ -1,17 +1,18 @@
import appContext from "./components/app_context.js";
import utils from "./services/utils.js";
import noteTooltipService from "./services/note_tooltip.js";
import bundleService from "./services/bundle.js";
import toastService from "./services/toast.js";
import noteAutocompleteService from "./services/note_autocomplete.js";
import electronContextMenu from "./menus/electron_context_menu.js";
import glob from "./services/glob.js";
import { t } from "./services/i18n.js";
import options from "./services/options.js";
import "autocomplete.js/index_jquery.js";
import type ElectronRemote from "@electron/remote";
import type Electron from "electron";
import "boxicons/css/boxicons.min.css";
import "autocomplete.js/index_jquery.js";
import appContext from "./components/app_context.js";
import electronContextMenu from "./menus/electron_context_menu.js";
import bundleService from "./services/bundle.js";
import glob from "./services/glob.js";
import { t } from "./services/i18n.js";
import noteAutocompleteService from "./services/note_autocomplete.js";
import noteTooltipService from "./services/note_tooltip.js";
import options from "./services/options.js";
import toastService from "./services/toast.js";
import utils from "./services/utils.js";
await appContext.earlyInit();

View File

@@ -582,6 +582,10 @@ export default class FNote {
}
getIcon() {
return `tn-icon ${this.#getIconInternal()}`;
}
#getIconInternal() {
const iconClassLabels = this.getLabels("iconClass");
const workspaceIconClass = this.getWorkspaceIconClass();

Binary file not shown.

View File

@@ -1,9 +1,9 @@
import appContext from "./components/app_context.js";
import noteAutocompleteService from "./services/note_autocomplete.js";
import glob from "./services/glob.js";
import "boxicons/css/boxicons.min.css";
import "autocomplete.js/index_jquery.js";
import appContext from "./components/app_context.js";
import glob from "./services/glob.js";
import noteAutocompleteService from "./services/note_autocomplete.js";
glob.setupGlobs();
await appContext.earlyInit();

View File

@@ -0,0 +1,498 @@
.bx-ul
{
margin-left: 2em;
padding-left: 0;
list-style: none;
}
.bx-ul > li
{
position: relative;
}
.bx-ul .bx
{
font-size: inherit;
line-height: inherit;
position: absolute;
left: -2em;
width: 2em;
text-align: center;
}
@-webkit-keyframes spin
{
0%
{
-webkit-transform: rotate(0);
transform: rotate(0);
}
100%
{
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes spin
{
0%
{
-webkit-transform: rotate(0);
transform: rotate(0);
}
100%
{
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-webkit-keyframes burst
{
0%
{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
90%
{
-webkit-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
}
@keyframes burst
{
0%
{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
90%
{
-webkit-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
}
@-webkit-keyframes flashing
{
0%
{
opacity: 1;
}
45%
{
opacity: 0;
}
90%
{
opacity: 1;
}
}
@keyframes flashing
{
0%
{
opacity: 1;
}
45%
{
opacity: 0;
}
90%
{
opacity: 1;
}
}
@-webkit-keyframes fade-left
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(-20px);
transform: translateX(-20px);
opacity: 0;
}
}
@keyframes fade-left
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(-20px);
transform: translateX(-20px);
opacity: 0;
}
}
@-webkit-keyframes fade-right
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(20px);
transform: translateX(20px);
opacity: 0;
}
}
@keyframes fade-right
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(20px);
transform: translateX(20px);
opacity: 0;
}
}
@-webkit-keyframes fade-up
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
opacity: 0;
}
}
@keyframes fade-up
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
opacity: 0;
}
}
@-webkit-keyframes fade-down
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(20px);
transform: translateY(20px);
opacity: 0;
}
}
@keyframes fade-down
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(20px);
transform: translateY(20px);
opacity: 0;
}
}
@-webkit-keyframes tada
{
from
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20%
{
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
}
30%,
50%,
70%,
90%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
}
40%,
60%,
80%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
}
to
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes tada
{
from
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20%
{
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
}
30%,
50%,
70%,
90%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
}
40%,
60%,
80%
{
-webkit-transform: rotate3d(0, 0, 1, -10deg);
transform: rotate3d(0, 0, 1, -10deg);
}
to
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
.bx-spin
{
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
.bx-spin-hover:hover
{
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
.bx-tada
{
-webkit-animation: tada 1.5s ease infinite;
animation: tada 1.5s ease infinite;
}
.bx-tada-hover:hover
{
-webkit-animation: tada 1.5s ease infinite;
animation: tada 1.5s ease infinite;
}
.bx-flashing
{
-webkit-animation: flashing 1.5s infinite linear;
animation: flashing 1.5s infinite linear;
}
.bx-flashing-hover:hover
{
-webkit-animation: flashing 1.5s infinite linear;
animation: flashing 1.5s infinite linear;
}
.bx-burst
{
-webkit-animation: burst 1.5s infinite linear;
animation: burst 1.5s infinite linear;
}
.bx-burst-hover:hover
{
-webkit-animation: burst 1.5s infinite linear;
animation: burst 1.5s infinite linear;
}
.bx-fade-up
{
-webkit-animation: fade-up 1.5s infinite linear;
animation: fade-up 1.5s infinite linear;
}
.bx-fade-up-hover:hover
{
-webkit-animation: fade-up 1.5s infinite linear;
animation: fade-up 1.5s infinite linear;
}
.bx-fade-down
{
-webkit-animation: fade-down 1.5s infinite linear;
animation: fade-down 1.5s infinite linear;
}
.bx-fade-down-hover:hover
{
-webkit-animation: fade-down 1.5s infinite linear;
animation: fade-down 1.5s infinite linear;
}
.bx-fade-left
{
-webkit-animation: fade-left 1.5s infinite linear;
animation: fade-left 1.5s infinite linear;
}
.bx-fade-left-hover:hover
{
-webkit-animation: fade-left 1.5s infinite linear;
animation: fade-left 1.5s infinite linear;
}
.bx-fade-right
{
-webkit-animation: fade-right 1.5s infinite linear;
animation: fade-right 1.5s infinite linear;
}
.bx-fade-right-hover:hover
{
-webkit-animation: fade-right 1.5s infinite linear;
animation: fade-right 1.5s infinite linear;
}
.bx-xs
{
font-size: 1rem!important;
}
.bx-sm
{
font-size: 1.55rem!important;
}
.bx-md
{
font-size: 2.25rem!important;
}
.bx-lg
{
font-size: 3.0rem!important;
}
.bx-fw
{
font-size: 1.2857142857em;
line-height: .8em;
width: 1.2857142857em;
height: .8em;
margin-top: -.2em!important;
vertical-align: middle;
}
.bx-pull-left
{
float: left;
margin-right: .3em!important;
}
.bx-pull-right
{
float: right;
margin-left: .3em!important;
}
.bx-rotate-90
{
transform: rotate(90deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=1)';
}
.bx-rotate-180
{
transform: rotate(180deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';
}
.bx-rotate-270
{
transform: rotate(270deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';
}
.bx-flip-horizontal
{
transform: scaleX(-1);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)';
}
.bx-flip-vertical
{
transform: scaleY(-1);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
}
.bx-border
{
padding: .25em;
border: .07em solid rgba(0,0,0,.1);
border-radius: .25em;
}
.bx-border-circle
{
padding: .25em;
border: .07em solid rgba(0,0,0,.1);
border-radius: 50%;
}
/** Custom icon **/
.bx-empty {
width: 1em;
display: inline-block;
}

View File

@@ -1,3 +1,5 @@
@import "./boxicons-compat.css";
@font-face {
font-family: Montserrat;
src: url(../fonts/Montserrat-Light.ttf);
@@ -1128,11 +1130,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
border-color: var(--main-border-color) !important;
}
.bx-empty {
width: 1em;
display: inline-block;
}
.modal-header {
padding: 0.5rem 1rem 0.5rem 1rem !important; /* make modal header padding slightly smaller */
}
@@ -1799,7 +1796,7 @@ button.close:hover {
display: none;
}
.reference-link .bx {
.reference-link .tn-icon {
position: relative;
top: 1px;
margin-inline-end: 3px;
@@ -2418,7 +2415,7 @@ footer.webview-footer button {
gap: 5px;
}
.right-pane-tab .tab-title .bx {
.right-pane-tab .tab-title .tn-icon {
font-size: 1.1em;
}
@@ -2546,18 +2543,11 @@ footer.webview-footer button {
inset-inline-end: 10px;
}
.content-floating-buttons button.bx {
.content-floating-buttons button.tn-icon {
font-size: 130%;
padding: 1px 10px 1px 10px;
}
/* Customized icons */
.bx-tn-toc::before {
content: "\ec24";
transform: rotate(180deg);
}
/* CK Editor */
/* Insert text snippet: limit the width of the listed items to avoid overly long names */

View File

@@ -134,7 +134,7 @@ body.backdrop-effects-disabled {
white-space-collapse: discard;
}
.dropdown-menu.tn-dropdown-menu .bx {
.dropdown-menu.tn-dropdown-menu .dropdown-item .tn-icon {
margin-inline-end: 6px;
}
@@ -249,7 +249,7 @@ html body .dropdown-item[disabled] {
}
/* Menu item icon */
.dropdown-item .bx {
.dropdown-item .tn-icon {
translate: 0 var(--menu-item-icon-vert-offset);
color: var(--menu-item-icon-color) !important;
font-size: 1.1em;
@@ -496,7 +496,7 @@ li.dropdown-item a.dropdown-item-button {
border: unset;
}
li.dropdown-item a.dropdown-item-button.bx {
li.dropdown-item a.dropdown-item-button.tn-icon {
color: var(--menu-text-color) !important;
}
@@ -557,13 +557,13 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
padding-top: 0;
}
#toast-container .toast:not(.no-title) .bx {
#toast-container .toast:not(.no-title) .tn-icon {
margin-inline-end: 0.5em;
font-size: 1.1em;
opacity: 0.85;
}
#toast-container .toast.no-title .bx {
#toast-container .toast.no-title .tn-icon {
margin-inline-end: 0;
font-size: 1.3em;
}
@@ -754,7 +754,7 @@ li.dropdown-item a.dropdown-item-button:focus-visible {
margin-bottom: 0;
}
.note-list-wrapper .note-book-card .bx {
.note-list-wrapper .note-book-card .tn-icon {
color: var(--left-pane-icon-color) !important;
}

View File

@@ -423,6 +423,6 @@ div.tn-tool-dialog {
font-size: unset;
}
.note-type-chooser-dialog div.note-type-dropdown .dropdown-item span.bx {
.note-type-chooser-dialog div.note-type-dropdown .dropdown-item span.tn-icon {
margin-inline-end: .25em;
}
}

View File

@@ -62,10 +62,10 @@ button.ck.ck-button:is(.ck-button-action, .ck-button-save, .ck-button-cancel, .c
}
/* Button's icon */
button.btn.btn-primary span.bx,
button.btn.btn-secondary span.bx,
button.btn.btn-sm span.bx,
button.btn.btn-success span.bx {
button.btn.btn-primary span.tn-icon,
button.btn.btn-secondary span.tn-icon,
button.btn.btn-sm span.tn-icon,
button.btn.btn-success span.tn-icon {
color: var(--cmd-button-icon-color);
padding-inline-end: 0.35em;
font-size: 1.2em;

View File

@@ -151,6 +151,11 @@
--options-title-font-size: .75rem;
--options-title-offset: 13px;
}
.note-split.options {
--preferred-max-content-width: var(--options-card-max-width);
}
/* Create a gap at the top of the option pages */
.note-detail-content-widget-content.options>*:first-child {
margin-top: var(--options-first-item-top-margin, 1em);
@@ -185,10 +190,6 @@ body.experimental-feature-new-layout .note-detail-content-widget-content.options
padding: var(--options-card-padding);
}
body.prefers-centered-content .options-section:not(.tn-no-card) {
margin-inline: auto;
}
body.desktop .options-section:not(.tn-no-card) {
min-width: var(--options-card-min-width);
max-width: var(--options-card-max-width);

View File

@@ -497,7 +497,7 @@ div.bookmark-folder-widget .note-link:hover a {
}
/* The item's icon */
div.bookmark-folder-widget .note-link .bx {
div.bookmark-folder-widget .note-link .tn-icon {
color: var(--menu-item-icon-color);
font-size: 1.2em;
}

View File

@@ -229,11 +229,11 @@ span.fancytree-node.archived {
opacity: 0.6;
}
.fancytree-node:hover .bx.tree-item-button {
.fancytree-node:hover .tn-icon.tree-item-button {
display: inline-block;
}
.bx.tree-item-button {
.tn-icon.tree-item-button {
display: none;
font-size: 120%;
cursor: pointer;
@@ -243,7 +243,7 @@ span.fancytree-node.archived {
border-radius: 5px;
}
.unhoist-button.bx.tree-item-button {
.unhoist-button.tn-icon.tree-item-button {
margin-inline-start: 0; /* unhoist button is on the left and doesn't need more margin */
display: block; /* keep always visible */
}

View File

@@ -223,7 +223,6 @@
"backlink_other": ""
},
"note_icon": {
"category": "الفئة:",
"search": "بحث:",
"change_note_icon": "تغيير ايقونة الملاحظة",
"reset-default": "اعادة تعيين الى الايقونة الافتراضية"

View File

@@ -146,7 +146,6 @@
"relation": "relació"
},
"note_icon": {
"category": "Categoria:",
"search": "Cerca:"
},
"basic_properties": {

View File

@@ -764,7 +764,6 @@
},
"note_icon": {
"change_note_icon": "更改笔记图标",
"category": "类别:",
"search": "搜索:",
"reset-default": "重置为默认图标"
},

View File

@@ -21,7 +21,10 @@
},
"bundle-error": {
"title": "Benutzerdefiniertes Skript konnte nicht geladen werden",
"message": "Skript aus der Notiz \"{{title}}\" mit der ID \"{{id}}\", konnte nicht ausgeführt werden wegen:\n\n{{message}}"
"message": "Skript konnte nicht ausgeführt werden wegen:\n\n{{message}}"
},
"widget-list-error": {
"title": "Abruf der Liste von Widgets vom Server ist fehlgeschlagen"
}
},
"add_link": {
@@ -746,7 +749,6 @@
},
"note_icon": {
"change_note_icon": "Notiz-Icon ändern",
"category": "Kategorie:",
"search": "Suche:",
"reset-default": "Standard wiederherstellen"
},

View File

@@ -765,9 +765,16 @@
},
"note_icon": {
"change_note_icon": "Change note icon",
"category": "Category:",
"search": "Search:",
"reset-default": "Reset to default icon"
"search_placeholder_one": "Search {{number}} icons across {{count}} packs",
"search_placeholder_other": "Search {{number}} icons across {{count}} packs",
"search_placeholder_filtered": "Search {{number}} icons in {{name}}",
"reset-default": "Reset to default icon",
"filter": "Filter",
"filter-none": "All icons",
"filter-default": "Default icons",
"icon_tooltip": "{{name}}\nIcon pack: {{iconPack}}",
"no_results": "No icons found."
},
"basic_properties": {
"note_type": "Note type",
@@ -1613,7 +1620,7 @@
"will_be_deleted_in": "This attachment will be automatically deleted in {{time}}",
"will_be_deleted_soon": "This attachment will be automatically deleted soon",
"deletion_reason": ", because the attachment is not linked in the note's content. To prevent deletion, add the attachment link back into the content or convert the attachment into note.",
"role_and_size": "Role: {{role}}, Size: {{size}}",
"role_and_size": "Role: {{role}}, size: {{size}}, MIME: {{- mimeType}}",
"link_copied": "Attachment link copied to clipboard.",
"unrecognized_role": "Unrecognized attachment role '{{role}}'."
},

View File

@@ -749,7 +749,6 @@
},
"note_icon": {
"change_note_icon": "Cambiar icono de nota",
"category": "Categoría:",
"search": "Búsqueda:",
"reset-default": "Restablecer a icono por defecto"
},

View File

@@ -756,7 +756,6 @@
},
"note_icon": {
"change_note_icon": "Changer l'icône de note",
"category": "Catégorie :",
"search": "Recherche :",
"reset-default": "Réinitialiser l'icône par défaut"
},

View File

@@ -1333,7 +1333,6 @@
},
"note_icon": {
"change_note_icon": "Cambia icona nota",
"category": "Categoria:",
"search": "Ricerca:",
"reset-default": "Ripristina l'icona predefinita"
},

View File

@@ -152,7 +152,6 @@
},
"note_icon": {
"change_note_icon": "ノートアイコンの変更",
"category": "カテゴリー:",
"search": "検索:",
"reset-default": "アイコンをデフォルトに戻す"
},
@@ -161,7 +160,7 @@
"editable": "編集可能",
"basic_properties": "基本プロパティ",
"language": "言語",
"configure_code_notes": "コードノートを設定しています..."
"configure_code_notes": "コードノートを設定..."
},
"i18n": {
"title": "ローカライゼーション",

View File

@@ -1286,7 +1286,6 @@
},
"note_icon": {
"change_note_icon": "Zmień ikonę notatki",
"category": "Kategoria:",
"search": "Szukaj:",
"reset-default": "Przywróć domyślną ikonę"
},

View File

@@ -724,7 +724,6 @@
},
"note_icon": {
"change_note_icon": "Alterar ícone da nota",
"category": "Categoria:",
"search": "Pesquisa:",
"reset-default": "Redefinir para o ícone padrão"
},

View File

@@ -1008,7 +1008,6 @@
},
"note_icon": {
"change_note_icon": "Alterar ícone da nota",
"category": "Categoria:",
"search": "Busca:",
"reset-default": "Redefinir para o ícone padrão"
},

View File

@@ -1483,7 +1483,6 @@
},
"note_icon": {
"change_note_icon": "Schimbă iconița notiței",
"category": "Categorie:",
"reset-default": "Resetează la iconița implicită",
"search": "Căutare:"
},

View File

@@ -1010,7 +1010,6 @@
"backlink_many": "{{count}} обратных ссылок"
},
"note_icon": {
"category": "Категория:",
"search": "Поиск:",
"change_note_icon": "Изменить иконку заметки",
"reset-default": "Сбросить к значку по умолчанию"

View File

@@ -21,7 +21,7 @@
},
"bundle-error": {
"title": "載入自訂腳本失敗",
"message": "來自 ID 為 \"{{id}}\"、標題為 \"{{title}}\" 的筆記的腳本因以下原因無法執行:\n\n{{message}}"
"message": "腳本因以下原因無法執行:\n\n{{message}}"
},
"widget-list-error": {
"title": "無法從伺服器取得元件清單"
@@ -761,7 +761,6 @@
},
"note_icon": {
"change_note_icon": "更改筆記圖標",
"category": "類別:",
"search": "搜尋:",
"reset-default": "重置為預設圖標"
},

View File

@@ -849,7 +849,6 @@
},
"note_icon": {
"change_note_icon": "Змінити значок нотатки",
"category": "Категорія:",
"search": "Пошук:",
"reset-default": "Скинути значок до стандартного значення"
},

View File

@@ -17,5 +17,3 @@ declare module "*?raw" {
var content: string;
export default content;
}
declare module "boxicons/css/boxicons.min.css" { }

View File

@@ -1,3 +1,5 @@
import { IconRegistry } from "@triliumnext/commons";
import appContext, { AppContext } from "./components/app_context";
import type FNote from "./entities/fnote";
import type { PrintReport } from "./print";
@@ -34,6 +36,7 @@ interface CustomGlobals {
isProtectedSessionAvailable: boolean;
isDev: boolean;
isMainWindow: boolean;
windowId: string;
maxEntityChangeIdAtLoad: number;
maxEntityChangeSyncIdAtLoad: number;
assetPath: string;
@@ -46,6 +49,7 @@ interface CustomGlobals {
linter: typeof lint;
hasNativeTitleBar: boolean;
isRtl: boolean;
iconRegistry: IconRegistry;
}
type RequireMethod = (moduleName: string) => any;

View File

@@ -142,7 +142,7 @@ function ShowTocWidgetButton({ note, noteContext, isDefaultViewMode }: FloatingB
return isEnabled && <FloatingButton
text={t("show_toc_widget_button.show_toc")}
icon="bx bx-tn-toc"
icon="bx bx-spreadsheet bx-rotate-180"
onClick={() => {
if (noteContext?.viewScope && noteContext.noteId) {
noteContext.viewScope.tocTemporarilyHidden = false;

View File

@@ -12,7 +12,7 @@ body.prefers-centered-content .note-list-widget:not(.full-height) {
}
.note-list-widget .note-list {
padding: 10px;
padding-block: 10px;
}
.note-list-widget.full-height,

View File

@@ -40,7 +40,7 @@
z-index: -1;
}
.geo-map-container .leaflet-div-icon .bx {
.geo-map-container .leaflet-div-icon .tn-icon {
position: absolute;
top: 3px;
inset-inline-start: 2px;

View File

@@ -74,11 +74,11 @@ describe("Presentation model", () => {
});
it("rewrites links to other slides", () => {
expect(data.slides[1].content.__html).toStrictEqual(`<div class="ck-content"><p>Go to&nbsp;<a class="reference-link" href="#/slide-slide1"><span><span class="bx bx-folder"></span>First slide</span></a>.</p></div>`);
expect(data.slides[1].verticalSlides![0].content.__html).toStrictEqual(`<div class="ck-content"><p>Go to&nbsp;<a class="reference-link" href="#/slide-slide2"><span><span class="bx bx-note"></span>First-sub</span></a>.</p></div>`);
expect(data.slides[1].content.__html).toStrictEqual(`<div class="ck-content"><p>Go to&nbsp;<a class="reference-link" href="#/slide-slide1"><span><span class="tn-icon bx bx-folder"></span>First slide</span></a>.</p></div>`);
expect(data.slides[1].verticalSlides![0].content.__html).toStrictEqual(`<div class="ck-content"><p>Go to&nbsp;<a class="reference-link" href="#/slide-slide2"><span><span class="tn-icon bx bx-note"></span>First-sub</span></a>.</p></div>`);
});
it("rewrites links even if they are not part of the slideshow", () => {
expect(data.slides[0].verticalSlides![0].content.__html).toStrictEqual(`<div class="ck-content"><p>Go to&nbsp;<a class="reference-link" href="#/slide-other"><span><span class="bx bx-note"></span>Other note</span></a>.</p></div>`);
expect(data.slides[0].verticalSlides![0].content.__html).toStrictEqual(`<div class="ck-content"><p>Go to&nbsp;<a class="reference-link" href="#/slide-other"><span><span class="tn-icon bx bx-note"></span>Other note</span></a>.</p></div>`);
});
});

View File

@@ -2,6 +2,13 @@
overflow: auto;
scroll-behavior: smooth;
position: relative;
> .inline-title,
> .note-detail > .note-detail-editable-text,
> .note-list-widget:not(.full-height) {
padding-inline: 24px;
}
}
.note-split.type-code:not(.mime-text-x-sqlite) {

File diff suppressed because it is too large Load Diff

View File

@@ -35,7 +35,7 @@
align-items: center;
min-width: 0;
.bx {
.tn-icon {
margin-inline: 6px;
}
@@ -55,7 +55,7 @@
.icon-action {
font-size: .9rem !important;
.bxs-chevron-right {
&.breadcrumb-separator {
transform: translateY(8%);
&::before {

View File

@@ -191,7 +191,7 @@ function BreadcrumbSeparator(props: BreadcrumbSeparatorProps) {
<Dropdown
text={<Icon icon="bx bxs-chevron-right" />}
noSelectButtonStyle
buttonClassName="icon-action"
buttonClassName="icon-action breadcrumb-separator"
hideToggleArrow
dropdownContainerClassName="tn-dropdown-menu-scrollable breadcrumb-child-list"
dropdownOptions={{ popperConfig: { strategy: "fixed", placement: "top" } }}

View File

@@ -10,7 +10,6 @@
max-width: var(--max-content-width);
container-type: inline-size;
padding-top: 20px;
padding-inline-start: 24px;
& > .inline-title-row {
--icon-size: 35px;

View File

@@ -4,12 +4,21 @@ body.experimental-feature-new-layout {
}
.title-actions {
--title-actions-padding-start: 12px;
--title-actions-padding-end: 8px;
display: flex;
max-width: var(--max-content-width);
flex-direction: column;
gap: 0.5em;
padding-inline: var(--title-actions-padding-start) var(--title-actions-padding-end);
body.prefers-centered-content .note-split:not(.full-content-width) & {
margin-inline: auto;
}
&:not(:empty) {
padding: 0.75em 15px;
padding-block: 0.75em;
}
.edited-notes {
@@ -40,5 +49,11 @@ body.experimental-feature-new-layout {
padding: 0;
}
}
> .collapsible,
> .note-type-switcher {
padding-inline-start: calc(24px - var(--title-actions-padding-start));
padding-inline-end: calc(24px - var(--title-actions-padding-end));
}
}
}

View File

@@ -9,7 +9,7 @@
background-color: var(--left-pane-background-color);
padding-inline: 0.25em;
font-size: 0.85em;
> .breadcrumb {
flex-grow: 1;
--icon-button-size: 23px;
@@ -104,7 +104,7 @@
/* Note path card */
li {
--border-radius: 6px;
position: relative;
background: var(--card-background-color);
padding: 8px 20px 8px 25px;
@@ -120,7 +120,7 @@
& + li {
margin-top: 2px;
}
/* Current path arrow */
&.path-current::before {
position: absolute;
@@ -180,7 +180,7 @@
&:last-child {
border-radius: 0 0 var(--border-radius) var(--border-radius);
}
/* Card header */
& > span:first-child {
display: block;
@@ -202,7 +202,7 @@
}
/* Note icon */
> .bx {
> .tn-icon {
color: var(--menu-item-icon-color);
}

View File

@@ -32,17 +32,14 @@ div.note-icon-widget {
}
.note-icon-widget .filter-row {
padding-top: 10px;
padding-bottom: 10px;
padding-inline-end: 20px;
padding: 10px;
display: flex;
align-items: baseline;
align-items: center;
gap: 1em;
}
.note-icon-widget .filter-row span {
display: block;
padding-inline-start: 15px;
padding-inline-end: 15px;
font-weight: bold;
}
@@ -75,6 +72,14 @@ div.note-icon-widget {
height: 1em;
}
.note-icon-widget {
.no-results {
padding: 20px;
text-align: center;
color: var(--muted-text-color);
}
}
body.experimental-feature-new-layout {
.note-icon-widget button.note-icon {
--input-focus-outline-color: var(--note-icon-hover-background-color);
@@ -111,4 +116,4 @@ body.experimental-feature-new-layout {
transition: background 200ms ease-out;
}
}
}
}

View File

@@ -1,37 +1,36 @@
import Dropdown from "./react/Dropdown";
import "./note_icon.css";
import { IconRegistry } from "@triliumnext/commons";
import { Dropdown as BootstrapDropdown } from "bootstrap";
import clsx from "clsx";
import { t } from "i18next";
import { useNoteContext, useNoteLabel } from "./react/hooks";
import { useEffect, useRef, useState } from "preact/hooks";
import server from "../services/server";
import type { Category, Icon } from "./icon_list";
import FormTextBox from "./react/FormTextBox";
import FormSelect from "./react/FormSelect";
import { CSSProperties, RefObject } from "preact";
import { useEffect, useMemo, useRef, useState } from "preact/hooks";
import { CellComponentProps, Grid } from "react-window";
import FNote from "../entities/fnote";
import attributes from "../services/attributes";
import Button from "./react/Button";
import server from "../services/server";
import ActionButton from "./react/ActionButton";
import Dropdown from "./react/Dropdown";
import { FormDropdownDivider, FormListItem } from "./react/FormList";
import FormTextBox from "./react/FormTextBox";
import { useNoteContext, useNoteLabel, useStaticTooltip } from "./react/hooks";
interface IconToCountCache {
iconClassToCountMap: Record<string, number>;
}
interface IconData {
iconToCount: Record<string, number>;
categories: Category[];
icons: Icon[];
}
let fullIconData: {
categories: Category[];
icons: Icon[];
};
let iconToCountCache!: Promise<IconToCountCache> | null;
type IconWithName = (IconRegistry["sources"][number]["icons"][number] & { iconPack: string });
export default function NoteIcon() {
const { note, viewScope } = useNoteContext();
const [ icon, setIcon ] = useState<string | null | undefined>();
const [ iconClass ] = useNoteLabel(note, "iconClass");
const [ workspaceIconClass ] = useNoteLabel(note, "workspaceIconClass");
const dropdownRef = useRef<BootstrapDropdown>(null);
useEffect(() => {
setIcon(note?.getIcon());
@@ -41,130 +40,219 @@ export default function NoteIcon() {
<Dropdown
className="note-icon-widget"
title={t("note_icon.change_note_icon")}
dropdownContainerStyle={{ width: "610px" }}
dropdownRef={dropdownRef}
dropdownContainerStyle={{ width: "620px" }}
dropdownOptions={{ autoClose: "outside" }}
buttonClassName={`note-icon tn-focusable-button ${icon ?? "bx bx-empty"}`}
hideToggleArrow
disabled={viewScope?.viewMode !== "default"}
>
{ note && <NoteIconList note={note} /> }
{ note && <NoteIconList note={note} dropdownRef={dropdownRef} /> }
</Dropdown>
)
);
}
function NoteIconList({ note }: { note: FNote }) {
function NoteIconList({ note, dropdownRef }: {
note: FNote,
dropdownRef: RefObject<BootstrapDropdown>;
}) {
const searchBoxRef = useRef<HTMLInputElement>(null);
const iconListRef = useRef<HTMLDivElement>(null);
const [ search, setSearch ] = useState<string>();
const [ categoryId, setCategoryId ] = useState<string>("0");
const [ iconData, setIconData ] = useState<IconData>();
const [ filterByPrefix, setFilterByPrefix ] = useState<string | null>(null);
useStaticTooltip(iconListRef, {
selector: "span",
customClass: "pre-wrap-text",
animation: false,
title() { return this.getAttribute("title") || ""; },
});
useEffect(() => {
async function loadIcons() {
if (!fullIconData) {
fullIconData = (await import("./icon_list.js")).default;
}
// Filter by text and/or category.
let icons: Icon[] = fullIconData.icons;
const processedSearch = search?.trim()?.toLowerCase();
if (processedSearch || categoryId) {
icons = icons.filter((icon) => {
if (categoryId !== "0" && String(icon.category_id) !== categoryId) {
return false;
}
if (processedSearch) {
if (!icon.name.includes(processedSearch) &&
!icon.term?.find((t) => t.includes(processedSearch))) {
return false;
}
}
return true;
});
}
// Sort by count.
const iconToCount = await getIconToCountMap();
if (iconToCount) {
icons.sort((a, b) => {
const countA = iconToCount[a.className ?? ""] || 0;
const countB = iconToCount[b.className ?? ""] || 0;
return countB - countA;
});
}
setIconData({
iconToCount,
icons,
categories: fullIconData.categories
})
}
loadIcons();
}, [ search, categoryId ]);
const allIcons = useAllIcons();
const filteredIcons = useFilteredIcons(allIcons, search, filterByPrefix);
return (
<>
<div class="filter-row">
<span>{t("note_icon.category")}</span>
<FormSelect
name="icon-category"
values={fullIconData?.categories ?? []}
currentValue={categoryId} onChange={setCategoryId}
keyProperty="id" titleProperty="name"
/>
<span>{t("note_icon.search")}</span>
<FormTextBox
inputRef={searchBoxRef}
type="text"
name="icon-search"
placeholder={ filterByPrefix
? t("note_icon.search_placeholder_filtered", {
number: filteredIcons.length ?? 0,
name: glob.iconRegistry.sources.find(s => s.prefix === filterByPrefix)?.name ?? ""
})
: t("note_icon.search_placeholder", { number: filteredIcons.length ?? 0, count: glob.iconRegistry.sources.length })}
currentValue={search} onChange={setSearch}
autoFocus
/>
</div>
<div
class="icon-list"
onClick={(e) => {
const clickedTarget = e.target as HTMLElement;
if (!clickedTarget.classList.contains("bx")) {
return;
}
const iconClass = Array.from(clickedTarget.classList.values()).join(" ");
if (note) {
const attributeToSet = note.hasOwnedLabel("workspace") ? "workspaceIconClass" : "iconClass";
attributes.setLabel(note.noteId, attributeToSet, iconClass);
}
}}
>
{getIconLabels(note).length > 0 && (
<div style={{ textAlign: "center" }}>
<Button
<ActionButton
icon="bx bx-reset"
text={t("note_icon.reset-default")}
onClick={() => {
if (!note) {
return;
}
if (!note) return;
for (const label of getIconLabels(note)) {
attributes.removeAttributeById(note.noteId, label.attributeId);
}
dropdownRef?.current?.hide();
}}
/>
</div>
)}
{(iconData?.icons ?? []).map(({className, name}) => (
<span class={`bx ${className}`} title={name} />
))}
{glob.iconRegistry.sources.length > 0 && <Dropdown
buttonClassName="bx bx-filter-alt"
hideToggleArrow
noSelectButtonStyle
noDropdownListStyle
iconAction
title={t("note_icon.filter")}
>
<IconFilterContent filterByPrefix={filterByPrefix} setFilterByPrefix={setFilterByPrefix} />
</Dropdown>}
</div>
<div
class="icon-list"
ref={iconListRef}
onClick={(e) => {
// Make sure we are not clicking on something else than a button.
const clickedTarget = e.target as HTMLElement;
if (!clickedTarget.classList.contains("tn-icon")) return;
const iconClass = Array.from(clickedTarget.classList.values()).filter(c => c !== "tn-icon").join(" ");
if (note) {
const attributeToSet = note.hasOwnedLabel("workspace") ? "workspaceIconClass" : "iconClass";
attributes.setLabel(note.noteId, attributeToSet, iconClass);
}
dropdownRef?.current?.hide();
}}
>
{filteredIcons.length ? (
<Grid
columnCount={12}
columnWidth={48}
rowCount={Math.ceil(filteredIcons.length / 12)}
rowHeight={48}
cellComponent={IconItemCell}
cellProps={{
filteredIcons
}}
/>
) : (
<div class="no-results">{t("note_icon.no_results")}</div>
)}
</div>
</>
);
}
function IconItemCell({ rowIndex, columnIndex, style, filteredIcons }: CellComponentProps<{
filteredIcons: IconWithName[];
}>): React.JSX.Element {
const iconIndex = rowIndex * 12 + columnIndex;
const iconData = filteredIcons[iconIndex] as IconWithName | undefined;
if (!iconData) return <></>;
const { id, terms, iconPack } = iconData;
return (
<span
key={id}
class={clsx(id, "tn-icon")}
title={t("note_icon.icon_tooltip", { name: terms?.[0] ?? id, iconPack })}
style={style as CSSProperties}
/>
);
}
function IconFilterContent({ filterByPrefix, setFilterByPrefix }: {
filterByPrefix: string | null;
setFilterByPrefix: (value: string | null) => void;
}) {
return (
<>
<FormListItem
checked={filterByPrefix === null}
onClick={() => setFilterByPrefix(null)}
>{t("note_icon.filter-none")}</FormListItem>
<FormListItem
checked={filterByPrefix === "bx"}
onClick={() => setFilterByPrefix("bx")}
>{t("note_icon.filter-default")}</FormListItem>
<FormDropdownDivider />
{glob.iconRegistry.sources.map(({ prefix, name, icon }) => (
prefix !== "bx" && <FormListItem
key={prefix}
onClick={() => setFilterByPrefix(prefix)}
icon={icon}
checked={filterByPrefix === prefix}
>{name}</FormListItem>
))}
</>
);
}
function useAllIcons() {
const [ allIcons, setAllIcons ] = useState<IconWithName[]>();
useEffect(() => {
getIconToCountMap().then((iconsToCount) => {
const allIcons = [
...glob.iconRegistry.sources.flatMap(s => s.icons.map((i) => ({
...i,
iconPack: s.name,
})))
];
// Sort by count.
if (iconsToCount) {
allIcons.sort((a, b) => {
const countA = iconsToCount[a.id ?? ""] || 0;
const countB = iconsToCount[b.id ?? ""] || 0;
return countB - countA;
});
}
setAllIcons(allIcons);
});
}, []);
return allIcons;
}
function useFilteredIcons(allIcons: IconWithName[] | undefined, search: string | undefined, filterByPrefix: string | null) {
// Filter by text and/or icon pack.
const filteredIcons = useMemo(() => {
let icons: IconWithName[] = allIcons ?? [];
const processedSearch = search?.trim()?.toLowerCase();
if (processedSearch || filterByPrefix !== null) {
icons = icons.filter((icon) => {
if (filterByPrefix) {
if (!icon.id?.startsWith(`${filterByPrefix} `)) {
return false;
}
}
if (processedSearch) {
if (!icon.terms?.some((t) => t.includes(processedSearch))) {
return false;
}
}
return true;
});
}
return icons;
}, [ allIcons, search, filterByPrefix ]);
return filteredIcons;
}
async function getIconToCountMap() {
if (!iconToCountCache) {
iconToCountCache = server.get<IconToCountCache>("other/icon-usage");
@@ -180,5 +268,5 @@ function getIconLabels(note: FNote) {
}
return note.getOwnedLabels()
.filter((label) => ["workspaceIconClass", "iconClass"]
.includes(label.name));
.includes(label.name));
}

View File

@@ -92,7 +92,7 @@ body.experimental-feature-new-layout {
height: var(--size);
padding: 0;
.bx {
.tn-icon {
opacity: 1;
margin: 0;
}

View File

@@ -1,38 +1,39 @@
import hoistedNoteService from "../services/hoisted_note.js";
import treeService from "../services/tree.js";
import utils from "../services/utils.js";
import contextMenu from "../menus/context_menu.js";
import froca from "../services/froca.js";
import branchService from "../services/branches.js";
import ws from "../services/ws.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
import server from "../services/server.js";
import noteCreateService from "../services/note_create.js";
import toastService from "../services/toast.js";
import appContext, { type CommandListenerData, type EventData } from "../components/app_context.js";
import keyboardActionsService from "../services/keyboard_actions.js";
import clipboard from "../services/clipboard.js";
import protectedSessionService from "../services/protected_session.js";
import linkService from "../services/link.js";
import options from "../services/options.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import dialogService from "../services/dialog.js";
import shortcutService from "../services/shortcuts.js";
import { t } from "../services/i18n.js";
import type FBranch from "../entities/fbranch.js";
import type LoadResults from "../services/load_results.js";
import type FNote from "../entities/fnote.js";
import type { NoteType } from "../entities/fnote.js";
import type { AttributeRow, BranchRow } from "../services/load_results.js";
import type { SetNoteOpts } from "../components/note_context.js";
import type { TouchBarItem } from "../components/touch_bar.js";
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
import "jquery.fancytree";
import "jquery.fancytree/dist/modules/jquery.fancytree.dnd5.js";
import "jquery.fancytree/dist/modules/jquery.fancytree.clones.js";
import "jquery.fancytree/dist/modules/jquery.fancytree.filter.js";
import "../stylesheets/tree.css";
import appContext, { type CommandListenerData, type EventData } from "../components/app_context.js";
import type { SetNoteOpts } from "../components/note_context.js";
import type { TouchBarItem } from "../components/touch_bar.js";
import type FBranch from "../entities/fbranch.js";
import type FNote from "../entities/fnote.js";
import type { NoteType } from "../entities/fnote.js";
import contextMenu from "../menus/context_menu.js";
import type { TreeCommandNames } from "../menus/tree_context_menu.js";
import branchService from "../services/branches.js";
import clipboard from "../services/clipboard.js";
import dialogService from "../services/dialog.js";
import froca from "../services/froca.js";
import hoistedNoteService from "../services/hoisted_note.js";
import { t } from "../services/i18n.js";
import keyboardActionsService from "../services/keyboard_actions.js";
import linkService from "../services/link.js";
import type LoadResults from "../services/load_results.js";
import type { AttributeRow, BranchRow } from "../services/load_results.js";
import noteCreateService from "../services/note_create.js";
import options from "../services/options.js";
import protectedSessionService from "../services/protected_session.js";
import protectedSessionHolder from "../services/protected_session_holder.js";
import server from "../services/server.js";
import shortcutService from "../services/shortcuts.js";
import toastService from "../services/toast.js";
import treeService from "../services/tree.js";
import utils from "../services/utils.js";
import ws from "../services/ws.js";
import NoteContextAwareWidget from "./note_context_aware_widget.js";
const TPL = /*html*/`
<div class="tree-wrapper">
<style>
@@ -242,7 +243,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
e.preventDefault();
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
activate: e.shiftKey ? true : false
activate: !!e.shiftKey
});
}
}
@@ -402,7 +403,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
} else if (ctrlKey) {
const notePath = treeService.getNotePath(node);
appContext.tabManager.openTabWithNoteWithHoisting(notePath, {
activate: event.shiftKey ? true : false
activate: !!event.shiftKey
});
} else if (event.altKey) {
node.setSelected(!node.isSelected());
@@ -499,9 +500,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
return ["before", "after"];
} else if (["_lbAvailableLaunchers", "_lbVisibleLaunchers"].includes(node.data.noteId)) {
return ["over"];
} else {
return true;
}
return true;
},
dragDrop: async (node, data) => {
if (
@@ -597,7 +598,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
clones: {
highlightActiveClones: true
},
enhanceTitle: async function (
async enhanceTitle (
event: Event,
data: {
node: Fancytree.FancytreeNode;
@@ -627,7 +628,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
const isHoistedNote = activeNoteContext && activeNoteContext.hoistedNoteId === note.noteId && note.noteId !== "root";
if (note.hasLabel("workspace") && !isHoistedNote) {
const $enterWorkspaceButton = $(`<span class="tree-item-button enter-workspace-button bx bx-door-open" title="${t("note_tree.hoist-this-note-workspace")}"></span>`).on(
const $enterWorkspaceButton = $(`<span class="tree-item-button tn-icon enter-workspace-button bx bx-door-open" title="${t("note_tree.hoist-this-note-workspace")}"></span>`).on(
"click",
cancelClickPropagation
);
@@ -636,7 +637,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
}
if (note.type === "search") {
const $refreshSearchButton = $(`<span class="tree-item-button refresh-search-button bx bx-refresh" title="${t("note_tree.refresh-saved-search-results")}"></span>`).on(
const $refreshSearchButton = $(`<span class="tree-item-button tn-icon refresh-search-button bx bx-refresh" title="${t("note_tree.refresh-saved-search-results")}"></span>`).on(
"click",
cancelClickPropagation
);
@@ -650,7 +651,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
&& !note.isLaunchBarConfig()
&& !note.noteId.startsWith("_help")
) {
const $createChildNoteButton = $(`<span class="tree-item-button add-note-button bx bx-plus" title="${t("note_tree.create-child-note")}"></span>`).on(
const $createChildNoteButton = $(`<span class="tree-item-button tn-icon add-note-button bx bx-plus" title="${t("note_tree.create-child-note")}"></span>`).on(
"click",
cancelClickPropagation
);
@@ -659,7 +660,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
}
if (isHoistedNote) {
const $unhoistButton = $(`<span class="tree-item-button unhoist-button bx bx-door-open" title="${t("note_tree.unhoist")}"></span>`).on("click", cancelClickPropagation);
const $unhoistButton = $(`<span class="tree-item-button tn-icon unhoist-button bx bx-door-open" title="${t("note_tree.unhoist")}"></span>`).on("click", cancelClickPropagation);
$span.append($unhoistButton);
}
@@ -1281,7 +1282,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
// activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded.
let movedActiveNode: Fancytree.FancytreeNode | null = null;
let parentsOfAddedNodes: Fancytree.FancytreeNode[] = [];
const parentsOfAddedNodes: Fancytree.FancytreeNode[] = [];
for (const branchRow of branchRows) {
if (branchRow.noteId) {
@@ -1452,10 +1453,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
if (branchId && branchId.startsWith("virt")) {
// in case of virtual branches there's nothing to update
return;
} else {
logError(`Cannot find branch=${branchId}`);
return;
}
logError(`Cannot find branch=${branchId}`);
return;
}
branch.isExpanded = isExpanded;
@@ -1594,7 +1595,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
// Trigger the event with the selected branch IDs
appContext.triggerEvent("editBranchPrefix", {
selectedOrActiveBranchIds: branchIds,
node: node
node
});
}
@@ -1779,12 +1780,12 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
#moveLaunchers(selectedOrActiveBranchIds: string[], desktopParent: string, mobileParent: string) {
const desktopLaunchersToMove = selectedOrActiveBranchIds.filter((branchId) => !branchId.startsWith("_lbMobile"));
if (desktopLaunchersToMove) {
branchService.moveToParentNote(desktopLaunchersToMove, "_lbRoot_" + desktopParent);
branchService.moveToParentNote(desktopLaunchersToMove, `_lbRoot_${ desktopParent}`);
}
const mobileLaunchersToMove = selectedOrActiveBranchIds.filter((branchId) => branchId.startsWith("_lbMobile"));
if (mobileLaunchersToMove) {
branchService.moveToParentNote(mobileLaunchersToMove, "_lbMobileRoot_" + mobileParent);
branchService.moveToParentNote(mobileLaunchersToMove, `_lbMobileRoot_${ mobileParent}`);
}
}
@@ -1829,7 +1830,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
selectedOrActiveBranchIds: this.getSelectedOrActiveBranchIds(node),
selectedOrActiveNoteIds: this.getSelectedOrActiveNoteIds(node)
});
}
};
const items: TouchBarItem[] = [
new TouchBar.TouchBarButton({

View File

@@ -22,7 +22,7 @@
}
}
.bx {
.tn-icon {
font-size: 1.2em;
margin-inline-end: 4px;
opacity: .6;

View File

@@ -5,7 +5,7 @@
white-space: normal;
}
span.bx {
span.tn-icon {
flex-shrink: 0;
}

View File

@@ -9,7 +9,7 @@ interface IconProps extends Pick<HTMLAttributes<HTMLSpanElement>, "className" |
export default function Icon({ icon, className, ...restProps }: IconProps) {
return (
<span
class={clsx(icon ?? "bx bx-empty", className)}
class={clsx(icon ?? "bx bx-empty", className, "tn-icon")}
{...restProps}
/>
);

View File

@@ -135,7 +135,7 @@ function RibbonTab({ icon, title, active, onClick, toggleCommand }: { icon: stri
>
<span
ref={iconRef}
className={`ribbon-tab-title-icon ${icon}`}
className={`ribbon-tab-title-icon tn-icon ${icon}`}
/>
&nbsp;
{ active && <span class="ribbon-tab-title-label">{title}</span> }

View File

@@ -38,7 +38,7 @@
padding-top: 2px;
}
.ribbon-tab-title .bx {
.ribbon-tab-title .tn-icon {
font-size: 150%;
position: relative;
}

View File

@@ -28,7 +28,7 @@ body.experimental-feature-new-layout #right-pane {
border-bottom: 0;
}
&.collapsed .card-header > .bx {
&.collapsed .card-header > .tn-icon {
transform: rotate(-90deg);
}
}
@@ -50,7 +50,7 @@ body.experimental-feature-new-layout #right-pane {
padding: 0.75em;
color: var(--muted-text-color);
.bx {
.tn-icon {
font-size: 3em;
}

View File

@@ -131,7 +131,7 @@
width: 20em;
}
.attachment-actions .dropdown-item .bx {
.attachment-actions .dropdown-item .tn-icon {
position: relative;
top: 3px;
font-size: 120%;

View File

@@ -195,7 +195,11 @@ function AttachmentInfo({ attachment, isFullDetail }: { attachment: FAttachment,
) : (attachment.title)}
</h4>
<div className="attachment-details">
{t("attachment_detail_2.role_and_size", { role: attachment.role, size: utils.formatSize(attachment.contentLength) })}
{t("attachment_detail_2.role_and_size", {
role: attachment.role,
size: utils.formatSize(attachment.contentLength),
mimeType: attachment.mime
})}
</div>
<div style="flex: 1 1;" />
</div>

View File

@@ -20,7 +20,7 @@
margin-bottom: 5px;
}
.bx {
.tn-icon {
margin: 4px 0;
font-size: 12px;
opacity: 0.5;
@@ -78,7 +78,7 @@
.ribbon {
padding: 0 5px;
.bx {
.tn-icon {
font-size: 10px;
}

View File

@@ -1,6 +1,5 @@
.note-detail-editable-text {
font-family: var(--detail-font-family);
padding-inline-start: 14px;
height: 100%;
}
@@ -49,4 +48,8 @@ body.heading-style-underline .note-detail-editable-text h6 { border-bottom: 1px
box-shadow: none !important;
min-height: 50px;
height: 100%;
}
.note-detail-editable-text .ck.ck-editor__editable_inline {
padding: 0;
}

View File

@@ -35,5 +35,5 @@ describe("CK config", () => {
expect(config.translations, locale.id).toHaveLength(2);
}
}
});
}, 10_000);
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 727 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

View File

@@ -6,6 +6,8 @@ import sqlInit from "@triliumnext/server/src/services/sql_init.js";
import windowService from "@triliumnext/server/src/services/window.js";
import tray from "@triliumnext/server/src/services/tray.js";
import options from "@triliumnext/server/src/services/options.js";
import { randomString } from "@triliumnext/server/src/services/utils.js";
import electronDebug from "electron-debug";
import electronDl from "electron-dl";
import { PRODUCT_NAME } from "./app-info";
@@ -72,7 +74,8 @@ async function main() {
app.on("second-instance", (event, commandLine) => {
const lastFocusedWindow = windowService.getLastFocusedWindow();
if (commandLine.includes("--new-window")) {
windowService.createExtraWindow("");
const extraWindowId = randomString(4);
windowService.createExtraWindow(extraWindowId, "");
} else if (lastFocusedWindow) {
if (lastFocusedWindow.isMinimized()) {
lastFocusedWindow.restore();

1
apps/icon-pack-builder/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.zip

View File

@@ -0,0 +1,112 @@
Boxicons Free License
Free Icons offered by Boxicons is open source. You can use them for your personal and commercial projects.
Icons
----------------------------------------------------------------------------------------------
# Icons are free under — CC 4.0 License
You are free to:
- Share — copy and redistribute the material in any medium or format for any purpose, even commercially.
- Adapt — remix, transform, and build upon the material for any purpose, even commercially.
- The licensor cannot revoke these freedoms as long as you follow the license terms.
Under the following terms:
- Attribution — You must give appropriate credit , provide a link to the license, and indicate if changes were made . You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
- No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
Notices:
You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation .
No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material.
----------------------------------------------------------------------------------------------
# Fonts
Fonts are free under - SIL OFL 1.1 License:
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
“Reserved Font Name” refers to any names specified as such after the copyright statement(s).
“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s).
“Modified Version” refers to any derivative made by adding to, deleting, or substituting in part or in whole any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1 - Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2 - Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3 - No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4 - The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5 - The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
----------------------------------------------------------------------------------------------
# Code is free under - MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
----------------------------------------------------------------------------------------------
# Attribution
Attribution is required according to MIT,SIL OFL 1.1 and CC 4.0 License.
They are already included in the Free icons , you are not required to take any actions
----------------------------------------------------------------------------------------------
# Brand Icons License
The brand icons offered in Boxicons are trademarks of their respective brands. Their usage here is solely intended to represent the respective brands and does not imply any affiliation or endorsement.
Copyright 2025 Boxicons.

View File

@@ -0,0 +1,515 @@
@-webkit-keyframes spin
{
0%
{
-webkit-transform: rotate(0);
transform: rotate(0);
}
100%
{
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes spin
{
0%
{
-webkit-transform: rotate(0);
transform: rotate(0);
}
100%
{
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-webkit-keyframes burst
{
0%
{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
90%
{
-webkit-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
}
@keyframes burst
{
0%
{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
90%
{
-webkit-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
}
@-webkit-keyframes flashing
{
0%
{
opacity: 1;
}
45%
{
opacity: 0;
}
90%
{
opacity: 1;
}
}
@keyframes flashing
{
0%
{
opacity: 1;
}
45%
{
opacity: 0;
}
90%
{
opacity: 1;
}
}
@-webkit-keyframes fade-left
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(-20px);
transform: translateX(-20px);
opacity: 0;
}
}
@keyframes fade-left
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(-20px);
transform: translateX(-20px);
opacity: 0;
}
}
@-webkit-keyframes fade-right
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(20px);
transform: translateX(20px);
opacity: 0;
}
}
@keyframes fade-right
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(20px);
transform: translateX(20px);
opacity: 0;
}
}
@-webkit-keyframes fade-up
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
opacity: 0;
}
}
@keyframes fade-up
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
opacity: 0;
}
}
@-webkit-keyframes fade-down
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(20px);
transform: translateY(20px);
opacity: 0;
}
}
@keyframes fade-down
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(20px);
transform: translateY(20px);
opacity: 0;
}
}
@-webkit-keyframes tada
{
from
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20%
{
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
}
30%,
50%,
70%,
90%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
}
40%,
60%,
80%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
}
to
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes tada
{
from
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20%
{
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
}
30%,
50%,
70%,
90%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
}
40%,
60%,
80%
{
-webkit-transform: rotate3d(0, 0, 1, -10deg);
transform: rotate3d(0, 0, 1, -10deg);
}
to
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes beat {
to {
-webkit-transform: scale(1.4);
transform: scale(1.4);
}
}
@keyframes bounce
{
from
{
-webkit-transform: scale(1.1,1);
transform: scale(1.1,1);
}
25%
{
-webkit-transform: scale(0.9,1) translateY(-.25em);
transform: scale(0.9,1) translateY(-.25em);
}
50%
{
-webkit-transform: scale(1.1,0.9);
transform: scale(1.1,0.9);
}
75%
{
-webkit-transform: scale(1, 1);
transform: scale(1, 1);
}
87.5%
{
-webkit-transform: scale(1, 1) translateY(-.1em);
transform: scale(1, 1) translateY(-.1em);
}
to
{
-webkit-transform: scale(1, 1);
transform: scale(1, 1);
}
}
@keyframes breathe {
from{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
50%{
-webkit-transform: scale(1.4);
transform: scale(1.4);
opacity: 0.4;
}
to {
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
}
@keyframes wiggle {
from{
-webkit-transform: translateX(0);
transform:translateX(0);
}
30%{
-webkit-transform: translateX(0.075em);
transform: translateX(0.075em);
}
60%{
-webkit-transform: translateX(-0.075em);
transform: translateX(-0.075em);
}
75%{
-webkit-transform: translateX(0.025em);
transform: translateX(0.025em);
}
90%{
-webkit-transform: translateX(-0.025em);
transform: translateX(-0.025em);
}
to {
-webkit-transform: translateX(0);
transform:translateX(0);
}
}
.bx-wiggle{
-webkit-animation: wiggle 1s infinite;
animation: wiggle 1s infinite;
animation-timing-function:cubic-bezier(.23,.57,.79,.58);
}
.bx-wiggle-hover:hover{
-webkit-animation: wiggle 1s infinite;
animation: wiggle 1s infinite;
animation-timing-function:cubic-bezier(.23,.57,.79,.58);
}
.bx-breathe{
-webkit-animation: breathe 3s infinite;
animation: breathe 3s infinite ease-in-out;
}
.bx-breathe-hover:hover
{
-webkit-animation: breathe 3s infinite;
animation: breathe 3s infinite ease-in-out;
}
.bx-bounce{
-webkit-animation: bounce 1s infinite;
animation: bounce 1s infinite;
animation-timing-function: cubic-bezier(.98,.97,.64,1.62);
}
.bx-bounce-hover:hover
{
-webkit-animation: bounce 1s infinite;
animation: bounce 1s infinite;
animation-timing-function: cubic-bezier(.98,.97,.64,1.62);
}
.bx-beat
{
-webkit-animation: beat .5s infinite alternate;
animation: beat .5s infinite alternate;
animation-timing-function: cubic-bezier(.19,.96,.65,1);
transform-origin: center;
}
.bx-spin
{
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
.bx-spin-hover:hover
{
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
.bx-tada
{
-webkit-animation: tada 1.5s ease infinite;
animation: tada 1.5s ease infinite;
}
.bx-tada-hover:hover
{
-webkit-animation: tada 1.5s ease infinite;
animation: tada 1.5s ease infinite;
}
.bx-flashing
{
-webkit-animation: flashing 1.5s infinite linear;
animation: flashing 1.5s infinite linear;
}
.bx-flashing-hover:hover
{
-webkit-animation: flashing 1.5s infinite linear;
animation: flashing 1.5s infinite linear;
}
.bx-burst
{
-webkit-animation: burst 1.5s infinite linear;
animation: burst 1.5s infinite linear;
}
.bx-burst-hover:hover
{
-webkit-animation: burst 1.5s infinite linear;
animation: burst 1.5s infinite linear;
}
.bx-fade-up
{
-webkit-animation: fade-up 1.5s infinite linear;
animation: fade-up 1.5s infinite linear;
}
.bx-fade-up-hover:hover
{
-webkit-animation: fade-up 1.5s infinite linear;
animation: fade-up 1.5s infinite linear;
}
.bx-fade-down
{
-webkit-animation: fade-down 1.5s infinite linear;
animation: fade-down 1.5s infinite linear;
}
.bx-fade-down-hover:hover
{
-webkit-animation: fade-down 1.5s infinite linear;
animation: fade-down 1.5s infinite linear;
}
.bx-fade-left
{
-webkit-animation: fade-left 1.5s infinite linear;
animation: fade-left 1.5s infinite linear;
}
.bx-fade-left-hover:hover
{
-webkit-animation: fade-left 1.5s infinite linear;
animation: fade-left 1.5s infinite linear;
}
.bx-fade-right
{
-webkit-animation: fade-right 1.5s infinite linear;
animation: fade-right 1.5s infinite linear;
}
.bx-fade-right-hover:hover
{
-webkit-animation: fade-right 1.5s infinite linear;
animation: fade-right 1.5s infinite linear;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,515 @@
@-webkit-keyframes spin
{
0%
{
-webkit-transform: rotate(0);
transform: rotate(0);
}
100%
{
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@keyframes spin
{
0%
{
-webkit-transform: rotate(0);
transform: rotate(0);
}
100%
{
-webkit-transform: rotate(359deg);
transform: rotate(359deg);
}
}
@-webkit-keyframes burst
{
0%
{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
90%
{
-webkit-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
}
@keyframes burst
{
0%
{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
90%
{
-webkit-transform: scale(1.5);
transform: scale(1.5);
opacity: 0;
}
}
@-webkit-keyframes flashing
{
0%
{
opacity: 1;
}
45%
{
opacity: 0;
}
90%
{
opacity: 1;
}
}
@keyframes flashing
{
0%
{
opacity: 1;
}
45%
{
opacity: 0;
}
90%
{
opacity: 1;
}
}
@-webkit-keyframes fade-left
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(-20px);
transform: translateX(-20px);
opacity: 0;
}
}
@keyframes fade-left
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(-20px);
transform: translateX(-20px);
opacity: 0;
}
}
@-webkit-keyframes fade-right
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(20px);
transform: translateX(20px);
opacity: 0;
}
}
@keyframes fade-right
{
0%
{
-webkit-transform: translateX(0);
transform: translateX(0);
opacity: 1;
}
75%
{
-webkit-transform: translateX(20px);
transform: translateX(20px);
opacity: 0;
}
}
@-webkit-keyframes fade-up
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
opacity: 0;
}
}
@keyframes fade-up
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(-20px);
transform: translateY(-20px);
opacity: 0;
}
}
@-webkit-keyframes fade-down
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(20px);
transform: translateY(20px);
opacity: 0;
}
}
@keyframes fade-down
{
0%
{
-webkit-transform: translateY(0);
transform: translateY(0);
opacity: 1;
}
75%
{
-webkit-transform: translateY(20px);
transform: translateY(20px);
opacity: 0;
}
}
@-webkit-keyframes tada
{
from
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20%
{
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
}
30%,
50%,
70%,
90%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
}
40%,
60%,
80%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, -10deg);
}
to
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes tada
{
from
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
10%,
20%
{
-webkit-transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
transform: scale3d(.95, .95, .95) rotate3d(0, 0, 1, -10deg);
}
30%,
50%,
70%,
90%
{
-webkit-transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
transform: scale3d(1, 1, 1) rotate3d(0, 0, 1, 10deg);
}
40%,
60%,
80%
{
-webkit-transform: rotate3d(0, 0, 1, -10deg);
transform: rotate3d(0, 0, 1, -10deg);
}
to
{
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes beat {
to {
-webkit-transform: scale(1.4);
transform: scale(1.4);
}
}
@keyframes bounce
{
from
{
-webkit-transform: scale(1.1,1);
transform: scale(1.1,1);
}
25%
{
-webkit-transform: scale(0.9,1) translateY(-.25em);
transform: scale(0.9,1) translateY(-.25em);
}
50%
{
-webkit-transform: scale(1.1,0.9);
transform: scale(1.1,0.9);
}
75%
{
-webkit-transform: scale(1, 1);
transform: scale(1, 1);
}
87.5%
{
-webkit-transform: scale(1, 1) translateY(-.1em);
transform: scale(1, 1) translateY(-.1em);
}
to
{
-webkit-transform: scale(1, 1);
transform: scale(1, 1);
}
}
@keyframes breathe {
from{
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
50%{
-webkit-transform: scale(1.4);
transform: scale(1.4);
opacity: 0.4;
}
to {
-webkit-transform: scale(1);
transform: scale(1);
opacity: 1;
}
}
@keyframes wiggle {
from{
-webkit-transform: translateX(0);
transform:translateX(0);
}
30%{
-webkit-transform: translateX(0.075em);
transform: translateX(0.075em);
}
60%{
-webkit-transform: translateX(-0.075em);
transform: translateX(-0.075em);
}
75%{
-webkit-transform: translateX(0.025em);
transform: translateX(0.025em);
}
90%{
-webkit-transform: translateX(-0.025em);
transform: translateX(-0.025em);
}
to {
-webkit-transform: translateX(0);
transform:translateX(0);
}
}
.bx-wiggle{
-webkit-animation: wiggle 1s infinite;
animation: wiggle 1s infinite;
animation-timing-function:cubic-bezier(.23,.57,.79,.58);
}
.bx-wiggle-hover:hover{
-webkit-animation: wiggle 1s infinite;
animation: wiggle 1s infinite;
animation-timing-function:cubic-bezier(.23,.57,.79,.58);
}
.bx-breathe{
-webkit-animation: breathe 3s infinite;
animation: breathe 3s infinite ease-in-out;
}
.bx-breathe-hover:hover
{
-webkit-animation: breathe 3s infinite;
animation: breathe 3s infinite ease-in-out;
}
.bx-bounce{
-webkit-animation: bounce 1s infinite;
animation: bounce 1s infinite;
animation-timing-function: cubic-bezier(.98,.97,.64,1.62);
}
.bx-bounce-hover:hover
{
-webkit-animation: bounce 1s infinite;
animation: bounce 1s infinite;
animation-timing-function: cubic-bezier(.98,.97,.64,1.62);
}
.bx-beat
{
-webkit-animation: beat .5s infinite alternate;
animation: beat .5s infinite alternate;
animation-timing-function: cubic-bezier(.19,.96,.65,1);
transform-origin: center;
}
.bx-spin
{
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
.bx-spin-hover:hover
{
-webkit-animation: spin 2s linear infinite;
animation: spin 2s linear infinite;
}
.bx-tada
{
-webkit-animation: tada 1.5s ease infinite;
animation: tada 1.5s ease infinite;
}
.bx-tada-hover:hover
{
-webkit-animation: tada 1.5s ease infinite;
animation: tada 1.5s ease infinite;
}
.bx-flashing
{
-webkit-animation: flashing 1.5s infinite linear;
animation: flashing 1.5s infinite linear;
}
.bx-flashing-hover:hover
{
-webkit-animation: flashing 1.5s infinite linear;
animation: flashing 1.5s infinite linear;
}
.bx-burst
{
-webkit-animation: burst 1.5s infinite linear;
animation: burst 1.5s infinite linear;
}
.bx-burst-hover:hover
{
-webkit-animation: burst 1.5s infinite linear;
animation: burst 1.5s infinite linear;
}
.bx-fade-up
{
-webkit-animation: fade-up 1.5s infinite linear;
animation: fade-up 1.5s infinite linear;
}
.bx-fade-up-hover:hover
{
-webkit-animation: fade-up 1.5s infinite linear;
animation: fade-up 1.5s infinite linear;
}
.bx-fade-down
{
-webkit-animation: fade-down 1.5s infinite linear;
animation: fade-down 1.5s infinite linear;
}
.bx-fade-down-hover:hover
{
-webkit-animation: fade-down 1.5s infinite linear;
animation: fade-down 1.5s infinite linear;
}
.bx-fade-left
{
-webkit-animation: fade-left 1.5s infinite linear;
animation: fade-left 1.5s infinite linear;
}
.bx-fade-left-hover:hover
{
-webkit-animation: fade-left 1.5s infinite linear;
animation: fade-left 1.5s infinite linear;
}
.bx-fade-right
{
-webkit-animation: fade-right 1.5s infinite linear;
animation: fade-right 1.5s infinite linear;
}
.bx-fade-right-hover:hover
{
-webkit-animation: fade-right 1.5s infinite linear;
animation: fade-right 1.5s infinite linear;
}

View File

@@ -0,0 +1,906 @@
@font-face {
font-family: "boxicons-brands";
src: url("./boxicons-brands.ttf?945bfe89057cc7627be1ecdc648441aa") format("truetype"),
url("./boxicons-brands.woff?945bfe89057cc7627be1ecdc648441aa") format("woff"),
url("./boxicons-brands.woff2?945bfe89057cc7627be1ecdc648441aa") format("woff2");
}
.bxl {
font-family: boxicons-brands !important;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
display:inline-block;
speak:none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.bxl.variable-selector-00:before {
content: "\fb1e";
}
.bxl.bx-500px:before {
content: "\f101";
}
.bxl.bx-99designs:before {
content: "\f102";
}
.bxl.bx-adobe:before {
content: "\f103";
}
.bxl.bx-airbnb:before {
content: "\f104";
}
.bxl.bx-algolia:before {
content: "\f105";
}
.bxl.bx-amazon:before {
content: "\f106";
}
.bxl.bx-amex:before {
content: "\f107";
}
.bxl.bx-android:before {
content: "\f108";
}
.bxl.bx-angular:before {
content: "\f109";
}
.bxl.bx-anthropic:before {
content: "\f10a";
}
.bxl.bx-apple-music:before {
content: "\f10b";
}
.bxl.bx-apple:before {
content: "\f10c";
}
.bxl.bx-arc-browser:before {
content: "\f10d";
}
.bxl.bx-artstation:before {
content: "\f10e";
}
.bxl.bx-asana:before {
content: "\f10f";
}
.bxl.bx-atlassian:before {
content: "\f110";
}
.bxl.bx-atom-editor:before {
content: "\f111";
}
.bxl.bx-audible:before {
content: "\f112";
}
.bxl.bx-auth0:before {
content: "\f113";
}
.bxl.bx-autodesk:before {
content: "\f114";
}
.bxl.bx-aws:before {
content: "\f115";
}
.bxl.bx-baidu:before {
content: "\f116";
}
.bxl.bx-bash:before {
content: "\f117";
}
.bxl.bx-behance:before {
content: "\f118";
}
.bxl.bx-better-auth:before {
content: "\f119";
}
.bxl.bx-bing:before {
content: "\f11a";
}
.bxl.bx-bitcoin-logo:before {
content: "\f11b";
}
.bxl.bx-blender:before {
content: "\f11c";
}
.bxl.bx-blogger:before {
content: "\f11d";
}
.bxl.bx-bluesky:before {
content: "\f11e";
}
.bxl.bx-bolt-b:before {
content: "\f11f";
}
.bxl.bx-bootstrap:before {
content: "\f120";
}
.bxl.bx-boxicons:before {
content: "\f121";
}
.bxl.bx-brave-browser:before {
content: "\f122";
}
.bxl.bx-bun:before {
content: "\f123";
}
.bxl.bx-buy-me-a-coffee:before {
content: "\f124";
}
.bxl.bx-c-plus-plus:before {
content: "\f125";
}
.bxl.bx-c-sharp:before {
content: "\f126";
}
.bxl.bx-c:before {
content: "\f127";
}
.bxl.bx-canva:before {
content: "\f128";
}
.bxl.bx-chess-com:before {
content: "\f129";
}
.bxl.bx-chrome:before {
content: "\f12a";
}
.bxl.bx-claude-ai:before {
content: "\f12b";
}
.bxl.bx-clerk:before {
content: "\f12c";
}
.bxl.bx-cloudflare:before {
content: "\f12d";
}
.bxl.bx-codepen:before {
content: "\f12e";
}
.bxl.bx-convex:before {
content: "\f12f";
}
.bxl.bx-creative-commons:before {
content: "\f130";
}
.bxl.bx-crunchyroll:before {
content: "\f131";
}
.bxl.bx-css3:before {
content: "\f132";
}
.bxl.bx-cursor-ai:before {
content: "\f133";
}
.bxl.bx-dailymotion:before {
content: "\f134";
}
.bxl.bx-deepmind:before {
content: "\f135";
}
.bxl.bx-deepseek:before {
content: "\f136";
}
.bxl.bx-deezer:before {
content: "\f137";
}
.bxl.bx-deno:before {
content: "\f138";
}
.bxl.bx-dev-to:before {
content: "\f139";
}
.bxl.bx-deviantart:before {
content: "\f13a";
}
.bxl.bx-devpost:before {
content: "\f13b";
}
.bxl.bx-digg:before {
content: "\f13c";
}
.bxl.bx-digitalocean:before {
content: "\f13d";
}
.bxl.bx-discord-alt:before {
content: "\f13e";
}
.bxl.bx-discord:before {
content: "\f13f";
}
.bxl.bx-discourse:before {
content: "\f140";
}
.bxl.bx-discover:before {
content: "\f141";
}
.bxl.bx-django:before {
content: "\f142";
}
.bxl.bx-docker:before {
content: "\f143";
}
.bxl.bx-dot-env:before {
content: "\f144";
}
.bxl.bx-dribbble:before {
content: "\f145";
}
.bxl.bx-drizzle-orm:before {
content: "\f146";
}
.bxl.bx-dropbox:before {
content: "\f147";
}
.bxl.bx-ebay:before {
content: "\f148";
}
.bxl.bx-edge:before {
content: "\f149";
}
.bxl.bx-ember-js:before {
content: "\f14a";
}
.bxl.bx-etsy:before {
content: "\f14b";
}
.bxl.bx-expo:before {
content: "\f14c";
}
.bxl.bx-express-js:before {
content: "\f14d";
}
.bxl.bx-facebook-circle:before {
content: "\f14e";
}
.bxl.bx-facebook-square:before {
content: "\f14f";
}
.bxl.bx-facebook:before {
content: "\f150";
}
.bxl.bx-fastapi:before {
content: "\f151";
}
.bxl.bx-fastify:before {
content: "\f152";
}
.bxl.bx-figma-alt:before {
content: "\f153";
}
.bxl.bx-figma:before {
content: "\f154";
}
.bxl.bx-firebase:before {
content: "\f155";
}
.bxl.bx-firefox:before {
content: "\f156";
}
.bxl.bx-fiverr:before {
content: "\f157";
}
.bxl.bx-flask-old:before {
content: "\f158";
}
.bxl.bx-flask:before {
content: "\f159";
}
.bxl.bx-flickr-square:before {
content: "\f15a";
}
.bxl.bx-flickr:before {
content: "\f15b";
}
.bxl.bx-flutter:before {
content: "\f15c";
}
.bxl.bx-foursquare:before {
content: "\f15d";
}
.bxl.bx-framer:before {
content: "\f15e";
}
.bxl.bx-gatsby-js:before {
content: "\f15f";
}
.bxl.bx-gemini:before {
content: "\f160";
}
.bxl.bx-git:before {
content: "\f161";
}
.bxl.bx-github-copilot:before {
content: "\f162";
}
.bxl.bx-github:before {
content: "\f163";
}
.bxl.bx-gitlab:before {
content: "\f164";
}
.bxl.bx-gmail:before {
content: "\f165";
}
.bxl.bx-go-lang:before {
content: "\f166";
}
.bxl.bx-google-antigravity:before {
content: "\f167";
}
.bxl.bx-google-cloud:before {
content: "\f168";
}
.bxl.bx-google-pay:before {
content: "\f169";
}
.bxl.bx-google:before {
content: "\f16a";
}
.bxl.bx-graphql:before {
content: "\f16b";
}
.bxl.bx-grok:before {
content: "\f16c";
}
.bxl.bx-groq-ai:before {
content: "\f16d";
}
.bxl.bx-gsap:before {
content: "\f16e";
}
.bxl.bx-gumroad:before {
content: "\f16f";
}
.bxl.bx-hashnode:before {
content: "\f170";
}
.bxl.bx-hcaptcha:before {
content: "\f171";
}
.bxl.bx-heroku:before {
content: "\f172";
}
.bxl.bx-hono-js:before {
content: "\f173";
}
.bxl.bx-html5:before {
content: "\f174";
}
.bxl.bx-hugo:before {
content: "\f175";
}
.bxl.bx-ibm:before {
content: "\f176";
}
.bxl.bx-imdb:before {
content: "\f177";
}
.bxl.bx-instagram-alt:before {
content: "\f178";
}
.bxl.bx-instagram:before {
content: "\f179";
}
.bxl.bx-internet-explorer:before {
content: "\f17a";
}
.bxl.bx-invision:before {
content: "\f17b";
}
.bxl.bx-java:before {
content: "\f17c";
}
.bxl.bx-javascript:before {
content: "\f17d";
}
.bxl.bx-joomla:before {
content: "\f17e";
}
.bxl.bx-jquery:before {
content: "\f17f";
}
.bxl.bx-jsfiddle:before {
content: "\f180";
}
.bxl.bx-jwt:before {
content: "\f181";
}
.bxl.bx-kick:before {
content: "\f182";
}
.bxl.bx-kickstarter:before {
content: "\f183";
}
.bxl.bx-kotlin:before {
content: "\f184";
}
.bxl.bx-kubernetes:before {
content: "\f185";
}
.bxl.bx-laravel:before {
content: "\f186";
}
.bxl.bx-leetcode:before {
content: "\f187";
}
.bxl.bx-lemon-squeezy:before {
content: "\f188";
}
.bxl.bx-less:before {
content: "\f189";
}
.bxl.bx-letterboxd:before {
content: "\f18a";
}
.bxl.bx-lichess:before {
content: "\f18b";
}
.bxl.bx-line-chat:before {
content: "\f18c";
}
.bxl.bx-linear-app:before {
content: "\f18d";
}
.bxl.bx-linkedin-square:before {
content: "\f18e";
}
.bxl.bx-linkedin:before {
content: "\f18f";
}
.bxl.bx-linktree:before {
content: "\f190";
}
.bxl.bx-loom:before {
content: "\f191";
}
.bxl.bx-lottie-files:before {
content: "\f192";
}
.bxl.bx-lottie-lab:before {
content: "\f193";
}
.bxl.bx-lovable:before {
content: "\f194";
}
.bxl.bx-lyft:before {
content: "\f195";
}
.bxl.bx-magento:before {
content: "\f196";
}
.bxl.bx-mailchimp:before {
content: "\f197";
}
.bxl.bx-markdown:before {
content: "\f198";
}
.bxl.bx-mastercard:before {
content: "\f199";
}
.bxl.bx-mastodon:before {
content: "\f19a";
}
.bxl.bx-mcp:before {
content: "\f19b";
}
.bxl.bx-medium-old:before {
content: "\f19c";
}
.bxl.bx-medium-square:before {
content: "\f19d";
}
.bxl.bx-medium:before {
content: "\f19e";
}
.bxl.bx-messenger:before {
content: "\f19f";
}
.bxl.bx-meta:before {
content: "\f1a0";
}
.bxl.bx-microsoft-teams:before {
content: "\f1a1";
}
.bxl.bx-microsoft-windows:before {
content: "\f1a2";
}
.bxl.bx-microsoft:before {
content: "\f1a3";
}
.bxl.bx-midjourney:before {
content: "\f1a4";
}
.bxl.bx-mongodb:before {
content: "\f1a5";
}
.bxl.bx-motion-js:before {
content: "\f1a6";
}
.bxl.bx-mozilla:before {
content: "\f1a7";
}
.bxl.bx-my-sql:before {
content: "\f1a8";
}
.bxl.bx-neon-tech:before {
content: "\f1a9";
}
.bxl.bx-neovim:before {
content: "\f1aa";
}
.bxl.bx-nest-js:before {
content: "\f1ab";
}
.bxl.bx-netlify:before {
content: "\f1ac";
}
.bxl.bx-next-js:before {
content: "\f1ad";
}
.bxl.bx-nodejs:before {
content: "\f1ae";
}
.bxl.bx-notion:before {
content: "\f1af";
}
.bxl.bx-npm:before {
content: "\f1b0";
}
.bxl.bx-nuxt-js:before {
content: "\f1b1";
}
.bxl.bx-ok-ru:before {
content: "\f1b2";
}
.bxl.bx-ollama:before {
content: "\f1b3";
}
.bxl.bx-openai:before {
content: "\f1b4";
}
.bxl.bx-opensea:before {
content: "\f1b5";
}
.bxl.bx-opera:before {
content: "\f1b6";
}
.bxl.bx-paddle-p:before {
content: "\f1b7";
}
.bxl.bx-paper-design:before {
content: "\f1b8";
}
.bxl.bx-patreon:before {
content: "\f1b9";
}
.bxl.bx-payload-cms:before {
content: "\f1ba";
}
.bxl.bx-paypal:before {
content: "\f1bb";
}
.bxl.bx-periscope:before {
content: "\f1bc";
}
.bxl.bx-perplexity-ai:before {
content: "\f1bd";
}
.bxl.bx-php:before {
content: "\f1be";
}
.bxl.bx-pinterest-alt:before {
content: "\f1bf";
}
.bxl.bx-pinterest:before {
content: "\f1c0";
}
.bxl.bx-planetscale:before {
content: "\f1c1";
}
.bxl.bx-play-store:before {
content: "\f1c2";
}
.bxl.bx-playstation:before {
content: "\f1c3";
}
.bxl.bx-pocket:before {
content: "\f1c4";
}
.bxl.bx-polar:before {
content: "\f1c5";
}
.bxl.bx-postgresql:before {
content: "\f1c6";
}
.bxl.bx-prisma-orm:before {
content: "\f1c7";
}
.bxl.bx-product-hunt:before {
content: "\f1c8";
}
.bxl.bx-python:before {
content: "\f1c9";
}
.bxl.bx-qdrant:before {
content: "\f1ca";
}
.bxl.bx-qq:before {
content: "\f1cb";
}
.bxl.bx-quora:before {
content: "\f1cc";
}
.bxl.bx-radix-ui:before {
content: "\f1cd";
}
.bxl.bx-railway:before {
content: "\f1ce";
}
.bxl.bx-rasberry-pi:before {
content: "\f1cf";
}
.bxl.bx-react-query:before {
content: "\f1d0";
}
.bxl.bx-react-router:before {
content: "\f1d1";
}
.bxl.bx-react:before {
content: "\f1d2";
}
.bxl.bx-redbubble:before {
content: "\f1d3";
}
.bxl.bx-reddit:before {
content: "\f1d4";
}
.bxl.bx-redux:before {
content: "\f1d5";
}
.bxl.bx-remix-js:before {
content: "\f1d6";
}
.bxl.bx-replit:before {
content: "\f1d7";
}
.bxl.bx-resend:before {
content: "\f1d8";
}
.bxl.bx-roblox:before {
content: "\f1d9";
}
.bxl.bx-sanity:before {
content: "\f1da";
}
.bxl.bx-sass:before {
content: "\f1db";
}
.bxl.bx-sentry:before {
content: "\f1dc";
}
.bxl.bx-shadcn-ui:before {
content: "\f1dd";
}
.bxl.bx-shopify:before {
content: "\f1de";
}
.bxl.bx-sketch:before {
content: "\f1df";
}
.bxl.bx-skype:before {
content: "\f1e0";
}
.bxl.bx-slack-old:before {
content: "\f1e1";
}
.bxl.bx-slack:before {
content: "\f1e2";
}
.bxl.bx-snapchat:before {
content: "\f1e3";
}
.bxl.bx-socket-io:before {
content: "\f1e4";
}
.bxl.bx-soundcloud:before {
content: "\f1e5";
}
.bxl.bx-spotify:before {
content: "\f1e6";
}
.bxl.bx-spring-boot:before {
content: "\f1e7";
}
.bxl.bx-squarespace:before {
content: "\f1e8";
}
.bxl.bx-sst:before {
content: "\f1e9";
}
.bxl.bx-stack-overflow:before {
content: "\f1ea";
}
.bxl.bx-stackblitz:before {
content: "\f1eb";
}
.bxl.bx-steam:before {
content: "\f1ec";
}
.bxl.bx-stripe:before {
content: "\f1ed";
}
.bxl.bx-supabase:before {
content: "\f1ee";
}
.bxl.bx-svelte:before {
content: "\f1ef";
}
.bxl.bx-tailwind-css:before {
content: "\f1f0";
}
.bxl.bx-telegram:before {
content: "\f1f1";
}
.bxl.bx-terraform:before {
content: "\f1f2";
}
.bxl.bx-threads:before {
content: "\f1f3";
}
.bxl.bx-three-js:before {
content: "\f1f4";
}
.bxl.bx-tiktok:before {
content: "\f1f5";
}
.bxl.bx-trello:before {
content: "\f1f6";
}
.bxl.bx-trip-advisor:before {
content: "\f1f7";
}
.bxl.bx-trpc:before {
content: "\f1f8";
}
.bxl.bx-trustpilot:before {
content: "\f1f9";
}
.bxl.bx-tumblr:before {
content: "\f1fa";
}
.bxl.bx-tux:before {
content: "\f1fb";
}
.bxl.bx-twitch:before {
content: "\f1fc";
}
.bxl.bx-twitter-x:before {
content: "\f1fd";
}
.bxl.bx-twitter:before {
content: "\f1fe";
}
.bxl.bx-typescript:before {
content: "\f1ff";
}
.bxl.bx-uber:before {
content: "\f200";
}
.bxl.bx-ubuntu:before {
content: "\f201";
}
.bxl.bx-udacity:before {
content: "\f202";
}
.bxl.bx-union-pay:before {
content: "\f203";
}
.bxl.bx-unity:before {
content: "\f204";
}
.bxl.bx-unsplash:before {
content: "\f205";
}
.bxl.bx-upi:before {
content: "\f206";
}
.bxl.bx-upwork:before {
content: "\f207";
}
.bxl.bx-v0:before {
content: "\f208";
}
.bxl.bx-venmo:before {
content: "\f209";
}
.bxl.bx-vercel:before {
content: "\f20a";
}
.bxl.bx-vimeo:before {
content: "\f20b";
}
.bxl.bx-visa:before {
content: "\f20c";
}
.bxl.bx-visual-studio:before {
content: "\f20d";
}
.bxl.bx-vite-js:before {
content: "\f20e";
}
.bxl.bx-vk:before {
content: "\f20f";
}
.bxl.bx-vuejs:before {
content: "\f210";
}
.bxl.bx-waze:before {
content: "\f211";
}
.bxl.bx-web-components:before {
content: "\f212";
}
.bxl.bx-webflow:before {
content: "\f213";
}
.bxl.bx-wechat:before {
content: "\f214";
}
.bxl.bx-weibo:before {
content: "\f215";
}
.bxl.bx-whatsapp-square:before {
content: "\f216";
}
.bxl.bx-whatsapp:before {
content: "\f217";
}
.bxl.bx-wikipedia:before {
content: "\f218";
}
.bxl.bx-windsurf:before {
content: "\f219";
}
.bxl.bx-wix:before {
content: "\f21a";
}
.bxl.bx-wordpress:before {
content: "\f21b";
}
.bxl.bx-work-os:before {
content: "\f21c";
}
.bxl.bx-xai:before {
content: "\f21d";
}
.bxl.bx-xbox:before {
content: "\f21e";
}
.bxl.bx-xing:before {
content: "\f21f";
}
.bxl.bx-yahoo:before {
content: "\f220";
}
.bxl.bx-yarn:before {
content: "\f221";
}
.bxl.bx-yelp:before {
content: "\f222";
}
.bxl.bx-youtube-music:before {
content: "\f223";
}
.bxl.bx-youtube:before {
content: "\f224";
}
.bxl.bx-zen-browser:before {
content: "\f225";
}
.bxl.bx-zoom-workplace:before {
content: "\f226";
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,297 @@
{
"variable-selector-00": 64286,
"bx-500px": 61697,
"bx-99designs": 61698,
"bx-adobe": 61699,
"bx-airbnb": 61700,
"bx-algolia": 61701,
"bx-amazon": 61702,
"bx-amex": 61703,
"bx-android": 61704,
"bx-angular": 61705,
"bx-anthropic": 61706,
"bx-apple-music": 61707,
"bx-apple": 61708,
"bx-arc-browser": 61709,
"bx-artstation": 61710,
"bx-asana": 61711,
"bx-atlassian": 61712,
"bx-atom-editor": 61713,
"bx-audible": 61714,
"bx-auth0": 61715,
"bx-autodesk": 61716,
"bx-aws": 61717,
"bx-baidu": 61718,
"bx-bash": 61719,
"bx-behance": 61720,
"bx-better-auth": 61721,
"bx-bing": 61722,
"bx-bitcoin-logo": 61723,
"bx-blender": 61724,
"bx-blogger": 61725,
"bx-bluesky": 61726,
"bx-bolt-b": 61727,
"bx-bootstrap": 61728,
"bx-boxicons": 61729,
"bx-brave-browser": 61730,
"bx-bun": 61731,
"bx-buy-me-a-coffee": 61732,
"bx-c-plus-plus": 61733,
"bx-c-sharp": 61734,
"bx-c": 61735,
"bx-canva": 61736,
"bx-chess-com": 61737,
"bx-chrome": 61738,
"bx-claude-ai": 61739,
"bx-clerk": 61740,
"bx-cloudflare": 61741,
"bx-codepen": 61742,
"bx-convex": 61743,
"bx-creative-commons": 61744,
"bx-crunchyroll": 61745,
"bx-css3": 61746,
"bx-cursor-ai": 61747,
"bx-dailymotion": 61748,
"bx-deepmind": 61749,
"bx-deepseek": 61750,
"bx-deezer": 61751,
"bx-deno": 61752,
"bx-dev-to": 61753,
"bx-deviantart": 61754,
"bx-devpost": 61755,
"bx-digg": 61756,
"bx-digitalocean": 61757,
"bx-discord-alt": 61758,
"bx-discord": 61759,
"bx-discourse": 61760,
"bx-discover": 61761,
"bx-django": 61762,
"bx-docker": 61763,
"bx-dot-env": 61764,
"bx-dribbble": 61765,
"bx-drizzle-orm": 61766,
"bx-dropbox": 61767,
"bx-ebay": 61768,
"bx-edge": 61769,
"bx-ember-js": 61770,
"bx-etsy": 61771,
"bx-expo": 61772,
"bx-express-js": 61773,
"bx-facebook-circle": 61774,
"bx-facebook-square": 61775,
"bx-facebook": 61776,
"bx-fastapi": 61777,
"bx-fastify": 61778,
"bx-figma-alt": 61779,
"bx-figma": 61780,
"bx-firebase": 61781,
"bx-firefox": 61782,
"bx-fiverr": 61783,
"bx-flask-old": 61784,
"bx-flask": 61785,
"bx-flickr-square": 61786,
"bx-flickr": 61787,
"bx-flutter": 61788,
"bx-foursquare": 61789,
"bx-framer": 61790,
"bx-gatsby-js": 61791,
"bx-gemini": 61792,
"bx-git": 61793,
"bx-github-copilot": 61794,
"bx-github": 61795,
"bx-gitlab": 61796,
"bx-gmail": 61797,
"bx-go-lang": 61798,
"bx-google-antigravity": 61799,
"bx-google-cloud": 61800,
"bx-google-pay": 61801,
"bx-google": 61802,
"bx-graphql": 61803,
"bx-grok": 61804,
"bx-groq-ai": 61805,
"bx-gsap": 61806,
"bx-gumroad": 61807,
"bx-hashnode": 61808,
"bx-hcaptcha": 61809,
"bx-heroku": 61810,
"bx-hono-js": 61811,
"bx-html5": 61812,
"bx-hugo": 61813,
"bx-ibm": 61814,
"bx-imdb": 61815,
"bx-instagram-alt": 61816,
"bx-instagram": 61817,
"bx-internet-explorer": 61818,
"bx-invision": 61819,
"bx-java": 61820,
"bx-javascript": 61821,
"bx-joomla": 61822,
"bx-jquery": 61823,
"bx-jsfiddle": 61824,
"bx-jwt": 61825,
"bx-kick": 61826,
"bx-kickstarter": 61827,
"bx-kotlin": 61828,
"bx-kubernetes": 61829,
"bx-laravel": 61830,
"bx-leetcode": 61831,
"bx-lemon-squeezy": 61832,
"bx-less": 61833,
"bx-letterboxd": 61834,
"bx-lichess": 61835,
"bx-line-chat": 61836,
"bx-linear-app": 61837,
"bx-linkedin-square": 61838,
"bx-linkedin": 61839,
"bx-linktree": 61840,
"bx-loom": 61841,
"bx-lottie-files": 61842,
"bx-lottie-lab": 61843,
"bx-lovable": 61844,
"bx-lyft": 61845,
"bx-magento": 61846,
"bx-mailchimp": 61847,
"bx-markdown": 61848,
"bx-mastercard": 61849,
"bx-mastodon": 61850,
"bx-mcp": 61851,
"bx-medium-old": 61852,
"bx-medium-square": 61853,
"bx-medium": 61854,
"bx-messenger": 61855,
"bx-meta": 61856,
"bx-microsoft-teams": 61857,
"bx-microsoft-windows": 61858,
"bx-microsoft": 61859,
"bx-midjourney": 61860,
"bx-mongodb": 61861,
"bx-motion-js": 61862,
"bx-mozilla": 61863,
"bx-my-sql": 61864,
"bx-neon-tech": 61865,
"bx-neovim": 61866,
"bx-nest-js": 61867,
"bx-netlify": 61868,
"bx-next-js": 61869,
"bx-nodejs": 61870,
"bx-notion": 61871,
"bx-npm": 61872,
"bx-nuxt-js": 61873,
"bx-ok-ru": 61874,
"bx-ollama": 61875,
"bx-openai": 61876,
"bx-opensea": 61877,
"bx-opera": 61878,
"bx-paddle-p": 61879,
"bx-paper-design": 61880,
"bx-patreon": 61881,
"bx-payload-cms": 61882,
"bx-paypal": 61883,
"bx-periscope": 61884,
"bx-perplexity-ai": 61885,
"bx-php": 61886,
"bx-pinterest-alt": 61887,
"bx-pinterest": 61888,
"bx-planetscale": 61889,
"bx-play-store": 61890,
"bx-playstation": 61891,
"bx-pocket": 61892,
"bx-polar": 61893,
"bx-postgresql": 61894,
"bx-prisma-orm": 61895,
"bx-product-hunt": 61896,
"bx-python": 61897,
"bx-qdrant": 61898,
"bx-qq": 61899,
"bx-quora": 61900,
"bx-radix-ui": 61901,
"bx-railway": 61902,
"bx-rasberry-pi": 61903,
"bx-react-query": 61904,
"bx-react-router": 61905,
"bx-react": 61906,
"bx-redbubble": 61907,
"bx-reddit": 61908,
"bx-redux": 61909,
"bx-remix-js": 61910,
"bx-replit": 61911,
"bx-resend": 61912,
"bx-roblox": 61913,
"bx-sanity": 61914,
"bx-sass": 61915,
"bx-sentry": 61916,
"bx-shadcn-ui": 61917,
"bx-shopify": 61918,
"bx-sketch": 61919,
"bx-skype": 61920,
"bx-slack-old": 61921,
"bx-slack": 61922,
"bx-snapchat": 61923,
"bx-socket-io": 61924,
"bx-soundcloud": 61925,
"bx-spotify": 61926,
"bx-spring-boot": 61927,
"bx-squarespace": 61928,
"bx-sst": 61929,
"bx-stack-overflow": 61930,
"bx-stackblitz": 61931,
"bx-steam": 61932,
"bx-stripe": 61933,
"bx-supabase": 61934,
"bx-svelte": 61935,
"bx-tailwind-css": 61936,
"bx-telegram": 61937,
"bx-terraform": 61938,
"bx-threads": 61939,
"bx-three-js": 61940,
"bx-tiktok": 61941,
"bx-trello": 61942,
"bx-trip-advisor": 61943,
"bx-trpc": 61944,
"bx-trustpilot": 61945,
"bx-tumblr": 61946,
"bx-tux": 61947,
"bx-twitch": 61948,
"bx-twitter-x": 61949,
"bx-twitter": 61950,
"bx-typescript": 61951,
"bx-uber": 61952,
"bx-ubuntu": 61953,
"bx-udacity": 61954,
"bx-union-pay": 61955,
"bx-unity": 61956,
"bx-unsplash": 61957,
"bx-upi": 61958,
"bx-upwork": 61959,
"bx-v0": 61960,
"bx-venmo": 61961,
"bx-vercel": 61962,
"bx-vimeo": 61963,
"bx-visa": 61964,
"bx-visual-studio": 61965,
"bx-vite-js": 61966,
"bx-vk": 61967,
"bx-vuejs": 61968,
"bx-waze": 61969,
"bx-web-components": 61970,
"bx-webflow": 61971,
"bx-wechat": 61972,
"bx-weibo": 61973,
"bx-whatsapp-square": 61974,
"bx-whatsapp": 61975,
"bx-wikipedia": 61976,
"bx-windsurf": 61977,
"bx-wix": 61978,
"bx-wordpress": 61979,
"bx-work-os": 61980,
"bx-xai": 61981,
"bx-xbox": 61982,
"bx-xing": 61983,
"bx-yahoo": 61984,
"bx-yarn": 61985,
"bx-yelp": 61986,
"bx-youtube-music": 61987,
"bx-youtube": 61988,
"bx-zen-browser": 61989,
"bx-zoom-workplace": 61990
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,108 @@
.bx-rotate-90
{
transform: rotate(90deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=1)';
}
.bx-rotate-180
{
transform: rotate(180deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';
}
.bx-rotate-270
{
transform: rotate(270deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';
}
.bx-flip-horizontal
{
transform: scaleX(-1);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)';
}
.bx-flip-vertical
{
transform: scaleY(-1);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
}
.bx-xs
{
font-size: 1rem!important;
}
.bx-sm
{
font-size: 1.55rem!important;
}
.bx-md
{
font-size: 2.25rem!important;
}
.bx-lg
{
font-size: 3.0rem!important;
}
.bx-fw
{
font-size: 1.2857142857em;
line-height: .8em;
width: 1.2857142857em;
height: .8em;
margin-top: -.2em!important;
vertical-align: middle;
}
.bx-pull-left
{
float: left;
margin-right: .3em!important;
}
.bx-pull-right
{
float: right;
margin-left: .3em!important;
}
.bx-border
{
padding: .25em;
border: .07em solid rgba(0,0,0,.1);
border-radius: .25em;
}
.bx-border-circle
{
padding: .25em;
border: .07em solid rgba(0,0,0,.1);
border-radius: 50%;
}
.bx-ul
{
margin-left: 2em;
padding-left: 0;
list-style: none;
}
.bx-ul > li
{
position: relative;
}
.bx-ul .bx,.bx-ul .bxr,.bx-ul .bxs
{
font-size: inherit;
line-height: inherit;
position: absolute;
left: -2em;
width: 2em;
text-align: center;
}

View File

@@ -0,0 +1,108 @@
.bx-rotate-90
{
transform: rotate(90deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=1)';
}
.bx-rotate-180
{
transform: rotate(180deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';
}
.bx-rotate-270
{
transform: rotate(270deg);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';
}
.bx-flip-horizontal
{
transform: scaleX(-1);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)';
}
.bx-flip-vertical
{
transform: scaleY(-1);
-ms-filter: 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
}
.bx-xs
{
font-size: 1rem!important;
}
.bx-sm
{
font-size: 1.55rem!important;
}
.bx-md
{
font-size: 2.25rem!important;
}
.bx-lg
{
font-size: 3.0rem!important;
}
.bx-fw
{
font-size: 1.2857142857em;
line-height: .8em;
width: 1.2857142857em;
height: .8em;
margin-top: -.2em!important;
vertical-align: middle;
}
.bx-pull-left
{
float: left;
margin-right: .3em!important;
}
.bx-pull-right
{
float: right;
margin-left: .3em!important;
}
.bx-border
{
padding: .25em;
border: .07em solid rgba(0,0,0,.1);
border-radius: .25em;
}
.bx-border-circle
{
padding: .25em;
border: .07em solid rgba(0,0,0,.1);
border-radius: 50%;
}
.bx-ul
{
margin-left: 2em;
padding-left: 0;
list-style: none;
}
.bx-ul > li
{
position: relative;
}
.bx-ul .bx,.bx-ul .bxr,.bx-ul .bxs
{
font-size: inherit;
line-height: inherit;
position: absolute;
left: -2em;
width: 2em;
text-align: center;
}

View File

@@ -0,0 +1 @@
.bx-rotate-90{transform:rotate(90deg);-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=1)';}.bx-rotate-180{transform:rotate(180deg);-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';}.bx-rotate-270{transform:rotate(270deg);-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';}.bx-flip-horizontal{transform:scaleX(-1);-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)';}.bx-flip-vertical{transform:scaleY(-1);-ms-filter:'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';}.bx-xs{font-size:1rem!important;}.bx-sm{font-size:1.55rem!important;}.bx-md{font-size:2.25rem!important;}.bx-lg{font-size:3.0rem!important;}.bx-fw{font-size:1.2857142857em;line-height:.8em;width:1.2857142857em;height:.8em;margin-top:-.2em!important;vertical-align:middle;}.bx-pull-left{float:left;margin-right:.3em!important;}.bx-pull-right{float:right;margin-left:.3em!important;}.bx-border{padding:.25em;border:.07em solid rgba(0,0,0,.1);border-radius:.25em;}.bx-border-circle{padding:.25em;border:.07em solid rgba(0,0,0,.1);border-radius:50%;}.bx-ul{margin-left:2em;padding-left:0;list-style:none;}.bx-ul > li{position:relative;}.bx-ul .bx,.bx-ul .bxr,.bx-ul .bxs{font-size:inherit;line-height:inherit;position:absolute;left:-2em;width:2em;text-align:center;}

View File

@@ -0,0 +1,14 @@
{
"name": "@triliumnext/icon-pack-builder",
"version": "1.0.0",
"description": "",
"main": "src/index.ts",
"scripts": {
"start": "tsx ."
},
"keywords": [],
"devDependencies": {
"@mdi/font": "7.4.47",
"@phosphor-icons/web": "2.1.2"
}
}

View File

@@ -0,0 +1,76 @@
import { createWriteStream, mkdirSync } from "node:fs";
import { join } from "node:path";
import cls from "@triliumnext/server/src/services/cls.js";
import type { IconPackData } from "./provider";
import boxicons3 from "./providers/boxicons3";
import mdi from "./providers/mdi";
import phosphor from "./providers/phosphor";
process.env.TRILIUM_INTEGRATION_TEST = "memory-no-store";
process.env.TRILIUM_RESOURCE_DIR = "../server/src";
process.env.NODE_ENV = "development";
async function main() {
const outputDir = join(__dirname, "output");
mkdirSync(outputDir, { recursive: true });
const i18n = await import("@triliumnext/server/src/services/i18n.js");
await i18n.initializeTranslations();
const sqlInit = (await import("../../server/src/services/sql_init.js")).default;
await sqlInit.createInitialDatabase(true);
// Wait for becca to be loaded before importing data
const beccaLoader = await import("../../server/src/becca/becca_loader.js");
await beccaLoader.beccaLoaded;
const notesService = (await import("../../server/src/services/notes.js")).default;
async function buildIconPack(iconPack: IconPackData) {
// Create the icon pack note.
const { note, branch } = notesService.createNewNote({
parentNoteId: "root",
type: "file",
title: iconPack.name,
mime: "application/json",
content: JSON.stringify(iconPack.manifest)
});
note.setLabel("iconPack", iconPack.prefix);
note.setLabel("iconClass", iconPack.icon);
// Add the attachment.
note.saveAttachment({
role: "file",
title: iconPack.fontFile.name,
mime: iconPack.fontFile.mime,
content: iconPack.fontFile.content
});
// Export to zip.
const zipFilePath = join(outputDir, `${iconPack.name}.zip`);
const fileOutputStream = createWriteStream(zipFilePath);
const { exportToZip } = (await import("@triliumnext/server/src/services/export/zip.js")).default;
const taskContext = new (await import("@triliumnext/server/src/services/task_context.js")).default(
"no-progress-reporting", "export", null
);
await exportToZip(taskContext, branch, "html", fileOutputStream, false, { skipExtraFiles: true });
await new Promise<void>((resolve) => { fileOutputStream.on("finish", resolve); });
console.log(`Built icon pack: ${iconPack.name} (${zipFilePath})`);
}
const builtIconPacks = [
boxicons3("basic"),
boxicons3("brands"),
mdi(),
phosphor("regular"),
phosphor("fill")
];
await Promise.all(builtIconPacks.map(buildIconPack));
}
cls.init(() => {
main();
});

View File

@@ -0,0 +1,13 @@
import type { IconPackManifest } from "@triliumnext/server/src/services/icon_packs";
export interface IconPackData {
name: string;
prefix: string;
manifest: IconPackManifest;
icon: string;
fontFile: {
name: string;
mime: string;
content: Buffer;
}
}

View File

@@ -0,0 +1,42 @@
import { readFileSync } from "fs";
import { join } from "path";
import { IconPackData } from "../provider";
export default function buildIcons(pack: "basic" | "brands"): IconPackData {
const inputDir = join(__dirname, "../../boxicons-free/fonts");
const fileName = pack === "basic" ? "boxicons" : `boxicons-${pack}`;
const jsonPath = `${inputDir}/${pack}/${fileName}.json`;
const inputData = JSON.parse(readFileSync(jsonPath, "utf-8"));
const icons = {};
for (const [ key, value ] of Object.entries(inputData)) {
if (key.startsWith("variable-selector")) continue;
let name = key;
if (name.startsWith('bx-')) {
name = name.slice(3);
}
if (name.startsWith('bxs-')) {
name = name.slice(4);
}
icons[key] = {
glyph: String.fromCodePoint(value as number),
terms: [ name ]
};
}
return {
name: pack === "basic" ? "Boxicons 3 (Basic)" : "Boxicons 3 (Brands)",
prefix: pack === "basic" ? "bx3" : "bxl3",
icon: pack === "basic" ? "bx3 bx-cube" : "bxl3 bxl-boxicons",
fontFile: {
name: `${fileName}.woff2`,
mime: "font/woff2",
content: readFileSync(join(inputDir, pack, `${fileName}.woff2`))
},
manifest: {
icons
}
};
}

View File

@@ -0,0 +1,26 @@
import { readFileSync } from "fs";
import { join } from "path";
import type { IconPackData } from "../provider";
import { extractClassNamesFromCss, getModulePath } from "../utils";
export default function buildIcons(): IconPackData {
const baseDir = getModulePath("@mdi/font");
const cssFilePath = join(baseDir, "css", "materialdesignicons.min.css");
const cssFileContent = readFileSync(cssFilePath, "utf-8");
return {
name: "Material Design Icons",
prefix: "mdi",
icon: "mdi mdi-material-design",
manifest: {
icons: extractClassNamesFromCss(cssFileContent, "mdi"),
},
fontFile: {
name: "materialdesignicons-webfont.woff2",
mime: "font/woff2",
content: readFileSync(join(baseDir, "fonts", "materialdesignicons-webfont.woff2"))
}
};
}

View File

@@ -0,0 +1,46 @@
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { IconPackData } from "../provider";
import { getModulePath } from "../utils";
export default function buildIcons(packName: "regular" | "fill"): IconPackData {
const baseDir = join(getModulePath("@phosphor-icons/web"), "src", packName);
const iconIndex = JSON.parse(readFileSync(join(baseDir, "selection.json"), "utf-8"));
const icons: IconPackData["manifest"]["icons"] = {};
function removeSuffix(name: string) {
if (name.endsWith(`-${packName}`)) {
name = name.split("-").slice(0, -1).join("-");
}
return name;
}
for (const icon of iconIndex.icons) {
const terms = icon.properties.name.split(", ").map((t: string) => removeSuffix(t));
const name = removeSuffix(icon.icon.tags[0]);
const id = `ph-${name}`;
icons[id] = {
glyph: `${String.fromCharCode(icon.properties.code)}`,
terms
};
}
const fontFile = readdirSync(baseDir).find(f => f.endsWith(".woff2"));
const prefix = packName === "regular" ? "ph" : `ph-${packName}`;
return {
name: `Phosphor Icons (${packName.charAt(0).toUpperCase() + packName.slice(1)})`,
prefix,
icon: `${prefix} ph-phosphor-logo`,
manifest: {
icons
},
fontFile: {
name: fontFile!,
mime: "font/woff2",
content: readFileSync(join(baseDir, fontFile!))
}
};
}

View File

@@ -0,0 +1,26 @@
import { join } from "path";
import { IconPackManifest } from "../../server/src/services/icon_packs";
export function extractClassNamesFromCss(css: string, prefix: string): IconPackManifest["icons"] {
const regex = /\.([a-zA-Z0-9-]+)::before\s*\{\s*content:\s*"\\([A-Fa-f0-9]+)"\s*\}/g;
const icons: IconPackManifest["icons"] = {};
let match: string[];
while ((match = regex.exec(css)) !== null) {
let name = match[1];
if (prefix && name.startsWith(`${prefix}-`)) {
name = name.substring(prefix.length + 1);
}
icons[match[1]] = {
glyph: String.fromCodePoint(parseInt(match[2], 16)),
terms: [ name ]
};
}
return icons;
}
export function getModulePath(moduleName: string): string {
return join(__dirname, "../../../node_modules", moduleName);
}

View File

@@ -0,0 +1,36 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"target": "ES2020",
"outDir": "dist",
"strict": false,
"types": [
"node",
"express"
],
"rootDir": "src",
"tsBuildInfoFile": "dist/tsconfig.app.tsbuildinfo"
},
"include": [
"src/**/*.ts",
"../server/src/*.d.ts"
],
"exclude": [
"eslint.config.js",
"eslint.config.cjs",
"eslint.config.mjs"
],
"references": [
{
"path": "../server/tsconfig.app.json"
},
{
"path": "../desktop/tsconfig.app.json"
},
{
"path": "../client/tsconfig.app.json"
}
]
}

View File

@@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.base.json",
"include": [],
"references": [
{
"path": "../server"
},
{
"path": "../client"
},
{
"path": "./tsconfig.app.json"
}
]
}

Some files were not shown because too many files have changed in this diff Show More