784cacf259
- design1:预览/最近文件缩略图按 background 边界截取,去掉多余白盘 - design2:设为背景贴齐 1012x648 卡框,保存/打开保留用户背景 src - design2:列表可选中锁定对象并加载完整属性,缩略图叠加 front_bg 模板 - design2:固定卡框锚点,避免选中背景时标尺/蒙版随属性面板漂移 Co-authored-by: Cursor <cursoragent@cursor.com>
977 lines
33 KiB
JavaScript
977 lines
33 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
var FILE_PREFIX = 'soondesign_file:';
|
|
var TEMPLATE_PREFIX = 'soondesign_template:';
|
|
|
|
function soonParseTemplateKey(key) {
|
|
if (!key || typeof key !== 'string') return null;
|
|
var m = key.match(/^soondesign_template:(\d+)$/);
|
|
if (!m) return null;
|
|
return { id: parseInt(m[1], 10) };
|
|
}
|
|
|
|
function soonMakeTemplateKey(id) {
|
|
return TEMPLATE_PREFIX + id;
|
|
}
|
|
|
|
function soonIsWebPortal() {
|
|
if (typeof window === 'undefined') return false;
|
|
if (window.SOON_DEPLOY_CONFIG) return true;
|
|
return !!(window.platformBridge && window.fs == null);
|
|
}
|
|
|
|
function soonGetAccessToken() {
|
|
if (typeof window.soonGetAccessToken === 'function') {
|
|
return window.soonGetAccessToken();
|
|
}
|
|
try { return localStorage.getItem('soon_access') || ''; } catch (e) { return ''; }
|
|
}
|
|
|
|
function soonParseFileKey(key) {
|
|
if (!key || typeof key !== 'string') return null;
|
|
var m = key.match(/^soondesign_file:(\d+)(?::v(\d+))?$/);
|
|
if (!m) return null;
|
|
return { id: parseInt(m[1], 10), version: m[2] ? parseInt(m[2], 10) : null };
|
|
}
|
|
|
|
function soonMakeFileKey(id, version) {
|
|
return FILE_PREFIX + id + ':v' + version;
|
|
}
|
|
|
|
function soonIsTemplateKey(key) {
|
|
return !!(key && typeof key === 'string' && key.indexOf(TEMPLATE_PREFIX) === 0);
|
|
}
|
|
|
|
function soonNeedsSaveDialog(key) {
|
|
return !key || soonIsTemplateKey(key);
|
|
}
|
|
|
|
function soonDefaultNewSoonName() {
|
|
var d = new Date();
|
|
function pad(n) { return n < 10 ? '0' + n : String(n); }
|
|
var yy = String(d.getFullYear()).slice(-2);
|
|
var stamp = yy + pad(d.getMonth() + 1) + pad(d.getDate()) +
|
|
pad(d.getHours()) + pad(d.getMinutes()) + pad(d.getSeconds());
|
|
return 'design' + stamp + '.soon';
|
|
}
|
|
|
|
function soonEnsureSoonExt(name) {
|
|
var n = String(name || 'design.soon').trim();
|
|
if (!/\.soon$/i.test(n)) n = (n.replace(/\.soon$/i, '') || 'design') + '.soon';
|
|
return n;
|
|
}
|
|
|
|
function soonSessionSlug(nameOrKey) {
|
|
var s = String(nameOrKey || 'design').replace(/^soondesign_session:/, '').split(/[/\\]/).pop();
|
|
s = s.replace(/\.soon$/i, '');
|
|
s = s.replace(/-\d{10,}$/, '');
|
|
return s || 'design';
|
|
}
|
|
|
|
function soonMakeSessionKey(nameOrKey) {
|
|
return 'soondesign_session:' + soonSessionSlug(nameOrKey);
|
|
}
|
|
|
|
function soonSessionDisplayName(keyOrName) {
|
|
return soonEnsureSoonExt(soonSessionSlug(keyOrName));
|
|
}
|
|
|
|
/** 缩略图截取框:对齐 background_image 设计边界(design1 光盘外接方 / design2 卡面) */
|
|
function soonResolveSavePreviewCrop(canvas) {
|
|
if (!canvas) return null;
|
|
var bg = null;
|
|
if (typeof window !== 'undefined' && window.canvas1 === canvas && window.background_image1) {
|
|
bg = window.background_image1;
|
|
}
|
|
if (!bg && typeof canvas.getObjects === 'function') {
|
|
var objs = canvas.getObjects();
|
|
if (objs.length && objs[0] && objs[0].type === 'image') bg = objs[0];
|
|
}
|
|
if (!bg || !bg.width || !bg.height) return null;
|
|
var sx = bg.scaleX == null ? 1 : bg.scaleX;
|
|
var sy = bg.scaleY == null ? 1 : bg.scaleY;
|
|
var cw = canvas.width || 0;
|
|
var ch = canvas.height || 0;
|
|
var w;
|
|
var h;
|
|
var left = bg.left;
|
|
var top = bg.top;
|
|
w = bg.width * sx;
|
|
h = bg.height * sy;
|
|
if (!(w > 0 && h > 0)) return null;
|
|
if (cw > 0 && ch > 0) {
|
|
var x1 = Math.max(0, left);
|
|
var y1 = Math.max(0, top);
|
|
var x2 = Math.min(cw, left + w);
|
|
var y2 = Math.min(ch, top + h);
|
|
w = x2 - x1;
|
|
h = y2 - y1;
|
|
if (w <= 0 || h <= 0) return null;
|
|
left = x1;
|
|
top = y1;
|
|
}
|
|
return { left: left, top: top, width: w, height: h };
|
|
}
|
|
|
|
function soonIsDesign2SaveCanvas(canvas) {
|
|
return !!(typeof window.soonBuildDesign2SavePreviewPic === 'function'
|
|
&& window.canvas1 && canvas === window.canvas1);
|
|
}
|
|
|
|
function soonIsDesign1SaveCanvas(canvas) {
|
|
return !!(typeof window.soonBuildDesign1SavePreviewPic === 'function'
|
|
&& window.canvas1 && canvas === window.canvas1);
|
|
}
|
|
|
|
function soonBuildSavePreviewPic(canvas) {
|
|
var THUMB_MAX = 102400;
|
|
if (!canvas || typeof canvas.getObjects !== 'function') return '';
|
|
if (soonIsDesign2SaveCanvas(canvas)) {
|
|
var d2Pic = window.soonBuildDesign2SavePreviewPic(canvas);
|
|
if (d2Pic) return d2Pic;
|
|
return '';
|
|
}
|
|
if (soonIsDesign1SaveCanvas(canvas)) {
|
|
var d1Pic = window.soonBuildDesign1SavePreviewPic(canvas);
|
|
if (d1Pic) return d1Pic;
|
|
return '';
|
|
}
|
|
var hidden = [];
|
|
canvas.getObjects().forEach(function (obj) {
|
|
if (obj.isGuideLine) {
|
|
hidden.push(obj);
|
|
obj.visible = false;
|
|
}
|
|
});
|
|
if (hidden.length) canvas.renderAll();
|
|
var crop = soonResolveSavePreviewCrop(canvas);
|
|
var mult = crop ? 0.28 : 0.18;
|
|
var quality = 0.72;
|
|
var url = '';
|
|
for (var i = 0; i < 3; i++) {
|
|
try {
|
|
var opts = { format: 'jpeg', quality: quality, multiplier: mult };
|
|
if (crop) {
|
|
opts.left = crop.left;
|
|
opts.top = crop.top;
|
|
opts.width = crop.width;
|
|
opts.height = crop.height;
|
|
}
|
|
canvas.renderAll();
|
|
var candidate = canvas.toDataURL(opts);
|
|
if (candidate && candidate.indexOf('data:image/') === 0 && candidate.length <= THUMB_MAX) {
|
|
url = candidate;
|
|
break;
|
|
}
|
|
} catch (e) { /* ignore */ }
|
|
quality -= 0.18;
|
|
mult -= 0.04;
|
|
}
|
|
hidden.forEach(function (obj) { obj.visible = true; });
|
|
canvas.renderAll();
|
|
if (typeof window.soonClearFabricOverlay === 'function') window.soonClearFabricOverlay(canvas);
|
|
if (canvas.requestRenderAll) canvas.requestRenderAll();
|
|
return url;
|
|
}
|
|
|
|
function soonApplySavePreview(con_o, canvas) {
|
|
if (!con_o) return con_o;
|
|
var pic = soonBuildSavePreviewPic(canvas);
|
|
if (pic) con_o.frontDisplayPic = pic;
|
|
return con_o;
|
|
}
|
|
|
|
function soonSaveDialogDefaultPath(openKey) {
|
|
if (soonNeedsSaveDialog(openKey)) return soonDefaultNewSoonName();
|
|
return openKey || undefined;
|
|
}
|
|
|
|
function soonEnsureSaveFileName(fp) {
|
|
var name = String(fp || 'design.soon');
|
|
if (typeof window !== 'undefined' && window.platformBridge && window.fs == null) {
|
|
return soonEnsureSoonExt(name);
|
|
}
|
|
if (typeof window !== 'undefined' && window.path && window.path.extname) {
|
|
if (window.path.extname(name) !== '.soon') {
|
|
name = (name.replace(/\.soon$/i, '') || 'design') + '.soon';
|
|
}
|
|
} else if (!/\.soon$/i.test(name)) {
|
|
name = (name.replace(/\.soon$/i, '') || 'design') + '.soon';
|
|
}
|
|
return soonEnsureSoonExt(name);
|
|
}
|
|
|
|
function dismissLayerMask() {
|
|
if (typeof window.soonCleanupLayerMask === 'function') {
|
|
window.soonCleanupLayerMask();
|
|
return;
|
|
}
|
|
soonHideOpenFileLoading();
|
|
invokeLayerCleanup();
|
|
}
|
|
|
|
function soonReportSaveError(err) {
|
|
if (typeof window.soonFinishSaveUi === 'function') window.soonFinishSaveUi();
|
|
else dismissLayerMask();
|
|
if (err && err.conflictSynced) return;
|
|
if (err && err.apiNotified) return;
|
|
if (typeof window.soonShowApiError === 'function') {
|
|
window.soonShowApiError({
|
|
status: err && err.status,
|
|
message: (err && err.message) || '保存失败',
|
|
code: err && err.code
|
|
});
|
|
} else {
|
|
soonToast((err && err.message) || '保存失败', 'error');
|
|
}
|
|
}
|
|
|
|
function soonWriteSoonContentWeb(fp, content, prevOpenKey, onDone, onError) {
|
|
if (!window.platformBridge || !window.platformBridge.writeFile) return false;
|
|
if (typeof window.soonShowOpenFileLoading === 'function') window.soonShowOpenFileLoading();
|
|
var writePromise;
|
|
try {
|
|
writePromise = window.platformBridge.writeFile(fp, content);
|
|
} catch (syncErr) {
|
|
dismissLayerMask();
|
|
if (typeof onError === 'function') onError(syncErr);
|
|
else soonReportSaveError(syncErr);
|
|
return true;
|
|
}
|
|
if (!writePromise || typeof writePromise.then !== 'function') {
|
|
dismissLayerMask();
|
|
return true;
|
|
}
|
|
writePromise.then(function (res) {
|
|
if (typeof onDone === 'function') onDone(res, prevOpenKey);
|
|
}).catch(function (err) {
|
|
if (typeof onError === 'function') onError(err);
|
|
else soonReportSaveError(err);
|
|
}).finally(function () {
|
|
dismissLayerMask();
|
|
});
|
|
return true;
|
|
}
|
|
|
|
function soonDownloadBlob(filename, content, mime) {
|
|
var name = soonEnsureSoonExt(filename || 'design.soon');
|
|
var blob = content instanceof Blob
|
|
? content
|
|
: new Blob([content], { type: mime || 'application/json' });
|
|
var a = document.createElement('a');
|
|
a.download = name;
|
|
a.href = URL.createObjectURL(blob);
|
|
a.click();
|
|
URL.revokeObjectURL(a.href);
|
|
}
|
|
|
|
function soonDownloadRecentItem(item) {
|
|
if (!item) return Promise.resolve();
|
|
var key = item.filePath || item.key || '';
|
|
var name = soonEnsureSoonExt(item.name || 'design.soon');
|
|
var kind = item.kind || (typeof window.soonRecentKindFromKey === 'function'
|
|
? window.soonRecentKindFromKey(key) : 'local');
|
|
|
|
if (kind === 'template') {
|
|
var tpl = soonParseTemplateKey(key);
|
|
if (!tpl) {
|
|
soonToast('无法下载模板', 'error');
|
|
return Promise.resolve();
|
|
}
|
|
var base = (window.SOON_DEPLOY_CONFIG && window.SOON_DEPLOY_CONFIG.api_v1_base) || '';
|
|
if (!base) {
|
|
soonToast('下载地址未配置', 'error');
|
|
return Promise.resolve();
|
|
}
|
|
return fetch(base + '/templates/' + tpl.id + '/file', { headers: { Accept: 'application/json' } })
|
|
.then(function (r) {
|
|
if (!r.ok) throw new Error('download_failed');
|
|
return r.text();
|
|
})
|
|
.then(function (text) {
|
|
soonDownloadBlob(name, text, 'application/json');
|
|
})
|
|
.catch(function () {
|
|
soonToast('模板下载失败', 'error');
|
|
});
|
|
}
|
|
|
|
if (kind === 'cloud') {
|
|
function tryCloudApiDownload() {
|
|
if (!item.fileId || !soonGetAccessToken()) return Promise.resolve(false);
|
|
var bridge = window.platformBridge;
|
|
if (!bridge || typeof bridge.downloadCloudFile !== 'function') return Promise.resolve(false);
|
|
return bridge.downloadCloudFile(item.fileId, name).then(function () { return true; }).catch(function () {
|
|
soonToast('下载失败', 'error');
|
|
return true;
|
|
});
|
|
}
|
|
if (key && typeof window.soonLocalGet === 'function') {
|
|
return window.soonLocalGet(key).then(function (hit) {
|
|
if (hit && hit.json) {
|
|
soonDownloadBlob(name, JSON.stringify(hit.json), 'application/json');
|
|
return;
|
|
}
|
|
return tryCloudApiDownload().then(function (done) {
|
|
if (!done) soonToast('请先打开或保存该文件', 'warn');
|
|
});
|
|
});
|
|
}
|
|
return tryCloudApiDownload().then(function (done) {
|
|
if (!done) soonToast('请先打开或保存该文件', 'warn');
|
|
});
|
|
}
|
|
|
|
if (key.indexOf('soondesign_session:') === 0 || kind === 'local') {
|
|
if (typeof window.soonLocalGet !== 'function') {
|
|
soonToast('本地缓存不可用', 'error');
|
|
return Promise.resolve();
|
|
}
|
|
return window.soonLocalGet(key).then(function (hit) {
|
|
if (!hit || !hit.json) {
|
|
soonToast('请先打开或保存该文件', 'warn');
|
|
return;
|
|
}
|
|
soonDownloadBlob(name, JSON.stringify(hit.json), 'application/json');
|
|
});
|
|
}
|
|
|
|
soonToast('无法下载该文件', 'warn');
|
|
return Promise.resolve();
|
|
}
|
|
|
|
function soonLookupRecentCloudName(fileId) {
|
|
if (!fileId || typeof window.soonRecentList !== 'function') return '';
|
|
var items = window.soonRecentList();
|
|
for (var i = 0; i < items.length; i++) {
|
|
if (items[i].fileId === fileId && items[i].name) return items[i].name;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
function soonResolveCloudFileName(fileKey) {
|
|
if (!fileKey || fileKey.indexOf(FILE_PREFIX) !== 0) return '';
|
|
var parsed = soonParseFileKey(fileKey);
|
|
if (!parsed) return '';
|
|
var meta = window._soonFileMeta;
|
|
if (meta && meta.id === parsed.id && meta.name) return meta.name;
|
|
var recentName = soonLookupRecentCloudName(parsed.id);
|
|
if (recentName) return recentName;
|
|
return '';
|
|
}
|
|
|
|
function soonNormalizeSoonName(pathOrName) {
|
|
var n = String(pathOrName || 'design.soon');
|
|
if (n.indexOf(FILE_PREFIX) === 0) {
|
|
var resolved = soonResolveCloudFileName(n);
|
|
if (resolved) return resolved;
|
|
return 'design.soon';
|
|
}
|
|
if (n.indexOf(TEMPLATE_PREFIX) === 0) {
|
|
return soonDefaultNewSoonName();
|
|
}
|
|
if (n.indexOf('soondesign_session:') === 0) {
|
|
return soonSessionDisplayName(n);
|
|
}
|
|
n = n.split(/[/\\]/).pop();
|
|
if (!/\.soon$/i.test(n)) n = (n.replace(/\.soon$/i, '') || 'design') + '.soon';
|
|
return n;
|
|
}
|
|
|
|
function soonApplyCloudMeta(data) {
|
|
if (!data || !data.id) return null;
|
|
window._soonFileMeta = { id: data.id, version: data.version, name: data.name };
|
|
return soonMakeFileKey(data.id, data.version);
|
|
}
|
|
|
|
function soonSyncOpenNavigation(fileKey, type) {
|
|
if (typeof sessionStorage === 'undefined') {
|
|
if (!fileKey || fileKey.indexOf(FILE_PREFIX) !== 0) window._soonFileMeta = null;
|
|
return;
|
|
}
|
|
try {
|
|
if (fileKey) {
|
|
sessionStorage.setItem('soondesign_open_file', fileKey);
|
|
sessionStorage.setItem('soondesign_open_type', String(type || 1));
|
|
if (fileKey.indexOf(FILE_PREFIX) === 0 && window._soonFileMeta) {
|
|
sessionStorage.setItem('soondesign_open_meta', JSON.stringify(window._soonFileMeta));
|
|
} else {
|
|
sessionStorage.removeItem('soondesign_open_meta');
|
|
if (fileKey.indexOf(FILE_PREFIX) !== 0) window._soonFileMeta = null;
|
|
}
|
|
} else {
|
|
sessionStorage.removeItem('soondesign_open_file');
|
|
sessionStorage.removeItem('soondesign_open_type');
|
|
sessionStorage.removeItem('soondesign_open_meta');
|
|
window._soonFileMeta = null;
|
|
}
|
|
} catch (e) { /* ignore quota */ }
|
|
}
|
|
|
|
function soonResolveOpenFileKey(getParams) {
|
|
var params = typeof getParams === 'function' ? getParams() : null;
|
|
if (!params || typeof params.has !== 'function') return '';
|
|
var file = params.has('file') ? (params.get('file') || '') : null;
|
|
if (file === null && typeof sessionStorage !== 'undefined') {
|
|
try {
|
|
file = sessionStorage.getItem('soondesign_open_file') || '';
|
|
if (file) {
|
|
sessionStorage.removeItem('soondesign_open_file');
|
|
sessionStorage.removeItem('soondesign_open_type');
|
|
}
|
|
} catch (e) {
|
|
file = '';
|
|
}
|
|
}
|
|
return file || '';
|
|
}
|
|
|
|
function soonConsumeOpenMeta(fileKey) {
|
|
if (!fileKey || fileKey.indexOf(FILE_PREFIX) !== 0) {
|
|
window._soonFileMeta = null;
|
|
return;
|
|
}
|
|
if (typeof sessionStorage === 'undefined') return;
|
|
try {
|
|
var metaRaw = sessionStorage.getItem('soondesign_open_meta');
|
|
if (metaRaw) {
|
|
window._soonFileMeta = JSON.parse(metaRaw);
|
|
sessionStorage.removeItem('soondesign_open_meta');
|
|
}
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
|
|
function soonBindFileMeta(fileKey, opts) {
|
|
if (!fileKey || fileKey.indexOf(FILE_PREFIX) !== 0) {
|
|
window._soonFileMeta = null;
|
|
return;
|
|
}
|
|
var parsed = soonParseFileKey(fileKey);
|
|
if (!parsed) return;
|
|
var prev = window._soonFileMeta || {};
|
|
var name = (opts && opts.name) || (prev.id === parsed.id ? prev.name : '') || '';
|
|
if (!name) name = soonLookupRecentCloudName(parsed.id);
|
|
window._soonFileMeta = {
|
|
id: parsed.id,
|
|
version: parsed.version != null ? parsed.version : prev.version,
|
|
name: name
|
|
};
|
|
}
|
|
|
|
function soonFormatOpenTitle(name) {
|
|
if (!name) return '';
|
|
if (typeof window.soonDisplayFileName === 'function') return window.soonDisplayFileName(name);
|
|
return String(name).split(/[/\\]/).pop();
|
|
}
|
|
|
|
var _membership = {
|
|
loaded: false,
|
|
isMember: false,
|
|
name: '普通用户',
|
|
tier: 'free',
|
|
loading: null,
|
|
};
|
|
|
|
function soonApiBase() {
|
|
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 window.location.origin + '/api/v1';
|
|
}
|
|
return '/api/v1';
|
|
}
|
|
|
|
function soonApplyMembership(data) {
|
|
var m = data || {};
|
|
var sub = m.subscription || {};
|
|
var isMember = m.is_member === true
|
|
|| m.tier === 'member'
|
|
|| m.tier === 'pro'
|
|
|| (sub.status && sub.status !== 'free' && sub.status !== 'expired');
|
|
_membership = {
|
|
loaded: true,
|
|
isMember: !!isMember,
|
|
name: isMember ? (m.name || '会员') : '普通用户',
|
|
tier: m.tier || (isMember ? 'member' : 'free'),
|
|
loading: null,
|
|
};
|
|
if (typeof window.soonRefreshPortalIdentity === 'function') {
|
|
window.soonRefreshPortalIdentity();
|
|
}
|
|
return _membership;
|
|
}
|
|
|
|
function soonRefreshPortalIdentity() {
|
|
var el = document.getElementById('auth_identity');
|
|
if (!el) return;
|
|
if (!soonGetAccessToken()) {
|
|
el.style.display = 'none';
|
|
el.textContent = '';
|
|
return;
|
|
}
|
|
if (!_membership.loaded) {
|
|
soonLoadMembership(false).then(function () {
|
|
soonRefreshPortalIdentity();
|
|
});
|
|
return;
|
|
}
|
|
var text;
|
|
var cls = 'soon-portal-identity';
|
|
if (_membership.isMember) {
|
|
cls += ' soon-portal-identity--member';
|
|
text = _membership.name || '会员';
|
|
} else {
|
|
cls += ' soon-portal-identity--free';
|
|
text = _membership.name || '普通用户';
|
|
}
|
|
el.className = cls;
|
|
el.textContent = text;
|
|
el.style.display = 'inline-flex';
|
|
var account = document.getElementById('auth_account');
|
|
if (account) account.style.display = 'inline-flex';
|
|
}
|
|
|
|
function soonLoadMembership(force) {
|
|
if (_membership.loaded && !force) return Promise.resolve(_membership);
|
|
if (_membership.loading && !force) return _membership.loading;
|
|
if (!soonGetAccessToken()) {
|
|
soonApplyMembership(null);
|
|
return Promise.resolve(_membership);
|
|
}
|
|
var url = soonApiBase() + '/auth/me';
|
|
var fetchFn = typeof window.soonAuthedFetch === 'function'
|
|
? window.soonAuthedFetch(url, { headers: { Accept: 'application/json' } })
|
|
: fetch(url, { headers: { Authorization: 'Bearer ' + soonGetAccessToken(), Accept: 'application/json' } });
|
|
_membership.loading = fetchFn
|
|
.then(function (r) {
|
|
if (r.status === 401) {
|
|
soonApplyMembership(null);
|
|
return null;
|
|
}
|
|
return r.json();
|
|
})
|
|
.then(function (j) {
|
|
if (j && j.ok && j.data) {
|
|
soonApplyMembership(j.data);
|
|
} else if (j !== null) {
|
|
soonApplyMembership(null);
|
|
}
|
|
return _membership;
|
|
})
|
|
.catch(function () {
|
|
soonApplyMembership(null);
|
|
return _membership;
|
|
})
|
|
.finally(function () {
|
|
_membership.loading = null;
|
|
});
|
|
return _membership.loading;
|
|
}
|
|
|
|
function soonIsMember() {
|
|
return !!_membership.isMember;
|
|
}
|
|
|
|
function soonGuardPreviewDeliver(actionLabel, onAllowed) {
|
|
if (typeof onAllowed !== 'function') return;
|
|
if (!soonIsWebPortal()) {
|
|
onAllowed();
|
|
return;
|
|
}
|
|
if (!soonGetAccessToken()) {
|
|
if (typeof window.soonShowLoginGate === 'function') {
|
|
window.soonShowLoginGate({
|
|
reason: actionLabel || '导出或打印',
|
|
onSuccess: function () {
|
|
soonGuardPreviewDeliver(actionLabel, onAllowed);
|
|
},
|
|
});
|
|
} else {
|
|
soonRequireLogin(actionLabel);
|
|
}
|
|
return;
|
|
}
|
|
function allowOrActivate() {
|
|
if (soonIsMember()) {
|
|
onAllowed();
|
|
return;
|
|
}
|
|
if (typeof window.soonShowActivateGate === 'function') {
|
|
window.soonShowActivateGate({
|
|
reason: actionLabel || '导出或打印',
|
|
onSuccess: function () {
|
|
onAllowed();
|
|
},
|
|
});
|
|
} else {
|
|
soonToast('请先激活会员后再' + (actionLabel || '操作'), 'warn');
|
|
}
|
|
}
|
|
if (!_membership.loaded) {
|
|
soonLoadMembership(false).then(allowOrActivate);
|
|
return;
|
|
}
|
|
allowOrActivate();
|
|
}
|
|
|
|
function soonGuardCloudSave() {
|
|
return true;
|
|
}
|
|
|
|
function soonSoonTypeFromJson(j) {
|
|
return j && j.soonType ? j.soonType : (j && j.backBlackPic ? 2 : 1);
|
|
}
|
|
|
|
function soonPutSoonSession(j, fileName) {
|
|
if (!j) return '';
|
|
var displayName = soonEnsureSoonExt(fileName || 'design.soon');
|
|
var key = soonMakeSessionKey(displayName);
|
|
if (typeof window.soonLocalCacheAndThumb === 'function') {
|
|
window.soonLocalCacheAndThumb(key, j, { source: 'session', name: displayName });
|
|
try { sessionStorage.setItem(key, 'idb'); } catch (e) { /* ignore */ }
|
|
return key;
|
|
}
|
|
try {
|
|
sessionStorage.setItem(key, JSON.stringify(j));
|
|
return key;
|
|
} catch (e) {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function soonOpenSoonJsonLocally(j, fileName) {
|
|
var key = soonPutSoonSession(j, fileName);
|
|
if (!key) {
|
|
soonToast('无法打开文件(存储空间不足)', 'error');
|
|
return '';
|
|
}
|
|
if (typeof window.soonRecentUpsertFromOpen === 'function') {
|
|
window.soonRecentUpsertFromOpen(key, j);
|
|
}
|
|
soonScheduleCloudImport(key, fileName);
|
|
var type = soonSoonTypeFromJson(j);
|
|
if (window.platformBridge && window.platformBridge.openDesignPage) {
|
|
window.platformBridge.openDesignPage(key, type);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
var PENDING_IMPORT_KEY = 'soondesign_pending_import';
|
|
|
|
function soonScheduleCloudImport(sessionKey, fileName) {
|
|
if (!soonGetAccessToken() || !sessionKey) return;
|
|
if (sessionKey.indexOf('soondesign_session:') !== 0) return;
|
|
try {
|
|
sessionStorage.setItem(PENDING_IMPORT_KEY, JSON.stringify({
|
|
sessionKey: sessionKey,
|
|
name: soonNormalizeSoonName(fileName || 'design.soon'),
|
|
at: Date.now()
|
|
}));
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
|
|
function soonClearPendingImport() {
|
|
try { sessionStorage.removeItem(PENDING_IMPORT_KEY); } catch (e) { /* ignore */ }
|
|
}
|
|
|
|
function soonTryConsumeCloudImport(currentFileKey) {
|
|
if (!currentFileKey || currentFileKey.indexOf('soondesign_session:') !== 0) return;
|
|
if (!soonGetAccessToken()) return;
|
|
if (typeof window.openAs !== 'undefined' && window.openAs && window.openAs.name &&
|
|
window.openAs.name.indexOf(FILE_PREFIX) === 0) {
|
|
soonClearPendingImport();
|
|
return;
|
|
}
|
|
var pendingRaw;
|
|
try {
|
|
pendingRaw = sessionStorage.getItem(PENDING_IMPORT_KEY);
|
|
} catch (e) {
|
|
return;
|
|
}
|
|
if (!pendingRaw) return;
|
|
var pending;
|
|
try {
|
|
pending = JSON.parse(pendingRaw);
|
|
} catch (e) {
|
|
try { sessionStorage.removeItem(PENDING_IMPORT_KEY); } catch (e2) { /* ignore */ }
|
|
return;
|
|
}
|
|
if (!pending || pending.sessionKey !== currentFileKey) return;
|
|
try { sessionStorage.removeItem(PENDING_IMPORT_KEY); } catch (e) { /* ignore */ }
|
|
|
|
function loadJsonForImport() {
|
|
if (typeof window.soonLocalGet === 'function') {
|
|
return window.soonLocalGet(currentFileKey).then(function (hit) {
|
|
if (hit && hit.json) return hit.json;
|
|
try {
|
|
var raw = sessionStorage.getItem(currentFileKey);
|
|
if (raw && raw !== 'idb') return JSON.parse(raw);
|
|
} catch (e) { /* ignore */ }
|
|
return null;
|
|
});
|
|
}
|
|
try {
|
|
var raw = sessionStorage.getItem(currentFileKey);
|
|
if (raw && raw !== 'idb') return JSON.parse(raw);
|
|
} catch (e) { /* ignore */ }
|
|
return Promise.resolve(null);
|
|
}
|
|
|
|
var bridge = window.platformBridge;
|
|
if (!bridge || typeof bridge.importSoonFile !== 'function') return;
|
|
|
|
loadJsonForImport().then(function (jsonObj) {
|
|
if (!jsonObj) return;
|
|
var oldKey = currentFileKey;
|
|
return bridge.importSoonFile(pending.name || 'design.soon', jsonObj).then(function (res) {
|
|
if (res && res.fileKey && typeof window.openAs !== 'undefined' && window.openAs) {
|
|
window.openAs.name = res.fileKey;
|
|
}
|
|
if (res && res.fileKey && typeof window.soonLocalRenameKey === 'function') {
|
|
window.soonLocalRenameKey(oldKey, res.fileKey, { source: 'cloud', name: res.name || pending.name });
|
|
}
|
|
try {
|
|
sessionStorage.removeItem(oldKey);
|
|
localStorage.removeItem(oldKey);
|
|
} catch (e) { /* ignore */ }
|
|
if (typeof window.soonRecentOnCloudSave === 'function') {
|
|
window.soonRecentOnCloudSave(res, oldKey, soonSoonTypeFromJson(jsonObj));
|
|
}
|
|
});
|
|
}).catch(function (err) {
|
|
if (typeof window.soonShowApiError === 'function') {
|
|
window.soonShowApiError({
|
|
status: err && err.status,
|
|
message: (err && err.message) || '云端登记失败,可稍后保存重试',
|
|
code: err && err.code
|
|
});
|
|
} else {
|
|
soonToast((err && err.message) || '云端登记失败,可稍后保存重试', 'warn');
|
|
}
|
|
});
|
|
}
|
|
|
|
function invokeLayerCleanup() {
|
|
if (typeof window.soonCleanupLayerMask === 'function') {
|
|
window.soonCleanupLayerMask();
|
|
}
|
|
}
|
|
|
|
function soonReportOpenLoadError(message) {
|
|
soonHideOpenFileLoading();
|
|
invokeLayerCleanup();
|
|
soonToast(message || '文件加载失败', 'error');
|
|
}
|
|
|
|
function soonShowOpenFileLoading() {
|
|
if (typeof layer === 'undefined' || !layer.load) return;
|
|
soonHideOpenFileLoading();
|
|
window._soonOpenLoadIdx = layer.load(1, { shade: 0 });
|
|
}
|
|
|
|
function soonHideOpenFileLoading() {
|
|
if (typeof layer !== 'undefined' && layer.closeAll) {
|
|
layer.closeAll('loading');
|
|
}
|
|
if (window._soonOpenLoadIdx != null && typeof layer !== 'undefined' && layer.close) {
|
|
try { layer.close(window._soonOpenLoadIdx); } catch (e) { /* ignore */ }
|
|
}
|
|
window._soonOpenLoadIdx = null;
|
|
invokeLayerCleanup();
|
|
}
|
|
|
|
function soonToast(message, type) {
|
|
if (typeof window.soonToast === 'function') window.soonToast(message, type);
|
|
}
|
|
|
|
function soonRequireLogin(actionLabel, options) {
|
|
options = options || {};
|
|
if (soonGetAccessToken()) return true;
|
|
var label = actionLabel || '继续';
|
|
soonToast('请先登录后再' + (label || '操作'), 'warn');
|
|
if (options.redirect) {
|
|
if (typeof window.soonRedirectToLogin === 'function') {
|
|
setTimeout(function () { window.soonRedirectToLogin(); }, 600);
|
|
} else if (typeof window.redirectToLogin === 'function') {
|
|
setTimeout(function () { window.redirectToLogin(); }, 600);
|
|
} else {
|
|
window.location.href = 'login.web.html';
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function soonParseApiError(response, bodyText) {
|
|
var msg = '操作失败,请稍后重试';
|
|
var code = '';
|
|
try {
|
|
var j = bodyText ? JSON.parse(bodyText) : null;
|
|
if (j && j.message) msg = j.message;
|
|
if (j && j.error) code = j.error;
|
|
} catch (e) { /* ignore */ }
|
|
if (response && response.status === 401) {
|
|
return { status: 401, message: '请先登录', code: code || 'unauthorized' };
|
|
}
|
|
if (response && response.status === 409) {
|
|
return { status: 409, message: '文件已被其他端修改,请刷新后重试', code: code || 'conflict' };
|
|
}
|
|
if (response && response.status === 413) {
|
|
if (code === 'file_limit_exceeded') {
|
|
return { status: 413, message: msg || '文件数量已达上限,请清理文件', code: code };
|
|
}
|
|
return { status: 413, message: msg || '存储空间不足,请清理文件', code: code || 'quota_exceeded' };
|
|
}
|
|
if (response && response.status === 404) {
|
|
return { status: 404, message: '文件不存在', code: code || 'not_found' };
|
|
}
|
|
return { status: response ? response.status : 0, message: msg, code: code };
|
|
}
|
|
|
|
function soonShowApiError(info) {
|
|
if (!info) return;
|
|
var kind = 'error';
|
|
if (info.status === 401) kind = 'warn';
|
|
else if (info.status === 409) kind = 'warn';
|
|
else if (info.status === 413) kind = 'warn';
|
|
soonToast(info.message, kind);
|
|
if (info.status === 401 && info.requireLogin) {
|
|
soonRequireLogin('操作', { redirect: true });
|
|
}
|
|
}
|
|
|
|
function soonDisplayFileName(pathOrKey) {
|
|
if (!pathOrKey) return '';
|
|
if (pathOrKey.indexOf(TEMPLATE_PREFIX) === 0) {
|
|
var tplMeta = window._soonTemplateMeta;
|
|
var tplParsed = soonParseTemplateKey(pathOrKey);
|
|
if (tplMeta && tplParsed && tplMeta.id === tplParsed.id && tplMeta.name) {
|
|
return tplMeta.name;
|
|
}
|
|
return tplParsed ? ('模板 #' + tplParsed.id) : pathOrKey;
|
|
}
|
|
if (pathOrKey.indexOf(FILE_PREFIX) === 0) {
|
|
var resolved = soonResolveCloudFileName(pathOrKey);
|
|
if (resolved) return resolved;
|
|
var parsed = soonParseFileKey(pathOrKey);
|
|
return parsed ? ('文件 #' + parsed.id) : pathOrKey;
|
|
}
|
|
if (pathOrKey.indexOf('soondesign_session:') === 0) {
|
|
return soonSessionDisplayName(pathOrKey);
|
|
}
|
|
return String(pathOrKey).split(/[/\\]/).pop();
|
|
}
|
|
|
|
function soonFetchOrderStatus(orderNo) {
|
|
var url = soonApiBase() + '/pay/orders/' + encodeURIComponent(orderNo);
|
|
var fetchFn = typeof window.soonAuthedFetch === 'function'
|
|
? window.soonAuthedFetch(url, { headers: { Accept: 'application/json' } })
|
|
: fetch(url, { headers: { Authorization: 'Bearer ' + soonGetAccessToken(), Accept: 'application/json' } });
|
|
return fetchFn.then(function (r) {
|
|
if (!r.ok) return null;
|
|
return r.json();
|
|
}).then(function (j) {
|
|
return j && j.ok && j.data ? j.data : null;
|
|
}).catch(function () {
|
|
return null;
|
|
});
|
|
}
|
|
|
|
function soonPollOrderPaid(orderNo, attemptsLeft, delayMs) {
|
|
return soonFetchOrderStatus(orderNo).then(function (data) {
|
|
if (data && data.status === 'paid') return data;
|
|
if (attemptsLeft <= 1) return data;
|
|
return new Promise(function (resolve) {
|
|
setTimeout(function () {
|
|
resolve(soonPollOrderPaid(orderNo, attemptsLeft - 1, delayMs));
|
|
}, delayMs);
|
|
});
|
|
});
|
|
}
|
|
|
|
function soonHandlePayReturn() {
|
|
var orderNo = null;
|
|
try {
|
|
orderNo = new URLSearchParams(location.search).get('paid');
|
|
} catch (e) { /* ignore */ }
|
|
try {
|
|
sessionStorage.removeItem('soon_pay_return');
|
|
} catch (e) { /* ignore */ }
|
|
if (!orderNo) return;
|
|
try {
|
|
var u = new URL(location.href);
|
|
u.searchParams.delete('paid');
|
|
history.replaceState(null, '', u.pathname + (u.search || '') + (u.hash || ''));
|
|
} catch (e) { /* ignore */ }
|
|
if (!soonGetAccessToken()) return;
|
|
soonPollOrderPaid(orderNo, 10, 3000).then(function (data) {
|
|
if (data && data.status === 'paid') {
|
|
return soonLoadMembership(true).then(function () {
|
|
soonRefreshPortalIdentity();
|
|
soonToast('支付成功,会员已激活', 'success');
|
|
});
|
|
}
|
|
if (data && data.status === 'cancelled') {
|
|
soonToast('订单已取消', 'warn');
|
|
return;
|
|
}
|
|
soonToast('支付尚未完成,如已付款请稍后在会员中心查看', 'warn');
|
|
});
|
|
}
|
|
|
|
window.soonIsWebPortal = soonIsWebPortal;
|
|
window.soonParseFileKey = soonParseFileKey;
|
|
window.soonMakeFileKey = soonMakeFileKey;
|
|
window.soonNormalizeSoonName = soonNormalizeSoonName;
|
|
window.soonResolveCloudFileName = soonResolveCloudFileName;
|
|
window.soonApplyCloudMeta = soonApplyCloudMeta;
|
|
window.soonBindFileMeta = soonBindFileMeta;
|
|
window.soonSyncOpenNavigation = soonSyncOpenNavigation;
|
|
window.soonResolveOpenFileKey = soonResolveOpenFileKey;
|
|
window.soonConsumeOpenMeta = soonConsumeOpenMeta;
|
|
window.soonFormatOpenTitle = soonFormatOpenTitle;
|
|
window.soonGuardCloudSave = soonGuardCloudSave;
|
|
window.soonGuardPreviewDeliver = soonGuardPreviewDeliver;
|
|
window.soonHandlePayReturn = soonHandlePayReturn;
|
|
window.soonOpenSoonJsonLocally = soonOpenSoonJsonLocally;
|
|
window.soonPutSoonSession = soonPutSoonSession;
|
|
window.soonScheduleCloudImport = soonScheduleCloudImport;
|
|
window.soonTryConsumeCloudImport = soonTryConsumeCloudImport;
|
|
window.soonClearPendingImport = soonClearPendingImport;
|
|
window.soonReportOpenLoadError = soonReportOpenLoadError;
|
|
window.soonShowOpenFileLoading = soonShowOpenFileLoading;
|
|
window.soonHideOpenFileLoading = soonHideOpenFileLoading;
|
|
window.soonParseTemplateKey = soonParseTemplateKey;
|
|
window.soonMakeTemplateKey = soonMakeTemplateKey;
|
|
window.soonSoonTypeFromJson = soonSoonTypeFromJson;
|
|
window.soonLoadMembership = soonLoadMembership;
|
|
window.soonApplyMembership = soonApplyMembership;
|
|
window.soonRefreshPortalIdentity = soonRefreshPortalIdentity;
|
|
window.soonIsMember = soonIsMember;
|
|
window.soonRequireLogin = soonRequireLogin;
|
|
window.soonParseApiError = soonParseApiError;
|
|
window.soonShowApiError = soonShowApiError;
|
|
window.soonDisplayFileName = soonDisplayFileName;
|
|
window.soonIsTemplateKey = soonIsTemplateKey;
|
|
window.soonNeedsSaveDialog = soonNeedsSaveDialog;
|
|
window.soonDefaultNewSoonName = soonDefaultNewSoonName;
|
|
window.soonEnsureSoonExt = soonEnsureSoonExt;
|
|
window.soonSessionSlug = soonSessionSlug;
|
|
window.soonMakeSessionKey = soonMakeSessionKey;
|
|
window.soonSessionDisplayName = soonSessionDisplayName;
|
|
window.soonBuildSavePreviewPic = soonBuildSavePreviewPic;
|
|
window.soonApplySavePreview = soonApplySavePreview;
|
|
window.soonSaveDialogDefaultPath = soonSaveDialogDefaultPath;
|
|
window.soonEnsureSaveFileName = soonEnsureSaveFileName;
|
|
window.soonReportSaveError = soonReportSaveError;
|
|
window.soonWriteSoonContentWeb = soonWriteSoonContentWeb;
|
|
window.soonDownloadBlob = soonDownloadBlob;
|
|
window.soonDownloadRecentItem = soonDownloadRecentItem;
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', soonHandlePayReturn);
|
|
} else {
|
|
soonHandlePayReturn();
|
|
}
|
|
})();
|