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:
kate522andClaude Opus 5 committed 2026-11-06 23:23:59 +08:00
1 parent 7c33482be7
commit 7885f42cff
14 files changed
+1005 -54

No files matched your search

+1 -1
View File
@@ -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
View File
@@ -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
+3 -3
View File
@@ -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
(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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");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,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}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.__)("请输入标题(最多 %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()})();
(()=>{"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 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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"),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: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
*/
import { useEffect, useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
type Capabilities = Record<string, boolean>;
@@ -23,6 +24,7 @@ type Props = {
onOpenLink: () => void;
onOpenMedia: (type: 'image' | 'video' | 'file') => void;
onInsertBlock: (kind: string) => void;
onInsertQuote: (color: '' | 'qe_wzk_c-red' | 'qe_wzk_lan' | 'qe_wzk_lv') => void;
};
type ToolButtonProps = {
@@ -61,7 +63,34 @@ export function EditorToolbar({
onOpenLink,
onOpenMedia,
onInsertBlock,
onInsertQuote,
}: 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 (
<div
className="jio-creator__toolbar-tools"
@@ -112,7 +141,33 @@ export function EditorToolbar({
<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-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-code" ready={ready} onClick={() => onInsertBlock('code')} />
<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;
}
`;
+129 -17
View File
@@ -18,7 +18,19 @@ import { SaveState, type SaveStateValue } from './components/save-state';
import { useAutosave } from './hooks/use-autosave';
import { useLocalRecovery } from './hooks/use-local-recovery';
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';
const bootstrap: Record<string, any> = window['jioCreatorStudio'] || {};
@@ -132,6 +144,9 @@ function Studio(): JSX.Element {
at: '',
}));
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 [recovery, setRecovery] = useState<null | { savedAt: string }>(null);
const dirtyRef = useRef(false);
@@ -384,7 +399,7 @@ function Studio(): JSX.Element {
wpautop: true,
body_class: 'jio-creator__tinymce',
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,
placeholder: String(bootstrap.limits?.contentPlaceholder || ''),
setup: (editor: any) => {
@@ -400,6 +415,24 @@ function Studio(): JSX.Element {
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', () => {
updateContent(editor.getContent());
updateHistoryAvailability(editor);
@@ -508,22 +541,9 @@ function Studio(): JSX.Element {
}
}, [focusEditor]);
const insertBlock = useCallback((kind: string) => {
const insertHtml = useCallback((html: string) => {
const editor = focusEditor();
if (!editor) {
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) {
if (!editor || !html) {
return;
}
editor.insertContent(html);
@@ -532,6 +552,60 @@ function Studio(): JSX.Element {
updateHistoryAvailability(editor);
}, [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 editor = editorRef.current;
const media = (window as any).JioSubmitMedia;
@@ -608,6 +682,7 @@ function Studio(): JSX.Element {
onOpenLink={openLink}
onOpenMedia={openMedia}
onInsertBlock={insertBlock}
onInsertQuote={insertQuote}
/>
<div className="jio-creator__bar-actions jio-creator__toolbar-extension" data-toolbar-extension aria-label={__('工具扩展区', 'jio')}>
<SaveState value={saveState} />
@@ -676,6 +751,43 @@ function Studio(): JSX.Element {
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 && (
<PublishDrawer
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;
}
}
}
/* ---------- 发布设置卡片 ---------- */
@@ -678,6 +679,7 @@ body.jio-page-submit.has-static-nav {
}
@import './_publish-drawer';
@import './_block-tools';
/* ---------- 手机 / 平板 ---------- */
+89 -25
View File
@@ -2,8 +2,8 @@
* 正文块的 HTML 片段。
*
* 这些块都由服务端再次清洗(FrontSubmitSave 的 wp_kses_post),因此这里只负责
* 生成符合前台渲染习惯的结构:代码块沿用 enlighter 的既有高亮约定,脚注与公式
* 使用主题前台已有的渲染口径,目录使用文章目录树识别标题的 data-nav 结构。
* 生成符合前台渲染习惯的结构:代码块沿用 enlighter 的既有高亮约定,引用沿用
* 主题的 .quote_q 结构,目录使用文章目录树识别标题的占位结构。
*
* @package Jio
*/
@@ -24,27 +24,71 @@ export function dividerMarkup(): string {
return '<hr><p></p>';
}
/**
* 代码块:沿用前台 enlighter 的高亮标记,未选择语言时按通用代码展示。
*/
export function codeBlockMarkup(): string {
export type CodeLanguage = { value: string; label: string };
/** 常用语言;与前台 enlighter 的语言标识保持一致。 */
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 (
'<pre class="enlighter-pre"><code class="gl" data-enlighter-language="generic">' +
escapeHtml(__('在此输入代码', 'jio')) +
`<pre class="enlighter-pre" data-enlighter-language="${escapeHtml(language)}"><code class="gl" data-enlighter-language="${escapeHtml(language)}">` +
body +
'</code></pre><p></p>'
);
}
/**
* 表格:3×3 带表头,方便作者直接在单元格里改写。
*/
export function tableMarkup(): string {
const cell = (tag: string, text: string): string => `<${tag}>${escapeHtml(text)}</${tag}>`;
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>`)
.join('');
return `<table class="jio-content-table"><thead>${header}</thead><tbody>${rows}</tbody></table><p></p>`;
export type TableOptions = {
rows: number;
cols: number;
header: boolean;
striped: boolean;
};
/** 带尺寸参数的表格;首行可选表头,隔行变色写入 class。 */
export function tableMarkup({ rows, cols, header, striped }: TableOptions): string {
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 {
return `<div class="jio-toc-placeholder"></div><p></p>`;
return '<div class="jio-toc-placeholder"></div><p></p>';
}
/** 注释:Markdown 脚注语法,前台由文章渲染流程转成编号脚注。 */
export function footnoteMarkup(): string {
return '<p>[^1]<br>[^1]: ' + escapeHtml(__('注释内容', 'jio')) + '</p>';
/** 注释:Markdown 脚注语法,前台渲染成编号脚注列表。 */
export function footnoteMarkup(note: string): string {
const text = note.trim() || __('注释内容', 'jio');
return `<p>[^1]<br>[^1]: ${escapeHtml(text)}</p>`;
}
/** 公式:行内 LaTeX,包在主题可识别的公式容器里。 */
export function mathMarkup(): string {
return `<span class="jio-math">E = mc^2</span>`;
export type MathOptions = { formula: string; display: boolean };
/** 公式:行内或独占一行,数据保存在 .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>`;
}
+50
View File
@@ -752,6 +752,56 @@ article.article.box-body,
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 */
/* ===== Content Footer ===== */
.article-footer {
+1 -1
View File
@@ -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 {
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\('video'\)/);
assert.match(editorToolbar, /onClick=\{onOpenLink\}/);
// 引用不再是标题下拉的一部分,而是独立按钮。
assert.match(editorToolbar, /onCommand\('format', 'blockquote'\)/);
// 引用不再是标题下拉的一部分,而是独立按钮(带样式菜单)。
assert.match(editorToolbar, /onInsertQuote/);
assert.doesNotMatch(editorToolbar, /<option value="blockquote"/);
// 新增的块级按钮。
assert.doesNotMatch(editorToolbar, /onCommand\('format', 'blockquote'\)/);
for (const kind of ['list', 'toc', 'divider', 'code', 'footnote', 'math', 'table']) {
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(read('assets/creator-studio/src/utils/block-markup.ts'), /jio-toc-placeholder/);
assert.match(read('assets/creator-studio/src/utils/block-markup.ts'), /jio-math/);
assert.match(appSource, /onInsertQuote=\{insertQuote\}/);
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(read('assets/creator-studio/src/styles/studio.scss'), /\.jio-creator__tool-btn\s*\{[\s\S]*?flex-direction:\s*column/);