mirror of
https://github.com/zadam/trilium.git
synced 2026-01-08 16:32:13 +01:00
Compare commits
6 Commits
lightweigh
...
feat/show-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dd541e1d0 | ||
|
|
8157ef5e74 | ||
|
|
494b55d685 | ||
|
|
0185dd0d18 | ||
|
|
142ed42d90 | ||
|
|
51513d3779 |
@@ -5,7 +5,7 @@ import { Dropdown as BootstrapDropdown } from "bootstrap";
|
||||
import clsx from "clsx";
|
||||
import { type ComponentChildren, RefObject } from "preact";
|
||||
import { createPortal } from "preact/compat";
|
||||
import { useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { CommandNames } from "../../components/app_context";
|
||||
import NoteContext from "../../components/note_context";
|
||||
@@ -338,15 +338,19 @@ interface AttributesProps extends StatusBarContext {
|
||||
function AttributesButton({ note, attributesShown, setAttributesShown }: AttributesProps) {
|
||||
const [ count, setCount ] = useState(note.attributes.length);
|
||||
|
||||
const refreshCount = useCallback((note: FNote) => {
|
||||
return note.getAttributes().filter(a => !a.isAutoLink).length;
|
||||
}, []);
|
||||
|
||||
// React to note changes.
|
||||
useEffect(() => {
|
||||
setCount(note.getAttributes().filter(a => !a.isAutoLink).length);
|
||||
}, [ note ]);
|
||||
setCount(refreshCount(note));
|
||||
}, [ note, refreshCount ]);
|
||||
|
||||
// React to changes in count.
|
||||
useTriliumEvent("entitiesReloaded", (({loadResults}) => {
|
||||
if (loadResults.getAttributeRows().some(attr => attributes.isAffecting(attr, note))) {
|
||||
setCount(note.attributes.length);
|
||||
setCount(refreshCount(note));
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ describe("data_dir.ts unit tests", async () => {
|
||||
const mockFn = {
|
||||
existsSyncMock: vi.fn(),
|
||||
mkdirSyncMock: vi.fn(),
|
||||
statSyncMock: vi.fn(),
|
||||
osHomedirMock: vi.fn(),
|
||||
osPlatformMock: vi.fn(),
|
||||
pathJoinMock: vi.fn()
|
||||
@@ -21,7 +22,8 @@ describe("data_dir.ts unit tests", async () => {
|
||||
return {
|
||||
default: {
|
||||
existsSync: mockFn.existsSyncMock,
|
||||
mkdirSync: mockFn.mkdirSyncMock
|
||||
mkdirSync: mockFn.mkdirSyncMock,
|
||||
statSync: mockFn.statSyncMock
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -109,34 +111,36 @@ describe("data_dir.ts unit tests", async () => {
|
||||
*/
|
||||
|
||||
describe("case A", () => {
|
||||
it("when folder exists – it should return the path, without attempting to create the folder", async () => {
|
||||
it("when folder exists – it should return the path, handling EEXIST gracefully", async () => {
|
||||
const mockTriliumDataPath = "/home/mock/trilium-data-ENV-A1";
|
||||
process.env.TRILIUM_DATA_DIR = mockTriliumDataPath;
|
||||
|
||||
// set fs.existsSync to true, i.e. the folder does exist
|
||||
mockFn.existsSyncMock.mockImplementation(() => true);
|
||||
// mkdirSync throws EEXIST when folder already exists (EAFP pattern)
|
||||
const eexistError = new Error("EEXIST: file already exists") as NodeJS.ErrnoException;
|
||||
eexistError.code = "EEXIST";
|
||||
mockFn.mkdirSyncMock.mockImplementation(() => { throw eexistError; });
|
||||
|
||||
// statSync confirms it's a directory
|
||||
mockFn.statSyncMock.mockImplementation(() => ({ isDirectory: () => true }));
|
||||
|
||||
const result = getTriliumDataDir("trilium-data");
|
||||
|
||||
// createDirIfNotExisting should call existsync 1 time and mkdirSync 0 times -> as it does not need to create the folder
|
||||
// and return value should be TRILIUM_DATA_DIR value from process.env
|
||||
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1);
|
||||
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(0);
|
||||
// createDirIfNotExisting tries mkdirSync first (EAFP), then statSync to verify it's a directory
|
||||
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1);
|
||||
expect(mockFn.statSyncMock).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual(process.env.TRILIUM_DATA_DIR);
|
||||
});
|
||||
|
||||
it("when folder does not exist – it should attempt to create the folder and return the path", async () => {
|
||||
it("when folder does not exist – it should create the folder and return the path", async () => {
|
||||
const mockTriliumDataPath = "/home/mock/trilium-data-ENV-A2";
|
||||
process.env.TRILIUM_DATA_DIR = mockTriliumDataPath;
|
||||
|
||||
// set fs.existsSync mock to return false, i.e. the folder does not exist
|
||||
mockFn.existsSyncMock.mockImplementation(() => false);
|
||||
// mkdirSync succeeds when folder doesn't exist
|
||||
mockFn.mkdirSyncMock.mockImplementation(() => undefined);
|
||||
|
||||
const result = getTriliumDataDir("trilium-data");
|
||||
|
||||
// createDirIfNotExisting should call existsync 1 time and mkdirSync 1 times -> as it has to create the folder
|
||||
// and return value should be TRILIUM_DATA_DIR value from process.env
|
||||
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1);
|
||||
// createDirIfNotExisting calls mkdirSync which succeeds
|
||||
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual(process.env.TRILIUM_DATA_DIR);
|
||||
});
|
||||
@@ -171,19 +175,19 @@ describe("data_dir.ts unit tests", async () => {
|
||||
|
||||
// use Generator to precisely control order of fs.existSync return values
|
||||
const existsSyncMockGen = (function* () {
|
||||
// 1) fs.existSync -> case B
|
||||
// 1) fs.existSync -> case B -> checking if folder exists in home dir
|
||||
yield false;
|
||||
// 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists
|
||||
yield true;
|
||||
// 3) fs.existSync -> case C -> checking if Trilium Data folder exists
|
||||
yield false;
|
||||
})();
|
||||
|
||||
mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value);
|
||||
// mkdirSync succeeds (folder doesn't exist)
|
||||
mockFn.mkdirSyncMock.mockImplementation(() => undefined);
|
||||
|
||||
const result = getTriliumDataDir(dataDirName);
|
||||
|
||||
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3);
|
||||
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(2);
|
||||
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual(mockPlatformDataPath);
|
||||
});
|
||||
@@ -198,21 +202,26 @@ describe("data_dir.ts unit tests", async () => {
|
||||
|
||||
// use Generator to precisely control order of fs.existSync return values
|
||||
const existsSyncMockGen = (function* () {
|
||||
// 1) fs.existSync -> case B
|
||||
// 1) fs.existSync -> case B -> checking if folder exists in home dir
|
||||
yield false;
|
||||
// 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists
|
||||
yield true;
|
||||
// 3) fs.existSync -> case C -> checking if Trilium Data folder exists
|
||||
yield true;
|
||||
})();
|
||||
|
||||
mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value);
|
||||
|
||||
// mkdirSync throws EEXIST (folder already exists), statSync confirms it's a directory
|
||||
const eexistError = new Error("EEXIST: file already exists") as NodeJS.ErrnoException;
|
||||
eexistError.code = "EEXIST";
|
||||
mockFn.mkdirSyncMock.mockImplementation(() => { throw eexistError; });
|
||||
mockFn.statSyncMock.mockImplementation(() => ({ isDirectory: () => true }));
|
||||
|
||||
const result = getTriliumDataDir(dataDirName);
|
||||
|
||||
expect(result).toEqual(mockPlatformDataPath);
|
||||
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3);
|
||||
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(0);
|
||||
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(2);
|
||||
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1);
|
||||
expect(mockFn.statSyncMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("w/ Platform 'win32' and set process.env.APPDATA behaviour", async () => {
|
||||
@@ -227,20 +236,20 @@ describe("data_dir.ts unit tests", async () => {
|
||||
|
||||
// use Generator to precisely control order of fs.existSync return values
|
||||
const existsSyncMockGen = (function* () {
|
||||
// 1) fs.existSync -> case B
|
||||
// 1) fs.existSync -> case B -> checking if folder exists in home dir
|
||||
yield false;
|
||||
// 2) fs.existSync -> case C -> checking if default OS PlatformAppDataDir exists
|
||||
yield true;
|
||||
// 3) fs.existSync -> case C -> checking if Trilium Data folder exists
|
||||
yield false;
|
||||
})();
|
||||
|
||||
mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value);
|
||||
// mkdirSync succeeds (folder doesn't exist)
|
||||
mockFn.mkdirSyncMock.mockImplementation(() => undefined);
|
||||
|
||||
const result = getTriliumDataDir(dataDirName);
|
||||
|
||||
expect(result).toEqual(mockPlatformDataPath);
|
||||
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(3);
|
||||
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(2);
|
||||
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -253,19 +262,15 @@ describe("data_dir.ts unit tests", async () => {
|
||||
|
||||
setMockPlatform("aix", homedir, mockPlatformDataPath);
|
||||
|
||||
const existsSyncMockGen = (function* () {
|
||||
// first fs.existSync -> case B -> checking if folder exists in home folder
|
||||
yield false;
|
||||
// second fs.existSync -> case D -> triggered by createDirIfNotExisting
|
||||
yield false;
|
||||
})();
|
||||
|
||||
mockFn.existsSyncMock.mockImplementation(() => existsSyncMockGen.next().value);
|
||||
// fs.existSync -> case B -> checking if folder exists in home folder
|
||||
mockFn.existsSyncMock.mockImplementation(() => false);
|
||||
// mkdirSync succeeds (folder doesn't exist)
|
||||
mockFn.mkdirSyncMock.mockImplementation(() => undefined);
|
||||
|
||||
const result = getTriliumDataDir(dataDirName);
|
||||
|
||||
expect(result).toEqual(mockPlatformDataPath);
|
||||
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(2);
|
||||
expect(mockFn.existsSyncMock).toHaveBeenCalledTimes(1);
|
||||
expect(mockFn.mkdirSyncMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,9 +75,56 @@ export function getPlatformAppDataDir(platform: ReturnType<typeof os.platform>,
|
||||
}
|
||||
}
|
||||
|
||||
function outputPermissionDiagnostics(targetPath: fs.PathLike) {
|
||||
const pathStr = targetPath.toString();
|
||||
const parentDir = pathJoin(pathStr, "..");
|
||||
|
||||
console.error("\n========== PERMISSION ERROR DIAGNOSTICS ==========");
|
||||
console.error(`Failed to create directory: ${pathStr}`);
|
||||
|
||||
// Output current process UID:GID (Unix only)
|
||||
if (typeof process.getuid === "function" && typeof process.getgid === "function") {
|
||||
console.error(`Process running as UID:GID = ${process.getuid()}:${process.getgid()}`);
|
||||
}
|
||||
|
||||
// Try to get parent directory stats
|
||||
try {
|
||||
const stats = fs.statSync(parentDir);
|
||||
console.error(`Parent directory: ${parentDir}`);
|
||||
console.error(` Owner UID:GID = ${stats.uid}:${stats.gid}`);
|
||||
console.error(` Permissions = ${(stats.mode & 0o777).toString(8)} (octal)`);
|
||||
} catch {
|
||||
console.error(`Parent directory ${parentDir} is not accessible`);
|
||||
}
|
||||
|
||||
console.error("\nTo fix this issue:");
|
||||
console.error(" - Ensure the data directory is owned by the user running Trilium");
|
||||
console.error(" - Or set USER_UID and USER_GID environment variables to match the directory owner");
|
||||
console.error(" - Example: docker run -e USER_UID=$(id -u) -e USER_GID=$(id -g) ...");
|
||||
console.error("====================================================\n");
|
||||
}
|
||||
|
||||
function createDirIfNotExisting(path: fs.PathLike, permissionMode: fs.Mode = FOLDER_PERMISSIONS) {
|
||||
if (!fs.existsSync(path)) {
|
||||
try {
|
||||
fs.mkdirSync(path, permissionMode);
|
||||
} catch (err: unknown) {
|
||||
if (err && typeof err === "object" && "code" in err) {
|
||||
const code = (err as { code: string }).code;
|
||||
|
||||
if (code === "EACCES") {
|
||||
outputPermissionDiagnostics(path);
|
||||
} else if (code === "EEXIST") {
|
||||
// Directory already exists - verify it's actually a directory
|
||||
try {
|
||||
if (fs.statSync(path).isDirectory()) {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// If we can't stat it, fall through to re-throw original error
|
||||
}
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,11 @@ const LOCALE_MAPPINGS: Record<DISPLAYABLE_LOCALE_IDS, LocaleMapping | null> = {
|
||||
coreTranslation: () => import("ckeditor5/translations/ja.js"),
|
||||
premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/ja.js"),
|
||||
},
|
||||
pl: {
|
||||
languageCode: "pl",
|
||||
coreTranslation: () => import("ckeditor5/translations/pl.js"),
|
||||
premiumFeaturesTranslation: () => import("ckeditor5-premium-features/translations/pl.js"),
|
||||
},
|
||||
pt: {
|
||||
languageCode: "pt",
|
||||
coreTranslation: () => import("ckeditor5/translations/pt.js"),
|
||||
|
||||
Reference in New Issue
Block a user