Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
8487c8c
WIP: allow no share path url prefix at all (not working yet)
maphew Feb 25, 2025
6725f81
WIP: options page works, but routing broken when logged out
maphew Feb 26, 2025
bba2f6d
wip: another attempt (and use translation syntax this time)
maphew Mar 8, 2025
c9d1512
conflicts fixed - Merge remote-tracking branch 'origin/develop'
maphew Mar 8, 2025
fbe7d64
Merge branch 'develop' into feat/clean-share-url
JYC333 Apr 4, 2025
1aecf66
Merge branch 'develop' into feat/clean-share-url
JYC333 Apr 10, 2025
81d2fbc
fix: 🐛 add back missing translation
JYC333 Apr 11, 2025
37f3a9b
Merge branch 'develop' into feat/clean-share-url
pano9000 Apr 17, 2025
0be508e
fix(share/routes): fix crash on clean DB startup when sharePath optio…
pano9000 Apr 17, 2025
d72a0d3
fix(services/auth): fix crash on clean DB startup when options are no…
pano9000 Apr 17, 2025
9a11fc1
fix(share_settings): fix missing class in redirect-bare-domain input
pano9000 Apr 17, 2025
df45fa2
fix(share/routes): fix redirect loop
pano9000 Apr 17, 2025
56fc2d9
fix(share_settings): fix not being able to set share path
pano9000 Apr 18, 2025
ab901a5
refactor(share_settings): get rid of save() method
pano9000 Apr 18, 2025
a8901e6
chore: revert back unnecessary changes from unclean merge
pano9000 Apr 18, 2025
dabdfad
fix(share/routes): remove unnecessary redirects that cause loops
pano9000 Apr 20, 2025
9f0a023
fix(share_settings): stop runnning checkShareRoot on init and on redi…
pano9000 Apr 20, 2025
b0030f8
feat(share_settings): group options that belong together logically
pano9000 Apr 20, 2025
0e31aab
refactor(share_settings): use this.$shareRootCheck instead of creatin…
pano9000 Apr 20, 2025
6dc687e
feat(share_settings): improve checkShareRoot
pano9000 Apr 21, 2025
c90364b
feat(share_settings): improve sharePath input handling
pano9000 Apr 21, 2025
d1d4b47
test(share_settings): add initial test for normalizeSharePathInput
pano9000 Apr 21, 2025
1b7266f
chore(share_settings): remove unnecessary comment
pano9000 Apr 21, 2025
00b5aef
feat(share_settings): add support for adding "/" as sharePath
pano9000 Apr 21, 2025
43166db
refactor: remove "cleanUrl" related code for now
pano9000 Apr 25, 2025
f4b5ed7
refactor(options_init): remove sharePath normalization
pano9000 Apr 25, 2025
0ae9a29
refactor: remove "cleanUrl" related code for now
pano9000 Apr 25, 2025
3b1d7d0
feat: improve example and wording for share_path_description
pano9000 Apr 25, 2025
128d890
chore(share_settings): add a TODO hint for currently active bug
pano9000 Apr 25, 2025
30a191c
fix(share_settings): disallow "/" as share root for now as it is not …
pano9000 Apr 25, 2025
34e7901
refactor: remove "cleanUrl" related string for now
pano9000 Apr 25, 2025
2bd8c21
Merge branch 'develop' into feat/clean-share-url
pano9000 May 12, 2025
b25fd6c
feat: 🎸 update custom share path without restart server
JYC333 Jun 18, 2025
519b964
test: 💍 Fix test for path input
JYC333 Jun 18, 2025
c52d6a6
Merge branch 'main' into feat/clean-share-url
JYC333 Jun 26, 2025
923eabd
feat: 🎸 Allow multi-segement path
JYC333 Jun 26, 2025
6ab8750
feat: 🎸 update translation
JYC333 Jun 26, 2025
ea5564c
docs: ✏️ update docs
JYC333 Jun 26, 2025
979fbe2
Merge branch 'main' into feat/clean-share-url
JYC333 Aug 13, 2025
64f191b
Merge branch 'main' into feat/clean-share-url
JYC333 Oct 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion apps/client/src/translations/cn/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1743,9 +1743,17 @@
"show_login_link": "在共享主题中显示登录链接",
"show_login_link_description": "在共享页面底部添加登录链接",
"check_share_root": "检查共享根状态",
"check_share_root_error": "检查共享根状态时发生意外错误,请检查日志以获取更多信息。",
"share_note_title": "'{{noteTitle}}'",
"share_root_found": "共享根笔记 '{{noteTitle}}' 已准备好",
"share_root_not_found": "未找到带有 #shareRoot 标签的笔记",
"share_root_not_shared": "笔记 '{{noteTitle}}' 具有 #shareRoot 标签,但未共享"
"share_root_not_shared": "笔记 '{{noteTitle}}' 具有 #shareRoot 标签,但未共享",
"share_root_multiple_found": "找到多个具有 #shareRoot 标签的共享笔记:{{- foundNoteTitles}}。将使用笔记 {{- activeNoteTitle}} 作为共享根笔记。",
"share_path": "共享路径",
"share_path_description": "共享笔记的 URL 前缀(例如 '/share' --> '/share/noteId' 或 '/custom-path' --> '/custom-path/noteId')。支持多级嵌套(例如 '/custom-path/sub-path' --> '/custom-path/sub-path/noteId')。刷新页面以应用更改。",
"share_path_placeholder": "/share 或 /custom-path",
"share_subtree": "共享子树",
"share_subtree_description": "共享整个子树,而不是仅共享笔记"
},
"time_selector": {
"invalid_input": "输入的时间值不是有效数字。",
Expand Down
10 changes: 9 additions & 1 deletion apps/client/src/translations/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1907,9 +1907,17 @@
"show_login_link": "Show Login link in Share theme",
"show_login_link_description": "Add a login link to the Share page footer",
"check_share_root": "Check Share Root Status",
"check_share_root_error": "An unexpected error happened while checking the Share Root Status, please check the logs for more information.",
"share_note_title": "'{{noteTitle}}'",
"share_root_found": "Share root note '{{noteTitle}}' is ready",
"share_root_not_found": "No note with #shareRoot label found",
"share_root_not_shared": "Note '{{noteTitle}}' has #shareRoot label but is not shared"
"share_root_not_shared": "Note '{{noteTitle}}' has #shareRoot label but is not Shared",
"share_root_multiple_found": "Found multiple shared notes with a #shareRoot label: {{- foundNoteTitles}}. The note {{- activeNoteTitle}} will be used as shared root note.",
"share_path": "Share path",
"share_path_description": "The url prefix for shared notes (e.g. '/share' --> '/share/noteId' or '/custom-path' --> '/custom-path/noteId'). Multiple levels of nesting are supported (e.g. '/custom-path/sub-path' --> '/custom-path/sub-path/noteId'). Refresh the page to apply the changes.",
"share_path_placeholder": "/share or /custom-path",
"share_subtree": "Share subtree",
"share_subtree_description": "Share the entire subtree, not just the note"
},
"time_selector": {
"invalid_input": "The entered time value is not a valid number.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Ensure sharePath always starts with a single slash and does not end with (one or multiple) trailing slashes
export function normalizeSharePathInput(sharePathInput: string) {
const REGEXP_STARTING_SLASH = /^\/+/g;
const REGEXP_TRAILING_SLASH = /\b\/+$/g;

const normalizedSharePath = (!sharePathInput.startsWith("/")
? `/${sharePathInput}`
: sharePathInput)
.replaceAll(REGEXP_TRAILING_SLASH, "")
.replaceAll(REGEXP_STARTING_SLASH, "/");

return normalizedSharePath;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describe, it, expect } from "vitest";
import { normalizeSharePathInput } from "./share_path_utils.js";

type TestCase<T extends (...args: any) => any> = [
desc: string,
fnParams: Parameters<T>,
expected: ReturnType<T>
];

describe("ShareSettingsOptions", () => {

describe("#normalizeSharePathInput", () => {

const testCases: TestCase<typeof normalizeSharePathInput>[] = [
[
"should handle multiple trailing '/' and remove them completely",
["/trailingtest////"],
"/trailingtest"
],
[
"should handle multiple starting '/' and replace them by a single '/'",
["////startingtest"],
"/startingtest"
],
[
"should handle multiple starting & trailing '/' and replace them by a single '/'",
["////startingAndTrailingTest///"],
"/startingAndTrailingTest"
],
[
"should not remove any '/' other than at the end or start of the input",
["/test/with/subpath"],
"/test/with/subpath"
],
[
"should prepend the string with a '/' if it does not start with one",
["testpath"],
"/testpath"
],
[
"should not change anything, if the string is a single '/'",
["/"],
"/"
],
];

testCases.forEach((testCase) => {
const [desc, fnParams, expected] = testCase;
it(desc, () => {
const actual = normalizeSharePathInput(...fnParams);
expect(actual).toStrictEqual(expected);
});
});


})

})

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions apps/server/src/routes/api/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ const ALLOWED_OPTIONS = new Set<OptionNames>([
"allowedHtmlTags",
"redirectBareDomain",
"showLoginInShareTheme",
"shareSubtree",
"sharePath",
"splitEditorOrientation",
"seenCallToActions",

Expand Down
1 change: 1 addition & 0 deletions apps/server/src/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const GET = "get",
DEL = "delete";

function register(app: express.Application) {

route(GET, "/", [auth.checkAuth, csrfMiddleware], indexRoute.index);
route(GET, "/login", [auth.checkAppInitialized, auth.checkPasswordSet], loginRoute.loginPage);
route(GET, "/set-password", [auth.checkAppInitialized, auth.checkPasswordNotSet], loginRoute.setPasswordPage);
Expand Down
26 changes: 17 additions & 9 deletions apps/server/src/services/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,26 @@
// Check if any note has the #shareRoot label
const shareRootNotes = attributes.getNotesWithLabel("shareRoot");
if (shareRootNotes.length === 0) {
// should this be a translation string?
res.status(404).json({ message: "Share root not found. Please set up a note with #shareRoot label first." });
return;
}

// Get the configured share path
const sharePath = options.getOption("sharePath") || '/share';

// Check if we're already at the share path to prevent redirect loops
if (req.path === sharePath || req.path.startsWith(`${sharePath}/`)) {
log.info(`checkAuth: Already at share path, skipping redirect. Path: ${req.path}, SharePath: ${sharePath}`);
next();
return;
}

// Redirect to the share path
log.info(`checkAuth: Redirecting to share path. From: ${req.path}, To: ${sharePath}`);
res.redirect(`${sharePath}/`);
} else {
res.redirect("login");
}
res.redirect(hasRedirectBareDomain ? "share" : "login");
} else if (currentTotpStatus !== lastAuthState.totpEnabled || currentSsoStatus !== lastAuthState.ssoEnabled) {
Expand Down Expand Up @@ -81,15 +98,6 @@
}
}

function checkApiAuth(req: Request, res: Response, next: NextFunction) {
if (!req.session.loggedIn && !noAuthentication) {
console.warn(`Missing session with ID '${req.sessionID}'.`);
reject(req, res, "Logged in session not found");
} else {
next();
}
}

function checkAppInitialized(req: Request, res: Response, next: NextFunction) {
if (!sqlInit.isDbInitialized()) {
res.redirect("setup");
Expand Down Expand Up @@ -168,7 +176,7 @@

export default {
checkAuth,
checkApiAuth,

Check failure on line 179 in apps/server/src/services/auth.ts

View workflow job for this annotation

GitHub Actions / Test development

Cannot find name 'checkApiAuth'. Did you mean 'checkAuth'?

Check failure on line 179 in apps/server/src/services/auth.ts

View workflow job for this annotation

GitHub Actions / Test development

Cannot find name 'checkApiAuth'. Did you mean 'checkAuth'?
checkAppInitialized,
checkPasswordSet,
checkPasswordNotSet,
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/services/options_init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,10 @@ const defaultOptions: DefaultOption[] = [
},

// Share settings
{ name: "sharePath", value: "/share", isSynced: true },
{ name: "redirectBareDomain", value: "false", isSynced: true },
{ name: "showLoginInShareTheme", value: "false", isSynced: true },
{ name: "shareSubtree", value: "false", isSynced: true },

// AI Options
{ name: "aiEnabled", value: "false", isSynced: true },
Expand Down
10 changes: 5 additions & 5 deletions apps/server/src/share/content_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
};

if (note.type === "text") {
renderText(result, note);
renderText(result, note, relativePath);

Check failure on line 35 in apps/server/src/share/content_renderer.ts

View workflow job for this annotation

GitHub Actions / Test development

Cannot find name 'relativePath'.

Check failure on line 35 in apps/server/src/share/content_renderer.ts

View workflow job for this annotation

GitHub Actions / Test development

Expected 2 arguments, but got 3.

Check failure on line 35 in apps/server/src/share/content_renderer.ts

View workflow job for this annotation

GitHub Actions / Test development

Cannot find name 'relativePath'.
} else if (note.type === "code") {
renderCode(result);
} else if (note.type === "mermaid") {
Expand Down Expand Up @@ -106,10 +106,10 @@

if (result.content.includes(`<span class="math-tex">`)) {
result.header += `
<script src="../${assetPath}/node_modules/katex/dist/katex.min.js"></script>
<link rel="stylesheet" href="../${assetPath}/node_modules/katex/dist/katex.min.css">
<script src="../${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>
<script src="../${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script>
<script src="${relativePath}${assetPath}/node_modules/katex/dist/katex.min.js"></script>

Check failure on line 109 in apps/server/src/share/content_renderer.ts

View workflow job for this annotation

GitHub Actions / Test development

Cannot find name 'relativePath'.
<link rel="stylesheet" href="${relativePath}${assetPath}/node_modules/katex/dist/katex.min.css">

Check failure on line 110 in apps/server/src/share/content_renderer.ts

View workflow job for this annotation

GitHub Actions / Test development

Cannot find name 'relativePath'.
<script src="${relativePath}${assetPath}/node_modules/katex/dist/contrib/auto-render.min.js"></script>

Check failure on line 111 in apps/server/src/share/content_renderer.ts

View workflow job for this annotation

GitHub Actions / Test development

Cannot find name 'relativePath'.
<script src="${relativePath}${assetPath}/node_modules/katex/dist/contrib/mhchem.min.js"></script>

Check failure on line 112 in apps/server/src/share/content_renderer.ts

View workflow job for this annotation

GitHub Actions / Test development

Cannot find name 'relativePath'.
<script>
document.addEventListener("DOMContentLoaded", function() {
renderMathInElement(document.getElementById('content'));
Expand Down
36 changes: 36 additions & 0 deletions apps/server/src/share/routes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,39 @@ describe("Share API test", () => {
});

});

describe("Share Routes - Asset Path Calculation", () => {
it("should calculate correct relative path depth for different share paths", () => {
// Helper function to simulate the path depth calculation
const calculateRelativePath = (sharePath: string) => {
const pathDepth = sharePath.split('/').filter(segment => segment.length > 0).length;
return '../'.repeat(pathDepth);
};

// Test single level path
expect(calculateRelativePath("/share")).toBe("../");

// Test double level path
expect(calculateRelativePath("/sharePath/test")).toBe("../../");

// Test triple level path
expect(calculateRelativePath("/my/custom/share")).toBe("../../../");

// Test root path
expect(calculateRelativePath("/")).toBe("");

// Test path with trailing slash
expect(calculateRelativePath("/share/")).toBe("../");
});

it("should handle normalized share paths correctly", () => {
const calculateRelativePath = (sharePath: string) => {
const pathDepth = sharePath.split('/').filter(segment => segment.length > 0).length;
return '../'.repeat(pathDepth);
};

// Test the examples from the original TODO comment
expect(calculateRelativePath("/sharePath")).toBe("../");
expect(calculateRelativePath("/sharePath/test")).toBe("../../");
});
});
Loading
Loading