feat: 1.3.0 release, fix export error

This commit is contained in:
binaryify
2026-08-07 12:25:28 +08:00
parent c32890fe09
commit e37d865f70
235 changed files with 2823 additions and 2011 deletions
+38
View File
@@ -0,0 +1,38 @@
# NeoDeck Local(完全离线 · 无 iframe)
夺舍官方 Kimi neo-ppt 前端镜像,单页运行:
- 无 iframe、无 www.kimi.com、无云同步 / 分享 / 云盘 / AI
- 本地打开 PPTD 文件夹并自动保存
- 官方编辑器内「导出」走本地 patched WASM
## 启动
```bash
cd editor
npx serve .
# 或 python3 -m http.server 55173
```
浏览器打开根路径即可(不要用 file://)。仓库内也可用 `npx open-kimi-ppt-skill serve`。
## 使用
1. **打开 PPTD 文件夹**(Chromium 可读写;其它浏览器可退化为只读上传)
2. 在官方编辑器 UI 中编辑
3. 顶栏 **导出** → 下载 PPTX / 图片(无分享、无 Google 云盘)
## 结构
```
editor/
index.html # 唯一入口(官方 UI + 本地顶栏)
local-bridge.js # 取代 Penpal 父页面的本地桥
local-shell.css
neo-ppt/ # 官方前端镜像
assets/
pptd_wasm_bg-DPPWdROu.wasm # 唯一一份 patched PPTX WASM(真源)
index_bg-*.wasm # resvg(渲染用,勿与上者混淆)
```
Agent 侧 `scripts/local-export/` 在 skill install 时会从上述真源拷贝一份 `pptd_wasm_bg.wasm`;仓库内导出脚本直接引用本路径。
-798
View File
@@ -1,798 +0,0 @@
import {
assertWritableChangePath,
basename,
dirname,
extractPagePaths,
joinDeckPath,
normalizeRelativePath,
titleFromManifest,
} from "./lib.js";
const EDITOR_ORIGIN = "https://www.kimi.com";
const PENPAL_MODULE = "https://statics.moonshot.cn/neo-design/assets/penpal-C4NjirZE.js";
const MAX_TEXT_BYTES = 20 * 1024 * 1024;
const MAX_IMAGE_BYTES = 20 * 1024 * 1024;
const MAX_CHANGE_COUNT = 600;
const elements = {
frame: document.querySelector("#editor"),
openFolder: document.querySelector("#open-folder"),
openDemo: document.querySelector("#open-demo"),
reload: document.querySelector("#reload-deck"),
documentTitle: document.querySelector("#document-title"),
documentPath: document.querySelector("#document-path"),
connectionStatus: document.querySelector("#connection-status"),
connectionLabel: document.querySelector("#connection-label"),
saveState: document.querySelector("#save-state"),
loadingCard: document.querySelector("#loading-card"),
loadingTitle: document.querySelector("#loading-title"),
loadingMessage: document.querySelector("#loading-message"),
activityPanel: document.querySelector("#activity-panel"),
activityList: document.querySelector("#activity-list"),
toggleActivity: document.querySelector("#toggle-activity"),
closeActivity: document.querySelector("#close-activity"),
openDialog: document.querySelector("#open-dialog"),
closeOpenDialog: document.querySelector("#close-open-dialog"),
uploadDropzone: document.querySelector("#upload-dropzone"),
chooseWritableFolder: document.querySelector("#choose-writable-folder"),
uploadFolder: document.querySelector("#upload-folder"),
deckDialog: document.querySelector("#deck-dialog"),
deckOptions: document.querySelector("#deck-options"),
folderFallback: document.querySelector("#folder-fallback"),
toastRegion: document.querySelector("#toast-region"),
};
const state = {
remote: null,
connection: null,
source: "demo",
directoryHandle: null,
fileIndex: new Map(),
memoryFiles: new Map(),
manifestPath: "presentation.pptd",
manifestDirectory: "",
manifestContent: "",
deckTitle: "演示文稿",
lastDeckPayload: null,
saveQueue: Promise.resolve(),
imageCache: new Map(),
dragDepth: 0,
readOnlyFallback: false,
};
function setConnection(kind, label) {
elements.connectionStatus.className = `status-pill status-${kind}`;
elements.connectionLabel.textContent = label;
document.documentElement.dataset.connection = kind;
}
function setSaveState(label, kind = "idle") {
elements.saveState.textContent = label;
elements.saveState.dataset.kind = kind;
document.documentElement.dataset.saveState = kind;
}
function setLoading(visible, title, message) {
elements.loadingCard.classList.toggle("is-hidden", !visible);
elements.loadingCard.setAttribute("aria-hidden", String(!visible));
if (title) elements.loadingTitle.textContent = title;
if (message) elements.loadingMessage.textContent = message;
}
function formatTime(date = new Date()) {
return new Intl.DateTimeFormat("zh-CN", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
}).format(date);
}
function addActivity(message, tone = "info") {
const item = document.createElement("li");
item.dataset.tone = tone;
item.innerHTML = `<time>${formatTime()}</time><span></span>`;
item.querySelector("span").textContent = message;
elements.activityList.prepend(item);
while (elements.activityList.children.length > 40) {
elements.activityList.lastElementChild.remove();
}
}
function toast(message, tone = "info") {
const item = document.createElement("div");
item.className = `toast toast-${tone}`;
item.textContent = message;
elements.toastRegion.append(item);
requestAnimationFrame(() => item.classList.add("is-visible"));
setTimeout(() => {
item.classList.remove("is-visible");
setTimeout(() => item.remove(), 220);
}, 3200);
}
function editorQuery() {
return new URLSearchParams({
sdkMode: "ppt-editor",
pptPlatform: "neodeck-local",
functional: JSON.stringify({
fullscreen: true,
present: true,
export: true,
close: false,
annotation: false,
feedback: false,
share: false,
versionHistory: false,
}),
sdkSaveMode: "external",
sdkImageMode: "external",
});
}
function demoDeck() {
const manifest = JSON.stringify({
version: "v2",
title: "NeoDeck Local",
size: [960, 540],
pages: ["pages/01.page", "pages/02.page"],
});
const pages = [
{
path: "pages/01.page",
content: JSON.stringify({
pageType: "content",
background: { color: "#f7f8fc" },
elements: [
{
elementId: "eyebrow",
elementType: "text",
bounds: [92, 96, 776, 40],
content: {
text: '<p><span style="font-size:16px;color:#6c5ce7;font-weight:700;letter-spacing:2px">NEODECK LOCAL</span></p>',
},
},
{
elementId: "title",
elementType: "text",
bounds: [92, 155, 776, 142],
content: {
text: '<p><span style="font-size:52px;color:#171923;font-weight:700">直接打开和保存<br/>本地 PPTD</span></p>',
},
},
{
elementId: "subtitle",
elementType: "text",
bounds: [94, 330, 660, 58],
content: {
text: '<p><span style="font-size:20px;color:#667085">第三方宿主 · 本地文件 · Kimi neo-ppt 编辑内核</span></p>',
},
},
],
animations: [
{
elementId: "title",
effect: "fade-in",
trigger: "onClick",
direction: "up",
easing: "ease-in-out",
durationMs: 700,
},
],
}),
},
{
path: "pages/02.page",
content: JSON.stringify({
pageType: "content",
background: { color: "#171923" },
elements: [
{
elementId: "second-title",
elementType: "text",
bounds: [100, 120, 760, 96],
content: {
text: '<p><span style="font-size:42px;color:#ffffff;font-weight:700">文件系统桥接已经就绪</span></p>',
},
},
{
elementId: "steps",
elementType: "text",
bounds: [102, 250, 730, 165],
content: {
text: '<p><span style="font-size:22px;color:#cbd5e1">① 选择完整的 PPTD 项目文件夹</span></p><p><span style="font-size:22px;color:#cbd5e1">② 编辑器自动读取清单、页面与素材</span></p><p><span style="font-size:22px;color:#cbd5e1">③ 自动保存变更到原目录</span></p>',
},
},
],
}),
},
];
return {
id: "neodeck-demo",
title: "NeoDeck Local",
manifestPath: "presentation.pptd",
manifestContent: manifest,
pages,
basePath: "",
isCreate: true,
};
}
function requireRemote() {
if (!state.remote) throw new Error("编辑器还没有连接完成");
return state.remote;
}
async function setDeck(payload, sourceLabel) {
const remote = requireRemote();
setLoading(true, "正在载入 PPTD", sourceLabel);
state.lastDeckPayload = payload;
state.manifestContent = payload.manifestContent;
state.manifestPath = payload.manifestPath;
state.manifestDirectory = dirname(payload.manifestPath);
state.deckTitle = payload.title;
elements.documentTitle.textContent = payload.title;
elements.documentPath.textContent = sourceLabel;
elements.reload.disabled = false;
await remote.setPPTD(payload.id, {
pptdContent: payload.manifestContent,
pages: payload.pages,
basePath: payload.basePath,
pptdPath: payload.manifestPath,
isCreate: true,
});
await remote.setEditable(true);
const status = await remote.getSlideStatus();
setLoading(false);
addActivity(`已载入「${payload.title}」,共 ${payload.pages.length} 页`, "success");
document.documentElement.dataset.deckStatus = "ready";
window.dispatchEvent(new CustomEvent("neodeck:ready", { detail: status }));
}
async function openDemo() {
state.source = "demo";
state.directoryHandle = null;
state.fileIndex.clear();
state.imageCache.clear();
state.memoryFiles.clear();
state.readOnlyFallback = false;
const payload = demoDeck();
state.memoryFiles.set(payload.manifestPath, payload.manifestContent);
for (const page of payload.pages) state.memoryFiles.set(page.path, page.content);
await setDeck(payload, "内置示例 · 修改保存在本页内存");
setSaveState("示例 · 内存保存", "memory");
}
async function indexDirectory(directoryHandle) {
const index = new Map();
async function walk(handle, prefix = "") {
for await (const [name, entry] of handle.entries()) {
if (name === ".DS_Store") continue;
const path = prefix ? `${prefix}/${name}` : name;
if (entry.kind === "directory") await walk(entry, path);
else index.set(normalizeRelativePath(path), entry);
}
}
await walk(directoryHandle);
return index;
}
function indexFallbackFiles(fileList) {
const index = new Map();
const files = [...fileList];
const firstPath = files[0]?.webkitRelativePath || files[0]?.name || "";
const rootName = firstPath.includes("/") ? firstPath.split("/")[0] : "";
for (const file of files) {
let path = file.webkitRelativePath || file.name;
if (rootName && path.startsWith(`${rootName}/`)) path = path.slice(rootName.length + 1);
index.set(normalizeRelativePath(path), { kind: "file", getFile: async () => file });
}
return index;
}
async function chooseManifest(paths) {
if (paths.length === 1) return paths[0];
elements.deckOptions.replaceChildren();
const choice = new Promise((resolve) => {
for (const path of paths) {
const button = document.createElement("button");
button.className = "deck-option";
button.type = "button";
button.innerHTML = `<span class="deck-file-icon">P</span><span><strong></strong><small></small></span><span>›</span>`;
button.querySelector("strong").textContent = basename(path);
button.querySelector("small").textContent = path;
button.addEventListener("click", () => {
elements.deckDialog.close();
resolve(path);
});
elements.deckOptions.append(button);
}
elements.deckDialog.addEventListener(
"close",
() => {
if (elements.deckDialog.returnValue === "cancel") resolve(null);
},
{ once: true },
);
});
elements.deckDialog.showModal();
return choice;
}
async function textFromIndexedFile(path) {
const entry = state.fileIndex.get(normalizeRelativePath(path));
if (!entry) throw new Error(`找不到文件:${path}`);
const file = await entry.getFile();
if (file.size > MAX_TEXT_BYTES) throw new Error(`文件超过 20 MiB:${path}`);
return file.text();
}
async function loadManifest(manifestPath, sourceLabel) {
const manifestContent = await textFromIndexedFile(manifestPath);
const pagePaths = extractPagePaths(manifestContent);
const manifestDirectory = dirname(manifestPath);
const pages = [];
const missing = [];
for (const pagePath of pagePaths.slice(0, 500)) {
const indexedPath = joinDeckPath(manifestDirectory, pagePath);
try {
pages.push({ path: pagePath, content: await textFromIndexedFile(indexedPath) });
} catch {
missing.push(pagePath);
}
}
if (pages.length === 0) throw new Error(".pptd 清单引用的页面均无法读取");
if (missing.length) toast(`跳过 ${missing.length} 个无法读取的页面`, "warning");
const fallbackTitle = basename(manifestPath).replace(/\.pptd$/i, "");
const title = titleFromManifest(manifestContent, fallbackTitle);
await setDeck(
{
id: fallbackTitle,
title,
manifestPath,
manifestContent,
pages,
basePath: manifestDirectory ? `${manifestDirectory}/` : "",
isCreate: true,
},
sourceLabel,
);
setSaveState(state.readOnlyFallback ? "只读打开" : "自动保存已开启", state.readOnlyFallback ? "readonly" : "saved");
}
async function openDirectoryHandle(directoryHandle, label = directoryHandle.name) {
setLoading(true, "正在扫描文件夹", label);
let permission = await directoryHandle.queryPermission?.({ mode: "readwrite" });
if (permission !== "granted") permission = await directoryHandle.requestPermission?.({ mode: "readwrite" });
if (permission !== "granted") throw new Error("没有获得文件夹读写权限");
state.source = "directory";
state.directoryHandle = directoryHandle;
state.readOnlyFallback = false;
state.fileIndex = await indexDirectory(directoryHandle);
state.imageCache.clear();
const manifests = [...state.fileIndex.keys()].filter((path) => path.toLowerCase().endsWith(".pptd"));
if (manifests.length === 0) throw new Error("文件夹里没有找到 .pptd 清单文件");
const manifestPath = await chooseManifest(manifests.sort());
if (!manifestPath) {
setLoading(false);
return;
}
await loadManifest(manifestPath, `${label}/${manifestPath}`);
}
async function openFallbackFiles(files) {
state.source = "fallback";
state.directoryHandle = null;
state.readOnlyFallback = true;
state.fileIndex = indexFallbackFiles(files);
state.imageCache.clear();
const manifests = [...state.fileIndex.keys()].filter((path) => path.toLowerCase().endsWith(".pptd"));
if (manifests.length === 0) throw new Error("文件夹里没有找到 .pptd 清单文件");
const manifestPath = await chooseManifest(manifests.sort());
if (manifestPath) await loadManifest(manifestPath, `${manifestPath} · 只读兼容模式`);
}
async function openDirectoryHandleReadOnly(directoryHandle, label = directoryHandle.name) {
setLoading(true, "正在扫描文件夹", label);
state.source = "fallback";
state.directoryHandle = null;
state.readOnlyFallback = true;
state.fileIndex = await indexDirectory(directoryHandle);
state.imageCache.clear();
const manifests = [...state.fileIndex.keys()].filter((path) => path.toLowerCase().endsWith(".pptd"));
if (manifests.length === 0) throw new Error("文件夹里没有找到 .pptd 清单文件");
const manifestPath = await chooseManifest(manifests.sort());
if (manifestPath) await loadManifest(manifestPath, `${label}/${manifestPath} · 拖放只读模式`);
}
async function pickDirectory() {
try {
if ("showDirectoryPicker" in window) {
const directoryHandle = await window.showDirectoryPicker({ id: "neodeck-project", mode: "readwrite" });
setOpenDialog(false);
await openDirectoryHandle(directoryHandle);
} else {
elements.folderFallback.click();
}
} catch (error) {
if (error?.name === "AbortError") return;
handleError(error, "打开文件夹失败");
}
}
async function openDroppedFolder(handles) {
const usableHandles = handles.filter(Boolean);
if (usableHandles.length !== 1 || usableHandles[0].kind !== "directory") {
throw new Error("只能拖入一个完整的 PPTD 项目文件夹,不能单独拖入 .pptd 文件");
}
const directoryHandle = usableHandles[0];
try {
setOpenDialog(false);
await openDirectoryHandle(directoryHandle);
} catch (error) {
setOpenDialog(false);
toast("无法以可写模式载入,正在尝试只读打开", "warning");
await openDirectoryHandleReadOnly(directoryHandle);
}
}
function resolveIndexedPath(requestedPath) {
if (typeof requestedPath !== "string" || !requestedPath.trim()) return null;
if (/^(?:data:image\/|https?:\/\/|blob:)/i.test(requestedPath)) return requestedPath;
let candidate = requestedPath.replace(/^file:\/\/+/, "").replaceAll("\\", "/");
try {
candidate = decodeURIComponent(candidate);
} catch {
// Preserve literal percent signs.
}
const directCandidates = [];
try {
directCandidates.push(normalizeRelativePath(candidate));
} catch {
const parts = candidate.split("/").filter(Boolean);
for (let index = 0; index < parts.length; index += 1) {
try {
directCandidates.push(normalizeRelativePath(parts.slice(index).join("/")));
} catch {
// Keep looking for a safe suffix.
}
}
}
try {
directCandidates.push(joinDeckPath(state.manifestDirectory, candidate));
} catch {
// Absolute paths may still match a safe indexed suffix below.
}
for (const path of directCandidates) if (state.fileIndex.has(path)) return path;
for (const path of state.fileIndex.keys()) {
if (directCandidates.some((candidatePath) => path.endsWith(`/${candidatePath}`) || path === candidatePath)) return path;
}
return null;
}
function fileToDataUrl(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener("load", () => resolve(String(reader.result)));
reader.addEventListener("error", () => reject(reader.error || new Error("图片读取失败")));
reader.readAsDataURL(file);
});
}
async function resolveImage(requestedPath) {
if (/^(?:data:image\/|https?:\/\/|blob:)/i.test(requestedPath)) return requestedPath;
const path = resolveIndexedPath(requestedPath);
if (!path) return "";
const entry = state.fileIndex.get(path);
const file = await entry.getFile();
if (file.size > MAX_IMAGE_BYTES) return "";
const cacheKey = `${path}:${file.lastModified}:${file.size}`;
if (!state.imageCache.has(cacheKey)) state.imageCache.set(cacheKey, fileToDataUrl(file));
return state.imageCache.get(cacheKey);
}
async function getImages(payload = {}) {
const paths = Array.isArray(payload.filePath) ? payload.filePath : [];
const result = await Promise.all(paths.map((path) => resolveImage(path).catch(() => "")));
const misses = result.filter((value) => !value).length;
if (misses) addActivity(`${paths.length} 张图片中有 ${misses} 张未找到`, "warning");
return result;
}
async function getHandleForPath(root, path, create = false) {
const parts = normalizeRelativePath(path).split("/");
const fileName = parts.pop();
let directory = root;
for (const part of parts) directory = await directory.getDirectoryHandle(part, { create });
return { directory, fileName, file: await directory.getFileHandle(fileName, { create }) };
}
async function readBackup(path) {
try {
const { file } = await getHandleForPath(state.directoryHandle, path, false);
return { existed: true, bytes: await (await file.getFile()).arrayBuffer() };
} catch (error) {
if (error?.name === "NotFoundError") return { existed: false, bytes: null };
throw error;
}
}
async function writeFile(path, content) {
const { file } = await getHandleForPath(state.directoryHandle, path, true);
const writable = await file.createWritable();
try {
await writable.write(content);
} finally {
await writable.close();
}
state.fileIndex.set(path, file);
}
async function deleteFile(path) {
const parts = normalizeRelativePath(path).split("/");
const fileName = parts.pop();
let directory = state.directoryHandle;
for (const part of parts) directory = await directory.getDirectoryHandle(part, { create: false });
try {
await directory.removeEntry(fileName);
} catch (error) {
if (error?.name !== "NotFoundError") throw error;
}
state.fileIndex.delete(path);
}
async function rollbackChanges(backups) {
const entries = [...backups.entries()].reverse();
for (const [path, backup] of entries) {
if (backup.existed) await writeFile(path, backup.bytes);
else await deleteFile(path);
}
}
async function persistChanges(payload = {}) {
const changes = Array.isArray(payload.changes) ? payload.changes : [];
if (changes.length === 0) return { fileContent: payload.fileContent, lastModifiedTime: Date.now() };
if (changes.length > MAX_CHANGE_COUNT) throw new Error(`一次保存不能超过 ${MAX_CHANGE_COUNT} 个文件`);
const normalized = changes.map((change) => {
const path = assertWritableChangePath(state.manifestDirectory, change.path);
const operation = change.operate === "delete" ? "delete" : "put";
const content = operation === "put" ? String(change.content ?? "") : null;
if (content && new Blob([content]).size > MAX_TEXT_BYTES) throw new Error(`保存内容超过 20 MiB:${path}`);
return { path, operation, content };
});
if (state.source === "demo") {
for (const change of normalized) {
if (change.operation === "delete") state.memoryFiles.delete(change.path);
else state.memoryFiles.set(change.path, change.content);
}
setSaveState("刚刚保存到内存", "memory");
addActivity(`已在内存保存 ${normalized.length} 项变更`, "success");
return { fileContent: payload.fileContent, lastModifiedTime: Date.now() };
}
if (state.readOnlyFallback || !state.directoryHandle) {
throw new Error("当前是只读兼容模式,请用支持文件夹授权的 Chromium 浏览器打开");
}
const permission = await state.directoryHandle.queryPermission({ mode: "readwrite" });
if (permission !== "granted") throw new Error("项目文件夹的写入权限已经失效");
setSaveState("正在保存…", "saving");
const backups = new Map();
try {
for (const change of normalized) backups.set(change.path, await readBackup(change.path));
for (const change of normalized) {
if (change.operation === "delete") await deleteFile(change.path);
else await writeFile(change.path, change.content);
}
} catch (error) {
await rollbackChanges(backups).catch((rollbackError) => {
addActivity(`回滚未完全成功:${rollbackError.message}`, "error");
});
throw error;
}
setSaveState("刚刚已保存", "saved");
addActivity(`已保存 ${normalized.length} 个文件变更`, "success");
return { fileContent: payload.fileContent, lastModifiedTime: Date.now() };
}
function onSave(payload) {
const operation = state.saveQueue.then(() => persistChanges(payload));
state.saveQueue = operation.catch(() => undefined);
return operation.catch((error) => {
setSaveState("保存失败", "error");
addActivity(error.message, "error");
toast(`保存失败:${error.message}`, "error");
throw error;
});
}
async function toggleFullscreen(value) {
if (value === false || document.fullscreenElement) {
if (document.fullscreenElement) await document.exitFullscreen();
return false;
}
await document.documentElement.requestFullscreen();
return true;
}
function handleError(error, context = "操作失败") {
const message = error?.message || String(error);
setLoading(false);
addActivity(`${context}:${message}`, "error");
toast(`${context}:${message}`, "error");
console.error(context, error);
}
async function connectEditor() {
setConnection("connecting", "连接中");
addActivity("正在连接公开 neo-ppt 编辑器");
elements.frame.src = `${EDITOR_ORIGIN}/neo-ppt/?${editorQuery()}`;
try {
const [{ i: connect, r: WindowMessenger }] = await Promise.all([
import(PENPAL_MODULE),
new Promise((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error("编辑器 iframe 加载超时")), 20_000);
elements.frame.addEventListener(
"load",
() => {
clearTimeout(timeout);
resolve();
},
{ once: true },
);
}),
]);
const messenger = new WindowMessenger({
remoteWindow: elements.frame.contentWindow,
allowedOrigins: [EDITOR_ORIGIN],
});
state.connection = connect({
messenger,
methods: {
close() {
toast("当前文稿由 NeoDeck Local 托管");
},
reenter() {
return state.lastDeckPayload ? setDeck(state.lastDeckPayload, elements.documentPath.textContent) : undefined;
},
toggleFullScreen: toggleFullscreen,
showFeedback() {},
sendPrompt() {
toast("AI 提示词回传尚未接入", "warning");
},
showMessage(payload) {
const message = typeof payload === "string" ? payload : payload?.message || payload?.content;
if (message) toast(message);
},
hideMessage() {},
onSave,
getImages,
setAnnotationMode() {},
setAnnotationCurrentPage() {},
upsertAnnotation() {},
removeAnnotation() {},
clearAnnotations() {},
},
});
state.remote = await Promise.race([
state.connection.promise,
new Promise((_, reject) => setTimeout(() => reject(new Error("RPC 握手超时")), 20_000)),
]);
await state.remote.setSlideConfig({ editable: true, locale: "zh-CN", theme: "light" });
setConnection("ready", "已连接");
addActivity("编辑器 RPC 握手完成", "success");
await openDemo();
} catch (error) {
setConnection("error", "连接失败");
setLoading(true, "无法连接编辑器", "请检查网络,或 Kimi 是否更新了公开前端资源。");
handleError(error, "编辑器连接失败");
}
}
function setActivityPanel(open) {
elements.activityPanel.classList.toggle("is-open", open);
elements.activityPanel.setAttribute("aria-hidden", String(!open));
}
function setOpenDialog(open) {
if (open && !elements.openDialog.open) elements.openDialog.showModal();
if (!open && elements.openDialog.open) elements.openDialog.close();
elements.openDialog.dataset.dropState = "idle";
elements.uploadDropzone.classList.remove("is-dragging");
}
elements.openFolder.addEventListener("click", () => setOpenDialog(true));
elements.closeOpenDialog.addEventListener("click", () => setOpenDialog(false));
elements.chooseWritableFolder.addEventListener("click", pickDirectory);
elements.uploadFolder.addEventListener("click", () => elements.folderFallback.click());
elements.uploadDropzone.addEventListener("click", () => elements.folderFallback.click());
elements.openDialog.addEventListener("click", (event) => {
if (event.target === elements.openDialog) setOpenDialog(false);
});
elements.openDemo.addEventListener("click", () => openDemo().catch((error) => handleError(error, "示例载入失败")));
elements.reload.addEventListener("click", () => {
if (!state.lastDeckPayload) return;
const reload = state.source === "directory" ? loadManifest(state.manifestPath, elements.documentPath.textContent) : setDeck(state.lastDeckPayload, elements.documentPath.textContent);
reload.catch((error) => handleError(error, "重新载入失败"));
});
elements.toggleActivity.addEventListener("click", () => setActivityPanel(!elements.activityPanel.classList.contains("is-open")));
elements.closeActivity.addEventListener("click", () => setActivityPanel(false));
elements.folderFallback.addEventListener("change", () => {
if (elements.folderFallback.files?.length) {
setOpenDialog(false);
openFallbackFiles(elements.folderFallback.files).catch((error) => handleError(error, "上传文件夹失败"));
}
elements.folderFallback.value = "";
});
window.addEventListener("dragenter", (event) => {
event.preventDefault();
state.dragDepth += 1;
setOpenDialog(true);
elements.openDialog.dataset.dropState = "active";
elements.uploadDropzone.classList.add("is-dragging");
});
window.addEventListener("dragover", (event) => event.preventDefault());
window.addEventListener("dragleave", () => {
state.dragDepth = Math.max(0, state.dragDepth - 1);
if (state.dragDepth === 0) {
elements.openDialog.dataset.dropState = "idle";
elements.uploadDropzone.classList.remove("is-dragging");
}
});
window.addEventListener("drop", async (event) => {
event.preventDefault();
state.dragDepth = 0;
elements.openDialog.dataset.dropState = "loading";
elements.uploadDropzone.classList.remove("is-dragging");
try {
const handles = await Promise.all([...event.dataTransfer.items].map((item) => item.getAsFileSystemHandle?.()));
if (handles.some(Boolean)) await openDroppedFolder(handles);
else if (event.dataTransfer.files?.length) {
const files = [...event.dataTransfer.files];
const roots = new Set(files.map((file) => file.webkitRelativePath?.split("/")[0]).filter(Boolean));
const isSingleFolder = files.length > 0 && roots.size === 1 && files.every((file) => file.webkitRelativePath?.includes("/"));
if (!isSingleFolder) throw new Error("只能拖入一个完整的 PPTD 项目文件夹,不能单独拖入 .pptd 文件");
setOpenDialog(false);
await openFallbackFiles(files);
} else throw new Error("请拖入包含 .pptd、pages 和 media 的完整项目文件夹");
} catch (error) {
handleError(error, "拖放打开失败");
} finally {
elements.openDialog.dataset.dropState = "idle";
}
});
window.addEventListener("beforeunload", (event) => {
if (elements.saveState.dataset.kind === "saving") {
event.preventDefault();
event.returnValue = "";
}
});
window.neoDeck = {
openDemo,
getSlideStatus: () => state.remote?.getSlideStatus(),
get status() {
return {
connected: Boolean(state.remote),
source: state.source,
title: state.deckTitle,
manifestPath: state.manifestPath,
indexedFiles: state.fileIndex.size,
saveState: elements.saveState.dataset.kind,
};
},
};
connectEditor();
+99 -144
View File
@@ -1,150 +1,105 @@
<!doctype html>
<html lang="zh-CN">
<html lang="zh-CN" class="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light" />
<meta
name="description"
content="一个可打开、编辑并保存本地 PPTD 项目的 neo-ppt 第三方宿主。"
/>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="color-scheme" content="light only" />
<title>NeoDeck Local</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div class="app-shell">
<header class="topbar">
<div class="brand" aria-label="NeoDeck Local">
<span class="brand-mark">N</span>
<span class="brand-copy">
<strong>NeoDeck</strong>
<small>LOCAL</small>
</span>
</div>
<div class="toolbar" aria-label="文件工具栏">
<button class="button button-primary" id="open-folder" type="button">
<span class="button-icon" aria-hidden="true">+</span>
打开 PPTD 文件夹
</button>
<button class="button" id="open-demo" type="button">示例</button>
<button class="icon-button" id="reload-deck" type="button" title="重新载入当前文稿" disabled>
↻
</button>
</div>
<div class="document-meta" aria-live="polite">
<div class="document-title" id="document-title">等待编辑器连接</div>
<div class="document-path" id="document-path">当前使用演示文稿</div>
</div>
<div class="topbar-actions">
<span class="status-pill status-connecting" id="connection-status">
<span class="status-dot"></span>
<span id="connection-label">连接中</span>
</span>
<span class="save-state" id="save-state">未保存到本地</span>
<button class="icon-button" id="toggle-activity" type="button" title="打开活动记录">☰</button>
</div>
</header>
<main class="editor-stage">
<iframe
id="editor"
title="neo-ppt 编辑器"
allow="fullscreen; clipboard-read; clipboard-write"
></iframe>
<section class="loading-card" id="loading-card" aria-live="polite">
<span class="loading-orbit" aria-hidden="true"></span>
<h1 id="loading-title">正在连接 neo-ppt</h1>
<p id="loading-message">编辑器就绪后会自动载入示例文稿。</p>
</section>
<aside class="activity-panel" id="activity-panel" aria-hidden="true">
<div class="activity-header">
<div>
<strong>活动记录</strong>
<small>连接、加载与保存状态</small>
</div>
<button class="icon-button" id="close-activity" type="button" aria-label="关闭活动记录">×</button>
</div>
<ol class="activity-list" id="activity-list"></ol>
<div class="activity-footer">
文件只会在你授权的项目文件夹内读写;远程编辑器不会获得任意磁盘访问权限。
</div>
</aside>
</main>
</div>
<dialog class="open-dialog" id="open-dialog" aria-labelledby="open-dialog-title">
<div class="open-dialog-shell">
<div class="open-dialog-header">
<div class="dialog-heading">
<span class="dialog-mark">P</span>
<div>
<h2 id="open-dialog-title">打开 PPTD 项目文件夹</h2>
<p>请选择或拖入包含完整项目结构的文件夹</p>
</div>
</div>
<button class="icon-button" id="close-open-dialog" type="button" aria-label="关闭打开文稿弹窗">×</button>
</div>
<button class="upload-dropzone" id="upload-dropzone" type="button">
<span class="upload-illustration" aria-hidden="true">
<span class="upload-file">P</span>
<span class="upload-arrow">↑</span>
</span>
<strong>拖动 PPTD 项目文件夹到这里</strong>
<span>仅接受一个完整文件夹,不支持单独的 .pptd 文件</span>
<em>点击上传完整文件夹(只读)</em>
</button>
<div class="open-choice-row">
<button class="open-choice open-choice-primary" id="choose-writable-folder" type="button">
<span class="choice-icon" aria-hidden="true">▣</span>
<span>
<strong>选择项目文件夹</strong>
<small>授权后支持自动保存</small>
</span>
<span aria-hidden="true">›</span>
</button>
<button class="open-choice" id="upload-folder" type="button">
<span class="choice-icon choice-icon-muted" aria-hidden="true">↑</span>
<span>
<strong>上传项目文件夹</strong>
<small>兼容模式,只读打开</small>
</span>
<span aria-hidden="true">›</span>
</button>
</div>
<div class="open-dialog-note">
<span aria-hidden="true">ⓘ</span>
<span>PPTD 项目由 .pptd 清单、pages 页面目录和 media 素材目录组成,必须上传整个文件夹。若提示含系统文件,请移除 .DS_Store。</span>
</div>
<link rel="icon" href="./neo-ppt/favicon-kimi.ico" />
<style>
/* Avoid dark FOUC before Vue theme init (official default is system → OS dark). */
html, body { background: #ffffff; color-scheme: light; }
</style>
<!-- Official neo-ppt assets (local mirror) -->
<script type="module" crossorigin src="./neo-ppt/assets/polyfills-CXLN5T9E.js"></script>
<script>
(function () {
// Official app defaults theme to "system" and may flip to dark on mount.
// Lock light mode before the Vue bundle runs.
try {
localStorage.setItem("CUSTOM_THEME", JSON.stringify("light"));
} catch (_) {}
window.INJECT_ENV = Object.assign({}, window.INJECT_ENV, { theme: "light" });
document.documentElement.classList.remove("dark");
document.documentElement.classList.add("light");
// Completely offline: block cloud APIs early
const blocked = [
/\/apiv2\//,
/www\.kimi\.com/,
/kimi\.ai/,
/slides\.kimi\./,
/gator\.volces\.com/,
/volces\.com/,
/byteimg\.com\/obj\/.*log/,
/\/list$/,
/accounts\.google\.com/,
/googleapis\.com/,
/docs\.google\.com/,
];
const nativeFetch = window.fetch.bind(window);
window.fetch = function (input, init) {
const url = typeof input === "string" ? input : input && input.url;
if (url && blocked.some((re) => re.test(String(url)))) {
return Promise.resolve(
new Response(JSON.stringify({ signature: "offline-bypass", algorithm: "Ed25519", offline: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
}),
);
}
return nativeFetch(input, init);
};
const XO = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url) {
this.__ndUrl = url;
return XO.apply(this, arguments);
};
const XS = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function () {
if (this.__ndUrl && blocked.some((re) => re.test(String(this.__ndUrl)))) {
Object.defineProperty(this, "status", { value: 200 });
Object.defineProperty(this, "responseText", { value: '{"offline":true}' });
this.onload && this.onload();
return;
}
return XS.apply(this, arguments);
};
})();
</script>
<!-- Local bridge MUST load before official app (defines __NEODECK_CONNECT__) -->
<script type="module" src="./local-bridge.js"></script>
<script type="module" crossorigin src="./neo-ppt/assets/index-jtNAhQeK.js"></script>
<link rel="modulepreload" crossorigin href="./neo-ppt/assets/rolldown-runtime-DWdDZTNf.js" />
<link rel="modulepreload" crossorigin href="./neo-ppt/assets/vue-B11_pI9F.js" />
<link rel="modulepreload" crossorigin href="./neo-ppt/assets/common-utils-G6HD4Ji1.js" />
<link rel="modulepreload" crossorigin href="./neo-ppt/assets/_plugin-vue_export-helper-B67ILkmu.js" />
<link rel="modulepreload" crossorigin href="./neo-ppt/assets/preload-helper-NECkhNH4.js" />
<link rel="modulepreload" crossorigin href="./neo-ppt/assets/kimi-DRwOD7EI.js" />
<link rel="modulepreload" crossorigin href="./neo-ppt/assets/icons-CgDhqj4h.js" />
<link rel="modulepreload" crossorigin href="./neo-ppt/assets/const-BjfH-jZe.js" />
<link rel="stylesheet" crossorigin href="./neo-ppt/assets/index-Bn1xM_xZ.css" />
<link rel="stylesheet" href="./local-shell.css" />
<style id="neodeck-layout-fix">
/* editor-app height fix: avoid 100vh under local topbar */
.editor-app[data-v-ddddc63d] { height: 100% !important; width: 100% !important; }
.editor-app .editor-content[data-v-ddddc63d] { height: calc(100% - var(--topbar-height, 52px)) !important; }
.feedback-snackbar-wrap[data-v-ddddc63d] { z-index: 20000 !important; }
</style>
</head>
<body class="neodeck-local">
<header class="nd-topbar" id="nd-topbar">
<div class="nd-brand"><strong>NeoDeck</strong><small>LOCAL</small></div>
<div class="nd-actions">
<button type="button" class="nd-btn nd-btn-primary" id="nd-open">打开 PPTD 文件夹</button>
</div>
</dialog>
<dialog class="deck-dialog" id="deck-dialog">
<form method="dialog">
<div class="dialog-heading">
<span class="dialog-mark">P</span>
<div>
<h2>选择 PPTD 文稿</h2>
<p>这个文件夹里发现了多个 .pptd 清单,请选择一个。</p>
</div>
</div>
<div class="deck-options" id="deck-options"></div>
<div class="dialog-actions">
<button class="button" value="cancel">取消</button>
</div>
</form>
</dialog>
<input id="folder-fallback" type="file" webkitdirectory multiple hidden />
<div class="toast-region" id="toast-region" aria-live="polite"></div>
<script type="module" src="./app.js"></script>
<div class="nd-meta">
<span id="nd-title">未打开文稿</span>
<span id="nd-status" class="nd-status">初始化中</span>
</div>
<input type="file" id="nd-folder" webkitdirectory directory multiple hidden />
</header>
<div id="app"></div>
<div id="nd-toast" class="nd-toast" hidden></div>
</body>
</html>
+572
View File
@@ -0,0 +1,572 @@
/**
* NeoDeck Local bridge — no iframe, no cloud.
* Official neo-ppt talks to us via window.__NEODECK_CONNECT__ instead of Penpal parent.
*/
import {
basename,
dirname,
extractPagePaths,
joinDeckPath,
normalizeRelativePath,
titleFromManifest,
} from "./lib.js";
const state = {
editor: null, // methods exposed by official editor (setPPTD, ...)
directoryHandle: null,
fileIndex: new Map(),
memoryFiles: new Map(),
imageMap: Object.create(null), // path → data URL (export host / payload)
manifestPath: "",
manifestDirectory: "",
manifestContent: "",
deckTitle: "未打开文稿",
saveQueue: Promise.resolve(),
imageCache: new Map(),
readOnly: false,
ready: false,
exportMode: false,
};
const $ = (sel) => document.querySelector(sel);
const exportMode =
new URL(location.href).searchParams.get("ndExport") === "1" ||
new URL(location.href).searchParams.get("export") === "1";
state.exportMode = exportMode;
if (exportMode) {
document.documentElement.classList.add("nd-export-mode");
document.documentElement.dataset.deckStatus = "booting";
}
function toast(msg, kind = "info") {
const el = $("#nd-toast");
if (!el) return;
el.hidden = false;
el.dataset.kind = kind;
el.textContent = msg;
clearTimeout(toast._t);
toast._t = setTimeout(() => {
el.hidden = true;
}, 3200);
}
function setStatus(text) {
const el = $("#nd-status");
if (el) el.textContent = text;
}
function setTitle(text) {
state.deckTitle = text;
const el = $("#nd-title");
if (el) el.textContent = text;
}
async function indexDirectory(directoryHandle) {
const index = new Map();
async function walk(handle, prefix = "") {
for await (const [name, entry] of handle.entries()) {
if (name === ".DS_Store") continue;
const path = prefix ? `${prefix}/${name}` : name;
if (entry.kind === "directory") await walk(entry, path);
else index.set(normalizeRelativePath(path), entry);
}
}
await walk(directoryHandle);
return index;
}
function indexFallbackFiles(fileList) {
const index = new Map();
const files = [...fileList];
const firstPath = files[0]?.webkitRelativePath || files[0]?.name || "";
const rootName = firstPath.includes("/") ? firstPath.split("/")[0] : "";
for (const file of files) {
let path = file.webkitRelativePath || file.name;
if (rootName && path.startsWith(`${rootName}/`)) path = path.slice(rootName.length + 1);
index.set(normalizeRelativePath(path), { kind: "file", getFile: async () => file });
}
return index;
}
async function textFromIndexed(path) {
const key = normalizeRelativePath(path);
if (state.memoryFiles.has(key)) return state.memoryFiles.get(key);
const entry = state.fileIndex.get(key);
if (!entry) throw new Error(`找不到文件:${path}`);
return (await entry.getFile()).text();
}
function fileToDataUrl(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(file);
});
}
function lookupImageMap(path) {
if (!path || !state.imageMap) return "";
if (state.imageMap[path]) return state.imageMap[path];
const keys = Object.keys(state.imageMap);
const hit = keys.find(
(key) => path.endsWith(`/${key}`) || key.endsWith(`/${path}`) || key === path || key.endsWith(`/${path.split("/").pop()}`),
);
return hit ? state.imageMap[hit] : "";
}
async function resolveImage(requestedPath) {
if (requestedPath == null || requestedPath === "") return "";
const raw = String(requestedPath);
if (/^(?:data:image\/|https?:\/\/|blob:)/i.test(raw)) return raw;
let path;
try {
path = normalizeRelativePath(raw.replace(/^file:\/\/+/, "").replace(/^\.\//, ""));
} catch {
path = raw.replace(/^file:\/\/+/, "").replace(/^\.\//, "").replaceAll("\\", "/");
}
const mapped = lookupImageMap(path);
if (mapped) return mapped;
const candidates = [];
const add = (p) => {
if (p && !candidates.includes(p)) candidates.push(p);
};
add(path);
if (state.manifestDirectory) {
try {
add(joinDeckPath(state.manifestDirectory, path));
} catch {
/* ignore */
}
}
// common media layouts
const base = path.split("/").pop();
if (base) {
add(`media/${base}`);
add(`assets/${base}`);
add(`images/${base}`);
if (state.manifestDirectory) {
try {
add(joinDeckPath(state.manifestDirectory, `media/${base}`));
} catch {
/* ignore */
}
}
}
// fuzzy: any indexed file ending with same relative suffix or basename
for (const key of state.fileIndex.keys()) {
if (key === path || key.endsWith("/" + path) || (base && key.endsWith("/" + base)) || key === base) {
add(key);
}
}
for (const c of candidates) {
if (!state.fileIndex.has(c) && !state.memoryFiles.has(c)) continue;
const cacheKey = c;
if (!state.imageCache.has(cacheKey)) {
try {
if (state.fileIndex.has(c)) {
const file = await state.fileIndex.get(c).getFile();
state.imageCache.set(cacheKey, await fileToDataUrl(file));
} else {
// memory text shouldn't be image; skip
continue;
}
} catch (e) {
console.warn("[neodeck] image read failed", c, e);
continue;
}
}
const url = await state.imageCache.get(cacheKey);
if (url) return url;
}
console.warn("[neodeck] image not found", requestedPath, "tried", candidates.slice(0, 8));
return "";
}
async function getImages(payload = {}) {
// Official contract: { chatId, filePath: string[] } -> string[] (data URLs / public URLs)
let paths = payload?.filePath;
if (paths == null) paths = [];
if (!Array.isArray(paths)) paths = [paths];
const out = [];
for (const p of paths) {
try {
out.push(await resolveImage(p));
} catch (e) {
console.warn("[neodeck] getImages item failed", p, e);
out.push("");
}
}
return out;
}
async function writeIndexedFile(path, content) {
if (state.readOnly || !state.directoryHandle) {
state.memoryFiles.set(path, content);
return;
}
const parts = path.split("/");
let dir = state.directoryHandle;
for (let i = 0; i < parts.length - 1; i++) {
dir = await dir.getDirectoryHandle(parts[i], { create: true });
}
const handle = await dir.getFileHandle(parts[parts.length - 1], { create: true });
const writable = await handle.createWritable();
await writable.write(content);
await writable.close();
state.fileIndex.set(path, handle);
state.memoryFiles.set(path, content);
}
async function onSave(payload) {
const changes = payload?.changes || payload?.files || [];
const list = Array.isArray(changes) ? changes : [];
setStatus("正在保存…");
try {
for (const change of list) {
if (!change?.path) continue;
const path = normalizeRelativePath(change.path);
if (!/\.(?:pptd|page)$/i.test(path)) continue;
await writeIndexedFile(path, change.content ?? "");
}
setStatus(state.readOnly ? "只读 · 已存内存" : "已保存到本地");
} catch (e) {
setStatus("保存失败");
toast(`保存失败:${e.message || e}`, "error");
throw e;
}
}
/** Called by patched official editor instead of Penpal connect */
window.__NEODECK_CONNECT__ = function neoDeckConnect(options) {
const methods = options?.methods || {};
state.editor = methods;
state.ready = true;
setStatus("编辑器就绪");
// Force light chrome immediately (official default is system → OS dark FOUC).
Promise.resolve(
methods.setSlideConfig?.({ editable: true, locale: "zh-CN", theme: "light" }),
).catch((e) => console.warn("[neodeck] setSlideConfig", e));
// host methods the editor will call via connection.promise
const host = {
close() {
toast("本地模式无需关闭");
},
reenter() {},
async toggleFullScreen(want) {
try {
const el = document.documentElement;
const isFs = Boolean(document.fullscreenElement);
if (want === true || (want !== false && !isFs)) {
if (!isFs && el.requestFullscreen) await el.requestFullscreen();
} else if (want === false || isFs) {
if (isFs && document.exitFullscreen) await document.exitFullscreen();
}
} catch (e) {
console.warn("[neodeck] fullscreen", e);
}
return Boolean(document.fullscreenElement);
},
showFeedback() {},
sendPrompt() {
toast("本地模式已禁用 AI", "warning");
},
showMessage(payload) {
const message = typeof payload === "string" ? payload : payload?.message || payload?.content;
if (message) toast(String(message));
},
hideMessage() {},
onSave,
getImages,
setAnnotationMode() {},
setAnnotationCurrentPage() {},
upsertAnnotation() {},
removeAnnotation() {},
clearAnnotations() {},
};
if (state.exportMode) {
loadExportPayload().catch((error) => {
console.error("[neodeck] export payload failed", error);
document.documentElement.dataset.deckStatus = "error";
window.exportHostError = String(error?.stack || error);
setStatus("导出载荷加载失败");
});
} else {
// Auto-open demo if nothing loaded after a beat
setTimeout(() => {
if (!state.manifestPath) openDemo().catch((e) => console.error(e));
}, 400);
}
return {
promise: Promise.resolve(host),
destroy() {
state.editor = null;
state.ready = false;
},
};
};
/** Headless / agent-browser export: load deck from ./payload.json (no folder picker). */
async function loadExportPayload() {
document.documentElement.dataset.deckStatus = "loading";
setStatus("载入导出载荷…");
const response = await fetch("./payload.json", { cache: "no-store" });
if (!response.ok) throw new Error(`payload HTTP ${response.status}`);
const payload = await response.json();
if (!state.editor?.setPPTD) throw new Error("编辑器尚未就绪");
state.directoryHandle = null;
state.readOnly = true;
state.fileIndex = new Map();
state.memoryFiles = new Map();
state.imageCache = new Map();
state.imageMap = Object.create(null);
for (const [key, value] of Object.entries(payload.imageMap || {})) {
try {
state.imageMap[normalizeRelativePath(key)] = value;
} catch {
state.imageMap[String(key).replaceAll("\\", "/")] = value;
}
state.imageCache.set(key, value);
}
const manifestPath = payload.manifestPath || "deck.pptd";
state.manifestPath = manifestPath;
state.manifestDirectory = dirname(manifestPath);
state.manifestContent = payload.manifestContent || "";
state.memoryFiles.set(normalizeRelativePath(manifestPath), state.manifestContent);
for (const page of payload.pages || []) {
if (!page?.path) continue;
state.memoryFiles.set(normalizeRelativePath(page.path), page.content ?? "");
}
const title = payload.title || titleFromManifest(state.manifestContent, basename(manifestPath));
setTitle(title);
await state.editor.setSlideConfig?.({
editable: true,
locale: "zh-CN",
theme: "light",
slideId: payload.id,
});
await state.editor.setPPTD(payload.id || `export-${Date.now()}`, {
pptdContent: state.manifestContent,
pages: payload.pages || [],
pptdPath: manifestPath,
basePath: "",
isCreate: false,
});
await state.editor.setEditable?.(true);
window.exportRemote = state.editor;
try {
window.exportSlideStatus = await state.editor.getSlideStatus?.();
} catch {
window.exportSlideStatus = null;
}
setStatus(`导出模式 · ${title}`);
document.documentElement.dataset.deckStatus = "ready";
}
async function loadDeckFromIndex(manifestPath, sourceLabel, options = {}) {
if (typeof options === "boolean") {
options = { readOnly: options, editable: !options };
}
const readOnly = Boolean(options.readOnly);
const editable = options.editable !== false;
if (!state.editor?.setPPTD) throw new Error("编辑器尚未就绪");
const manifestContent = await textFromIndexed(manifestPath);
const pagePaths = extractPagePaths(manifestContent);
const pages = [];
const missing = [];
const manifestDirectory = dirname(manifestPath);
for (const pagePath of pagePaths.slice(0, 500)) {
const indexedPath = joinDeckPath(manifestDirectory, pagePath);
try {
pages.push({ path: pagePath, content: await textFromIndexed(indexedPath) });
} catch {
missing.push(pagePath);
}
}
if (missing.length) throw new Error(`缺少页面:${missing.slice(0, 5).join(", ")}`);
const title = titleFromManifest(manifestContent, basename(manifestPath));
state.manifestPath = manifestPath;
state.manifestDirectory = manifestDirectory;
state.manifestContent = manifestContent;
state.readOnly = readOnly;
setTitle(title);
setStatus(sourceLabel);
// isCreate:true leaves the official UI in a "generating / loading" state and
// disables export / present until generate_end — use false for local opens.
await state.editor.setPPTD(`local-${Date.now()}`, {
pptdContent: manifestContent,
pages,
basePath: "",
pptdPath: manifestPath,
isCreate: false,
});
await state.editor.setEditable?.(editable);
await state.editor.setSlideConfig?.({ editable, locale: "zh-CN", theme: "light" });
toast(`已载入「${title}」· ${pages.length} 页`);
}
async function openDemo() {
state.directoryHandle = null;
state.fileIndex.clear();
state.imageCache.clear();
state.memoryFiles.clear();
const manifest = JSON.stringify({
version: "v2",
title: "NeoDeck Local",
size: [960, 540],
pages: ["pages/01.page", "pages/02.page"],
});
const page1 = JSON.stringify({
pageType: "content",
background: { color: "#f7f8fc" },
elements: [
{
elementId: "title",
elementType: "text",
bounds: [80, 160, 800, 120],
content: {
text: '<p><span style="font-size:48px;color:#171923;font-weight:700">完全本地离线</span></p>',
},
},
{
elementId: "sub",
elementType: "text",
bounds: [80, 300, 720, 60],
content: {
text: '<p><span style="font-size:20px;color:#667085">官方编辑器镜像 · 无 iframe · 无云端接口</span></p>',
},
},
],
});
const page2 = JSON.stringify({
pageType: "content",
background: { color: "#171923" },
elements: [
{
elementId: "t",
elementType: "text",
bounds: [80, 200, 800, 80],
content: {
text: '<p><span style="font-size:36px;color:#fff;font-weight:700">打开 PPTD 文件夹开始编辑</span></p>',
},
},
],
});
state.memoryFiles.set("presentation.pptd", manifest);
state.memoryFiles.set("pages/01.page", page1);
state.memoryFiles.set("pages/02.page", page2);
state.fileIndex.clear();
// synthetic index for demo
for (const [path, content] of state.memoryFiles) {
state.fileIndex.set(path, {
kind: "file",
getFile: async () => new File([content], basename(path), { type: "text/plain" }),
});
}
// Editable in UI; readOnly only means "don't write to disk" (saves stay in memory).
await loadDeckFromIndex("presentation.pptd", "内置示例 · 内存", {
readOnly: true,
editable: true,
});
}
async function openDirectoryHandle(handle) {
setStatus("扫描文件夹…");
state.directoryHandle = handle;
state.fileIndex = await indexDirectory(handle);
state.imageCache.clear();
state.memoryFiles.clear();
const manifests = [...state.fileIndex.keys()].filter((p) => p.toLowerCase().endsWith(".pptd"));
if (!manifests.length) throw new Error("文件夹内没有 .pptd");
const manifestPath = manifests.length === 1 ? manifests[0] : manifests.sort()[0];
await loadDeckFromIndex(manifestPath, `${handle.name} · 可写`, {
readOnly: false,
editable: true,
});
}
async function openFallbackFiles(fileList) {
setStatus("读取上传…");
state.directoryHandle = null;
state.readOnly = true;
state.fileIndex = indexFallbackFiles(fileList);
state.imageCache.clear();
state.memoryFiles.clear();
const manifests = [...state.fileIndex.keys()].filter((p) => p.toLowerCase().endsWith(".pptd"));
if (!manifests.length) throw new Error("没有 .pptd");
await loadDeckFromIndex(manifests.sort()[0], "上传 · 只读", {
readOnly: true,
editable: true,
});
}
function wireUi() {
$("#nd-open")?.addEventListener("click", async () => {
try {
if (window.showDirectoryPicker) {
const handle = await window.showDirectoryPicker({ mode: "readwrite" });
await openDirectoryHandle(handle);
} else {
$("#nd-folder")?.click();
}
} catch (e) {
if (e?.name === "AbortError") return;
toast(e.message || String(e), "error");
}
});
$("#nd-folder")?.addEventListener("change", async (ev) => {
const files = ev.target.files;
if (!files?.length) return;
try {
await openFallbackFiles(files);
} catch (e) {
toast(e.message || String(e), "error");
}
ev.target.value = "";
});
}
// Force sdk query params so official app enters ppt-editor external mode
(function forceSdkQuery() {
const url = new URL(location.href);
let changed = false;
const params = {
sdkMode: "ppt-editor",
pptPlatform: "neodeck-local",
functional: JSON.stringify({
fullscreen: true,
present: true,
export: true,
close: false,
annotation: false,
feedback: false,
share: false,
versionHistory: false,
}),
sdkSaveMode: "external",
sdkImageMode: "external",
};
for (const [k, v] of Object.entries(params)) {
if (url.searchParams.get(k) !== v) {
url.searchParams.set(k, v);
changed = true;
}
}
if (changed) history.replaceState(null, "", url);
})();
wireUi();
setStatus("等待编辑器…");
+174
View File
@@ -0,0 +1,174 @@
html, body {
margin: 0;
height: 100%;
overflow: hidden;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
}
body.neodeck-local {
display: grid;
grid-template-rows: 48px minmax(0, 1fr);
}
/* Agent export host: hide NeoDeck chrome, full-bleed official UI */
html.nd-export-mode body.neodeck-local {
grid-template-rows: minmax(0, 1fr);
}
html.nd-export-mode .nd-topbar,
html.nd-export-mode .nd-toast {
display: none !important;
}
html.nd-export-mode .editor-app[data-v-ddddc63d] {
height: 100% !important;
}
html.nd-export-mode .editor-app .editor-content[data-v-ddddc63d] {
height: 100% !important;
}
.nd-topbar {
z-index: 50;
display: flex;
align-items: center;
gap: 12px;
padding: 0 12px;
border-bottom: 1px solid #e6e9f0;
background: rgba(255,255,255,0.96);
box-sizing: border-box;
}
.nd-brand {
display: flex;
align-items: baseline;
gap: 6px;
min-width: 96px;
}
.nd-brand strong { font-size: 14px; color: #171923; }
.nd-brand small {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.08em;
color: #5b4dff;
}
.nd-actions {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.nd-btn {
height: 32px;
padding: 0 12px;
border-radius: 8px;
border: 1px solid #d8dee9;
background: #fff;
color: #3a4251;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
.nd-btn:hover { background: #f7f8fb; }
.nd-btn-primary {
border-color: #6c5ce7;
background: #6c5ce7;
color: #fff;
}
.nd-btn-primary:hover { background: #5b4dff; border-color: #5b4dff; }
.nd-meta {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 2px;
min-width: 120px;
max-width: 280px;
}
#nd-title {
font-size: 12px;
font-weight: 600;
color: #171923;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
.nd-status {
font-size: 11px;
color: #7a8499;
}
#app {
min-height: 0;
height: 100%;
overflow: hidden;
position: relative;
}
.nd-toast {
position: fixed;
left: 50%;
bottom: 28px;
transform: translateX(-50%);
z-index: 10001;
max-width: min(520px, 90vw);
padding: 10px 14px;
border-radius: 10px;
background: #171923;
color: #fff;
font-size: 13px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.nd-toast[data-kind="error"] { background: #c0392b; }
.nd-toast[data-kind="warning"] { background: #b7791f; }
/* Offline: hide share / cloud / Google Drive chrome from the official UI */
.share-dialog-trigger,
[class*="share-dialog"],
.export-tabs .tabs,
.export-tabs [name="google-slides"],
.export-dialog [class*="GoogleSlides"],
.export-dialog .tab[name="google-slides"] {
display: none !important;
}
.export-tabs.without-tabs .tabs {
display: none !important;
}
.nd-hint {
font-size: 12px;
color: #7a8499;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ===== Fix official editor inside local topbar shell ===== */
.nd-topbar {
z-index: 50;
position: relative;
}
#app {
overflow: hidden;
position: relative;
z-index: 1;
}
/* Official app assumes full viewport; we host it under a 48px topbar */
#app .editor-app {
width: 100% !important;
height: 100% !important;
max-height: 100% !important;
}
#app .editor-app .editor-content {
height: calc(100% - var(--topbar-height, 52px)) !important;
}
/* Snackbars / toasts above local chrome */
#app .feedback-snackbar-wrap {
z-index: 20000 !important;
bottom: 24px !important;
top: auto !important;
}
#app .feedback-snackbar {
z-index: 20000 !important;
}
/* Message toasts that are fixed to viewport */
body .kimi-message,
body [class*="message-list"],
body [class*="MessageList"],
#app [class*="snackbar"] {
z-index: 20000 !important;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
.loading[data-v-cb4a6e17]{aspect-ratio:1;width:16px;height:16px;color:var(--Labels-Primary);border-radius:50%;position:relative}.loading[data-v-cb4a6e17]:after{content:"";background:conic-gradient(from 90deg,transparent 30deg,currentColor 330.922deg,transparent 330.922deg,transparent 360deg);border-radius:50%;width:calc(90% - 2px);height:calc(90% - 2px);animation:1s linear infinite spin-cb4a6e17;position:absolute;top:2px;left:2px;-webkit-mask:radial-gradient(transparent 0,transparent 57%,currentColor 57%,currentColor 80%);mask:radial-gradient(transparent 0,transparent 57%,currentColor 57%,currentColor 80%)}@keyframes spin-cb4a6e17{0%{transform:rotate(0)}to{transform:rotate(360deg)}}
@@ -0,0 +1 @@
import{K as e,ct as t}from"./vue-B11_pI9F.js";import{t as n}from"./_plugin-vue_export-helper-B67ILkmu.js";var r={},i={class:`loading`};function a(n,r){return t(),e(`div`,i)}var o=n(r,[[`render`,a],[`__scopeId`,`data-v-cb4a6e17`]]);export{o as t};
@@ -0,0 +1 @@
System.register(["./vue-legacy-BnJhYd-u.js","./_plugin-vue_export-helper-legacy-DNBbTZ02.js"],function(e,t){var r,a,n,o,i;function c(e,t){return a(),r("div",i)}return{setters:[function(e){r=e.K,a=e.ct},function(e){n=e.t}],execute:function(){(o=document.createElement("style")).textContent='.loading[data-v-cb4a6e17]{aspect-ratio:1;width:16px;height:16px;color:var(--Labels-Primary);border-radius:50%;position:relative}.loading[data-v-cb4a6e17]:after{content:"";background:conic-gradient(from 90deg,transparent 30deg,currentColor 330.922deg,transparent 330.922deg,transparent 360deg);border-radius:50%;width:calc(90% - 2px);height:calc(90% - 2px);animation:1s linear infinite spin-cb4a6e17;position:absolute;top:2px;left:2px;-webkit-mask:radial-gradient(transparent 0,transparent 57%,currentColor 57%,currentColor 80%);mask:radial-gradient(transparent 0,transparent 57%,currentColor 57%,currentColor 80%)}@keyframes spin-cb4a6e17{0%{transform:rotate(0)}to{transform:rotate(360deg)}}\n/*$vite$:1*/',document.head.appendChild(o),i={class:"loading"},e("t",n({},[["render",c],["__scopeId","data-v-cb4a6e17"]]))}}});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
import{G as e,H as t,K as n,Nt as r,U as i,W as a,Y as o,Z as s,ct as c,kt as l,ot as u,vt as d,z as f}from"./vue-B11_pI9F.js";import{t as p}from"./_plugin-vue_export-helper-B67ILkmu.js";import{r as m,s as h}from"./useMessage-Cpv2N0cC.js";import{I as g,d as _,ot as v,q as y,t as b}from"./ThumbnailSlide-COLdjDXw.js";import{t as x}from"./kimi.button-DiBbOOiZ.js";import{t as S}from"./usePPTDTitle-senR0KSa.js";var C={class:`sdk-player`},w={class:`sdk-player-stage`},T={key:1,class:`sdk-player-controls`},E={class:`page-number`},D=p(s({__name:`Player`,setup(s){let{player:p}=h(),{pages:D,pageOrder:O,pageSize:k,meta:A,currentPageId:j,setCurrentPage:M}=v(),{setEditable:N}=y(),{setPPTDTitle:P}=S();g(`screen`),_(),m();let F=t(()=>j.value??O.value[0]),I=t(()=>{let e=F.value;return e?D.value.get(e):void 0}),L=t(()=>{let e=F.value;return e?O.value.indexOf(e):-1}),R=e=>typeof e==`number`?O.value[Math.max(0,Math.trunc(e)-1)]:e.replace(/\.page$/,``),z=t(()=>O.value.length===0||L.value<0?``:`${L.value+1} / ${O.value.length}`),B=t(()=>L.value>0),V=t(()=>L.value>=0&&L.value<O.value.length-1),H=()=>{if(!B.value)return;let e=O.value[L.value-1];e&&M(e)},U=()=>{if(!V.value)return;let e=O.value[L.value+1];e&&M(e)};return d(()=>A.value.title,e=>P(e),{immediate:!0}),d(()=>[O.value.join(`|`),p.value.page],()=>{if(p.value.page===void 0||O.value.length===0)return;let e=R(p.value.page);e&&M(e)},{immediate:!0}),u(()=>{N(!1)}),(t,s)=>(c(),n(`div`,C,[i(`div`,w,[I.value?(c(),a(b,{key:0,class:`sdk-player-slide`,slide:I.value,"page-width":l(k)[0],"page-height":l(k)[1]},null,8,[`slide`,`page-width`,`page-height`])):e(``,!0),l(p).controls&&l(O).length?(c(),n(`div`,T,[o(l(x),{type:`icon`,size:`small`,icon:`Left_b`,disabled:!B.value,onClick:f(H,[`stop`])},null,8,[`disabled`]),i(`span`,E,r(z.value),1),o(l(x),{type:`icon`,size:`small`,icon:`Right_b`,disabled:!V.value,onClick:f(U,[`stop`])},null,8,[`disabled`])])):e(``,!0)])]))}}),[[`__scopeId`,`data-v-d393e207`]]);export{D as default};
@@ -0,0 +1 @@
html,body{background:var(--Bg-Secondary,#f5f5f5);width:100%;height:auto;margin:0;overflow:hidden}#app{background:var(--Bg-Secondary,#f5f5f5);width:100%}.sdk-player[data-v-d393e207]{background:var(--Bg-Secondary,#f5f5f5);width:100vw}.sdk-player-stage[data-v-d393e207]{width:100%;position:relative}.sdk-player-slide[data-v-d393e207]{width:100%}.sdk-player-controls[data-v-d393e207]{z-index:2;backdrop-filter:blur(8px);color:#fff;pointer-events:auto;background:rgba(0,0,0,.4);border-radius:999px;align-items:center;gap:10px;padding:8px 12px;display:inline-flex;position:absolute;bottom:16px;left:50%;right:auto;transform:translate(-50%)}.page-number[data-v-d393e207]{text-align:center;color:#fff;min-width:72px;font-size:12px;line-height:1}
@@ -0,0 +1 @@
System.register(["./vue-legacy-BnJhYd-u.js","./_plugin-vue_export-helper-legacy-DNBbTZ02.js","./useMessage-legacy-BwPIj8SG.js","./ThumbnailSlide-legacy-CSXTq44l.js","./kimi.button-legacy-Dfu-bBlw.js","./usePPTDTitle-legacy-BtadEyXI.js"],function(e,a){var t,l,n,i,s,u,d,r,o,c,v,p,g,f,h,y,m,b,k,x,w,_,j,P,z,S,C,T;return{setters:[function(e){t=e.G,l=e.H,n=e.K,i=e.Nt,s=e.U,u=e.W,d=e.Y,r=e.Z,o=e.ct,c=e.kt,v=e.ot,p=e.vt,g=e.z},function(e){f=e.t},function(e){h=e.r,y=e.s},function(e){m=e.I,b=e.d,k=e.ot,x=e.q,w=e.t},function(e){_=e.t},function(e){j=e.t}],execute:function(){(P=document.createElement("style")).textContent="html,body{background:var(--Bg-Secondary,#f5f5f5);width:100%;height:auto;margin:0;overflow:hidden}#app{background:var(--Bg-Secondary,#f5f5f5);width:100%}.sdk-player[data-v-d393e207]{background:var(--Bg-Secondary,#f5f5f5);width:100vw}.sdk-player-stage[data-v-d393e207]{width:100%;position:relative}.sdk-player-slide[data-v-d393e207]{width:100%}.sdk-player-controls[data-v-d393e207]{z-index:2;backdrop-filter:blur(8px);color:#fff;pointer-events:auto;background:rgba(0,0,0,.4);border-radius:999px;align-items:center;gap:10px;padding:8px 12px;display:inline-flex;position:absolute;bottom:16px;left:50%;right:auto;transform:translate(-50%)}.page-number[data-v-d393e207]{text-align:center;color:#fff;min-width:72px;font-size:12px;line-height:1}\n/*$vite$:1*/",document.head.appendChild(P),z={class:"sdk-player"},S={class:"sdk-player-stage"},C={key:1,class:"sdk-player-controls"},T={class:"page-number"},e("default",f(r({__name:"Player",setup(e){const{player:a}=y(),{pages:r,pageOrder:f,pageSize:P,meta:$,currentPageId:B,setCurrentPage:I}=k(),{setEditable:M}=x(),{setPPTDTitle:D}=j();m("screen"),b(),h();const E=l(()=>B.value??f.value[0]),O=l(()=>{const e=E.value;return e?r.value.get(e):void 0}),W=l(()=>{const e=E.value;return e?f.value.indexOf(e):-1}),q=l(()=>0===f.value.length||W.value<0?"":`${W.value+1} / ${f.value.length}`),G=l(()=>W.value>0),H=l(()=>W.value>=0&&W.value<f.value.length-1),K=()=>{if(!G.value)return;const e=f.value[W.value-1];e&&I(e)},L=()=>{if(!H.value)return;const e=f.value[W.value+1];e&&I(e)};return p(()=>$.value.title,e=>D(e),{immediate:!0}),p(()=>[f.value.join("|"),a.value.page],()=>{if(void 0===a.value.page||0===f.value.length)return;const e="number"==typeof(t=a.value.page)?f.value[Math.max(0,Math.trunc(t)-1)]:t.replace(/\.page$/,"");var t;e&&I(e)},{immediate:!0}),v(()=>{M(!1)}),(e,l)=>(o(),n("div",z,[s("div",S,[O.value?(o(),u(w,{key:0,class:"sdk-player-slide",slide:O.value,"page-width":c(P)[0],"page-height":c(P)[1]},null,8,["slide","page-width","page-height"])):t("",!0),c(a).controls&&c(f).length?(o(),n("div",C,[d(c(_),{type:"icon",size:"small",icon:"Left_b",disabled:!G.value,onClick:g(K,["stop"])},null,8,["disabled"]),s("span",T,i(q.value),1),d(c(_),{type:"icon",size:"small",icon:"Right_b",disabled:!H.value,onClick:g(L,["stop"])},null,8,["disabled"])])):t("",!0)])]))}}),[["__scopeId","data-v-d393e207"]]))}}});
@@ -0,0 +1 @@
.popover-menu[data-v-2856486f]{cursor:pointer;border-radius:8px;justify-content:center;align-items:center;height:28px;display:inline-flex;position:relative}.popover-menu-trigger[data-v-2856486f]{justify-content:center;align-items:center;width:100%;height:100%;display:flex}.popover-menu[data-v-2856486f]:not(.disabled):hover{background-color:var(--Fills-F1)}.popover-menu.disabled[data-v-2856486f]{cursor:not-allowed;opacity:.5}.popover-menu.disabled[data-v-2856486f]:hover{background-color:transparent}.popover-menu-caret[data-v-2856486f]{width:16px;color:var(--Labels-Tertiary)}.popover-menu-content[data-v-2856486f]{top:var(--menu-top-margin,28px);bottom:var(--menu-bottom-margin,auto);background-color:var(--Bg-Tertiary);white-space:nowrap;z-index:1;border-radius:12px;position:absolute;left:50%;overflow:hidden;transform:translate(-50%,20px);box-shadow:0 4px 16.4px rgba(0,0,0,.1)}.popover-menu-content.is-floating[data-v-2856486f]{z-index:auto;position:static;top:auto;bottom:auto;left:auto;transform:none}
@@ -0,0 +1 @@
import{At as e,G as t,H as n,K as r,Tt as i,U as a,W as o,Y as s,Z as c,bt as l,ct as u,dt as d,kt as f,n as p,vt as m,xt as h,z as g}from"./vue-B11_pI9F.js";import{t as _}from"./_plugin-vue_export-helper-B67ILkmu.js";import{v}from"./index-jtNAhQeK.js";import{r as y}from"./dom-CWEl93tC.js";import{n as b,t as x}from"./menuExclusive-DWJqMbvd.js";var S=_(c({__name:`PopoverMenu`,props:{tooltip:{},tooltipPlacement:{},hideCaret:{type:Boolean},floatOptions:{},disabled:{type:Boolean},contentClass:{},contentClickCloses:{type:Boolean,default:!0}},emits:[`update:open`],setup(c,{expose:_,emit:S}){let C=c,w=S,T=i(!1),E=i(),D=n(()=>C.floatOptions?.teleport??!1),{notifyOpen:O}=x(()=>{T.value=!1}),k=()=>{D.value||(T.value=!1)},A=()=>{C.disabled||(T.value=!T.value)},j=()=>{C.contentClickCloses&&A()};return m(()=>C.disabled,e=>{e&&(T.value=!1)}),m(T,e=>{e&&O(),w(`update:open`,e)}),_({close:()=>{T.value=!1}}),(n,i)=>h((u(),r(`div`,{class:e([`popover-menu`,{disabled:c.disabled}]),onClick:g(A,[`stop`])},[s(f(y),{content:c.tooltip,disabled:!c.tooltip,placement:c.tooltipPlacement},{trigger:l(()=>[a(`div`,{ref_key:`triggerRef`,ref:E,class:`popover-menu-trigger`},[d(n.$slots,`trigger`,{popoverVisible:T.value},void 0,!0),c.hideCaret?t(``,!0):(u(),o(f(v),{key:0,class:`popover-menu-caret`,name:`Down_b`,width:`12`,height:`12`}))],512)]),_:3},8,[`content`,`disabled`,`placement`]),s(b,{open:T.value,"onUpdate:open":i[0]||(i[0]=e=>T.value=e),"trigger-el":E.value,options:c.floatOptions},{default:l(()=>[a(`div`,{class:e([`popover-menu-content auto-rtl`,[{"is-floating":D.value},c.contentClass]]),onClick:g(j,[`stop`])},[d(n.$slots,`popover`,{},void 0,!0)],2)]),_:3},8,[`open`,`trigger-el`,`options`])],2)),[[f(p),k]])}}),[[`__scopeId`,`data-v-2856486f`]]);export{S as t};
@@ -0,0 +1 @@
System.register(["./vue-legacy-BnJhYd-u.js","./_plugin-vue_export-helper-legacy-DNBbTZ02.js","./index-legacy-Ce-qKpT_.js","./dom-legacy-D-syBzLK.js","./menuExclusive-legacy-CBHfJWDM.js"],function(e,t){var o,n,a,i,r,l,p,s,d,u,c,v,m,f,g,b,x,y,h,C,k,_,w,j;return{setters:[function(e){o=e.At,n=e.G,a=e.H,i=e.K,r=e.Tt,l=e.U,p=e.W,s=e.Y,d=e.Z,u=e.bt,c=e.ct,v=e.dt,m=e.kt,f=e.n,g=e.vt,b=e.xt,x=e.z},function(e){y=e.t},function(e){h=e.v},function(e){C=e.r},function(e){k=e.n,_=e.t}],execute:function(){(w=document.createElement("style")).textContent=".popover-menu[data-v-2856486f]{cursor:pointer;border-radius:8px;justify-content:center;align-items:center;height:28px;display:inline-flex;position:relative}.popover-menu-trigger[data-v-2856486f]{justify-content:center;align-items:center;width:100%;height:100%;display:flex}.popover-menu[data-v-2856486f]:not(.disabled):hover{background-color:var(--Fills-F1)}.popover-menu.disabled[data-v-2856486f]{cursor:not-allowed;opacity:.5}.popover-menu.disabled[data-v-2856486f]:hover{background-color:transparent}.popover-menu-caret[data-v-2856486f]{width:16px;color:var(--Labels-Tertiary)}.popover-menu-content[data-v-2856486f]{top:var(--menu-top-margin,28px);bottom:var(--menu-bottom-margin,auto);background-color:var(--Bg-Tertiary);white-space:nowrap;z-index:1;border-radius:12px;position:absolute;left:50%;overflow:hidden;transform:translate(-50%,20px);box-shadow:0 4px 16.4px rgba(0,0,0,.1)}.popover-menu-content.is-floating[data-v-2856486f]{z-index:auto;position:static;top:auto;bottom:auto;left:auto;transform:none}\n/*$vite$:1*/",document.head.appendChild(w),j=d({__name:"PopoverMenu",props:{tooltip:{},tooltipPlacement:{},hideCaret:{type:Boolean},floatOptions:{},disabled:{type:Boolean},contentClass:{},contentClickCloses:{type:Boolean,default:!0}},emits:["update:open"],setup(e,{expose:t,emit:d}){const y=e,w=d,j=r(!1),B=r(),O=a(()=>y.floatOptions?.teleport??!1),{notifyOpen:$}=_(()=>{j.value=!1}),z=()=>{O.value||(j.value=!1)},E=()=>{y.disabled||(j.value=!j.value)},P=()=>{y.contentClickCloses&&E()};return g(()=>y.disabled,e=>{e&&(j.value=!1)}),g(j,e=>{e&&$(),w("update:open",e)}),t({close:()=>{j.value=!1}}),(t,a)=>b((c(),i("div",{class:o(["popover-menu",{disabled:e.disabled}]),onClick:x(E,["stop"])},[s(m(C),{content:e.tooltip,disabled:!e.tooltip,placement:e.tooltipPlacement},{trigger:u(()=>[l("div",{ref_key:"triggerRef",ref:B,class:"popover-menu-trigger"},[v(t.$slots,"trigger",{popoverVisible:j.value},void 0,!0),e.hideCaret?n("",!0):(c(),p(m(h),{key:0,class:"popover-menu-caret",name:"Down_b",width:"12",height:"12"}))],512)]),_:3},8,["content","disabled","placement"]),s(k,{open:j.value,"onUpdate:open":a[0]||(a[0]=e=>j.value=e),"trigger-el":B.value,options:e.floatOptions},{default:u(()=>[l("div",{class:o(["popover-menu-content auto-rtl",[{"is-floating":O.value},e.contentClass]]),onClick:x(P,["stop"])},[v(t.$slots,"popover",{},void 0,!0)],2)]),_:3},8,["open","trigger-el","options"])],2)),[[m(f),z]])}}),e("t",y(j,[["__scopeId","data-v-2856486f"]]))}}});
@@ -0,0 +1 @@
import{H as e,J as t,K as n,Nt as r,Tt as i,U as a,W as o,Y as ee,Z as s,at as c,ct as l,k as u,kt as d,ot as f,vt as p}from"./vue-B11_pI9F.js";import{t as m}from"./_plugin-vue_export-helper-B67ILkmu.js";import{B as h,I as te,L as g,v as _}from"./index-jtNAhQeK.js";import{ot as v}from"./ThumbnailSlide-COLdjDXw.js";import{i as y,t as b}from"./Screen-e_2r2R5P.js";import{t as x}from"./usePPTDTitle-senR0KSa.js";var S={key:1,class:`presenter-page`},C={class:`presenter-panel`},w=[`disabled`],T={class:`presenter-start-icon`},E=m(s({__name:`Presenter`,setup(s){let m=u(),{t:E}=g(),{restorePPTD:D,setCurrentPage:O,meta:ne,pageOrder:k}=v(),{setPPTDTitle:re}=x(),A=i(!1),j=i(!1),M=i(!1),N=i(),P=i(0),F=null,I=null,L=e(()=>{let e=m.query.sessionId;return typeof e==`string`?e:``}),R=e(()=>!!L.value&&!!window.opener),z=()=>navigator.presentation?.receiver,B=e=>{if(typeof e==`string`)try{return JSON.parse(e)}catch{return null}return typeof e==`object`&&e?e:null},V=()=>h(m.query.lang)??h(m.query.locale)??null;(()=>{let e=V();e&&te(e)})(),p(()=>ne.value.title,e=>re(e),{immediate:!0});let H=e=>{if(I){try{I.send(JSON.stringify(e))}catch{}return}window.opener?.postMessage(e,window.location.origin)},U=()=>{F&&(clearTimeout(F),F=null)},W=()=>{L.value&&(window.opener||I)&&H({type:`connect-failed`,source:`presenter`,sessionId:L.value}),window.close()},G=e=>{let t=k.value.includes(e.pageId)?e.pageId:k.value[e.index];if(!t)return;let n=k.value.indexOf(t);N.value=t,P.value=n===-1?0:n,O(t)},K=e=>{let t=N.value,n=P.value;D(e),A.value=!0;let r=Math.min(n,k.value.length-1),i=t&&k.value.includes(t)?t:k.value[r];if(!i){N.value=void 0,P.value=0;return}let a=k.value.indexOf(i);N.value=i,P.value=a===-1?0:a,O(i)},q=e=>{if(!(!L.value||e.sessionId!==L.value)&&e.source===`editor`)switch(e.type){case`snapshot`:U(),D(e.pptd),A.value=!0,G(e.page),M.value&&(j.value=!0);break;case`content-sync`:K(e.pptd);break;case`page-change`:G(e.page);break;case`close`:j.value=!1,window.close();break}},J=e=>{e.origin===window.location.origin&&(window.opener&&e.source!==window.opener||q(e.data))},Y=()=>{window.addEventListener(`message`,J),H({type:`presenter-ready`,source:`presenter`,sessionId:L.value}),F=setTimeout(()=>{A.value||W()},3e3)},X=e=>{let t=B(e.data);t&&q(t)},Z=()=>{window.close()},Q=e=>{I=e,M.value=!0,I.addEventListener(`message`,X),I.addEventListener(`close`,Z),I.addEventListener(`terminate`,Z),H({type:`presenter-ready`,source:`presenter`,sessionId:L.value}),F=setTimeout(()=>{A.value||W()},3e3)},ie=async()=>{let e=z();if(!e)return!1;let t=await e.connectionList,n=t.connections[0];return n?(Q(n),!0):new Promise(e=>{let n=setTimeout(()=>{t.removeEventListener(`connectionavailable`,r),e(!1)},3e3),r=i=>{clearTimeout(n),t.removeEventListener(`connectionavailable`,r),Q(i.connection),e(!0)};t.addEventListener(`connectionavailable`,r)})},ae=async()=>{if(R.value){Y();return}await ie()||W()},oe=async()=>{if(!A.value)return;let e=k.value[0];e&&$({pageId:e,index:0});try{await y()}catch{}j.value=!0},$=e=>{L.value&&(G(e),H({type:`page-change`,source:`presenter`,sessionId:L.value,page:e}))},se=()=>{L.value&&H({type:`close`,source:`presenter`,sessionId:L.value});try{I?.close()}catch{}window.close()},ce=()=>{if(M.value){se();return}j.value=!1};return f(ae),c(()=>{window.removeEventListener(`message`,J),U(),L.value&&H({type:`close`,source:`presenter`,sessionId:L.value}),I&&(I.removeEventListener(`message`,X),I.removeEventListener(`close`,Z),I.removeEventListener(`terminate`,Z),I=null)}),(e,i)=>j.value&&A.value?(l(),o(b,{key:0,"start-index":P.value,"current-page-id":N.value,"auto-fullscreen":!1,onPageChange:$,onExit:ce},null,8,[`start-index`,`current-page-id`])):(l(),n(`div`,S,[a(`div`,C,[a(`button`,{type:`button`,class:`presenter-start-button`,disabled:!A.value,onClick:oe},[a(`span`,T,[ee(d(_),{name:`Play`,height:24})]),t(` `+r(d(E)(`presenter.start`)),1)],8,w)])]))}}),[[`__scopeId`,`data-v-65fc3c19`]]);export{E as default};
@@ -0,0 +1 @@
.presenter-page[data-v-65fc3c19]{background:var(--Bg-Secondary);width:100vw;height:100vh;color:var(--Labels-Primary);justify-content:center;align-items:center;display:flex}.presenter-panel[data-v-65fc3c19]{box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:14px;width:100%;height:100%;display:flex}.presenter-start-button[data-v-65fc3c19]{background:var(--Labels-Secondary);color:#fff;cursor:pointer;border:none;justify-content:center;align-items:center;gap:10px;width:100%;height:100%;padding:0 28px;font-size:18px;font-weight:600;line-height:24px;transition:background-color .18s,opacity .18s,transform .18s;display:inline-flex}.presenter-start-button[data-v-65fc3c19]:hover:not(:disabled){background:var(--Labels-Primary)}.presenter-start-button[data-v-65fc3c19]:disabled{opacity:.45;cursor:not-allowed}.presenter-start-icon[data-v-65fc3c19]{justify-content:center;align-items:center;display:flex}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{At as e,B as t,H as n,K as r,Mt as i,U as a,W as o,Z as s,ct as c,kt as l,ot as u,ut as d,vt as f}from"./vue-B11_pI9F.js";import{t as p}from"./_plugin-vue_export-helper-B67ILkmu.js";import{r as m,s as h}from"./useMessage-Cpv2N0cC.js";import{d as g,ot as _,q as v,t as y}from"./ThumbnailSlide-COLdjDXw.js";import{t as b}from"./usePPTDTitle-senR0KSa.js";var x={class:`sdk-preview`},S=p(s({__name:`Preview`,setup(s){g(),m();let{pages:p,pageOrder:S,pageSize:C,meta:w,currentPageId:T,setCurrentPage:E}=_(),{setEditable:D}=v(),{setPPTDTitle:O}=b(),{preview:k}=h(),A=n(()=>({"--preview-page-margin":k.value.margin,gridTemplateColumns:`repeat(${k.value.col}, minmax(0, 1fr))`}));function j(e){E(e)}return f(()=>w.value.title,e=>O(e),{immediate:!0}),u(()=>{D(!1)}),(n,s)=>(c(),r(`div`,x,[a(`div`,{class:`sdk-preview-pages`,style:i(A.value)},[(c(!0),r(t,null,d(l(S),(t,n)=>(c(),o(y,{key:t,class:e([`sdk-preview-page`,{current:l(T)===t}]),"data-preview-page-id":t,"data-preview-page-index":n,slide:l(p).get(t),"page-width":l(C)[0],"page-height":l(C)[1],lazy:``,onClick:e=>j(t)},null,8,[`class`,`data-preview-page-id`,`data-preview-page-index`,`slide`,`page-width`,`page-height`,`onClick`]))),128))],4)]))}}),[[`__scopeId`,`data-v-8acb7e59`]]);export{S as default};
@@ -0,0 +1 @@
html,body{background:var(--Bg-Secondary,#f5f5f5);height:auto;min-height:100vh;overflow-y:auto}#app{background:var(--Bg-Secondary,#f5f5f5);width:100%;min-height:100vh}.sdk-preview[data-v-8acb7e59]{width:100vw;min-height:100vh;background:var(--Bg-Secondary,#f5f5f5);min-height:100dvh}.sdk-preview-pages[data-v-8acb7e59]{--preview-page-margin:20px;gap:var(--preview-page-margin);width:100vw;min-height:100vh;box-sizing:border-box;min-height:100dvh;padding:var(--preview-page-margin);grid-template-columns:minmax(0,1fr);display:grid}.sdk-preview-page[data-v-8acb7e59]{border:1px solid var(--Always-White,#fff);outline:1px solid var(--Separators-S1,rgba(0,0,0,.13));cursor:pointer;width:100%;min-width:0;scroll-margin:var(--preview-page-margin);background:#fff;border-radius:12px;flex-shrink:0;overflow:hidden;box-shadow:0 4px 16.4px rgba(0,0,0,.1)}
@@ -0,0 +1 @@
System.register(["./vue-legacy-BnJhYd-u.js","./_plugin-vue_export-helper-legacy-DNBbTZ02.js","./useMessage-legacy-BwPIj8SG.js","./ThumbnailSlide-legacy-CSXTq44l.js","./usePPTDTitle-legacy-BtadEyXI.js"],function(e,a){var i,t,r,n,d,g,p,s,l,v,o,c,u,h,m,w,f,b,x,y,k,S,P;return{setters:[function(e){i=e.At,t=e.B,r=e.H,n=e.K,d=e.Mt,g=e.U,p=e.W,s=e.Z,l=e.ct,v=e.kt,o=e.ot,c=e.ut,u=e.vt},function(e){h=e.t},function(e){m=e.r,w=e.s},function(e){f=e.d,b=e.ot,x=e.q,y=e.t},function(e){k=e.t}],execute:function(){(S=document.createElement("style")).textContent="html,body{background:var(--Bg-Secondary,#f5f5f5);height:auto;min-height:100vh;overflow-y:auto}#app{background:var(--Bg-Secondary,#f5f5f5);width:100%;min-height:100vh}.sdk-preview[data-v-8acb7e59]{width:100vw;min-height:100vh;background:var(--Bg-Secondary,#f5f5f5);min-height:100dvh}.sdk-preview-pages[data-v-8acb7e59]{--preview-page-margin:20px;gap:var(--preview-page-margin);width:100vw;min-height:100vh;box-sizing:border-box;min-height:100dvh;padding:var(--preview-page-margin);grid-template-columns:minmax(0,1fr);display:grid}.sdk-preview-page[data-v-8acb7e59]{border:1px solid var(--Always-White,#fff);outline:1px solid var(--Separators-S1,rgba(0,0,0,.13));cursor:pointer;width:100%;min-width:0;scroll-margin:var(--preview-page-margin);background:#fff;border-radius:12px;flex-shrink:0;overflow:hidden;box-shadow:0 4px 16.4px rgba(0,0,0,.1)}\n/*$vite$:1*/",document.head.appendChild(S),P={class:"sdk-preview"},e("default",h(s({__name:"Preview",setup(e){f(),m();const{pages:a,pageOrder:s,pageSize:h,meta:S,currentPageId:C,setCurrentPage:T}=b(),{setEditable:_}=x(),{setPPTDTitle:j}=k(),{preview:B}=w(),z=r(()=>({"--preview-page-margin":B.value.margin,gridTemplateColumns:`repeat(${B.value.col}, minmax(0, 1fr))`}));return u(()=>S.value.title,e=>j(e),{immediate:!0}),o(()=>{_(!1)}),(e,r)=>(l(),n("div",P,[g("div",{class:"sdk-preview-pages",style:d(z.value)},[(l(!0),n(t,null,c(v(s),(e,t)=>(l(),p(y,{key:e,class:i(["sdk-preview-page",{current:v(C)===e}]),"data-preview-page-id":e,"data-preview-page-index":t,slide:v(a).get(e),"page-width":v(h)[0],"page-height":v(h)[1],lazy:"",onClick:a=>function(e){T(e)}(e)},null,8,["class","data-preview-page-id","data-preview-page-index","slide","page-width","page-height","onClick"]))),128))],4)]))}}),[["__scopeId","data-v-8acb7e59"]]))}}});
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
html,body,#app{background:#fff;width:100%;height:100%;min-height:100vh}.raw-preview[data-v-2545fa61]{width:100vw;min-height:100vh;background:#fff;min-height:100dvh}.raw-preview-pages[data-v-2545fa61]{flex-direction:column;width:100%;display:flex}.raw-preview-page[data-v-2545fa61]{background:#fff;border:0;border-radius:0;flex-shrink:0;margin:0;overflow:hidden}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.thumbnail-slide-switcher[data-v-8b20498e]{width:100%;height:100%}.slide-container[data-v-8b20498e]{background-color:#1d1d1d;justify-content:center;align-items:center;width:100%;height:100%;display:flex}.slide-wrapper[data-v-8b20498e]{max-width:100vw}.screen[data-v-a6f7cc7b]{z-index:200;background-color:#1d1d1d;width:100vw;height:100vh;position:fixed;top:0;left:0}.screen-controls[data-v-a6f7cc7b]{z-index:10;opacity:0;pointer-events:none;justify-content:space-between;align-items:flex-end;transition:opacity .26s,transform .26s;display:flex;position:fixed;bottom:8px;left:8px;right:16px;transform:translateY(4px)}.screen-controls.is-visible[data-v-a6f7cc7b]{opacity:1;transform:translateY(0)}.screen-controls.is-visible .tools-left[data-v-a6f7cc7b],.screen-controls.is-visible .tools-right[data-v-a6f7cc7b]{pointer-events:auto}.tools-left[data-v-a6f7cc7b]{color:#666;pointer-events:none;gap:8px;font-size:25px;display:flex}.tool-btn[data-v-a6f7cc7b]{color:#666;opacity:.3;cursor:pointer;background:0 0;border:0;justify-content:center;align-items:center;width:25px;height:25px;padding:0;transition:opacity .2s;display:inline-flex}.tool-btn[data-v-a6f7cc7b]:hover{opacity:.95}.tools-right[data-v-a6f7cc7b]{pointer-events:none;align-items:center;gap:8px;margin-bottom:4px;display:flex}.presenter-button[data-v-a6f7cc7b]{color:#999;display:inline-flex}.presenter-button[data-v-a6f7cc7b] .button-icon-only.button-small{color:inherit;background:rgba(0,0,0,.28);border-color:rgba(255,255,255,.08);border-radius:4px}.presenter-button[data-v-a6f7cc7b] .button-icon-only.button-small:hover:not([disabled]):not(.button-disabled){color:#ddd;background:rgba(0,0,0,.38);border-color:rgba(255,255,255,.18)}.effect-menu[data-v-a6f7cc7b]{color:#999;background:rgba(0,0,0,.28);border:1px solid rgba(255,255,255,.08);border-radius:4px;height:26px}.effect-menu[data-v-a6f7cc7b]:hover{color:#ddd;background:rgba(0,0,0,.38);border-color:rgba(255,255,255,.18)}.effect-menu-trigger[data-v-a6f7cc7b]{width:136px;padding-left:8px;font-size:12px;line-height:18px}.effect-menu-content{background:rgba(20,20,20,.96)!important;border-radius:6px!important;box-shadow:0 8px 24px rgba(0,0,0,.32)!important}.effect-menu-list{min-width:168px;box-shadow:none!important;background:0 0!important;border-radius:6px!important;padding:4px!important}.effect-menu-list .menu-item{color:#999!important;border-radius:4px!important;height:30px!important}.effect-menu-list .menu-item:hover{color:#ddd!important;background:rgba(255,255,255,.08)!important}.effect-menu-list .menu-item.active{color:#ddd!important;background:rgba(255,255,255,.12)!important}.effect-menu-list .menu-item-text{font-size:12px!important}.page-number[data-v-a6f7cc7b]{color:#666;text-align:center;background:rgba(0,0,0,.28);border-radius:4px;min-width:48px;padding:4px 8px;font-size:13px;line-height:18px}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.menu-select[data-v-4c23e34a]{cursor:pointer;user-select:none;border-radius:6px;align-items:center;height:28px;display:inline-flex;position:relative}.menu-select[data-v-4c23e34a]:hover,.menu-select.is-active[data-v-4c23e34a]{background:var(--Fills-F1)}.menu-select.is-active[data-v-4c23e34a]:hover{background:var(--Fills-F1-hover)}.menu-select.is-keep-highlight[data-v-4c23e34a]{background:var(--Fills-F1)}.menu-select.is-keep-highlight[data-v-4c23e34a]:hover,.menu-select.is-keep-highlight.is-active[data-v-4c23e34a]{background:var(--Fills-F1-hover)}.menu-select.is-keep-highlight.is-active[data-v-4c23e34a]:hover{background:var(--Fills-F1-active)}.menu-select.disabled[data-v-4c23e34a]{opacity:.35;cursor:not-allowed}.menu-select.disabled[data-v-4c23e34a]:hover{background:0 0}.menu-select-menu[data-v-4c23e34a]{top:var(--menu-top-margin,28px);bottom:var(--menu-bottom-margin,auto);z-index:1000;white-space:nowrap;min-width:max-content;max-height:172px;position:absolute;left:50%;overflow:auto;transform:translate(-50%,20px)}.menu-select-menu[data-v-4c23e34a]::-webkit-scrollbar-track{margin:8px 0}.menu-select-menu.is-floating[data-v-4c23e34a]{position:static;top:auto;bottom:auto;left:auto;transform:none}.menu-select-title[data-v-4c23e34a]{color:var(--Labels-Primary);align-items:center;padding:4px 6px;font-size:14px;display:flex}.menu-select-title-text.max-width[data-v-4c23e34a]{text-overflow:ellipsis;overflow:hidden}.menu-select-title-icon[data-v-4c23e34a]{margin-right:-10px}.menu-select-title-caret[data-v-4c23e34a]{margin-left:10px}
@@ -0,0 +1 @@
import{At as e,H as t,K as n,Mt as r,Nt as i,Tt as a,U as o,W as s,Y as c,Z as l,bt as u,ct as d,dt as f,kt as p,p as m,q as h,vt as g}from"./vue-B11_pI9F.js";import{t as _}from"./_plugin-vue_export-helper-B67ILkmu.js";import{v}from"./index-jtNAhQeK.js";import{r as y}from"./dom-CWEl93tC.js";import{i as b,n as x,r as S,t as C}from"./menuExclusive-DWJqMbvd.js";var w=_(l({__name:`SelectMenu`,props:{menuList:{},activeMenu:{},tooltip:{},maxWidth:{},keepHighlight:{type:Boolean},scrollIntoView:{type:Boolean},icon:{},floatOptions:{},disabled:{type:Boolean}},emits:[`click`],setup(l,{emit:_}){let w=l,T=_,E=a(),D=a(),O=a(!1),{notifyOpen:k}=C(()=>{O.value=!1}),A=e=>{T(`click`,e),O.value=!1};m(document,`click`,e=>{w.floatOptions?.teleport||E.value&&!E.value.contains(e.target)&&(O.value=!1)});let j=t(()=>w.menuList.find(e=>e!==`divider`&&e.value===w.activeMenu));return m(window,`keydown`,e=>{O.value&&e.key===`Escape`&&(O.value=!1)}),g(O,e=>{e&&k()}),(t,a)=>(d(),n(`div`,{ref_key:`menuSelectRef`,ref:E,class:e([`menu-select`,{"is-active":O.value,"is-keep-highlight":l.keepHighlight,disabled:l.disabled}])},[c(p(y),{content:l.tooltip,placement:`top`,disabled:!l.tooltip},{trigger:u(()=>[o(`div`,{ref_key:`menuTriggerRef`,ref:D,class:`menu-select-title`,onClick:a[0]||(a[0]=e=>!l.disabled&&(O.value=!O.value))},[f(t.$slots,`label`,{},()=>[l.icon?(d(),s(p(v),{key:0,class:`menu-select-title-icon`,name:l.icon,width:`20`,height:`20`},null,8,[`name`])):p(b)(j.value)?(d(),s(p(v),{key:1,class:`menu-select-title-icon`,name:j.value.name,width:`20`,height:`20`},null,8,[`name`])):(d(),n(`span`,{key:2,class:e([`menu-select-title-text`,{"max-width":w.maxWidth}]),style:r({maxWidth:w.maxWidth})},i(j.value?.label),7))],!0),c(p(v),{class:`menu-select-title-caret`,name:`Down_b`,width:`12`,height:`12`})],512)]),_:3},8,[`content`,`disabled`]),c(x,{open:O.value,"onUpdate:open":a[1]||(a[1]=e=>O.value=e),"trigger-el":D.value,options:l.floatOptions},{default:u(()=>[c(S,{class:e([`menu-select-menu auto-rtl`,{"is-floating":l.floatOptions?.teleport}]),style:r(l.floatOptions?.matchTriggerWidth?{width:`100%`,boxSizing:`border-box`}:void 0),"menu-list":l.menuList,"active-menu":l.activeMenu,"scroll-into-view":l.scrollIntoView,disabled:l.disabled,onClick:A},h({extra:u(()=>[f(t.$slots,`extra`,{},void 0,!0)]),_:2},[t.$slots.item?{name:`default`,fn:u(({item:e,index:n})=>[f(t.$slots,`item`,{item:e,index:n},void 0,!0)]),key:`0`}:void 0]),1032,[`class`,`style`,`menu-list`,`active-menu`,`scroll-into-view`,`disabled`])]),_:3},8,[`open`,`trigger-el`,`options`])],2))}}),[[`__scopeId`,`data-v-4c23e34a`]]);export{w as t};
@@ -0,0 +1 @@
System.register(["./vue-legacy-BnJhYd-u.js","./_plugin-vue_export-helper-legacy-DNBbTZ02.js","./index-legacy-Ce-qKpT_.js","./dom-legacy-D-syBzLK.js","./menuExclusive-legacy-CBHfJWDM.js"],function(e,t){var a,i,l,n,o,s,c,u,r,d,m,v,p,g,h,f,x,b,k,y,w,_,F,O,W,$;return{setters:[function(e){a=e.At,i=e.H,l=e.K,n=e.Mt,o=e.Nt,s=e.Tt,c=e.U,u=e.W,r=e.Y,d=e.Z,m=e.bt,v=e.ct,p=e.dt,g=e.kt,h=e.p,f=e.q,x=e.vt},function(e){b=e.t},function(e){k=e.v},function(e){y=e.r},function(e){w=e.i,_=e.n,F=e.r,O=e.t}],execute:function(){(W=document.createElement("style")).textContent=".menu-select[data-v-4c23e34a]{cursor:pointer;user-select:none;border-radius:6px;align-items:center;height:28px;display:inline-flex;position:relative}.menu-select[data-v-4c23e34a]:hover,.menu-select.is-active[data-v-4c23e34a]{background:var(--Fills-F1)}.menu-select.is-active[data-v-4c23e34a]:hover{background:var(--Fills-F1-hover)}.menu-select.is-keep-highlight[data-v-4c23e34a]{background:var(--Fills-F1)}.menu-select.is-keep-highlight[data-v-4c23e34a]:hover,.menu-select.is-keep-highlight.is-active[data-v-4c23e34a]{background:var(--Fills-F1-hover)}.menu-select.is-keep-highlight.is-active[data-v-4c23e34a]:hover{background:var(--Fills-F1-active)}.menu-select.disabled[data-v-4c23e34a]{opacity:.35;cursor:not-allowed}.menu-select.disabled[data-v-4c23e34a]:hover{background:0 0}.menu-select-menu[data-v-4c23e34a]{top:var(--menu-top-margin,28px);bottom:var(--menu-bottom-margin,auto);z-index:1000;white-space:nowrap;min-width:max-content;max-height:172px;position:absolute;left:50%;overflow:auto;transform:translate(-50%,20px)}.menu-select-menu[data-v-4c23e34a]::-webkit-scrollbar-track{margin:8px 0}.menu-select-menu.is-floating[data-v-4c23e34a]{position:static;top:auto;bottom:auto;left:auto;transform:none}.menu-select-title[data-v-4c23e34a]{color:var(--Labels-Primary);align-items:center;padding:4px 6px;font-size:14px;display:flex}.menu-select-title-text.max-width[data-v-4c23e34a]{text-overflow:ellipsis;overflow:hidden}.menu-select-title-icon[data-v-4c23e34a]{margin-right:-10px}.menu-select-title-caret[data-v-4c23e34a]{margin-left:10px}\n/*$vite$:1*/",document.head.appendChild(W),$=d({__name:"SelectMenu",props:{menuList:{},activeMenu:{},tooltip:{},maxWidth:{},keepHighlight:{type:Boolean},scrollIntoView:{type:Boolean},icon:{},floatOptions:{},disabled:{type:Boolean}},emits:["click"],setup(e,{emit:t}){const d=e,b=t,W=s(),$=s(),j=s(!1),{notifyOpen:M}=O(()=>{j.value=!1}),C=e=>{b("click",e),j.value=!1};h(document,"click",e=>{d.floatOptions?.teleport||W.value&&!W.value.contains(e.target)&&(j.value=!1)});const E=i(()=>d.menuList.find(e=>"divider"!==e&&e.value===d.activeMenu));return h(window,"keydown",e=>{j.value&&"Escape"===e.key&&(j.value=!1)}),x(j,e=>{e&&M()}),(t,i)=>(v(),l("div",{ref_key:"menuSelectRef",ref:W,class:a(["menu-select",{"is-active":j.value,"is-keep-highlight":e.keepHighlight,disabled:e.disabled}])},[r(g(y),{content:e.tooltip,placement:"top",disabled:!e.tooltip},{trigger:m(()=>[c("div",{ref_key:"menuTriggerRef",ref:$,class:"menu-select-title",onClick:i[0]||(i[0]=t=>!e.disabled&&(j.value=!j.value))},[p(t.$slots,"label",{},()=>[e.icon?(v(),u(g(k),{key:0,class:"menu-select-title-icon",name:e.icon,width:"20",height:"20"},null,8,["name"])):g(w)(E.value)?(v(),u(g(k),{key:1,class:"menu-select-title-icon",name:E.value.name,width:"20",height:"20"},null,8,["name"])):(v(),l("span",{key:2,class:a(["menu-select-title-text",{"max-width":d.maxWidth}]),style:n({maxWidth:d.maxWidth})},o(E.value?.label),7))],!0),r(g(k),{class:"menu-select-title-caret",name:"Down_b",width:"12",height:"12"})],512)]),_:3},8,["content","disabled"]),r(_,{open:j.value,"onUpdate:open":i[1]||(i[1]=e=>j.value=e),"trigger-el":$.value,options:e.floatOptions},{default:m(()=>[r(F,{class:a(["menu-select-menu auto-rtl",{"is-floating":e.floatOptions?.teleport}]),style:n(e.floatOptions?.matchTriggerWidth?{width:"100%",boxSizing:"border-box"}:void 0),"menu-list":e.menuList,"active-menu":e.activeMenu,"scroll-into-view":e.scrollIntoView,disabled:e.disabled,onClick:C},f({extra:m(()=>[p(t.$slots,"extra",{},void 0,!0)]),_:2},[t.$slots.item?{name:"default",fn:m(({item:e,index:a})=>[p(t.$slots,"item",{item:e,index:a},void 0,!0)]),key:"0"}:void 0]),1032,["class","style","menu-list","active-menu","scroll-into-view","disabled"])]),_:3},8,["open","trigger-el","options"])],2))}}),e("t",b($,[["__scopeId","data-v-4c23e34a"]]))}}});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

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