ebe191b06d
- 会员改为永久激活方案;云端文件不再拦截非会员;预览弹窗内导出/打印才校验 - 首页最近文件支持本地记录,登录后与云端合并;移除独立会员页与订阅页 - 模板库入库管理:soon_templates 表、Admin 上传 CRUD、/templates 轻量列表与按需下载 Co-authored-by: Cursor <cursoragent@cursor.com>
477 lines
18 KiB
JavaScript
477 lines
18 KiB
JavaScript
(function () {
|
||
'use strict';
|
||
|
||
var HISTORY_KEY = 'soondesign_history';
|
||
var VERSION = typeof __APP_VERSION__ !== 'undefined' ? __APP_VERSION__ : '3.2.101';
|
||
|
||
function getAppBasePathname() {
|
||
var p = (typeof window !== 'undefined' && window.location && window.location.pathname) ? window.location.pathname : '/';
|
||
while (p.length > 1 && p.endsWith('/')) {
|
||
p = p.slice(0, -1);
|
||
}
|
||
if (/\.web\.html$/i.test(p) || /\.html$/i.test(p)) {
|
||
p = p.replace(/[^/]+$/, '');
|
||
}
|
||
if (!p.endsWith('/')) p += '/';
|
||
return p;
|
||
}
|
||
|
||
var SERVER_API_BASE = (function () {
|
||
var cfg = (typeof window !== 'undefined' && window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base);
|
||
if (cfg) return String(cfg).replace(/\/+$/, '') + '/';
|
||
if (typeof window === 'undefined' || !window.location) return '/api/v1/';
|
||
return window.location.origin + getAppBasePathname() + 'api/v1/';
|
||
})();
|
||
|
||
function getAccessToken() {
|
||
if (typeof window.soonGetAccessToken === 'function') return window.soonGetAccessToken();
|
||
try { return localStorage.getItem('soon_access') || ''; } catch (e) { return ''; }
|
||
}
|
||
|
||
function authedFetch(path, opts) {
|
||
var url = SERVER_API_BASE + String(path).replace(/^\/+/, '');
|
||
if (typeof window.soonAuthedFetch === 'function') {
|
||
return window.soonAuthedFetch(url, opts);
|
||
}
|
||
opts = opts || {};
|
||
opts.headers = Object.assign({}, opts.headers || {});
|
||
if (!opts.headers['Authorization'] && !opts.headers['authorization']) {
|
||
var t = getAccessToken();
|
||
if (t) opts.headers['Authorization'] = 'Bearer ' + t;
|
||
}
|
||
return fetch(url, opts);
|
||
}
|
||
|
||
var JSON_HEADERS = {
|
||
'Content-Type': 'application/json; charset=utf-8',
|
||
'Accept': 'application/json'
|
||
};
|
||
|
||
function parseCloudRef(p) {
|
||
return typeof window.soonParseFileKey === 'function' ? window.soonParseFileKey(p) : null;
|
||
}
|
||
|
||
function normalizeSoonName(p) {
|
||
return typeof window.soonNormalizeSoonName === 'function'
|
||
? window.soonNormalizeSoonName(p)
|
||
: String(p || 'design.soon');
|
||
}
|
||
|
||
function packCloudResult(j) {
|
||
if (!j || !j.ok || !j.data) {
|
||
return Promise.reject(new Error('cloud_empty_response'));
|
||
}
|
||
var fileKey = typeof window.soonApplyCloudMeta === 'function'
|
||
? window.soonApplyCloudMeta(j.data)
|
||
: null;
|
||
if (!fileKey) {
|
||
var d = j.data;
|
||
window._soonFileMeta = { id: d.id, version: d.version, name: d.name };
|
||
fileKey = 'soondesign_file:' + d.id + ':v' + d.version;
|
||
}
|
||
return { fileId: j.data.id, version: j.data.version, fileKey: fileKey, name: j.data.name };
|
||
}
|
||
|
||
function handleCloudResponse(response, bodyText) {
|
||
if (response.ok) {
|
||
try { return Promise.resolve(JSON.parse(bodyText)); } catch (e) { return Promise.resolve(null); }
|
||
}
|
||
var info = typeof window.soonParseApiError === 'function'
|
||
? window.soonParseApiError(response, bodyText)
|
||
: { status: response.status, message: '操作失败' };
|
||
if (typeof window.soonShowApiError === 'function') window.soonShowApiError(info);
|
||
var err = new Error(info.message || 'cloud_error');
|
||
err.status = info.status;
|
||
err.code = info.code;
|
||
return Promise.reject(err);
|
||
}
|
||
|
||
function requireCloudAuth(actionLabel) {
|
||
if (getAccessToken()) return true;
|
||
if (typeof window.soonRequireLogin === 'function') return window.soonRequireLogin(actionLabel);
|
||
return false;
|
||
}
|
||
|
||
function navigateToPage(pathWithQuery) {
|
||
try {
|
||
location.href = new URL(pathWithQuery, window.location.href).href;
|
||
} catch (e) {
|
||
location.href = pathWithQuery;
|
||
}
|
||
}
|
||
|
||
function getLocale() {
|
||
var lang = typeof navigator !== 'undefined' ? (navigator.language || navigator.browserLanguage || '') : '';
|
||
if (lang.indexOf('zh') === 0) return lang.indexOf('TW') >= 0 ? 'ozh' : 'zh';
|
||
return 'en';
|
||
}
|
||
|
||
function getSystemFonts() {
|
||
var list = [
|
||
'Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Georgia',
|
||
'Impact', 'Microsoft YaHei', 'SimHei', 'SimSun', 'KaiTi', 'FangSong',
|
||
'Times New Roman', 'Trebuchet MS', 'Verdana', 'PingFang SC', 'Hiragino Sans GB'
|
||
];
|
||
if (typeof document !== 'undefined' && document.fonts && document.fonts.forEach) {
|
||
var set = {};
|
||
document.fonts.forEach(function (f) {
|
||
var name = (f.family || '').replace(/^["']|["']$/g, '');
|
||
if (name) set[name] = 1;
|
||
});
|
||
list = Object.keys(set).length ? Object.keys(set).sort() : list;
|
||
}
|
||
return Promise.resolve(list);
|
||
}
|
||
|
||
function listCloudFiles(pageOrLimit, sizeOrOffset) {
|
||
if (!getAccessToken()) {
|
||
var empty = { items: [], total: 0 };
|
||
if (typeof pageOrLimit === 'object' && pageOrLimit) {
|
||
empty.page = pageOrLimit.page || 1;
|
||
empty.size = pageOrLimit.size || 12;
|
||
}
|
||
return Promise.resolve(empty);
|
||
}
|
||
var url;
|
||
var fallback = { items: [], total: 0 };
|
||
if (typeof pageOrLimit === 'object' && pageOrLimit) {
|
||
var page = pageOrLimit.page || 1;
|
||
var size = pageOrLimit.size || 12;
|
||
url = 'files?page=' + page + '&size=' + size;
|
||
fallback = { items: [], total: 0, page: page, size: size };
|
||
} else {
|
||
var lim = pageOrLimit || 50;
|
||
var off = sizeOrOffset || 0;
|
||
url = 'files?limit=' + lim + '&offset=' + off;
|
||
fallback = { items: [], limit: lim, offset: off, total: 0 };
|
||
}
|
||
return authedFetch(url, { headers: { Accept: 'application/json' } })
|
||
.then(function (r) { return r.text().then(function (t) { return handleCloudResponse(r, t); }); })
|
||
.then(function (j) { return (j && j.ok && j.data) ? j.data : fallback; });
|
||
}
|
||
|
||
function createCloudFile(name, jsonStr) {
|
||
if (!requireCloudAuth('保存')) return Promise.reject(new Error('unauthorized'));
|
||
return authedFetch('files', {
|
||
method: 'POST',
|
||
headers: JSON_HEADERS,
|
||
body: JSON.stringify({ name: normalizeSoonName(name), json: jsonStr })
|
||
})
|
||
.then(function (r) { return r.text().then(function (t) { return handleCloudResponse(r, t); }); })
|
||
.then(packCloudResult);
|
||
}
|
||
|
||
function updateCloudFile(id, name, jsonStr, version) {
|
||
if (!requireCloudAuth('保存')) return Promise.reject(new Error('unauthorized'));
|
||
var body = { name: normalizeSoonName(name), json: jsonStr };
|
||
if (version != null) body.version = version;
|
||
return authedFetch('files/' + id, {
|
||
method: 'PUT',
|
||
headers: JSON_HEADERS,
|
||
body: JSON.stringify(body)
|
||
})
|
||
.then(function (r) { return r.text().then(function (t) { return handleCloudResponse(r, t); }); })
|
||
.then(packCloudResult);
|
||
}
|
||
|
||
function deleteCloudFile(id) {
|
||
if (!requireCloudAuth('删除')) return Promise.reject(new Error('unauthorized'));
|
||
return authedFetch('files/' + id, { method: 'DELETE', headers: { Accept: 'application/json' } })
|
||
.then(function (r) { return r.text().then(function (t) { return handleCloudResponse(r, t); }); });
|
||
}
|
||
|
||
function importSoonFile(name, jsonObj) {
|
||
var jsonStr = typeof jsonObj === 'string' ? jsonObj : JSON.stringify(jsonObj);
|
||
return createCloudFile(name, jsonStr);
|
||
}
|
||
|
||
function downloadCloudFile(id, fileName) {
|
||
if (!requireCloudAuth('下载')) return Promise.reject(new Error('unauthorized'));
|
||
return authedFetch('files/' + id + '/download', { headers: { Accept: 'application/octet-stream' } })
|
||
.then(function (response) {
|
||
if (!response.ok) {
|
||
return response.text().then(function (t) { return handleCloudResponse(response, t); });
|
||
}
|
||
return response.blob().then(function (blob) {
|
||
var a = document.createElement('a');
|
||
a.download = normalizeSoonName(fileName || 'design.soon');
|
||
a.href = URL.createObjectURL(blob);
|
||
a.click();
|
||
URL.revokeObjectURL(a.href);
|
||
});
|
||
});
|
||
}
|
||
|
||
var bridge = {
|
||
readHistory: function () {
|
||
try {
|
||
var raw = localStorage.getItem(HISTORY_KEY);
|
||
return Promise.resolve(raw ? JSON.parse(raw) : { history: [] });
|
||
} catch (e) {
|
||
return Promise.resolve({ history: [] });
|
||
}
|
||
},
|
||
writeHistory: function (data) {
|
||
try {
|
||
localStorage.setItem(HISTORY_KEY, JSON.stringify(data));
|
||
return Promise.resolve({ success: true });
|
||
} catch (e) {
|
||
return Promise.resolve({ success: false, error: e.message });
|
||
}
|
||
},
|
||
listCloudFiles: listCloudFiles,
|
||
createCloudFile: createCloudFile,
|
||
updateCloudFile: updateCloudFile,
|
||
deleteCloudFile: deleteCloudFile,
|
||
importSoonFile: importSoonFile,
|
||
downloadCloudFile: downloadCloudFile,
|
||
requireCloudAuth: requireCloudAuth,
|
||
readJsonFile: function (pathOrHandle) {
|
||
if (!pathOrHandle) return Promise.resolve(null);
|
||
if (typeof pathOrHandle === 'object' && pathOrHandle.text) {
|
||
return pathOrHandle.text().then(function (t) {
|
||
try { return JSON.parse(t); } catch (e) { return null; }
|
||
});
|
||
}
|
||
var key = typeof pathOrHandle === 'string' ? pathOrHandle : '';
|
||
if (key.indexOf('soondesign_session:') === 0) {
|
||
try {
|
||
var j = sessionStorage.getItem(key);
|
||
if (!j && typeof localStorage !== 'undefined') j = localStorage.getItem(key);
|
||
return Promise.resolve(j ? JSON.parse(j) : null);
|
||
} catch (e) { return Promise.resolve(null); }
|
||
}
|
||
if (key && key.indexOf('soondesign_file:') === 0 && typeof fetch !== 'undefined') {
|
||
var fileMatch = key.match(/^soondesign_file:(\d+)/);
|
||
if (fileMatch && getAccessToken()) {
|
||
return authedFetch('files/' + fileMatch[1] + '/download', { headers: { Accept: 'application/json' } })
|
||
.then(function (response) {
|
||
if (!response.ok) return null;
|
||
return response.text().then(function (text) {
|
||
try { return JSON.parse(text); } catch (e) { return null; }
|
||
});
|
||
})
|
||
.catch(function () { return null; });
|
||
}
|
||
}
|
||
return Promise.resolve(null);
|
||
},
|
||
showOpenDialog: function (options) {
|
||
return new Promise(function (resolve) {
|
||
var input = document.createElement('input');
|
||
input.type = 'file';
|
||
if (options && options.filters && Array.isArray(options.filters) && options.filters.length > 0) {
|
||
var acceptList = [];
|
||
options.filters.forEach(function (filter) {
|
||
if (filter.extensions && Array.isArray(filter.extensions)) {
|
||
filter.extensions.forEach(function (ext) {
|
||
var cleanExt = ext.replace(/^\./, '');
|
||
if (['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg'].indexOf(cleanExt.toLowerCase()) >= 0) {
|
||
var mimeType = 'image/' + (cleanExt.toLowerCase() === 'jpg' ? 'jpeg' : cleanExt.toLowerCase());
|
||
if (acceptList.indexOf(mimeType) < 0) acceptList.push(mimeType);
|
||
} else {
|
||
var extWithDot = '.' + cleanExt;
|
||
if (acceptList.indexOf(extWithDot) < 0) acceptList.push(extWithDot);
|
||
}
|
||
});
|
||
}
|
||
});
|
||
input.accept = acceptList.length > 0 ? acceptList.join(',') : '';
|
||
} else {
|
||
input.accept = '.soon,application/json';
|
||
}
|
||
input.style.display = 'none';
|
||
input.onchange = function () {
|
||
var f = input.files && input.files[0];
|
||
document.body.removeChild(input);
|
||
if (!f) resolve({ canceled: true });
|
||
else resolve({ canceled: false, filePaths: [], files: [f], file: f });
|
||
};
|
||
document.body.appendChild(input);
|
||
input.click();
|
||
});
|
||
},
|
||
showSaveDialog: function (options) {
|
||
var raw = (options && options.defaultPath) ? options.defaultPath : '';
|
||
var defaultName = normalizeSoonName(raw || 'design.soon');
|
||
if (typeof window.soonDisplayFileName === 'function' && raw.indexOf('soondesign_file:') === 0) {
|
||
defaultName = window.soonDisplayFileName(raw);
|
||
}
|
||
return new Promise(function (resolve) {
|
||
var name = typeof prompt === 'function' ? prompt('保存为文件名(如 xxx.soon)', defaultName) : defaultName;
|
||
if (name === null) {
|
||
resolve({ canceled: true });
|
||
return;
|
||
}
|
||
var fp = (name && String(name).trim()) ? String(name).trim() : defaultName;
|
||
fp = normalizeSoonName(fp);
|
||
resolve({ canceled: false, filePath: fp, useCloud: true });
|
||
});
|
||
},
|
||
writeFile: function (pathOrHandle, content) {
|
||
var isBlob = content instanceof Blob;
|
||
var str = typeof content === 'string' ? content : (isBlob ? null : (content && content.toString ? content.toString() : ''));
|
||
|
||
if (isBlob) {
|
||
var blobName = typeof pathOrHandle === 'string' ? pathOrHandle : 'output.bin';
|
||
var a = document.createElement('a');
|
||
a.download = blobName;
|
||
a.href = URL.createObjectURL(content);
|
||
a.click();
|
||
URL.revokeObjectURL(a.href);
|
||
return Promise.resolve();
|
||
}
|
||
|
||
if (pathOrHandle && pathOrHandle.createWritable) {
|
||
pathOrHandle = typeof pathOrHandle.name === 'string' ? pathOrHandle.name : 'design.soon';
|
||
}
|
||
|
||
var name = typeof pathOrHandle === 'string' ? pathOrHandle : 'design.soon';
|
||
var cloudRef = parseCloudRef(name);
|
||
var fileName = normalizeSoonName(name);
|
||
|
||
if (!getAccessToken()) {
|
||
if (cloudRef && cloudRef.id) {
|
||
requireCloudAuth('保存');
|
||
return Promise.reject(new Error('unauthorized'));
|
||
}
|
||
var sessionKey = name.indexOf('soondesign_session:') === 0
|
||
? name
|
||
: 'soondesign_session:' + fileName.replace(/\.soon$/i, '') + '-' + Date.now();
|
||
try {
|
||
sessionStorage.setItem(sessionKey, str);
|
||
try { localStorage.setItem(sessionKey, str); } catch (e2) { /* ignore quota */ }
|
||
return Promise.resolve({ fileKey: sessionKey, name: fileName, version: 0 });
|
||
} catch (e) {
|
||
return Promise.reject(new Error('save_failed'));
|
||
}
|
||
}
|
||
|
||
if (!requireCloudAuth('保存')) {
|
||
return Promise.reject(new Error('unauthorized'));
|
||
}
|
||
|
||
if (cloudRef && cloudRef.id) {
|
||
var ver = cloudRef.version;
|
||
if (ver == null && window._soonFileMeta && window._soonFileMeta.id === cloudRef.id) {
|
||
ver = window._soonFileMeta.version;
|
||
}
|
||
return updateCloudFile(cloudRef.id, fileName, str, ver);
|
||
}
|
||
|
||
return createCloudFile(fileName, str);
|
||
},
|
||
readFile: function (pathOrHandle) {
|
||
if (pathOrHandle && pathOrHandle.getFile) {
|
||
return pathOrHandle.getFile().then(function (f) {
|
||
return new Promise(function (res, rej) {
|
||
var r = new FileReader();
|
||
r.onload = function () { res(r.result); };
|
||
r.onerror = rej;
|
||
r.readAsArrayBuffer(f);
|
||
});
|
||
});
|
||
}
|
||
if (pathOrHandle && pathOrHandle.arrayBuffer) {
|
||
return pathOrHandle.arrayBuffer();
|
||
}
|
||
return Promise.reject(new Error('No file'));
|
||
},
|
||
getAppVersion: function () { return Promise.resolve(VERSION); },
|
||
getLocale: getLocale,
|
||
getUserDataPath: function () { return ''; },
|
||
getSystemFonts: getSystemFonts,
|
||
openDesignPage: function (file, type) {
|
||
var t = type || 1;
|
||
if (file && typeof sessionStorage !== 'undefined') {
|
||
try {
|
||
sessionStorage.setItem('soondesign_open_file', file);
|
||
sessionStorage.setItem('soondesign_open_type', String(t));
|
||
if (file.indexOf('soondesign_file:') === 0 && window._soonFileMeta) {
|
||
sessionStorage.setItem('soondesign_open_meta', JSON.stringify(window._soonFileMeta));
|
||
}
|
||
} catch (e) {}
|
||
}
|
||
var fileParam = file ? encodeURIComponent(file) : '';
|
||
navigateToPage('design' + t + '.web.html?file=' + fileParam + '&type=' + t);
|
||
},
|
||
openFirstPage: function () {
|
||
navigateToPage('index.web.html');
|
||
},
|
||
onClose: function (callback) {
|
||
if (typeof window !== 'undefined') {
|
||
window.addEventListener('beforeunload', callback);
|
||
}
|
||
},
|
||
runClose: function () {
|
||
if (typeof window !== 'undefined' && window.close) window.close();
|
||
},
|
||
openHelp: function () {
|
||
try {
|
||
window.open(new URL('../assets/help/User Manual.pdf', window.location.href).href, '_blank');
|
||
} catch (e) {
|
||
window.open('../assets/help/User Manual.pdf', '_blank');
|
||
}
|
||
},
|
||
printPdf: function (urlOrBlob) {
|
||
var url = urlOrBlob;
|
||
if (urlOrBlob && typeof urlOrBlob === 'object' && !(urlOrBlob instanceof String)) {
|
||
url = URL.createObjectURL(urlOrBlob);
|
||
}
|
||
var w = window.open(url, '_blank');
|
||
if (w) w.onload = function () { w.print(); };
|
||
},
|
||
getScaleRate: function () {
|
||
return typeof window !== 'undefined' && window.devicePixelRatio ? window.devicePixelRatio : 1;
|
||
},
|
||
clipboard: {
|
||
readText: function () {
|
||
return navigator.clipboard && navigator.clipboard.readText ? navigator.clipboard.readText() : Promise.resolve('');
|
||
},
|
||
writeText: function (text) {
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
return navigator.clipboard.writeText(text);
|
||
}
|
||
return Promise.resolve();
|
||
}
|
||
}
|
||
};
|
||
|
||
window.platformBridge = bridge;
|
||
|
||
var pathStub = {
|
||
join: function () { return [].slice.call(arguments).join('/').replace(/\/+/g, '/'); }
|
||
};
|
||
|
||
window.sysAPI = {
|
||
readHistory: bridge.readHistory,
|
||
writeHistory: bridge.writeHistory,
|
||
readJsonFile: bridge.readJsonFile,
|
||
getAppVersion: bridge.getAppVersion
|
||
};
|
||
window.dialog = {
|
||
showOpenDialog: bridge.showOpenDialog,
|
||
showSaveDialog: bridge.showSaveDialog
|
||
};
|
||
window.path = pathStub;
|
||
window.fs = null;
|
||
window.remote = null;
|
||
var loc = getLocale();
|
||
window.ipcRenderer = {
|
||
send: function (ch, a1, a2) {
|
||
if (ch === 'get-sys-language') { window._sysLanPending = true; setTimeout(function () { if (window._sysLanCb && window._sysLanPending) { window._sysLanCb(null, loc); window._sysLanPending = false; } }, 0); }
|
||
if (ch === 'open-first-page' && bridge.openFirstPage) { bridge.openFirstPage(); }
|
||
if (ch === 'open-design-page' && bridge.openDesignPage && (a1 !== undefined || a2 !== undefined)) { bridge.openDesignPage(a1 || '', a2 || 1); }
|
||
if (ch === 'open-help-file' && bridge.openHelp) { bridge.openHelp(); }
|
||
if (ch === 'run-close' && bridge.runClose) { bridge.runClose(); }
|
||
},
|
||
on: function (ch, cb) {
|
||
if (ch === 'sys-lan') { window._sysLanCb = cb; if (window._sysLanPending) setTimeout(function () { if (window._sysLanCb) { window._sysLanCb(null, loc); window._sysLanPending = false; } }, 0); }
|
||
if (ch === 'close') window._closeCb = cb;
|
||
}
|
||
};
|
||
window.clipboard = bridge.clipboard;
|
||
window.exePath = '';
|
||
window.fullPath = '';
|
||
})();
|