improve workspaces handling

This commit is contained in:
Nystik
2026-04-02 02:47:18 +02:00
parent ad06e05fed
commit 6865c049a3
3 changed files with 217 additions and 22 deletions

View File

@@ -1,4 +1,10 @@
import {
rewriteWorkspacePath,
rewriteWorkspacesContent,
} from "../workspace.js";
const API_BASE = "/api/fs";
const WORKSPACES_PATH = ".obsidian/workspaces.json";
function normPath(p) {
return (p || "").replace(/^\/+/, "");
@@ -19,22 +25,6 @@ function vaultId() {
return window.__currentVaultId || "";
}
const WORKSPACE_PATH = ".obsidian/workspace.json";
function rewriteWorkspacePath(normalizedPath) {
const name = window.__workspaceName;
if (!name) {
return normalizedPath;
}
if (normalizedPath === WORKSPACE_PATH) {
return `.obsidian/workspace.${name}.json`;
}
return normalizedPath;
}
async function request(method, endpoint, params = {}) {
const url = new URL(API_BASE + endpoint, window.location.origin);
@@ -156,10 +146,17 @@ export const transport = {
},
async writeFile(path, content, encoding) {
const isText = typeof content === "string";
const norm = normPath(path);
let data = content;
if (norm === WORKSPACES_PATH && typeof data === "string") {
data = rewriteWorkspacesContent(data);
}
const isText = typeof data === "string";
return requestJson("POST", "/writeFile", {
path: rewriteWorkspacePath(normPath(path)),
content: isText ? content : uint8ToBase64(content),
path: rewriteWorkspacePath(norm),
content: isText ? data : uint8ToBase64(data),
encoding: encoding || (isText ? "utf-8" : "binary"),
base64: !isText,
});
@@ -261,10 +258,17 @@ export const transport = {
},
writeFileSync(path, content, encoding) {
const isText = typeof content === "string";
const norm = normPath(path);
let data = content;
if (norm === WORKSPACES_PATH && typeof data === "string") {
data = rewriteWorkspacesContent(data);
}
const isText = typeof data === "string";
requestSync("POST", "/writeFile", {
path: rewriteWorkspacePath(normPath(path)),
content: isText ? content : uint8ToBase64(content),
path: rewriteWorkspacePath(norm),
content: isText ? data : uint8ToBase64(data),
encoding: encoding || (isText ? "utf-8" : "binary"),
base64: !isText,
});

View File

@@ -3,6 +3,7 @@ import { installRequestUrlShim } from "./request-url.js";
import { vaultService } from "../services/vault-service.js";
import { showPluginInstallDialog } from "../ui/bootstrap.js";
import { registerReadTransform } from "./fs/read-transforms.js";
import { resolveWorkspaceName, initWorkspacePatch } from "./workspace.js";
function resolveVaultId() {
const urlParams = new URLSearchParams(window.location.search);
@@ -171,9 +172,11 @@ function initCoreSyncGuard() {
export function initialize() {
resolveVaultId();
initVaultConfig();
resolveWorkspaceName();
initVaultList();
initMetadataCache();
initCoreSyncGuard();
installRequestUrlShim();
initWorkspacePatch();
initPluginPrompt();
}

188
src/shims/workspace.js Normal file
View File

@@ -0,0 +1,188 @@
import { fsShim } from "./fs/index.js";
import { registerReadTransform } from "./fs/read-transforms.js";
const WORKSPACE_PATH = ".obsidian/workspace.json";
const WORKSPACES_PATH = ".obsidian/workspaces.json";
export function rewriteWorkspacePath(normalizedPath) {
const name = window.__workspaceName;
if (!name) {
return normalizedPath;
}
if (normalizedPath === WORKSPACE_PATH) {
return `.obsidian/workspace.${name}.json`;
}
return normalizedPath;
}
export function rewriteWorkspacesContent(content) {
const original = window.__originalActiveWorkspace;
if (!original || !window.__workspaceName) {
return content;
}
try {
const parsed = JSON.parse(content);
if (parsed.active !== original) {
parsed.active = original;
return JSON.stringify(parsed);
}
} catch {}
return content;
}
function setWorkspaceParam(name) {
const url = new URL(window.location.href);
if (name) {
url.searchParams.set("workspace", name);
} else {
url.searchParams.delete("workspace");
}
history.replaceState(null, "", url.toString());
}
export function resolveWorkspaceName() {
try {
const vaultParam = window.__currentVaultId
? "?vault=" + encodeURIComponent(window.__currentVaultId)
: "";
const sep = vaultParam ? "&" : "?";
// If no param provided, check if workspaces plugin is enabled before resolving.
if (!window.__workspaceName) {
const coreXhr = new XMLHttpRequest();
coreXhr.open(
"GET",
"/api/fs/readFile" + vaultParam + sep + "path=.obsidian/core-plugins.json&encoding=utf-8",
false,
);
coreXhr.send();
if (coreXhr.status !== 200) {
return;
}
const corePlugins = JSON.parse(coreXhr.responseText);
if (!corePlugins.workspaces) {
return;
}
}
// Read workspaces.json to get the active field.
const xhr = new XMLHttpRequest();
xhr.open(
"GET",
"/api/fs/readFile" + vaultParam + sep + "path=.obsidian/workspaces.json&encoding=utf-8",
false,
);
xhr.send();
if (xhr.status !== 200) {
return;
}
const workspaces = JSON.parse(xhr.responseText);
// Always store the original active value for the write transform.
if (workspaces.active) {
window.__originalActiveWorkspace = workspaces.active;
}
// If no param was provided, seed from the active workspace.
if (!window.__workspaceName && workspaces.active) {
window.__workspaceName = workspaces.active;
setWorkspaceParam(workspaces.active);
console.log("[ignis] Workspace resolved from active:", workspaces.active);
}
} catch (e) {
console.warn("[ignis] Failed to resolve workspace name:", e);
}
}
export function initWorkspacePatch() {
const observer = new MutationObserver(() => {
if (!document.querySelector(".workspace")) {
return;
}
const plugin =
window.app &&
window.app.internalPlugins &&
window.app.internalPlugins.plugins &&
window.app.internalPlugins.plugins.workspaces;
if (!plugin || !plugin.enabled || !plugin.instance) {
return;
}
observer.disconnect();
const instance = plugin.instance;
const origLoad = instance.loadWorkspace.bind(instance);
const origSave = instance.saveWorkspace.bind(instance);
instance.loadWorkspace = function (name) {
window.__workspaceName = name;
setWorkspaceParam(name);
fsShim._contentCache.invalidate(".obsidian/workspace.json");
return origLoad(name);
};
instance.saveWorkspace = function (name) {
// Grab the current layout before switching the transport target.
const currentLayout = fsShim._contentCache.get(".obsidian/workspace.json");
window.__workspaceName = name;
setWorkspaceParam(name);
fsShim._contentCache.invalidate(".obsidian/workspace.json");
const result = origSave(name);
// Write the layout to the new workspace file so it exists on disk immediately.
if (currentLayout) {
fsShim.writeFileSync(".obsidian/workspace.json", currentLayout, "utf-8");
}
return result;
};
// Override the active field on reads so the menu matches this tab's workspace.
registerReadTransform(".obsidian/workspaces.json", (data) => {
if (!window.__workspaceName) {
return data;
}
let text =
typeof data === "string" ? data : new TextDecoder().decode(data);
try {
const parsed = JSON.parse(text);
if (parsed.active !== window.__workspaceName) {
parsed.active = window.__workspaceName;
return JSON.stringify(parsed);
}
} catch {}
return data;
});
console.log("[ignis] Workspaces plugin patched, workspace:", window.__workspaceName || "(none)");
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
}