feat(creator-studio): dialog-driven table/math/footnote, theme quote, code language picker
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
7c33482be7
commit
7885f42cff
14 files changed
+1005
-54
No files matched your search
@@ -1 +1 @@
|
|||||||
<?php return array('dependencies' => array('react', 'wp-element', 'wp-i18n'), 'version' => '1f4dc4e2d488af6bd803');
|
<?php return array('dependencies' => array('react', 'wp-element', 'wp-i18n'), 'version' => '5e199a1baccb9f8673e9');
|
||||||
@@ -1 +1 @@
|
|||||||
.jio-page-submit{background:#f6f6f6}body.jio-page-submit.has-static-nav,body.jio-page-submit.nav-fixed{padding-top:0!important}.jio-page-submit .float-btn,.jio-page-submit .footer,.jio-page-submit .footer-tabbar,.jio-page-submit .header,.jio-page-submit .jio-float-buttons{display:none!important}.jio-page-submit #main-content.container{max-width:none;padding:0}#jio-creator-studio.is-ready{display:block}.jio-creator{--jc-ink:#2b2b2b;--jc-ink-soft:#7d8683;--jc-paper:#f6f6f6;--jc-line:#e6e9e8;--jc-accent:#267665;--jc-accent-strong:#1d5f52;--jc-warn:#b4573f;--jc-bar:64px;-webkit-font-smoothing:antialiased;background:var(--jc-paper);color:var(--jc-ink);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Noto Sans SC,PingFang SC,Microsoft YaHei,sans-serif;min-height:100vh;min-height:100dvh;padding-top:var(--jc-bar)}.jio-creator *,.jio-creator :after,.jio-creator :before{box-sizing:border-box}.jio-creator button{color:inherit;font:inherit}.jio-creator__bar{align-items:center;background:rgba(250,251,250,.98);border-bottom:1px solid var(--jc-line);box-shadow:0 2px 8px rgba(27,46,39,.035);display:flex;gap:clamp(10px,1.7vw,24px);height:var(--jc-bar);left:0;padding:0 clamp(14px,2.4vw,30px);position:fixed;right:0;top:0;z-index:300}.jio-creator__bar-actions,.jio-creator__bar-left{align-items:center;display:flex;flex:0 0 auto;gap:9px;min-width:0}.jio-creator__bar-left{border-right:1px solid var(--jc-line);padding-right:clamp(10px,1.5vw,20px)}.jio-creator__bar-actions{gap:7px;margin-left:auto}.jio-creator__bar-brand{color:var(--jc-accent-strong);font-family:Songti SC,"Noto Serif SC","Source Han Serif SC",serif;font-size:17px;font-weight:700;letter-spacing:-.04em}.jio-creator__bar-divider{background:var(--jc-line);height:18px;width:1px}.jio-creator__bar-mode{color:var(--jc-ink-soft);font-size:13px;white-space:nowrap}.jio-creator__back{align-items:center;border-radius:7px;color:var(--jc-ink-soft);display:inline-flex;height:30px;justify-content:center;text-decoration:none;width:30px}.jio-creator__back:focus-visible,.jio-creator__back:hover{background:#edf3f0;color:var(--jc-accent);outline:0}.jio-creator__toolbar-tools{align-items:center;display:flex;flex:1 1 auto;gap:7px;min-width:0;overflow-x:auto;scrollbar-width:none}.jio-creator__toolbar-tools::-webkit-scrollbar{display:none}.jio-creator__toolbar-group{align-items:center;display:inline-flex;flex:0 0 auto;gap:2px}.jio-creator__toolbar-divider{background:var(--jc-line);flex:0 0 auto;height:22px;width:1px}.jio-creator__tool-btn{align-items:center;background:transparent;border:0;border-radius:6px;color:#5f6664;cursor:pointer;display:inline-flex;flex:0 0 auto;flex-direction:column;font-size:11px;gap:2px;height:46px;justify-content:center;line-height:1.2;min-width:40px;padding:5px 6px;width:auto}.jio-creator__tool-btn i{font-size:14px;line-height:1}.jio-creator__tool-btn:focus-visible:not(:disabled),.jio-creator__tool-btn:hover:not(:disabled){background:#f0f2f1;color:var(--jc-accent-strong);outline:0}.jio-creator__tool-btn:disabled{color:#c7d0cc;cursor:default}.jio-creator__tool-label{max-width:52px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.jio-creator__format-field{align-items:center;border-radius:6px;cursor:pointer;display:inline-flex;flex:0 0 auto;flex-direction:column;gap:2px;height:46px;justify-content:center;min-width:48px;padding:5px 6px;position:relative;width:auto}.jio-creator__format-field i{font-size:14px;line-height:1}.jio-creator__format-field:focus-within,.jio-creator__format-field:hover{background:#f0f2f1;color:var(--jc-accent-strong)}.jio-creator__format-field:focus-within{outline:2px solid rgba(38,118,101,.35);outline-offset:-2px}.jio-creator__format-select{background:transparent;border:0;color:inherit;cursor:pointer;font:inherit;height:100%;inset:0;opacity:0;padding:0;position:absolute;width:100%}.jio-creator__format-select:disabled{cursor:default}.jio-creator__save-draft{align-items:center;background:#fff;border:1px solid var(--jc-line);border-radius:7px;color:var(--jc-ink-soft);cursor:pointer;display:inline-flex;flex:0 0 auto;font-size:13px;gap:7px;justify-content:center;min-height:34px;padding:0 10px;white-space:nowrap}.jio-creator__save-draft:focus-visible:not(:disabled),.jio-creator__save-draft:hover:not(:disabled){background:#f7faf8;border-color:#b7cdc3;color:var(--jc-accent-strong);outline:0}.jio-creator__save-draft:disabled{cursor:default;opacity:.55}.jio-creator__save{align-items:center;color:var(--jc-ink-soft);display:inline-flex;flex:0 0 auto;font-size:12px;gap:6px;white-space:nowrap}.jio-creator__save>i{color:var(--jc-line);font-size:7px}.jio-creator__save time{font-variant-numeric:tabular-nums;opacity:.7}.jio-creator__save.is-saved>i{color:var(--jc-accent)}.jio-creator__save.is-conflict,.jio-creator__save.is-conflict>i,.jio-creator__save.is-error,.jio-creator__save.is-error>i{color:var(--jc-warn)}.jio-creator__btn,.jio-creator__ghost,.jio-creator__primary{align-items:center;border:1px solid transparent;border-radius:7px;cursor:poLine truncated
|
.jio-page-submit{background:#f6f6f6}body.jio-page-submit.has-static-nav,body.jio-page-submit.nav-fixed{padding-top:0!important}.jio-page-submit .float-btn,.jio-page-submit .footer,.jio-page-submit .footer-tabbar,.jio-page-submit .header,.jio-page-submit .jio-float-buttons{display:none!important}.jio-page-submit #main-content.container{max-width:none;padding:0}#jio-creator-studio.is-ready{display:block}.jio-creator{--jc-ink:#2b2b2b;--jc-ink-soft:#7d8683;--jc-paper:#f6f6f6;--jc-line:#e6e9e8;--jc-accent:#267665;--jc-accent-strong:#1d5f52;--jc-warn:#b4573f;--jc-bar:64px;-webkit-font-smoothing:antialiased;background:var(--jc-paper);color:var(--jc-ink);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Noto Sans SC,PingFang SC,Microsoft YaHei,sans-serif;min-height:100vh;min-height:100dvh;padding-top:var(--jc-bar)}.jio-creator *,.jio-creator :after,.jio-creator :before{box-sizing:border-box}.jio-creator button{color:inherit;font:inherit}.jio-creator__bar{align-items:center;background:rgba(250,251,250,.98);border-bottom:1px solid var(--jc-line);box-shadow:0 2px 8px rgba(27,46,39,.035);display:flex;gap:clamp(10px,1.7vw,24px);height:var(--jc-bar);left:0;padding:0 clamp(14px,2.4vw,30px);position:fixed;right:0;top:0;z-index:300}.jio-creator__bar-actions,.jio-creator__bar-left{align-items:center;display:flex;flex:0 0 auto;gap:9px;min-width:0}.jio-creator__bar-left{border-right:1px solid var(--jc-line);padding-right:clamp(10px,1.5vw,20px)}.jio-creator__bar-actions{gap:7px;margin-left:auto}.jio-creator__bar-brand{color:var(--jc-accent-strong);font-family:Songti SC,"Noto Serif SC","Source Han Serif SC",serif;font-size:17px;font-weight:700;letter-spacing:-.04em}.jio-creator__bar-divider{background:var(--jc-line);height:18px;width:1px}.jio-creator__bar-mode{color:var(--jc-ink-soft);font-size:13px;white-space:nowrap}.jio-creator__back{align-items:center;border-radius:7px;color:var(--jc-ink-soft);display:inline-flex;height:30px;justify-content:center;text-decoration:none;width:30px}.jio-creator__back:focus-visible,.jio-creator__back:hover{background:#edf3f0;color:var(--jc-accent);outline:0}.jio-creator__toolbar-tools{align-items:center;display:flex;flex:1 1 auto;gap:7px;min-width:0;overflow-x:auto;scrollbar-width:none}.jio-creator__toolbar-tools::-webkit-scrollbar{display:none}.jio-creator__toolbar-group{align-items:center;display:inline-flex;flex:0 0 auto;gap:2px}.jio-creator__toolbar-divider{background:var(--jc-line);flex:0 0 auto;height:22px;width:1px}.jio-creator__tool-btn{align-items:center;background:transparent;border:0;border-radius:6px;color:#5f6664;cursor:pointer;display:inline-flex;flex:0 0 auto;flex-direction:column;font-size:11px;gap:2px;height:46px;justify-content:center;line-height:1.2;min-width:40px;padding:5px 6px;width:auto}.jio-creator__tool-btn i{font-size:14px;line-height:1}.jio-creator__tool-btn:focus-visible:not(:disabled),.jio-creator__tool-btn:hover:not(:disabled){background:#f0f2f1;color:var(--jc-accent-strong);outline:0}.jio-creator__tool-btn:disabled{color:#c7d0cc;cursor:default}.jio-creator__tool-label{max-width:52px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.jio-creator__format-field{align-items:center;border-radius:6px;cursor:pointer;display:inline-flex;flex:0 0 auto;flex-direction:column;gap:2px;height:46px;justify-content:center;min-width:48px;padding:5px 6px;position:relative;width:auto}.jio-creator__format-field i{font-size:14px;line-height:1}.jio-creator__format-field:focus-within,.jio-creator__format-field:hover{background:#f0f2f1;color:var(--jc-accent-strong)}.jio-creator__format-field:focus-within{outline:2px solid rgba(38,118,101,.35);outline-offset:-2px}.jio-creator__format-select{background:transparent;border:0;color:inherit;cursor:pointer;font:inherit;height:100%;inset:0;opacity:0;padding:0;position:absolute;width:100%}.jio-creator__format-select:disabled{cursor:default}.jio-creator__save-draft{align-items:center;background:#fff;border:1px solid var(--jc-line);border-radius:7px;color:var(--jc-ink-soft);cursor:pointer;display:inline-flex;flex:0 0 auto;font-size:13px;gap:7px;justify-content:center;min-height:34px;padding:0 10px;white-space:nowrap}.jio-creator__save-draft:focus-visible:not(:disabled),.jio-creator__save-draft:hover:not(:disabled){background:#f7faf8;border-color:#b7cdc3;color:var(--jc-accent-strong);outline:0}.jio-creator__save-draft:disabled{cursor:default;opacity:.55}.jio-creator__save{align-items:center;color:var(--jc-ink-soft);display:inline-flex;flex:0 0 auto;font-size:12px;gap:6px;white-space:nowrap}.jio-creator__save>i{color:var(--jc-line);font-size:7px}.jio-creator__save time{font-variant-numeric:tabular-nums;opacity:.7}.jio-creator__save.is-saved>i{color:var(--jc-accent)}.jio-creator__save.is-conflict,.jio-creator__save.is-conflict>i,.jio-creator__save.is-error,.jio-creator__save.is-error>i{color:var(--jc-warn)}.jio-creator__btn,.jio-creator__ghost,.jio-creator__primary{align-items:center;border:1px solid transparent;border-radius:7px;cursor:poLine truncated
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
(()=>{"use strict";const e=window.React,t=window.wp.element,a=window.wp.i18n;function n(e,t){const a=e&&(e.message||e.error)||(401===t?"登录状态已失效,请刷新页面重新登录。":"请求失败,请稍后重试。"),n=new Error(String(a));return n.status=t,n.code=e&&e.code?String(e.code):"",n.data=e,n}async function r(e){try{return await e.json()}catch(e){return null}}async function o(e,t,a){const o=await fetch(e,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json","X-WP-Nonce":a},body:JSON.stringify(t)}),i=await r(o);if(!o.ok)throw n(i,o.status);return i}async function i(e,t,a,o={}){const i=new FormData;i.append("file",t),Object.entries(o).forEach(([e,t])=>i.append(e,t));const l=await fetch(e,{method:"POST",credentials:"same-origin",headers:{"X-WP-Nonce":a},body:i}),c=await r(l);if(!l.ok)throw n(c,l.status);return c}const l=window.jioCreatorStudio||{},c=l.nonce||"";function s(n){const{draft:r,setDraft:o,capabilities:s,limits:u,taxonomy:d,isPublishedEdit:m,onClose:p,onSaveDraft:_,onSubmit:f,privateStorage:b,privateUploads:g,saving:y,captcha:j}=n,[E,h]=(0,t.useState)(""),[v,N]=(0,t.useState)(""),[w,k]=(0,t.useState)(!1),C=(0,t.useRef)(null),S=(0,t.useRef)(null);(0,t.useEffect)(()=>{const e=S.current,t=document.getElementById("jio-creator-captcha");if(!e||!t)return;const a=t.parentElement;return e.appendChild(t),t.removeAttribute("hidden"),()=>{a&&(a.appendChild(t),t.setAttribute("hidden",""))}},[]);const I=(0,t.useMemo)(()=>(r.title||"").trim().length,[r.title])>=Number(u.titleMin||1),x=()=>{const e=E.trim();if(!e)return;const t=e.split(/[,,\n]+/u).map(e=>e.trim()).filter(Boolean);o({tags:Array.from(new Set([...r.tags,...t]))}),h("")};return(0,e.createElement)("div",{className:"jio-creator-drawer",role:"dialog","aria-modal":"true","aria-labelledby":"jio-creator-publish-title"},(0,e.createElement)("button",{type:"button",className:"jio-creator-drawer__backdrop","aria-label":(0,a.__)("关闭","jio"),onClick:p}),(0,e.createElement)("div",{className:"jio-creator-drawer__panel"},(0,e.createElement)("header",{className:"jio-creator-drawer__head"},(0,e.createElement)("h2",{id:"jio-creator-publish-title"},m?(0,a.__)("提交改稿","jio"):(0,a.__)("发布文章","jio")),(0,e.createElement)("button",{type:"button",className:"jio-creator-drawer__close",onClick:p,"aria-label":(0,a.__)("关闭","jio")},(0,e.createElement)("i",{className:"fa fas fa-times","aria-hidden":"true"}))),(0,e.createElement)("div",{className:"jio-creator-drawer__body"},m&&(0,e.createElement)("p",{className:"jio-creator-drawer__note"},(0,a.__)("审核通过前,读者看到的仍然是当前线上版本。","jio")),(0,e.createElement)("section",{className:"jio-creator-field"},(0,e.createElement)("h3",null,(0,a.__)("分类","jio")),(0,e.createElement)("div",{className:"jio-creator-chips"},0===d.categories.length&&(0,e.createElement)("span",{className:"jio-creator-muted"},(0,a.__)("暂无可选分类","jio")),d.categories.map(t=>(0,e.createElement)("label",{key:t.id,className:"jio-creator-chip"+(r.categories.includes(t.id)?" is-active":"")},(0,e.createElement)("input",{type:"checkbox",checked:r.categories.includes(t.id),onChange:()=>(e=>{const t=r.categories.includes(e);o({categories:t?r.categories.filter(t=>t!==e):[...r.categories,e]})})(t.id)}),(0,e.createElement)("span",null,t.name))))),d.tags&&(0,e.createElement)("section",{className:"jio-creator-field"},(0,e.createElement)("h3",null,(0,a.__)("标签","jio")),(0,e.createElement)("div",{className:"jio-creator-chips"},r.tags.map(t=>(0,e.createElement)("span",{key:t,className:"jio-creator-chip is-active"},(0,e.createElement)("span",null,t),(0,e.createElement)("button",{type:"button",onClick:()=>o({tags:r.tags.filter(e=>e!==t)}),"aria-label":(0,a.sprintf)((0,a.__)("移除标签 %s","jio"),t)},(0,e.createElement)("i",{className:"fa fas fa-times","aria-hidden":"true"}))))),(0,e.createElement)("div",{className:"jio-creator-inline"},(0,e.createElement)("input",{type:"text",value:E,placeholder:(0,a.__)("输入标签,逗号分隔","jio"),onChange:e=>h(e.target.value),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),x())}}),(0,e.createElement)("button",{type:"button",onClick:x},(0,a.__)("添加","jio")))),d.customDefs.map(t=>(0,e.createElement)("section",{className:"jio-creator-field",key:t.key},(0,e.createElement)("h3",null,t.name),(0,e.createElement)("div",{className:"jio-creator-chips"},t.values.map(a=>{const n=r.custom[t.key]||[],i=n.includes(a.key);return(0,e.createElement)("label",{key:a.key,className:"jio-creator-chip"+(i?" is-active":"")},(0,e.createElement)("input",{type:"checkbox",checked:i,onChange:()=>o({custom:{...r.custom,[t.key]:i?n.filter(e=>e!==a.key):[...n,a.key]}})}),(0,e.createElement)("span",null,a.name))})))),s.canPay&&(0,e.createElement)("section",{className:"jio-creator-field"},(0,e.createElement)("div",{className:"jio-creator-switch"},(0,e.createElement)("div",null,(0,e.createElement)("h3",null,(0,a.__)("付费内容","jio")),(0,e.Line truncated
|
(()=>{"use strict";const e=window.React,t=window.wp.element,a=window.wp.i18n;function n(e,t){const a=e&&(e.message||e.error)||(401===t?"登录状态已失效,请刷新页面重新登录。":"请求失败,请稍后重试。"),n=new Error(String(a));return n.status=t,n.code=e&&e.code?String(e.code):"",n.data=e,n}async function r(e){try{return await e.json()}catch(e){return null}}async function o(e,t,a){const o=await fetch(e,{method:"POST",credentials:"same-origin",headers:{"Content-Type":"application/json","X-WP-Nonce":a},body:JSON.stringify(t)}),l=await r(o);if(!o.ok)throw n(l,o.status);return l}async function l(e,t,a,o={}){const l=new FormData;l.append("file",t),Object.entries(o).forEach(([e,t])=>l.append(e,t));const i=await fetch(e,{method:"POST",credentials:"same-origin",headers:{"X-WP-Nonce":a},body:l}),c=await r(i);if(!i.ok)throw n(c,i.status);return c}const i=window.jioCreatorStudio||{},c=i.nonce||"";function s(n){const{draft:r,setDraft:o,capabilities:s,limits:u,taxonomy:d,isPublishedEdit:m,onClose:p,onSaveDraft:_,onSubmit:b,privateStorage:f,privateUploads:g,saving:h,captcha:j}=n,[E,y]=(0,t.useState)(""),[v,k]=(0,t.useState)(""),[N,w]=(0,t.useState)(!1),C=(0,t.useRef)(null),S=(0,t.useRef)(null);(0,t.useEffect)(()=>{const e=S.current,t=document.getElementById("jio-creator-captcha");if(!e||!t)return;const a=t.parentElement;return e.appendChild(t),t.removeAttribute("hidden"),()=>{a&&(a.appendChild(t),t.setAttribute("hidden",""))}},[]);const x=(0,t.useMemo)(()=>(r.title||"").trim().length,[r.title])>=Number(u.titleMin||1),M=()=>{const e=E.trim();if(!e)return;const t=e.split(/[,,\n]+/u).map(e=>e.trim()).filter(Boolean);o({tags:Array.from(new Set([...r.tags,...t]))}),y("")};return(0,e.createElement)("div",{className:"jio-creator-drawer",role:"dialog","aria-modal":"true","aria-labelledby":"jio-creator-publish-title"},(0,e.createElement)("button",{type:"button",className:"jio-creator-drawer__backdrop","aria-label":(0,a.__)("关闭","jio"),onClick:p}),(0,e.createElement)("div",{className:"jio-creator-drawer__panel"},(0,e.createElement)("header",{className:"jio-creator-drawer__head"},(0,e.createElement)("h2",{id:"jio-creator-publish-title"},m?(0,a.__)("提交改稿","jio"):(0,a.__)("发布文章","jio")),(0,e.createElement)("button",{type:"button",className:"jio-creator-drawer__close",onClick:p,"aria-label":(0,a.__)("关闭","jio")},(0,e.createElement)("i",{className:"fa fas fa-times","aria-hidden":"true"}))),(0,e.createElement)("div",{className:"jio-creator-drawer__body"},m&&(0,e.createElement)("p",{className:"jio-creator-drawer__note"},(0,a.__)("审核通过前,读者看到的仍然是当前线上版本。","jio")),(0,e.createElement)("section",{className:"jio-creator-field"},(0,e.createElement)("h3",null,(0,a.__)("分类","jio")),(0,e.createElement)("div",{className:"jio-creator-chips"},0===d.categories.length&&(0,e.createElement)("span",{className:"jio-creator-muted"},(0,a.__)("暂无可选分类","jio")),d.categories.map(t=>(0,e.createElement)("label",{key:t.id,className:"jio-creator-chip"+(r.categories.includes(t.id)?" is-active":"")},(0,e.createElement)("input",{type:"checkbox",checked:r.categories.includes(t.id),onChange:()=>(e=>{const t=r.categories.includes(e);o({categories:t?r.categories.filter(t=>t!==e):[...r.categories,e]})})(t.id)}),(0,e.createElement)("span",null,t.name))))),d.tags&&(0,e.createElement)("section",{className:"jio-creator-field"},(0,e.createElement)("h3",null,(0,a.__)("标签","jio")),(0,e.createElement)("div",{className:"jio-creator-chips"},r.tags.map(t=>(0,e.createElement)("span",{key:t,className:"jio-creator-chip is-active"},(0,e.createElement)("span",null,t),(0,e.createElement)("button",{type:"button",onClick:()=>o({tags:r.tags.filter(e=>e!==t)}),"aria-label":(0,a.sprintf)((0,a.__)("移除标签 %s","jio"),t)},(0,e.createElement)("i",{className:"fa fas fa-times","aria-hidden":"true"}))))),(0,e.createElement)("div",{className:"jio-creator-inline"},(0,e.createElement)("input",{type:"text",value:E,placeholder:(0,a.__)("输入标签,逗号分隔","jio"),onChange:e=>y(e.target.value),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),M())}}),(0,e.createElement)("button",{type:"button",onClick:M},(0,a.__)("添加","jio")))),d.customDefs.map(t=>(0,e.createElement)("section",{className:"jio-creator-field",key:t.key},(0,e.createElement)("h3",null,t.name),(0,e.createElement)("div",{className:"jio-creator-chips"},t.values.map(a=>{const n=r.custom[t.key]||[],l=n.includes(a.key);return(0,e.createElement)("label",{key:a.key,className:"jio-creator-chip"+(l?" is-active":"")},(0,e.createElement)("input",{type:"checkbox",checked:l,onChange:()=>o({custom:{...r.custom,[t.key]:l?n.filter(e=>e!==a.key):[...n,a.key]}})}),(0,e.createElement)("span",null,a.name))})))),s.canPay&&(0,e.createElement)("section",{className:"jio-creator-field"},(0,e.createElement)("div",{className:"jio-creator-switch"},(0,e.createElement)("div",null,(0,e.createElement)("h3",null,(0,a.__)("付费内容","jio")),(0,e.Line truncated
|
||||||
(0,a.__)("按站点时区(%s)发布,至少需要晚于当前时间 1 分钟。","jio"),String(m.timezone||"UTC")))))}const g={ready:(0,a.__)("尚未保存","jio"),saving:(0,a.__)("保存中…","jio"),saved:(0,a.__)("已保存","jio"),error:(0,a.__)("保存失败","jio"),conflict:(0,a.__)("内容已在别处更新","jio"),submitting:(0,a.__)("提交中…","jio"),submitted:(0,a.__)("已提交","jio")};function y({value:t}){return(0,e.createElement)("span",{className:`jio-creator__save is-${t.state}`,role:"status","aria-live":"polite"},(0,e.createElement)("i",{className:"fa fas fa-circle","aria-hidden":"true"}),(0,e.createElement)("span",null,g[t.state]||g.ready),"saved"===t.state&&t.at?(0,e.createElement)("time",null,t.at):null)}function j(e){const t=(e.downloads||[]).map(e=>{const t="string"==typeof e.assetKey?e.assetKey:"";if(t)return{type:"protected",asset_key:t,label:"string"==typeof e.label?e.label:""};const a="string"==typeof e.url?e.url.trim():"";return a?{type:"direct",url:a,label:"string"==typeof e.label?e.label:"",extract_code:"string"==typeof e.extractCode?e.extractCode:""}:null}).filter(Boolean);return{enabled:!!e.enabled,fields:{pay_type:e.enabled?t.length>0?"download":"reading":"no",pay_modo:"points"===e.modo?"points":"0",price:"points"===e.modo?"0.00":String(e.price||"0.00"),points_price:"points"===e.modo?Math.max(0,Number(e.points)||0):0,vip1_price:"points"===e.modo?"0.00":String(e.vip1Price||"0.00"),vip2_price:"points"===e.modo?"0.00":String(e.vip2Price||"0.00"),pay_limit:Number(e.payLimit)||0,downloads:t}}}const E=e=>e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");function h(e){const t=e?"ol":"ul";return`<${t}><li></li></${t}><p></p>`}function v(){const e=(e,t)=>`<${e}>${E(t)}</${e}>`;return`<table class="jio-content-table"><thead><tr>${[1,2,3].map(t=>e("th",`${(0,a.__)("列","jio")} ${t}`)).join("")}</tr></thead><tbody>${[1,2].map(()=>`<tr>${[1,2,3].map(()=>e("td","")).join("")}</tr>`).join("")}</tbody></table><p></p>`}const N=window.jioCreatorStudio||{},w=N.nonce||"",k=`jio-studio-${N.sourceId||0}`,C="jio-creator-content";function S(){const e=N.pay||{},t=N.categories&&N.categories.selected||[],a=N.tags&&N.tags.items||[];return{postId:Number(N.sourceId||0),versionId:Number(N.versionId||0),rev:Number(N.versionRev||0),title:String(N.title||""),content:String(N.content||""),schedule:{enabled:!1,at:""},categories:t.map(Number),tags:a.slice(),custom:N.custom||{},featured:N.featured||{type:"",data:{}},pay:{enabled:!!e.enabled,modo:"points"===e.payModo?"points":"cash",price:String(e.price||"0.00"),points:Number(e.points||0),vip1Price:String(e.vip1Price||"0.00"),vip2Price:String(e.vip2Price||"0.00"),payLimit:Number(e.payLimit||0),downloads:Array.isArray(e.downloads)?e.downloads:[]}}}function I(e){return e.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}function x(e){const t=String(e||"").trim();if(!t)return"";try{const e=new URL(t,window.location.href);return"http:"===e.protocol||"https:"===e.protocol?e.href:""}catch(e){return""}}function M(){const[n,r]=(0,t.useState)(S),i=(0,t.useRef)(n);i.current=n;const l=(0,t.useRef)(null),c=(0,t.useRef)(null),u=(0,t.useRef)(null),m=(0,t.useRef)(!1),p=(0,t.useRef)(()=>{}),[_,f]=(0,t.useState)(!1),[g,M]=(0,t.useState)({canUndo:!1,canRedo:!1}),[L,R]=(0,t.useState)("p"),[$,D]=(0,t.useState)(()=>({state:N.sourceId||N.versionId||N.content?"saved":"ready",at:""})),[P,U]=(0,t.useState)(!1),[F,T]=(0,t.useState)(null),[z,K]=(0,t.useState)(null),O=(0,t.useRef)(!1),B=(0,t.useRef)(0),A=N.capabilities||{},q={titleMin:Number(N.limits?.titleMin||1),titleMax:Number(N.limits?.titleMax||30),titlePlaceholder:String(N.limits?.titlePlaceholder||(0,a.__)("请输入标题","jio")),priceMin:Number(N.pay?.priceMin||0),priceMax:Number(N.pay?.priceMax||0),pointsMin:Number(N.pay?.pointsMin||0),pointsMax:Number(N.pay?.pointsMax||0)},J=!!N.isPublished,V=(0,t.useCallback)(e=>{O.current=!0,B.current+=1;const t={...i.current,...e};i.current=t,r(t)},[]),H=(0,t.useCallback)(e=>{if(e===i.current.content)return;O.current=!0,B.current+=1;const t={...i.current,content:e};i.current=t,r(t)},[]),W=(0,t.useCallback)(e=>{const t=e?.undoManager;t&&M({canUndo:"function"==typeof t.hasUndo&&t.hasUndo(),canRedo:"function"==typeof t.hasRedo&&t.hasRedo()})},[]),X=(0,t.useCallback)(e=>{const t=e?.selection?.getNode(),a=t?e.dom.getParent(t,"p,h1,h2,h3,blockquote")||t:null,n=a?.nodeName?.toLowerCase()||"p";R(["h1","h2","h3","blockquote"].includes(n)?n:"p")},[]),Y=(0,t.useCallback)((e={})=>{const t=i.current;return{post_id:t.postId,version_id:t.versionId,rev:t.rev,title:t.title,content:t.content,categories:t.categories,tags:t.tags,custom:t.custom,featured:t.featured,schedule:{enabled:!!t.schedule.enabled,at:t.schedule.enabled?t.schedule.at:""},pay:j(t.pay),...e}},[]),Z=(0,t.useCallback)(async(e=!1)=>{const t=l.current;t&&!t.removed&&H(t.getContent());const a=i.current;if(!O.current&&(a.postId>0||a.versionId>0))return!0;const n=Line truncated
|
(0,a.__)("按站点时区(%s)发布,至少需要晚于当前时间 1 分钟。","jio"),String(m.timezone||"UTC")))))}const g={ready:(0,a.__)("尚未保存","jio"),saving:(0,a.__)("保存中…","jio"),saved:(0,a.__)("已保存","jio"),error:(0,a.__)("保存失败","jio"),conflict:(0,a.__)("内容已在别处更新","jio"),submitting:(0,a.__)("提交中…","jio"),submitted:(0,a.__)("已提交","jio")};function h({value:t}){return(0,e.createElement)("span",{className:`jio-creator__save is-${t.state}`,role:"status","aria-live":"polite"},(0,e.createElement)("i",{className:"fa fas fa-circle","aria-hidden":"true"}),(0,e.createElement)("span",null,g[t.state]||g.ready),"saved"===t.state&&t.at?(0,e.createElement)("time",null,t.at):null)}function j(e){const t=(e.downloads||[]).map(e=>{const t="string"==typeof e.assetKey?e.assetKey:"";if(t)return{type:"protected",asset_key:t,label:"string"==typeof e.label?e.label:""};const a="string"==typeof e.url?e.url.trim():"";return a?{type:"direct",url:a,label:"string"==typeof e.label?e.label:"",extract_code:"string"==typeof e.extractCode?e.extractCode:""}:null}).filter(Boolean);return{enabled:!!e.enabled,fields:{pay_type:e.enabled?t.length>0?"download":"reading":"no",pay_modo:"points"===e.modo?"points":"0",price:"points"===e.modo?"0.00":String(e.price||"0.00"),points_price:"points"===e.modo?Math.max(0,Number(e.points)||0):0,vip1_price:"points"===e.modo?"0.00":String(e.vip1Price||"0.00"),vip2_price:"points"===e.modo?"0.00":String(e.vip2Price||"0.00"),pay_limit:Number(e.payLimit)||0,downloads:t}}}const E=e=>e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""),y=[{value:"generic",label:(0,a.__)("通用","jio")},{value:"javascript",label:"JavaScript"},{value:"typescript",label:"TypeScript"},{value:"php",label:"PHP"},{value:"python",label:"Python"},{value:"java",label:"Java"},{value:"go",label:"Go"},{value:"rust",label:"Rust"},{value:"csharp",label:"C#"},{value:"cpp",label:"C++"},{value:"sql",label:"SQL"},{value:"json",label:"JSON"},{value:"bash",label:"Shell"},{value:"css",label:"CSS"},{value:"html",label:"HTML"},{value:"markdown",label:"Markdown"}];function v(e="generic",t=""){const n=E(""!==t?t:(0,a.__)("在此输入代码","jio"));return`<pre class="enlighter-pre" data-enlighter-language="${E(e)}"><code class="gl" data-enlighter-language="${E(e)}">`+n+"</code></pre><p></p>"}const k=(e,t,a)=>Math.min(a,Math.max(t,Number.isFinite(e)?Math.round(e):t)),N={table:(0,a.__)("插入表格","jio"),math:(0,a.__)("插入公式","jio"),footnote:(0,a.__)("插入注释","jio"),code:(0,a.__)("插入代码块","jio")};function w({kind:n,initialLanguage:r="generic",initialCode:o="",onSubmit:l,onClose:i}){const[c,s]=(0,t.useState)(3),[u,d]=(0,t.useState)(3),[m,p]=(0,t.useState)(!0),[_,b]=(0,t.useState)(!1),[f,g]=(0,t.useState)("a^2 + b^2 = c^2"),[h,j]=(0,t.useState)(!0),[E,v]=(0,t.useState)(""),[w,C]=(0,t.useState)(r),[S,x]=(0,t.useState)(o),M=(0,t.useRef)(null),q=(0,t.useRef)(null),L=(0,t.useRef)(null),$=(0,t.useRef)(null);(0,t.useEffect)(()=>{(q.current||L.current||$.current)?.focus();const e=e=>{"Escape"===e.key&&(e.preventDefault(),i())};return document.addEventListener("keydown",e),()=>document.removeEventListener("keydown",e)},[i]);const I=(0,t.useMemo)(()=>"footnote"===n?""!==E.trim():"math"!==n||""!==f.trim(),[n,E,f]);return(0,e.createElement)("div",{className:"jio-creator-dialog",role:"dialog","aria-modal":"true","aria-labelledby":"jio-creator-dialog-title"},(0,e.createElement)("button",{type:"button",className:"jio-creator-dialog__backdrop","aria-label":(0,a.__)("关闭","jio"),onClick:i}),(0,e.createElement)("div",{className:"jio-creator-dialog__panel",ref:M},(0,e.createElement)("header",{className:"jio-creator-dialog__head"},(0,e.createElement)("h2",{id:"jio-creator-dialog-title"},N[n]),(0,e.createElement)("button",{type:"button",className:"jio-creator-dialog__close",onClick:i,"aria-label":(0,a.__)("关闭","jio")},(0,e.createElement)("i",{className:"fa fas fa-times","aria-hidden":"true"}))),(0,e.createElement)("div",{className:"jio-creator-dialog__body"},"table"===n&&(0,e.createElement)(e.Fragment,null,(0,e.createElement)("div",{className:"jio-creator-dialog__row"},(0,e.createElement)("label",null,(0,e.createElement)("span",null,(0,a.__)("行数","jio")),(0,e.createElement)("input",{ref:q,type:"number",min:1,max:20,value:c,onChange:e=>s(Number(e.target.value))})),(0,e.createElement)("label",null,(0,e.createElement)("span",null,(0,a.__)("列数","jio")),(0,e.createElement)("input",{type:"number",min:1,max:10,value:u,onChange:e=>d(Number(e.target.value))}))),(0,e.createElement)("label",{className:"jio-creator-dialog__check"},(0,e.createElement)("input",{type:"checkbox",checked:m,onChange:e=>p(e.target.checked)}),(0,e.createElement)("span",null,(0,a.__)("首行为表头","jio"))),(0,e.createElement)("label",{className:"jio-creator-dialog__check"},(0,e.createElement)("input",{type:"checkbox",checked:_,onChange:e=>b(e.target.checked)}),(0,e.createLine truncated
|
||||||
(0,a.__)("请输入标题(最多 %d 个字)","jio"),Number(q.titleMax)||100),spellCheck:!1,maxLength:Number(q.titleMax)||100})),(0,e.createElement)("div",{className:"jio-creator__editor","data-editor-ready":_?"true":"false"},(0,e.createElement)("textarea",{id:C,defaultValue:n.content,onChange:e=>H(e.target.value),"aria-label":(0,a.__)("文章正文","jio")}))),(0,e.createElement)(b,{featured:n.featured,canEditCover:!!A.canUploadImage,canSchedule:!!A.canSchedule,scheduleEnabled:!!n.schedule.enabled,scheduleAt:n.schedule.at,onFeaturedChange:e=>V({featured:e}),onScheduleChange:(e,t)=>V({schedule:{enabled:e,at:t}})}),P&&(0,e.createElement)(s,{draft:n,setDraft:V,capabilities:A,limits:q,taxonomy:{categories:N.categories&&N.categories.items||[],tags:!!N.tags?.enabled,customDefs:N.customDefs||[]},isPublishedEdit:J,onClose:()=>U(!1),onSaveDraft:()=>{Z(!1)},onSubmit:e=>{G(e)},privateStorage:!!N.privateStorage,privateUploads:N.privateUploads||[],captcha:!!N.captcha?.enabled,saving:"saving"===$.state||"submitting"===$.state}))}function L(){const a=document.getElementById("jio-creator-studio");a&&(a.classList.add("is-ready"),(0,t.createRoot)(a).render((0,e.createElement)(M,null)))}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",L):L()})();
|
(0,a.__)("请输入标题(最多 %d 个字)","jio"),Number(Q.titleMax)||100),spellCheck:!1,maxLength:Number(Q.titleMax)||100})),(0,e.createElement)("div",{className:"jio-creator__editor","data-editor-ready":_?"true":"false"},(0,e.createElement)("textarea",{id:M,defaultValue:n.content,onChange:e=>G(e.target.value),"aria-label":(0,a.__)("文章正文","jio")}))),(0,e.createElement)(f,{featured:n.featured,canEditCover:!!X.canUploadImage,canSchedule:!!X.canSchedule,scheduleEnabled:!!n.schedule.enabled,scheduleAt:n.schedule.at,onFeaturedChange:e=>W({featured:e}),onScheduleChange:(e,t)=>W({schedule:{enabled:e,at:t}})}),U&&(0,e.createElement)(w,{kind:U,onSubmit:fe,onClose:()=>z(null)}),F&&(0,e.createElement)(w,{kind:"code",initialLanguage:F.language,initialCode:F.code,onSubmit:e=>{if(T(null),"code"!==e.kind)return;const t=A.current;if(A.current=null,pe(v(e.language,e.code)),t){const e=i.current;e&&!e.removed&&(e.dom.remove(t),G(e.getContent()))}},onClose:()=>{T(null),A.current=null}}),D&&(0,e.createElement)(s,{draft:n,setDraft:W,capabilities:X,limits:Q,taxonomy:{categories:C.categories&&C.categories.items||[],tags:!!C.tags?.enabled,customDefs:C.customDefs||[]},isPublishedEdit:Y,onClose:()=>P(!1),onSaveDraft:()=>{ae(!1)},onSubmit:e=>{ne(e)},privateStorage:!!C.privateStorage,privateUploads:C.privateUploads||[],captcha:!!C.captcha?.enabled,saving:"saving"===I.state||"submitting"===I.state}))}function R(){const a=document.getElementById("jio-creator-studio");a&&(a.classList.add("is-ready"),(0,t.createRoot)(a).render((0,e.createElement)(I,null)))}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",R):R()})();
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
/**
|
||||||
|
* 通用插入弹窗:表格 / 公式 / 注释共用。
|
||||||
|
*
|
||||||
|
* 结构与媒体选择弹窗保持一致(遮罩 + 对话框 + 标题 + 主体 + 底部按钮),
|
||||||
|
* 让作者在三类块上都是「先填参数、再插入」的同一套交互。
|
||||||
|
*
|
||||||
|
* @package Jio
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useMemo, useRef, useState } from '@wordpress/element';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
|
import { CODE_LANGUAGES } from '../utils/block-markup';
|
||||||
|
|
||||||
|
export type BlockDialogKind = 'table' | 'math' | 'footnote' | 'code';
|
||||||
|
|
||||||
|
export type BlockDialogResult =
|
||||||
|
| { kind: 'table'; rows: number; cols: number; header: boolean; striped: boolean }
|
||||||
|
| { kind: 'math'; formula: string; display: boolean }
|
||||||
|
| { kind: 'footnote'; note: string }
|
||||||
|
| { kind: 'code'; language: string; code: string };
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
kind: BlockDialogKind;
|
||||||
|
initialLanguage?: string;
|
||||||
|
initialCode?: string;
|
||||||
|
onSubmit: (result: BlockDialogResult) => void;
|
||||||
|
onClose: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clamp = (value: number, min: number, max: number): number => Math.min(max, Math.max(min, Number.isFinite(value) ? Math.round(value) : min));
|
||||||
|
|
||||||
|
const TITLES: Record<BlockDialogKind, string> = {
|
||||||
|
table: __('插入表格', 'jio'),
|
||||||
|
math: __('插入公式', 'jio'),
|
||||||
|
footnote: __('插入注释', 'jio'),
|
||||||
|
code: __('插入代码块', 'jio'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export function BlockDialog({ kind, initialLanguage = 'generic', initialCode = '', onSubmit, onClose }: Props) {
|
||||||
|
const [rows, setRows] = useState(3);
|
||||||
|
const [cols, setCols] = useState(3);
|
||||||
|
const [header, setHeader] = useState(true);
|
||||||
|
const [striped, setStriped] = useState(false);
|
||||||
|
const [formula, setFormula] = useState('a^2 + b^2 = c^2');
|
||||||
|
const [display, setDisplay] = useState(true);
|
||||||
|
const [note, setNote] = useState('');
|
||||||
|
const [language, setLanguage] = useState(initialLanguage);
|
||||||
|
const [code, setCode] = useState(initialCode);
|
||||||
|
const panelRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const firstFieldRef = useRef<HTMLInputElement | null>(null);
|
||||||
|
const formulaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
|
const noteRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(firstFieldRef.current || formulaRef.current || noteRef.current)?.focus();
|
||||||
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
event.preventDefault();
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('keydown', onKeyDown);
|
||||||
|
return () => document.removeEventListener('keydown', onKeyDown);
|
||||||
|
}, [onClose]);
|
||||||
|
|
||||||
|
const canSubmit = useMemo(() => {
|
||||||
|
if (kind === 'footnote') {
|
||||||
|
return note.trim() !== '';
|
||||||
|
}
|
||||||
|
if (kind === 'math') {
|
||||||
|
return formula.trim() !== '';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, [kind, note, formula]);
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
if (!canSubmit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (kind === 'table') {
|
||||||
|
onSubmit({ kind, rows: clamp(rows, 1, 20), cols: clamp(cols, 1, 10), header, striped });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (kind === 'math') {
|
||||||
|
onSubmit({ kind, formula: formula.trim(), display });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (kind === 'code') {
|
||||||
|
onSubmit({ kind, language, code });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onSubmit({ kind, note: note.trim() });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="jio-creator-dialog" role="dialog" aria-modal="true" aria-labelledby="jio-creator-dialog-title">
|
||||||
|
<button type="button" className="jio-creator-dialog__backdrop" aria-label={__('关闭', 'jio')} onClick={onClose} />
|
||||||
|
<div className="jio-creator-dialog__panel" ref={panelRef}>
|
||||||
|
<header className="jio-creator-dialog__head">
|
||||||
|
<h2 id="jio-creator-dialog-title">{TITLES[kind]}</h2>
|
||||||
|
<button type="button" className="jio-creator-dialog__close" onClick={onClose} aria-label={__('关闭', 'jio')}>
|
||||||
|
<i className="fa fas fa-times" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="jio-creator-dialog__body">
|
||||||
|
{kind === 'table' && (
|
||||||
|
<>
|
||||||
|
<div className="jio-creator-dialog__row">
|
||||||
|
<label>
|
||||||
|
<span>{__('行数', 'jio')}</span>
|
||||||
|
<input
|
||||||
|
ref={firstFieldRef}
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={20}
|
||||||
|
value={rows}
|
||||||
|
onChange={(event) => setRows(Number(event.target.value))}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>{__('列数', 'jio')}</span>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
value={cols}
|
||||||
|
onChange={(event) => setCols(Number(event.target.value))}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<label className="jio-creator-dialog__check">
|
||||||
|
<input type="checkbox" checked={header} onChange={(event) => setHeader(event.target.checked)} />
|
||||||
|
<span>{__('首行为表头', 'jio')}</span>
|
||||||
|
</label>
|
||||||
|
<label className="jio-creator-dialog__check">
|
||||||
|
<input type="checkbox" checked={striped} onChange={(event) => setStriped(event.target.checked)} />
|
||||||
|
<span>{__('隔行变色', 'jio')}</span>
|
||||||
|
</label>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{kind === 'math' && (
|
||||||
|
<>
|
||||||
|
<label className="jio-creator-dialog__field">
|
||||||
|
<span>{__('公式', 'jio')}</span>
|
||||||
|
<textarea
|
||||||
|
ref={formulaRef}
|
||||||
|
rows={3}
|
||||||
|
value={formula}
|
||||||
|
onChange={(event) => setFormula(event.target.value)}
|
||||||
|
placeholder="a^2 + b^2 = c^2"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<p className="jio-creator-muted">
|
||||||
|
{__('支持上标 ^ 与下标 _,例如 x^2 与 a_n;暂不支持完整 LaTeX。', 'jio')}
|
||||||
|
</p>
|
||||||
|
<label className="jio-creator-dialog__check">
|
||||||
|
<input type="checkbox" checked={display} onChange={(event) => setDisplay(event.target.checked)} />
|
||||||
|
<span>{__('独占一行显示', 'jio')}</span>
|
||||||
|
</label>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{kind === 'code' && (
|
||||||
|
<>
|
||||||
|
<label className="jio-creator-dialog__field">
|
||||||
|
<span>{__('语言', 'jio')}</span>
|
||||||
|
<select value={language} onChange={(event) => setLanguage(event.target.value)}>
|
||||||
|
{CODE_LANGUAGES.map((item) => (
|
||||||
|
<option key={item.value} value={item.value}>{item.label}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label className="jio-creator-dialog__field">
|
||||||
|
<span>{__('代码(可留空,稍后在正文里填写)', 'jio')}</span>
|
||||||
|
<textarea
|
||||||
|
rows={6}
|
||||||
|
value={code}
|
||||||
|
onChange={(event) => setCode(event.target.value)}
|
||||||
|
placeholder="console.log('hello');"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{kind === 'footnote' && (
|
||||||
|
<label className="jio-creator-dialog__field">
|
||||||
|
<span>{__('注释内容', 'jio')}</span>
|
||||||
|
<textarea
|
||||||
|
ref={noteRef}
|
||||||
|
rows={4}
|
||||||
|
value={note}
|
||||||
|
onChange={(event) => setNote(event.target.value)}
|
||||||
|
placeholder={__('填写要补充说明的内容', 'jio')}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer className="jio-creator-dialog__foot">
|
||||||
|
<button type="button" className="jio-creator-dialog__ghost" onClick={onClose}>
|
||||||
|
{__('取消', 'jio')}
|
||||||
|
</button>
|
||||||
|
<button type="button" className="jio-creator-dialog__primary" onClick={submit} disabled={!canSubmit}>
|
||||||
|
{__('插入', 'jio')}
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
* @package Jio
|
* @package Jio
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from '@wordpress/element';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
|
|
||||||
type Capabilities = Record<string, boolean>;
|
type Capabilities = Record<string, boolean>;
|
||||||
@@ -23,6 +24,7 @@ type Props = {
|
|||||||
onOpenLink: () => void;
|
onOpenLink: () => void;
|
||||||
onOpenMedia: (type: 'image' | 'video' | 'file') => void;
|
onOpenMedia: (type: 'image' | 'video' | 'file') => void;
|
||||||
onInsertBlock: (kind: string) => void;
|
onInsertBlock: (kind: string) => void;
|
||||||
|
onInsertQuote: (color: '' | 'qe_wzk_c-red' | 'qe_wzk_lan' | 'qe_wzk_lv') => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ToolButtonProps = {
|
type ToolButtonProps = {
|
||||||
@@ -61,7 +63,34 @@ export function EditorToolbar({
|
|||||||
onOpenLink,
|
onOpenLink,
|
||||||
onOpenMedia,
|
onOpenMedia,
|
||||||
onInsertBlock,
|
onInsertBlock,
|
||||||
|
onInsertQuote,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const [quoteOpen, setQuoteOpen] = useState(false);
|
||||||
|
const quoteRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
// 点击别处或按 Esc 收起引用样式菜单。
|
||||||
|
useEffect(() => {
|
||||||
|
if (!quoteOpen) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const onPointerDown = (event: PointerEvent) => {
|
||||||
|
if (!quoteRef.current?.contains(event.target as Node)) {
|
||||||
|
setQuoteOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Escape') {
|
||||||
|
setQuoteOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.addEventListener('pointerdown', onPointerDown);
|
||||||
|
document.addEventListener('keydown', onKeyDown);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('pointerdown', onPointerDown);
|
||||||
|
document.removeEventListener('keydown', onKeyDown);
|
||||||
|
};
|
||||||
|
}, [quoteOpen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="jio-creator__toolbar-tools"
|
className="jio-creator__toolbar-tools"
|
||||||
@@ -112,7 +141,33 @@ export function EditorToolbar({
|
|||||||
<div className="jio-creator__toolbar-group" role="group" aria-label={__('段落块', 'jio')}>
|
<div className="jio-creator__toolbar-group" role="group" aria-label={__('段落块', 'jio')}>
|
||||||
<ToolButton label={__('列表', 'jio')} icon="fa fas fa-list-ul" ready={ready} onClick={() => onInsertBlock('list')} />
|
<ToolButton label={__('列表', 'jio')} icon="fa fas fa-list-ul" ready={ready} onClick={() => onInsertBlock('list')} />
|
||||||
<ToolButton label={__('目录', 'jio')} icon="fa fas fa-list-ol" ready={ready} onClick={() => onInsertBlock('toc')} />
|
<ToolButton label={__('目录', 'jio')} icon="fa fas fa-list-ol" ready={ready} onClick={() => onInsertBlock('toc')} />
|
||||||
<ToolButton label={__('引用', 'jio')} icon="fa fas fa-quote-left" ready={ready} onClick={() => onCommand('format', 'blockquote')} />
|
<div className="jio-creator__quote" ref={quoteRef}>
|
||||||
|
<ToolButton label={__('引用', 'jio')} icon="fa fas fa-quote-left" ready={ready} onClick={() => setQuoteOpen((open) => !open)} />
|
||||||
|
{quoteOpen && (
|
||||||
|
<div className="jio-creator__quote-menu" role="menu" aria-label={__('引用样式', 'jio')}>
|
||||||
|
{[
|
||||||
|
{ key: '' as const, label: __('灰色引用', 'jio'), className: '' },
|
||||||
|
{ key: 'qe_wzk_c-red' as const, label: __('红色引用', 'jio'), className: 'is-red' },
|
||||||
|
{ key: 'qe_wzk_lan' as const, label: __('蓝色引用', 'jio'), className: 'is-blue' },
|
||||||
|
{ key: 'qe_wzk_lv' as const, label: __('绿色引用', 'jio'), className: 'is-green' },
|
||||||
|
].map((option) => (
|
||||||
|
<button
|
||||||
|
key={option.key || 'default'}
|
||||||
|
type="button"
|
||||||
|
role="menuitem"
|
||||||
|
className={`jio-creator__quote-option ${option.className}`.trim()}
|
||||||
|
onClick={() => {
|
||||||
|
setQuoteOpen(false);
|
||||||
|
onInsertQuote(option.key);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="jio-creator__quote-swatch" aria-hidden="true" />
|
||||||
|
{option.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<ToolButton label={__('分割线', 'jio')} icon="fa fas fa-minus" ready={ready} onClick={() => onInsertBlock('divider')} />
|
<ToolButton label={__('分割线', 'jio')} icon="fa fas fa-minus" ready={ready} onClick={() => onInsertBlock('divider')} />
|
||||||
<ToolButton label={__('代码块', 'jio')} icon="fa fas fa-code" ready={ready} onClick={() => onInsertBlock('code')} />
|
<ToolButton label={__('代码块', 'jio')} icon="fa fas fa-code" ready={ready} onClick={() => onInsertBlock('code')} />
|
||||||
<ToolButton label={__('注释', 'jio')} icon="fa fas fa-comment-dots" ready={ready} onClick={() => onInsertBlock('footnote')} />
|
<ToolButton label={__('注释', 'jio')} icon="fa fas fa-comment-dots" ready={ready} onClick={() => onInsertBlock('footnote')} />
|
||||||
|
|||||||
@@ -0,0 +1,131 @@
|
|||||||
|
/**
|
||||||
|
* TinyMCE iframe 内的正文样式。
|
||||||
|
*
|
||||||
|
* 编辑器正文跑在 iframe 里,页面级 SCSS 影响不到它,所以这里显式给出:
|
||||||
|
* 正文排版、主题引用 .quote_q、表格、公式、目录占位,以及代码块右上角的
|
||||||
|
* 语言标签(可点击,由父组件监听 pre 的点击打开语言选择)。
|
||||||
|
*
|
||||||
|
* @package Jio
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const EDITOR_CONTENT_STYLE = `
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
color: #2b2b2b;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.8;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
p { margin: 0 0 1.1em; }
|
||||||
|
h1, h2, h3, h4, h5, h6 { margin: 1.5em 0 0.65em; line-height: 1.4; }
|
||||||
|
img, iframe { max-width: 100%; }
|
||||||
|
img { height: auto; }
|
||||||
|
|
||||||
|
/* 代码块:右上角显示当前语言,点击即可更换(交互在父组件里接管)。 */
|
||||||
|
.enlighter-pre {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 0 1.1em;
|
||||||
|
padding: 40px 14px 14px;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f2f4f5;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.enlighter-pre code.gl {
|
||||||
|
display: block;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #2b2b2b;
|
||||||
|
font-family: Consolas, Monaco, monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
.enlighter-pre::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 9px;
|
||||||
|
right: 9px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: linear-gradient(135deg, #3dcabd 10%, #254bdd 100%);
|
||||||
|
color: #fff;
|
||||||
|
content: attr(data-enlighter-language);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 引用:与前台主题的 .quote_q 视觉一致。 */
|
||||||
|
.quote_q {
|
||||||
|
position: relative;
|
||||||
|
margin: 10px 0 20px;
|
||||||
|
padding: 1.2em;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--quote-color, #555);
|
||||||
|
background: rgba(85, 85, 85, 0.04);
|
||||||
|
}
|
||||||
|
.quote_q.qe_wzk_c-red { --quote-color: #dc3f3f; background: rgba(220, 63, 63, 0.05); }
|
||||||
|
.quote_q.qe_wzk_lan { --quote-color: #0092ff; background: rgba(0, 146, 255, 0.05); }
|
||||||
|
.quote_q.qe_wzk_lv { --quote-color: #23790e; background: rgba(35, 121, 14, 0.05); }
|
||||||
|
.quote_q::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
left: 18px;
|
||||||
|
color: var(--quote-color, #555);
|
||||||
|
content: '\\201C';
|
||||||
|
font-size: 1.4em;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.quote_q > div { padding-left: 2em; opacity: 0.85; }
|
||||||
|
.quote_q > div > p:last-child { margin-bottom: 0; }
|
||||||
|
|
||||||
|
/* 表格。 */
|
||||||
|
.jio-content-table {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 1.1em;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.jio-content-table th,
|
||||||
|
.jio-content-table td {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #e6e9e8;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.jio-content-table thead th {
|
||||||
|
background: #f6f8f7;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.jio-content-table.is-striped tbody tr:nth-child(odd) td { background: #f7f9f8; }
|
||||||
|
|
||||||
|
/* 公式与目录占位。 */
|
||||||
|
.jio-math { font-family: "Cambria Math", "Latin Modern Math", "Times New Roman", serif; }
|
||||||
|
.jio-math--block { display: block; margin: 12px 0; text-align: center; }
|
||||||
|
.jio-toc-placeholder {
|
||||||
|
height: 40px;
|
||||||
|
margin: 0 0 1.1em;
|
||||||
|
border: 1px dashed #d3ded8;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f7faf9;
|
||||||
|
color: #8c9a94;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.jio-toc-placeholder::after { content: '目录将在发布后自动生成'; }
|
||||||
|
|
||||||
|
/* 附件链接。 */
|
||||||
|
.jio-file-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: 1px solid #e6e9e8;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #f7f8f8;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -18,7 +18,19 @@ import { SaveState, type SaveStateValue } from './components/save-state';
|
|||||||
import { useAutosave } from './hooks/use-autosave';
|
import { useAutosave } from './hooks/use-autosave';
|
||||||
import { useLocalRecovery } from './hooks/use-local-recovery';
|
import { useLocalRecovery } from './hooks/use-local-recovery';
|
||||||
import { normalizePayForRequest } from './utils/pay';
|
import { normalizePayForRequest } from './utils/pay';
|
||||||
import { codeBlockMarkup, dividerMarkup, footnoteMarkup, listMarkup, mathMarkup, tableMarkup, tocMarkup } from './utils/block-markup';
|
import { EDITOR_CONTENT_STYLE } from './editor-content-style';
|
||||||
|
import {
|
||||||
|
codeBlockMarkup,
|
||||||
|
dividerMarkup,
|
||||||
|
footnoteMarkup,
|
||||||
|
listMarkup,
|
||||||
|
mathMarkup,
|
||||||
|
quoteMarkup,
|
||||||
|
tableMarkup,
|
||||||
|
tocMarkup,
|
||||||
|
type QuoteColor,
|
||||||
|
} from './utils/block-markup';
|
||||||
|
import { BlockDialog, type BlockDialogKind, type BlockDialogResult } from './components/block-dialog';
|
||||||
import './styles/studio.scss';
|
import './styles/studio.scss';
|
||||||
|
|
||||||
const bootstrap: Record<string, any> = window['jioCreatorStudio'] || {};
|
const bootstrap: Record<string, any> = window['jioCreatorStudio'] || {};
|
||||||
@@ -132,6 +144,9 @@ function Studio(): JSX.Element {
|
|||||||
at: '',
|
at: '',
|
||||||
}));
|
}));
|
||||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||||
|
const [blockDialog, setBlockDialog] = useState<BlockDialogKind | null>(null);
|
||||||
|
const [codeDialog, setCodeDialog] = useState<null | { language: string; code: string }>(null);
|
||||||
|
const codeTargetRef = useRef<any>(null);
|
||||||
const [notice, setNotice] = useState<{ tone: 'ok' | 'error'; text: string } | null>(null);
|
const [notice, setNotice] = useState<{ tone: 'ok' | 'error'; text: string } | null>(null);
|
||||||
const [recovery, setRecovery] = useState<null | { savedAt: string }>(null);
|
const [recovery, setRecovery] = useState<null | { savedAt: string }>(null);
|
||||||
const dirtyRef = useRef(false);
|
const dirtyRef = useRef(false);
|
||||||
@@ -384,7 +399,7 @@ function Studio(): JSX.Element {
|
|||||||
wpautop: true,
|
wpautop: true,
|
||||||
body_class: 'jio-creator__tinymce',
|
body_class: 'jio-creator__tinymce',
|
||||||
content_css: bootstrap.creator?.contentCss || '',
|
content_css: bootstrap.creator?.contentCss || '',
|
||||||
content_style: 'body{margin:0;color:#243330;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans SC","PingFang SC","Microsoft YaHei",sans-serif;font-size:16px;line-height:1.85;letter-spacing:.01em}p{margin:0 0 1.1em}h1,h2,h3,h4,h5,h6{margin:1.5em 0 .65em;line-height:1.4}img,iframe{max-width:100%}img{height:auto}',
|
content_style: EDITOR_CONTENT_STYLE,
|
||||||
height: 650,
|
height: 650,
|
||||||
placeholder: String(bootstrap.limits?.contentPlaceholder || ''),
|
placeholder: String(bootstrap.limits?.contentPlaceholder || ''),
|
||||||
setup: (editor: any) => {
|
setup: (editor: any) => {
|
||||||
@@ -400,6 +415,24 @@ function Studio(): JSX.Element {
|
|||||||
updateFormatValue(editor);
|
updateFormatValue(editor);
|
||||||
});
|
});
|
||||||
editor.on('NodeChange', () => updateFormatValue(editor));
|
editor.on('NodeChange', () => updateFormatValue(editor));
|
||||||
|
// 代码块右上角的语言标签:点击整块即可换语言,等价于公众号的「高亮」入口。
|
||||||
|
editor.on('click', (event: any) => {
|
||||||
|
const pre = editor.dom.getParent(event.target, 'pre.enlighter-pre');
|
||||||
|
if (!pre) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 只响应右上角语言标签那一片,光标点进代码正文时保持正常编辑。
|
||||||
|
const rect = pre.getBoundingClientRect();
|
||||||
|
const relativeY = Number(event.clientY) - rect.top;
|
||||||
|
const relativeX = Number(event.clientX) - rect.left;
|
||||||
|
if (relativeY > 40 || relativeX < rect.width - 120) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
const language = String(pre.getAttribute('data-enlighter-language') || 'generic');
|
||||||
|
codeTargetRef.current = pre;
|
||||||
|
setCodeDialog({ language, code: String((pre.textContent || '').trim()) });
|
||||||
|
});
|
||||||
editor.on('input change Undo Redo AddUndo', () => {
|
editor.on('input change Undo Redo AddUndo', () => {
|
||||||
updateContent(editor.getContent());
|
updateContent(editor.getContent());
|
||||||
updateHistoryAvailability(editor);
|
updateHistoryAvailability(editor);
|
||||||
@@ -508,22 +541,9 @@ function Studio(): JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [focusEditor]);
|
}, [focusEditor]);
|
||||||
|
|
||||||
const insertBlock = useCallback((kind: string) => {
|
const insertHtml = useCallback((html: string) => {
|
||||||
const editor = focusEditor();
|
const editor = focusEditor();
|
||||||
if (!editor) {
|
if (!editor || !html) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
const markup: Record<string, string> = {
|
|
||||||
list: listMarkup(false),
|
|
||||||
toc: tocMarkup(),
|
|
||||||
divider: dividerMarkup(),
|
|
||||||
code: codeBlockMarkup(),
|
|
||||||
footnote: footnoteMarkup(),
|
|
||||||
math: mathMarkup(),
|
|
||||||
table: tableMarkup(),
|
|
||||||
};
|
|
||||||
const html = markup[kind];
|
|
||||||
if (!html) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
editor.insertContent(html);
|
editor.insertContent(html);
|
||||||
@@ -532,6 +552,60 @@ function Studio(): JSX.Element {
|
|||||||
updateHistoryAvailability(editor);
|
updateHistoryAvailability(editor);
|
||||||
}, [focusEditor]);
|
}, [focusEditor]);
|
||||||
|
|
||||||
|
// 直接插入、无需参数的小块;需要参数的块打开通用弹窗。
|
||||||
|
const insertBlock = useCallback((kind: string) => {
|
||||||
|
rememberSelection();
|
||||||
|
// code 走「语言选择」弹窗,其余三类走通用插入弹窗。
|
||||||
|
if (kind === 'table' || kind === 'math' || kind === 'footnote') {
|
||||||
|
setBlockDialog(kind as BlockDialogKind);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (kind === 'code') {
|
||||||
|
setCodeDialog({ language: 'generic', code: '' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (kind === 'list') {
|
||||||
|
insertHtml(listMarkup(false));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (kind === 'toc') {
|
||||||
|
insertHtml(tocMarkup());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (kind === 'divider') {
|
||||||
|
insertHtml(dividerMarkup());
|
||||||
|
}
|
||||||
|
}, [insertHtml, rememberSelection]);
|
||||||
|
|
||||||
|
const insertQuote = useCallback((colorClass: QuoteColor) => {
|
||||||
|
const editor = focusEditor();
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selected = String(editor.selection.getContent() || '').trim();
|
||||||
|
const existing = editor.dom.getParent(editor.selection.getNode(), '.quote_q');
|
||||||
|
if (existing) {
|
||||||
|
existing.className = `quote_q quote-mce${colorClass ? ` ${colorClass}` : ''}`;
|
||||||
|
editor.nodeChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
insertHtml(quoteMarkup(selected, colorClass));
|
||||||
|
}, [focusEditor, insertHtml]);
|
||||||
|
|
||||||
|
const applyBlockDialog = useCallback((result: BlockDialogResult) => {
|
||||||
|
setBlockDialog(null);
|
||||||
|
// 弹窗抢走焦点后,插入前把光标放回正文的原选区。
|
||||||
|
if (result.kind === 'table') {
|
||||||
|
insertHtml(tableMarkup(result));
|
||||||
|
} else if (result.kind === 'math') {
|
||||||
|
insertHtml(mathMarkup(result));
|
||||||
|
} else if (result.kind === 'footnote') {
|
||||||
|
insertHtml(footnoteMarkup(result.note));
|
||||||
|
} else {
|
||||||
|
insertHtml(codeBlockMarkup(result.language, result.code));
|
||||||
|
}
|
||||||
|
}, [insertHtml]);
|
||||||
|
|
||||||
const openMedia = useCallback((type: 'image' | 'video' | 'file') => {
|
const openMedia = useCallback((type: 'image' | 'video' | 'file') => {
|
||||||
const editor = editorRef.current;
|
const editor = editorRef.current;
|
||||||
const media = (window as any).JioSubmitMedia;
|
const media = (window as any).JioSubmitMedia;
|
||||||
@@ -608,6 +682,7 @@ function Studio(): JSX.Element {
|
|||||||
onOpenLink={openLink}
|
onOpenLink={openLink}
|
||||||
onOpenMedia={openMedia}
|
onOpenMedia={openMedia}
|
||||||
onInsertBlock={insertBlock}
|
onInsertBlock={insertBlock}
|
||||||
|
onInsertQuote={insertQuote}
|
||||||
/>
|
/>
|
||||||
<div className="jio-creator__bar-actions jio-creator__toolbar-extension" data-toolbar-extension aria-label={__('工具扩展区', 'jio')}>
|
<div className="jio-creator__bar-actions jio-creator__toolbar-extension" data-toolbar-extension aria-label={__('工具扩展区', 'jio')}>
|
||||||
<SaveState value={saveState} />
|
<SaveState value={saveState} />
|
||||||
@@ -676,6 +751,43 @@ function Studio(): JSX.Element {
|
|||||||
onScheduleChange={(enabled, at) => patchDraft({ schedule: { enabled, at } })}
|
onScheduleChange={(enabled, at) => patchDraft({ schedule: { enabled, at } })}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{blockDialog && (
|
||||||
|
<BlockDialog
|
||||||
|
kind={blockDialog}
|
||||||
|
onSubmit={applyBlockDialog}
|
||||||
|
onClose={() => setBlockDialog(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{codeDialog && (
|
||||||
|
<BlockDialog
|
||||||
|
kind="code"
|
||||||
|
initialLanguage={codeDialog.language}
|
||||||
|
initialCode={codeDialog.code}
|
||||||
|
onSubmit={(result) => {
|
||||||
|
setCodeDialog(null);
|
||||||
|
if (result.kind !== 'code') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const previous = codeTargetRef.current;
|
||||||
|
codeTargetRef.current = null;
|
||||||
|
insertHtml(codeBlockMarkup(result.language, result.code));
|
||||||
|
if (previous) {
|
||||||
|
// 换语言时移除旧块,避免留下两份代码。
|
||||||
|
const editor = editorRef.current;
|
||||||
|
if (editor && !editor.removed) {
|
||||||
|
editor.dom.remove(previous);
|
||||||
|
updateContent(editor.getContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onClose={() => {
|
||||||
|
setCodeDialog(null);
|
||||||
|
codeTargetRef.current = null;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{drawerOpen && (
|
{drawerOpen && (
|
||||||
<PublishDrawer
|
<PublishDrawer
|
||||||
draft={draft}
|
draft={draft}
|
||||||
|
|||||||
@@ -0,0 +1,261 @@
|
|||||||
|
/**
|
||||||
|
* 通用插入弹窗 + 引用样式菜单 + 正文块(表格 / 代码 / 公式)的编辑器内样式。
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ---------- 通用插入弹窗 ---------- */
|
||||||
|
|
||||||
|
.jio-creator-dialog {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 560;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__backdrop {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border: 0;
|
||||||
|
background: rgba(23, 34, 31, 0.34);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__panel {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
width: min(460px, 100%);
|
||||||
|
max-height: min(82vh, 640px);
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 14px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 24px 70px rgba(20, 32, 28, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 18px 20px 12px;
|
||||||
|
border-bottom: 1px solid #eef1f0;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__close {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: transparent;
|
||||||
|
color: #7d8683;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
background: #f2f5f3;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__body {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 14px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 18px 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__row {
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__row label > span,
|
||||||
|
.jio-creator-dialog__field > span {
|
||||||
|
color: #6f7875;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__field input,
|
||||||
|
.jio-creator-dialog__field select,
|
||||||
|
.jio-creator-dialog__row input {
|
||||||
|
height: 38px;
|
||||||
|
padding: 0 10px;
|
||||||
|
border: 1px solid #e3e7e5;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
color: #2b2b2b;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__field textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #e3e7e5;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
color: #2b2b2b;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.7;
|
||||||
|
resize: vertical;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 2px solid rgba(38, 118, 101, 0.25);
|
||||||
|
outline-offset: -1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__check {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
color: #4f5957;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
accent-color: var(--jc-accent);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__foot {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 14px 20px calc(14px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eef1f0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__ghost,
|
||||||
|
.jio-creator-dialog__primary {
|
||||||
|
display: inline-flex;
|
||||||
|
min-height: 36px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__ghost {
|
||||||
|
border: 1px solid #e3e7e5;
|
||||||
|
background: #fff;
|
||||||
|
color: #5f6664;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
border-color: #b7cdc3;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator-dialog__primary {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
background: var(--jc-accent);
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&:hover:not(:disabled),
|
||||||
|
&:focus-visible:not(:disabled) {
|
||||||
|
background: var(--jc-accent-strong);
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 引用样式菜单 ---------- */
|
||||||
|
|
||||||
|
.jio-creator__quote {
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator__quote-menu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 400;
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
left: 50%;
|
||||||
|
display: flex;
|
||||||
|
width: 148px;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 6px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 12px 34px rgba(20, 32, 28, 0.18);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator__quote-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 7px;
|
||||||
|
background: transparent;
|
||||||
|
color: #4f5957;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus-visible {
|
||||||
|
background: #f2f5f3;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.jio-creator__quote-swatch {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #6f7875;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-red .jio-creator__quote-swatch {
|
||||||
|
background: #dc3f3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-blue .jio-creator__quote-swatch {
|
||||||
|
background: #0092ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-green .jio-creator__quote-swatch {
|
||||||
|
background: #23790e;
|
||||||
|
}
|
||||||
@@ -508,6 +508,7 @@ body.jio-page-submit.has-static-nav {
|
|||||||
color: #b9bfbc;
|
color: #b9bfbc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- 发布设置卡片 ---------- */
|
/* ---------- 发布设置卡片 ---------- */
|
||||||
@@ -678,6 +679,7 @@ body.jio-page-submit.has-static-nav {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@import './_publish-drawer';
|
@import './_publish-drawer';
|
||||||
|
@import './_block-tools';
|
||||||
|
|
||||||
/* ---------- 手机 / 平板 ---------- */
|
/* ---------- 手机 / 平板 ---------- */
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
* 正文块的 HTML 片段。
|
* 正文块的 HTML 片段。
|
||||||
*
|
*
|
||||||
* 这些块都由服务端再次清洗(FrontSubmitSave 的 wp_kses_post),因此这里只负责
|
* 这些块都由服务端再次清洗(FrontSubmitSave 的 wp_kses_post),因此这里只负责
|
||||||
* 生成符合前台渲染习惯的结构:代码块沿用 enlighter 的既有高亮约定,脚注与公式
|
* 生成符合前台渲染习惯的结构:代码块沿用 enlighter 的既有高亮约定,引用沿用
|
||||||
* 使用主题前台已有的渲染口径,目录使用文章目录树识别标题的 data-nav 结构。
|
* 主题的 .quote_q 结构,目录使用文章目录树识别标题的占位结构。
|
||||||
*
|
*
|
||||||
* @package Jio
|
* @package Jio
|
||||||
*/
|
*/
|
||||||
@@ -24,27 +24,71 @@ export function dividerMarkup(): string {
|
|||||||
return '<hr><p></p>';
|
return '<hr><p></p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export type CodeLanguage = { value: string; label: string };
|
||||||
* 代码块:沿用前台 enlighter 的高亮标记,未选择语言时按通用代码展示。
|
|
||||||
*/
|
/** 常用语言;与前台 enlighter 的语言标识保持一致。 */
|
||||||
export function codeBlockMarkup(): string {
|
export const CODE_LANGUAGES: CodeLanguage[] = [
|
||||||
|
{ value: 'generic', label: __('通用', 'jio') },
|
||||||
|
{ value: 'javascript', label: 'JavaScript' },
|
||||||
|
{ value: 'typescript', label: 'TypeScript' },
|
||||||
|
{ value: 'php', label: 'PHP' },
|
||||||
|
{ value: 'python', label: 'Python' },
|
||||||
|
{ value: 'java', label: 'Java' },
|
||||||
|
{ value: 'go', label: 'Go' },
|
||||||
|
{ value: 'rust', label: 'Rust' },
|
||||||
|
{ value: 'csharp', label: 'C#' },
|
||||||
|
{ value: 'cpp', label: 'C++' },
|
||||||
|
{ value: 'sql', label: 'SQL' },
|
||||||
|
{ value: 'json', label: 'JSON' },
|
||||||
|
{ value: 'bash', label: 'Shell' },
|
||||||
|
{ value: 'css', label: 'CSS' },
|
||||||
|
{ value: 'html', label: 'HTML' },
|
||||||
|
{ value: 'markdown', label: 'Markdown' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 语言值 → 展示名;未知值原样返回。 */
|
||||||
|
export function codeLabel(language: string): string {
|
||||||
|
return CODE_LANGUAGES.find((item) => item.value === language)?.label || language;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 代码块:语言写在 data 属性里,前台 enlighter 与编辑器标签共用。 */
|
||||||
|
export function codeBlockMarkup(language = 'generic', code = ''): string {
|
||||||
|
const body = code !== '' ? escapeHtml(code) : escapeHtml(__('在此输入代码', 'jio'));
|
||||||
return (
|
return (
|
||||||
'<pre class="enlighter-pre"><code class="gl" data-enlighter-language="generic">' +
|
`<pre class="enlighter-pre" data-enlighter-language="${escapeHtml(language)}"><code class="gl" data-enlighter-language="${escapeHtml(language)}">` +
|
||||||
escapeHtml(__('在此输入代码', 'jio')) +
|
body +
|
||||||
'</code></pre><p></p>'
|
'</code></pre><p></p>'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export type TableOptions = {
|
||||||
* 表格:3×3 带表头,方便作者直接在单元格里改写。
|
rows: number;
|
||||||
*/
|
cols: number;
|
||||||
export function tableMarkup(): string {
|
header: boolean;
|
||||||
const cell = (tag: string, text: string): string => `<${tag}>${escapeHtml(text)}</${tag}>`;
|
striped: boolean;
|
||||||
const header = `<tr>${[1, 2, 3].map((i) => cell('th', `${__('列', 'jio')} ${i}`)).join('')}</tr>`;
|
};
|
||||||
const rows = [1, 2]
|
|
||||||
.map(() => `<tr>${[1, 2, 3].map(() => cell('td', '')).join('')}</tr>`)
|
/** 带尺寸参数的表格;首行可选表头,隔行变色写入 class。 */
|
||||||
.join('');
|
export function tableMarkup({ rows, cols, header, striped }: TableOptions): string {
|
||||||
return `<table class="jio-content-table"><thead>${header}</thead><tbody>${rows}</tbody></table><p></p>`;
|
const totalRows = Math.max(1, Math.min(20, rows));
|
||||||
|
const totalCols = Math.max(1, Math.min(10, cols));
|
||||||
|
const cellsFor = (tag: 'th' | 'td'): string =>
|
||||||
|
Array.from({ length: totalCols }, (_, column) =>
|
||||||
|
`<${tag}>${tag === 'th' ? escapeHtml(`${__('列', 'jio')} ${column + 1}`) : ''}</${tag}>`
|
||||||
|
).join('');
|
||||||
|
|
||||||
|
let body = '';
|
||||||
|
if (header) {
|
||||||
|
body += `<thead><tr>${cellsFor('th')}</tr></thead>`;
|
||||||
|
}
|
||||||
|
const bodyRows = Math.max(1, header ? totalRows - 1 : totalRows);
|
||||||
|
body += `<tbody>${Array.from({ length: bodyRows }, () => `<tr>${cellsFor('td')}</tr>`).join('')}</tbody>`;
|
||||||
|
|
||||||
|
const classes = ['jio-content-table'];
|
||||||
|
if (striped) {
|
||||||
|
classes.push('is-striped');
|
||||||
|
}
|
||||||
|
return `<table class="${classes.join(' ')}">${body}</table><p></p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,15 +96,35 @@ export function tableMarkup(): string {
|
|||||||
* 因此这里只放一个占位标记,读者端不会看到裸露的标记文本。
|
* 因此这里只放一个占位标记,读者端不会看到裸露的标记文本。
|
||||||
*/
|
*/
|
||||||
export function tocMarkup(): string {
|
export function tocMarkup(): string {
|
||||||
return `<div class="jio-toc-placeholder"></div><p></p>`;
|
return '<div class="jio-toc-placeholder"></div><p></p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 注释:Markdown 脚注语法,前台由文章渲染流程转成编号脚注。 */
|
/** 注释:Markdown 脚注语法,前台渲染成编号脚注列表。 */
|
||||||
export function footnoteMarkup(): string {
|
export function footnoteMarkup(note: string): string {
|
||||||
return '<p>[^1]<br>[^1]: ' + escapeHtml(__('注释内容', 'jio')) + '</p>';
|
const text = note.trim() || __('注释内容', 'jio');
|
||||||
|
return `<p>[^1]<br>[^1]: ${escapeHtml(text)}</p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 公式:行内 LaTeX,包在主题可识别的公式容器里。 */
|
export type MathOptions = { formula: string; display: boolean };
|
||||||
export function mathMarkup(): string {
|
|
||||||
return `<span class="jio-math">E = mc^2</span>`;
|
/** 公式:行内或独占一行,数据保存在 .jio-math 容器里。 */
|
||||||
|
export function mathMarkup({ formula, display }: MathOptions): string {
|
||||||
|
const className = display ? 'jio-math jio-math--block' : 'jio-math';
|
||||||
|
const body = `<span class="${className}">${escapeHtml(formula.trim())}</span>`;
|
||||||
|
return display ? `<p>${body}</p>` : body;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type QuoteColor = '' | 'qe_wzk_c-red' | 'qe_wzk_lan' | 'qe_wzk_lv';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 引用:沿用主题的 .quote_q 结构(.quote-mce 提供左侧引号标记),
|
||||||
|
* 颜色通过主题已有的 qe_wzk_* 类切换。
|
||||||
|
*/
|
||||||
|
export function quoteMarkup(content: string, color: QuoteColor): string {
|
||||||
|
const classes = ['quote_q', 'quote-mce'];
|
||||||
|
if (color) {
|
||||||
|
classes.push(color);
|
||||||
|
}
|
||||||
|
const inner = content.trim() !== '' ? `<p>${content}</p>` : '<p></p>';
|
||||||
|
return `<div class="${classes.join(' ')}"><div>${inner}</div></div><p></p>`;
|
||||||
}
|
}
|
||||||
@@ -752,6 +752,56 @@ article.article.box-body,
|
|||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- 正文表格:编辑器插入的 jio-content-table ---------- */
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.08));
|
||||||
|
border-radius: var(--main-radius, 8px);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table th,
|
||||||
|
.wp-posts-content .jio-content-table td {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.08));
|
||||||
|
line-height: 1.7;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table thead th {
|
||||||
|
background: var(--muted-bg-2-color, #f6f7f8);
|
||||||
|
color: var(--key-color, #333);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table tbody tr:last-child td {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table tbody tr:hover td {
|
||||||
|
background: var(--main-shadow, rgba(116, 116, 116, 0.06));
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table.is-striped tbody tr:nth-child(odd) td {
|
||||||
|
background: var(--muted-bg-2-color, #f6f7f8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 窄屏下表格横向滚动,避免撑破正文。 */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.wp-posts-content .jio-content-table {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* source: assets/css/frontend/single-post/author-actions.css */
|
/* source: assets/css/frontend/single-post/author-actions.css */
|
||||||
/* ===== Content Footer ===== */
|
/* ===== Content Footer ===== */
|
||||||
.article-footer {
|
.article-footer {
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
.relative{position:relative}.flex{display:flex}.flex.ac,.ac{align-items:center}.flex.jc,.jc{justify-content:center}.flex.jsb,.jsb{justify-content:space-between}.flex.at,.at{align-items:flex-start}.flex.ae,.ae{align-items:flex-end}.flex0{flex:0 0 auto}.flex1{flex:1 1 auto;min-width:0}.flex.column{flex-direction:column}.flex.wrap{flex-wrap:wrap}.flex.flex-row,.flex-row{flex-direction:row}.flex.jse,.jse{justify-content:space-evenly}.flex.jsa,.jsa{justify-content:space-around}.flex>.flex1,.flex .flex1{flex:1;overflow:hidden;min-width:0}.relative-h{position:relative;height:100%}.padding-10{padding:10px}.font-bold{font-weight:700}.mt6{margin-top:6px}.gutters-5{margin:-5px}.gutters-5>*{padding:5px}@media(max-width:767px){.flex.flex-row.flex-col-sm-2{flex-wrap:wrap}.flex.flex-row.flex-col-sm-2>*{flex-basis:50%}}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}.abs-right{position:absolute;right:0;top:0}.abs-center{position:absolute;left:0;width:100%;top:50%;right:.7em;transform:translateY(-50%);z-index:1}.abs-center.left-top,.abs-center.right-top{transform:unset;top:0}.abs-center.right-top{left:auto;width:auto;text-align:right}.abs-center.left-top{text-align:left}.abs-center.conter-bottom,.abs-center.conter-conter{text-align:center}.abs-center.right-top,.abs-center.right-bottom,.abs-center.right-conter{text-align:right}.abs-center.conter-bottom,.abs-center.right-bottom,.abs-center.left-bottom{top:auto;transform:unset;bottom:0}.abs-center.left-bottom{left:0;right:auto;text-align:left}.abs-center.right-bottom,.abs-center.right-conter{left:auto;right:0;text-align:right}.graphic,.page-cover{text-shadow:0 0 5px rgba(0,0,0,.2);--main-color: #fff;color:#fff;position:relative;overflow:hidden;box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08));padding:0 0 70%;border-radius:var(--main-radius, var(--radius, 8px))}.graphic>.fit-cover,.graphic>img.fit-cover{position:absolute;width:100%;height:100%;object-fit:cover;inset:0}.fit-cover{object-fit:cover;max-width:100%}.title-h-left{position:relative;padding-bottom:8px}.title-h-left:before{transition:.4s;transform-origin:left;position:absolute;content:"";width:40px;height:3px;background:var(--theme-color, var(--focus-color, #f04494));left:0;bottom:3px;border-radius:5px;box-shadow:1px 1px 3px -1px var(--theme-color, var(--focus-color, #f04494))}.line-form-line,.line-form-line:before{position:absolute;bottom:0;left:0;height:1px;width:100%;transition:.8s;background:var(--main-border-color, rgba(50, 50, 50, .06))}.line-form-line:before{content:"";width:0;background:var(--focus-color, #f04494)}.avatar-img{position:relative;display:inline-block;flex-shrink:0;--this-size: 38px;width:var(--this-size);height:var(--this-size)}.avatar-img .avatar,.avatar-img img{width:100%;height:100%;border-radius:50%;object-fit:cover}.avatar-lg{--this-size: 80px}.avatar-md{--this-size: 55px}.avatar-mini{--this-size: 20px}.shrink0{flex-shrink:0}.opacity8{opacity:.8}.muted-color{color:var(--muted-color, var(--color-ink-3))}.muted-2-color{color:var(--muted-2-color, var(--color-ink-4))}.muted-3-color{color:var(--muted-3-color, #b1b1b1)}.muted-4-color{color:var(--muted-4-color, #d2d2d2)}.text-center{text-align:center}.em09{font-size:.9em}.em12{font-size:1.2em}.ml3{margin-left:3px}.ml6{margin-left:6px}.ml10{margin-left:10px}.mr3{margin-right:3px}.mr6{margin-right:6px}.mt10{margin-top:10px}.mt20{margin-top:20px}.mb10{margin-bottom:10px}.mb20{margin-bottom:20px}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.text-ellipsis-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}.main-bg{background-color:var(--main-bg-color, var(--surface-1))}.separator{position:relative;letter-spacing:.08em}.icon-circle+.icon-circle:before{content:"·";display:inline-block;margin:0 .45em;opacity:.55}.article-avatar name,.article-avatar .name,.user-name{display:inline-flex;align-items:center;gap:4px;min-width:0;max-width:100%}.pull-right{float:right}.pull-left{float:left}body.single main.container .content-layout{display:block;min-width:0}body.single main.container .content-layout>*{min-width:0;max-width:100%}body.single main.container .content-layout>.breadcrumb,body.single main.container .content-layout>ul.breadcrumb,body.single main.container .content-layout>.breadcrumb-nav,body.single main.container .content-layout>.breadcrumbs-wrap{position:relative;z-index:1;margin-top:0;margin-bottom:12px;padding-top:2px}body.single .sidebar .jio-panel,body.single .sidebar .widget{margin-bottom:15px}body.single main.container{box-sizing:border-box}.article,article.article.box-body,.article.main-bg.box-body{padding:0 30px;overflow:hidden}@media(max-width:767px){.article,article.article.box-body,.article.main-bg.box-body{padding:0 15px}.article-header .article-title{font-size:18px}}.entry-header,.article-header{margin-bottom:10px;padding-bottom:0;border-bottom:none;background:transparent;box-shadow:none;boLine truncated
|
.relative{position:relative}.flex{display:flex}.flex.ac,.ac{align-items:center}.flex.jc,.jc{justify-content:center}.flex.jsb,.jsb{justify-content:space-between}.flex.at,.at{align-items:flex-start}.flex.ae,.ae{align-items:flex-end}.flex0{flex:0 0 auto}.flex1{flex:1 1 auto;min-width:0}.flex.column{flex-direction:column}.flex.wrap{flex-wrap:wrap}.flex.flex-row,.flex-row{flex-direction:row}.flex.jse,.jse{justify-content:space-evenly}.flex.jsa,.jsa{justify-content:space-around}.flex>.flex1,.flex .flex1{flex:1;overflow:hidden;min-width:0}.relative-h{position:relative;height:100%}.padding-10{padding:10px}.font-bold{font-weight:700}.mt6{margin-top:6px}.gutters-5{margin:-5px}.gutters-5>*{padding:5px}@media(max-width:767px){.flex.flex-row.flex-col-sm-2{flex-wrap:wrap}.flex.flex-row.flex-col-sm-2>*{flex-basis:50%}}.clearfix:before,.clearfix:after{content:"";display:table}.clearfix:after{clear:both}.abs-right{position:absolute;right:0;top:0}.abs-center{position:absolute;left:0;width:100%;top:50%;right:.7em;transform:translateY(-50%);z-index:1}.abs-center.left-top,.abs-center.right-top{transform:unset;top:0}.abs-center.right-top{left:auto;width:auto;text-align:right}.abs-center.left-top{text-align:left}.abs-center.conter-bottom,.abs-center.conter-conter{text-align:center}.abs-center.right-top,.abs-center.right-bottom,.abs-center.right-conter{text-align:right}.abs-center.conter-bottom,.abs-center.right-bottom,.abs-center.left-bottom{top:auto;transform:unset;bottom:0}.abs-center.left-bottom{left:0;right:auto;text-align:left}.abs-center.right-bottom,.abs-center.right-conter{left:auto;right:0;text-align:right}.graphic,.page-cover{text-shadow:0 0 5px rgba(0,0,0,.2);--main-color: #fff;color:#fff;position:relative;overflow:hidden;box-shadow:0 0 10px var(--main-shadow, rgba(116, 116, 116, .08));padding:0 0 70%;border-radius:var(--main-radius, var(--radius, 8px))}.graphic>.fit-cover,.graphic>img.fit-cover{position:absolute;width:100%;height:100%;object-fit:cover;inset:0}.fit-cover{object-fit:cover;max-width:100%}.title-h-left{position:relative;padding-bottom:8px}.title-h-left:before{transition:.4s;transform-origin:left;position:absolute;content:"";width:40px;height:3px;background:var(--theme-color, var(--focus-color, #f04494));left:0;bottom:3px;border-radius:5px;box-shadow:1px 1px 3px -1px var(--theme-color, var(--focus-color, #f04494))}.line-form-line,.line-form-line:before{position:absolute;bottom:0;left:0;height:1px;width:100%;transition:.8s;background:var(--main-border-color, rgba(50, 50, 50, .06))}.line-form-line:before{content:"";width:0;background:var(--focus-color, #f04494)}.avatar-img{position:relative;display:inline-block;flex-shrink:0;--this-size: 38px;width:var(--this-size);height:var(--this-size)}.avatar-img .avatar,.avatar-img img{width:100%;height:100%;border-radius:50%;object-fit:cover}.avatar-lg{--this-size: 80px}.avatar-md{--this-size: 55px}.avatar-mini{--this-size: 20px}.shrink0{flex-shrink:0}.opacity8{opacity:.8}.muted-color{color:var(--muted-color, var(--color-ink-3))}.muted-2-color{color:var(--muted-2-color, var(--color-ink-4))}.muted-3-color{color:var(--muted-3-color, #b1b1b1)}.muted-4-color{color:var(--muted-4-color, #d2d2d2)}.text-center{text-align:center}.em09{font-size:.9em}.em12{font-size:1.2em}.ml3{margin-left:3px}.ml6{margin-left:6px}.ml10{margin-left:10px}.mr3{margin-right:3px}.mr6{margin-right:6px}.mt10{margin-top:10px}.mt20{margin-top:20px}.mb10{margin-bottom:10px}.mb20{margin-bottom:20px}.text-ellipsis{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.text-ellipsis-2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}.main-bg{background-color:var(--main-bg-color, var(--surface-1))}.separator{position:relative;letter-spacing:.08em}.icon-circle+.icon-circle:before{content:"·";display:inline-block;margin:0 .45em;opacity:.55}.article-avatar name,.article-avatar .name,.user-name{display:inline-flex;align-items:center;gap:4px;min-width:0;max-width:100%}.pull-right{float:right}.pull-left{float:left}body.single main.container .content-layout{display:block;min-width:0}body.single main.container .content-layout>*{min-width:0;max-width:100%}body.single main.container .content-layout>.breadcrumb,body.single main.container .content-layout>ul.breadcrumb,body.single main.container .content-layout>.breadcrumb-nav,body.single main.container .content-layout>.breadcrumbs-wrap{position:relative;z-index:1;margin-top:0;margin-bottom:12px;padding-top:2px}body.single .sidebar .jio-panel,body.single .sidebar .widget{margin-bottom:15px}body.single main.container{box-sizing:border-box}.article,article.article.box-body,.article.main-bg.box-body{padding:0 30px;overflow:hidden}@media(max-width:767px){.article,article.article.box-body,.article.main-bg.box-body{padding:0 15px}.article-header .article-title{font-size:18px}}.entry-header,.article-header{margin-bottom:10px;padding-bottom:0;border-bottom:none;background:transparent;box-shadow:none;boLine truncated
|
||||||
@@ -413,3 +413,53 @@ article.article.box-body,
|
|||||||
.wp-posts-content .posts-nav-box {
|
.wp-posts-content .posts-nav-box {
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- 正文表格:编辑器插入的 jio-content-table ---------- */
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.08));
|
||||||
|
border-radius: var(--main-radius, 8px);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table th,
|
||||||
|
.wp-posts-content .jio-content-table td {
|
||||||
|
padding: 10px 14px;
|
||||||
|
border: 0;
|
||||||
|
border-bottom: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.08));
|
||||||
|
line-height: 1.7;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table thead th {
|
||||||
|
background: var(--muted-bg-2-color, #f6f7f8);
|
||||||
|
color: var(--key-color, #333);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table tbody tr:last-child td {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table tbody tr:hover td {
|
||||||
|
background: var(--main-shadow, rgba(116, 116, 116, 0.06));
|
||||||
|
}
|
||||||
|
|
||||||
|
.wp-posts-content .jio-content-table.is-striped tbody tr:nth-child(odd) td {
|
||||||
|
background: var(--muted-bg-2-color, #f6f7f8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 窄屏下表格横向滚动,避免撑破正文。 */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.wp-posts-content .jio-content-table {
|
||||||
|
display: block;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -158,16 +158,29 @@ test('fixed top toolbar has TinyMCE formatting, media, and extension actions', (
|
|||||||
assert.match(editorToolbar, /onOpenMedia\('image'\)/);
|
assert.match(editorToolbar, /onOpenMedia\('image'\)/);
|
||||||
assert.match(editorToolbar, /onOpenMedia\('video'\)/);
|
assert.match(editorToolbar, /onOpenMedia\('video'\)/);
|
||||||
assert.match(editorToolbar, /onClick=\{onOpenLink\}/);
|
assert.match(editorToolbar, /onClick=\{onOpenLink\}/);
|
||||||
// 引用不再是标题下拉的一部分,而是独立按钮。
|
// 引用不再是标题下拉的一部分,而是独立按钮(带样式菜单)。
|
||||||
assert.match(editorToolbar, /onCommand\('format', 'blockquote'\)/);
|
assert.match(editorToolbar, /onInsertQuote/);
|
||||||
assert.doesNotMatch(editorToolbar, /<option value="blockquote"/);
|
assert.doesNotMatch(editorToolbar, /<option value="blockquote"/);
|
||||||
// 新增的块级按钮。
|
assert.doesNotMatch(editorToolbar, /onCommand\('format', 'blockquote'\)/);
|
||||||
for (const kind of ['list', 'toc', 'divider', 'code', 'footnote', 'math', 'table']) {
|
for (const kind of ['list', 'toc', 'divider', 'code', 'footnote', 'math', 'table']) {
|
||||||
assert.ok(editorToolbar.includes(`onInsertBlock('${kind}')`), `missing block button: ${kind}`);
|
assert.ok(editorToolbar.includes(`onInsertBlock('${kind}')`), `missing block button: ${kind}`);
|
||||||
}
|
}
|
||||||
|
// 表格 / 公式 / 注释 / 代码都走参数弹窗,不再是固定内容。
|
||||||
|
assert.match(appSource, /<BlockDialog/);
|
||||||
|
assert.match(appSource, /setBlockDialog\(kind as BlockDialogKind\)/);
|
||||||
|
assert.match(appSource, /setCodeDialog\(\{ language: 'generic', code: '' \}\)/);
|
||||||
|
assert.match(read('assets/creator-studio/src/components/block-dialog.tsx'), /onSubmit\(\{ kind, rows: clamp\(rows, 1, 20\), cols: clamp\(cols, 1, 10\), header, striped \}\)/);
|
||||||
|
assert.match(read('assets/creator-studio/src/components/block-dialog.tsx'), /kind === 'code'/);
|
||||||
assert.match(appSource, /onInsertBlock=\{insertBlock\}/);
|
assert.match(appSource, /onInsertBlock=\{insertBlock\}/);
|
||||||
assert.match(read('assets/creator-studio/src/utils/block-markup.ts'), /jio-toc-placeholder/);
|
assert.match(appSource, /onInsertQuote=\{insertQuote\}/);
|
||||||
assert.match(read('assets/creator-studio/src/utils/block-markup.ts'), /jio-math/);
|
const markup = read('assets/creator-studio/src/utils/block-markup.ts');
|
||||||
|
assert.match(markup, /jio-toc-placeholder/);
|
||||||
|
assert.match(markup, /jio-math/);
|
||||||
|
// 引用沿用主题的 quote_q 结构与配色类。
|
||||||
|
assert.match(markup, /quote_q/);
|
||||||
|
assert.match(markup, /qe_wzk_c-red/);
|
||||||
|
assert.doesNotMatch(markup, /E = mc\^2/);
|
||||||
|
assert.doesNotMatch(markup, /注释内容', 'jio'\)\s*\+\s*'<\/p>'/);
|
||||||
// 工具栏按钮统一为「图标在上、文字在下」。
|
// 工具栏按钮统一为「图标在上、文字在下」。
|
||||||
assert.match(editorToolbar, /jio-creator__tool-label/);
|
assert.match(editorToolbar, /jio-creator__tool-label/);
|
||||||
assert.match(read('assets/creator-studio/src/styles/studio.scss'), /\.jio-creator__tool-btn\s*\{[\s\S]*?flex-direction:\s*column/);
|
assert.match(read('assets/creator-studio/src/styles/studio.scss'), /\.jio-creator__tool-btn\s*\{[\s\S]*?flex-direction:\s*column/);
|
||||||
|
|||||||
Reference in new issue
Block a user