feat(creator-studio): icon toolbar with block tools, drop author strip
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
7bdf7cf018
commit
7c33482be7
19 files changed
+600
-241
No files matched your search
@@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'wp-element', 'wp-i18n'), 'version' => 'ccaa65aec8e25f38d35c');
|
||||
<?php return array('dependencies' => array('react', 'wp-element', 'wp-i18n'), 'version' => '1f4dc4e2d488af6bd803');
|
||||
@@ -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:60px;-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:7px;color:var(--jc-ink-soft);cursor:pointer;display:inline-flex;flex:0 0 auto;font-size:13px;gap:7px;height:34px;justify-content:center;min-width:32px;padding:0 8px}.jio-creator__tool-btn:focus-visible:not(:disabled),.jio-creator__tool-btn:hover:not(:disabled){background:#edf3f0;color:var(--jc-accent-strong);outline:0}.jio-creator__tool-btn:disabled{color:#c7d0cc;cursor:default}.jio-creator__tool-labeled span{white-space:nowrap}.jio-creator__format-select{background:transparent;border:1px solid transparent;border-radius:7px;color:var(--jc-ink-soft);flex:0 0 auto;font:inherit;font-size:13px;height:34px;padding:0 8px;width:94px}.jio-creator__format-select:focus-visible,.jio-creator__format-select:hover{background:#fff;border-color:var(--jc-line);outline:0}.jio-creator__format-select:disabled{color:#aebbb5}.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:pointer;display:inline-flex;font-size:13px;justify-content:center;min-height:34px;padding:0 13px;transition:background .18s ease,border-color .18s ease,color .18s ease;white-space:nowrap}.jio-creator__ghost{background:transparent;border-color:var(--jc-line)}.jio-creator__ghost:focus-visible,.jio-creator__ghost:hover{border-color:var(--jc-accent);color:var(--jc-accent)}.jio-creator__primary{background:var(--jc-accent);color:#fff}.jio-creator__primary:focus-visible,.jio-creator__primary:hover{backLine 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 r(e,t){const a=e&&(e.message||e.error)||(401===t?"登录状态已失效,请刷新页面重新登录。":"请求失败,请稍后重试。"),r=new Error(String(a));return r.status=t,r.code=e&&e.code?String(e.code):"",r.data=e,r}async function n(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 n(o);if(!o.ok)throw r(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 n(l);if(!l.ok)throw r(c,l.status);return c}const l=window.jioCreatorStudio||{},c=l.nonce||"";function s(r){const{draft:n,setDraft:o,capabilities:s,limits:u,taxonomy:m,isPublishedEdit:d,onClose:p,onSaveDraft:_,onSubmit:b,privateStorage:f,privateUploads:g,saving:j,captcha:h}=r,[E,y]=(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 x=(0,t.useMemo)(()=>(n.title||"").trim().length,[n.title])>=Number(u.titleMin||1),I=()=>{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([...n.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"},d?(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"},d&&(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===m.categories.length&&(0,e.createElement)("span",{className:"jio-creator-muted"},(0,a.__)("暂无可选分类","jio")),m.categories.map(t=>(0,e.createElement)("label",{key:t.id,className:"jio-creator-chip"+(n.categories.includes(t.id)?" is-active":"")},(0,e.createElement)("input",{type:"checkbox",checked:n.categories.includes(t.id),onChange:()=>(e=>{const t=n.categories.includes(e);o({categories:t?n.categories.filter(t=>t!==e):[...n.categories,e]})})(t.id)}),(0,e.createElement)("span",null,t.name))))),m.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"},n.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:n.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(),I())}}),(0,e.createElement)("button",{type:"button",onClick:I},(0,a.__)("添加","jio")))),m.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 r=n.custom[t.key]||[],i=r.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:{...n.custom,[t.key]:i?r.filter(e=>e!==a.key):[...r,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")))))}function f({author:t}){const r="string"==typeof t?.name?t.name.trim():"";if(""===r)return null;const n="string"==typeof t?.avatar?t.avatar:"",o="string"==typeof t?.meta?t.meta:"";return(0,e.createElement)("div",{className:"jio-creator__author"},""!==n?(0,e.createElement)("img",{className:"jio-creator__author-avatar",src:n,alt:"",width:24,height:24}):(0,e.createElement)("span",{className:"jio-creator__author-avatar is-placeholder","aria-hidden":"true"},r.slice(0,1)),(0,e.createElement)("span",{className:"jio-creator__author-name"},r),""!==o&&(0,e.createElement)("span",{className:"jio-creator__author-meta"},o),(0,e.createElement)("span",{className:"jio-creator__author-hint"},(0,a.__)("原创内容","jio")))}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 j({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 h(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=window.jioCreatorStudio||{},y=E.nonce||"",v=`jio-studio-${E.sourceId||0}`,N="jio-creator-content";function w(){const e=E.pay||{},t=E.categories&&E.categories.selected||[],a=E.tags&&E.tags.items||[];return{postId:Number(E.sourceId||0),versionId:Number(E.versionId||0),rev:Number(E.versionRev||0),title:String(E.title||""),content:String(E.content||""),schedule:{enabled:!1,at:""},categories:t.map(Number),tags:a.slice(),custom:E.custom||{},featured:E.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 k(e){return e.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}function C(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 S(e){const t=document.createElement("div");return t.innerHTML=e,(t.textContent||"").replace(/\s+/g,"")}function x(){const[r,n]=(0,t.useState)(w),i=(0,t.useRef)(r);i.current=r;const l=(0,t.useRef)(null),c=(0,t.useRef)(null),m=(0,t.useRef)(null),d=(0,t.useRef)(!1),p=(0,t.useRef)(()=>{}),[_,g]=(0,t.useState)(!1),[x,I]=(0,t.useState)({canUndo:!1,canRedo:!1}),[M,L]=(0,t.useState)("p"),[R,D]=(0,t.useState)(()=>({state:E.sourceId||E.versionId||E.content?"saved":"ready",at:""})),[P,U]=(0,t.useState)(!1),[$,F]=(0,t.useState)(null),[T,z]=(0,t.useState)(null),K=(0,t.useRef)(!1),O=(0,t.useRef)(0),A=E.capabilities||{},B={titleMin:Number(E.limits?.titleMin||1),titleMax:Number(E.limits?.titleMax||30),titlePlaceholder:String(E.limits?.titlePlaceholder||(0,a.__)("请输入标题","jio")),priceMin:Number(E.pay?.priceMin||0),priceMax:Number(E.pay?.priceMax||0),pointsMin:Number(E.pay?.pointsMin||0),pointsMax:Number(E.pay?.pointsMax||0)},J=!!E.isPublished,q=(0,t.useCallback)(e=>{K.current=!0,O.current+=1;const t={...i.current,...e};i.current=t,n(t)},[]),V=(0,t.useCallback)(e=>{if(e===i.current.content)return;K.current=!0,O.current+=1;const t={...i.current,content:e};i.current=t,n(t)},[]),H=(0,t.useCallback)(e=>{const t=e?.undoManager;t&&I({canUndo:"function"==typeof t.hasUndo&&t.hasUndo(),canRedo:"function"==typeof t.hasRedo&&t.hasRedo()})},[]),W=(0,t.useCallback)(e=>{const t=e?.selection?.getNode(),a=t?e.dom.getParent(t,"p,h1,h2,h3,blockquote")||t:null,r=a?.nodeName?.toLowerCase()||"p";L(["h1","h2","h3","blockquote"].includes(r)?r:"p")},[]),X=(0,t.useMemo)(()=>Array.from(S(r.content)).length,[r.content]),Y=(0,t.useCallback)((e={})=>{const t=i.currenLine truncated
|
||||
(0,a.__)("请输入标题(最多 %d 个字)","jio"),Number(B.titleMax)||100),spellCheck:!1,maxLength:Number(B.titleMax)||100})),(0,e.createElement)("div",{className:"jio-creator__editor","data-editor-ready":_?"true":"false"},(0,e.createElement)("textarea",{id:N,defaultValue:r.content,onChange:e=>V(e.target.value),"aria-label":(0,a.__)("文章正文","jio")}))),(0,e.createElement)(b,{featured:r.featured,canEditCover:!!A.canUploadImage,canSchedule:!!A.canSchedule,scheduleEnabled:!!r.schedule.enabled,scheduleAt:r.schedule.at,onFeaturedChange:e=>q({featured:e}),onScheduleChange:(e,t)=>q({schedule:{enabled:e,at:t}})}),(0,e.createElement)("footer",{className:"jio-creator__footer"},(0,e.createElement)("span",null,(0,a.__)("字数","jio"),":",X),(0,e.createElement)("span",null,(0,a.__)("约","jio")," ",Math.max(1,Math.round(X/400))," ",(0,a.__)("分钟阅读","jio"))),P&&(0,e.createElement)(s,{draft:r,setDraft:q,capabilities:A,limits:B,taxonomy:{categories:E.categories&&E.categories.items||[],tags:!!E.tags?.enabled,customDefs:E.customDefs||[]},isPublishedEdit:J,onClose:()=>U(!1),onSaveDraft:()=>{Z(!1)},onSubmit:e=>{G(e)},privateStorage:!!E.privateStorage,privateUploads:E.privateUploads||[],captcha:!!E.captcha?.enabled,saving:"saving"===R.state||"submitting"===R.state}))}function I(){const a=document.getElementById("jio-creator-studio");a&&(a.classList.add("is-ready"),(0,t.createRoot)(a).render((0,e.createElement)(x,null)))}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",I):I()})();
|
||||
(()=>{"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,"&").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.__)("请输入标题(最多 %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()})();
|
||||
@@ -68,7 +68,6 @@ const mockBootstrap = {
|
||||
isLoggedIn: true,
|
||||
},
|
||||
limits: { titleMin: 5, titleMax: 100, titlePlaceholder: '请输入标题', contentPlaceholder: '请输入正文' },
|
||||
author: { name: '预览作者', avatar: '', meta: '' },
|
||||
privateUploads: [],
|
||||
privateStorage: false,
|
||||
media: { maxBytes: 5242880, batch: 6 },
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* 作者行:标题下方的头像 + 昵称(公众号式)。
|
||||
*
|
||||
* 数据来自服务端 bootstrap,前端只负责展示;没有作者信息时整行不渲染,
|
||||
* 避免出现空头像占位。
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
type Author = {
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
meta?: string;
|
||||
};
|
||||
|
||||
export function AuthorRow({ author }: { author: Author }) {
|
||||
const name = typeof author?.name === 'string' ? author.name.trim() : '';
|
||||
if (name === '') {
|
||||
return null;
|
||||
}
|
||||
const avatar = typeof author?.avatar === 'string' ? author.avatar : '';
|
||||
const meta = typeof author?.meta === 'string' ? author.meta : '';
|
||||
|
||||
return (
|
||||
<div className="jio-creator__author">
|
||||
{avatar !== '' ? (
|
||||
<img className="jio-creator__author-avatar" src={avatar} alt="" width={24} height={24} />
|
||||
) : (
|
||||
<span className="jio-creator__author-avatar is-placeholder" aria-hidden="true">
|
||||
{name.slice(0, 1)}
|
||||
</span>
|
||||
)}
|
||||
<span className="jio-creator__author-name">{name}</span>
|
||||
{meta !== '' && <span className="jio-creator__author-meta">{meta}</span>}
|
||||
<span className="jio-creator__author-hint">{__('原创内容', 'jio')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
/**
|
||||
* 固定顶栏里的正文工具条。
|
||||
*
|
||||
* 每个按钮都是「上图标 + 下文字」,与公众号/知乎的单行工具栏一致;
|
||||
* 只渲染当前账号真正有权限使用的能力,全部动作由父组件转给 TinyMCE。
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
type Capabilities = Record<string, boolean>;
|
||||
@@ -12,9 +21,34 @@ type Props = {
|
||||
onCommand: (name: string, value?: string) => void;
|
||||
onRememberSelection: () => void;
|
||||
onOpenLink: () => void;
|
||||
onOpenMedia: (type: 'image' | 'video') => void;
|
||||
onOpenMedia: (type: 'image' | 'video' | 'file') => void;
|
||||
onInsertBlock: (kind: string) => void;
|
||||
};
|
||||
|
||||
type ToolButtonProps = {
|
||||
label: string;
|
||||
icon: string;
|
||||
ready: boolean;
|
||||
onClick: () => void;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
function ToolButton({ label, icon, ready, onClick, title }: ToolButtonProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="jio-creator__tool-btn"
|
||||
disabled={!ready}
|
||||
onClick={onClick}
|
||||
title={title || label}
|
||||
aria-label={title || label}
|
||||
>
|
||||
<i className={icon} aria-hidden="true" />
|
||||
<span className="jio-creator__tool-label">{label}</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function EditorToolbar({
|
||||
ready,
|
||||
canUndo,
|
||||
@@ -26,6 +60,7 @@ export function EditorToolbar({
|
||||
onRememberSelection,
|
||||
onOpenLink,
|
||||
onOpenMedia,
|
||||
onInsertBlock,
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
@@ -38,58 +73,65 @@ export function EditorToolbar({
|
||||
}}
|
||||
>
|
||||
<div className="jio-creator__toolbar-group" role="group" aria-label={__('撤销和重做', 'jio')}>
|
||||
<button type="button" className="jio-creator__tool-btn" disabled={!canUndo} onClick={() => onCommand('undo')} title={__('撤销', 'jio')} aria-label={__('撤销', 'jio')}>
|
||||
<i className="fa fas fa-undo" aria-hidden="true" />
|
||||
</button>
|
||||
<button type="button" className="jio-creator__tool-btn" disabled={!canRedo} onClick={() => onCommand('redo')} title={__('重做', 'jio')} aria-label={__('重做', 'jio')}>
|
||||
<i className="fa fas fa-redo" aria-hidden="true" />
|
||||
</button>
|
||||
<ToolButton label={__('撤销', 'jio')} icon="fa fas fa-undo" ready={canUndo} onClick={() => onCommand('undo')} />
|
||||
<ToolButton label={__('重做', 'jio')} icon="fa fas fa-redo" ready={canRedo} onClick={() => onCommand('redo')} />
|
||||
</div>
|
||||
<span className="jio-creator__toolbar-divider" aria-hidden="true" />
|
||||
<select
|
||||
className="jio-creator__format-select"
|
||||
aria-label={__('段落样式', 'jio')}
|
||||
value={formatValue}
|
||||
disabled={!ready}
|
||||
onFocus={onRememberSelection}
|
||||
onChange={(event) => {
|
||||
onFormatChange(event.target.value);
|
||||
onCommand('format', event.target.value);
|
||||
}}
|
||||
>
|
||||
<option value="p">{__('正文', 'jio')}</option>
|
||||
<option value="h1">{__('标题 1', 'jio')}</option>
|
||||
<option value="h2">{__('标题 2', 'jio')}</option>
|
||||
<option value="h3">{__('标题 3', 'jio')}</option>
|
||||
<option value="blockquote">{__('引用', 'jio')}</option>
|
||||
</select>
|
||||
|
||||
<div className="jio-creator__toolbar-group" role="group" aria-label={__('段落样式', 'jio')}>
|
||||
<label className="jio-creator__format-field">
|
||||
<select
|
||||
className="jio-creator__format-select"
|
||||
aria-label={__('标题级别', 'jio')}
|
||||
value={formatValue === 'blockquote' ? 'p' : formatValue}
|
||||
disabled={!ready}
|
||||
onFocus={onRememberSelection}
|
||||
onChange={(event) => {
|
||||
onFormatChange(event.target.value);
|
||||
onCommand('format', event.target.value);
|
||||
}}
|
||||
>
|
||||
<option value="p">{__('正文', 'jio')}</option>
|
||||
<option value="h1">{__('标题 1', 'jio')}</option>
|
||||
<option value="h2">{__('标题 2', 'jio')}</option>
|
||||
<option value="h3">{__('标题 3', 'jio')}</option>
|
||||
</select>
|
||||
<i className="fa fas fa-heading" aria-hidden="true" />
|
||||
<span className="jio-creator__tool-label">{__('标题', 'jio')}</span>
|
||||
</label>
|
||||
</div>
|
||||
<span className="jio-creator__toolbar-divider" aria-hidden="true" />
|
||||
|
||||
<div className="jio-creator__toolbar-group" role="group" aria-label={__('文字格式', 'jio')}>
|
||||
<button type="button" className="jio-creator__tool-btn" disabled={!ready} onClick={() => onCommand('Bold')} title={__('加粗', 'jio')} aria-label={__('加粗', 'jio')}>
|
||||
<i className="fa fas fa-bold" aria-hidden="true" />
|
||||
</button>
|
||||
<button type="button" className="jio-creator__tool-btn" disabled={!ready} onClick={() => onCommand('Italic')} title={__('斜体', 'jio')} aria-label={__('斜体', 'jio')}>
|
||||
<i className="fa fas fa-italic" aria-hidden="true" />
|
||||
</button>
|
||||
<button type="button" className="jio-creator__tool-btn" disabled={!ready} onClick={() => onCommand('Underline')} title={__('下划线', 'jio')} aria-label={__('下划线', 'jio')}>
|
||||
<i className="fa fas fa-underline" aria-hidden="true" />
|
||||
</button>
|
||||
<ToolButton label={__('加粗', 'jio')} icon="fa fas fa-bold" ready={ready} onClick={() => onCommand('Bold')} />
|
||||
<ToolButton label={__('斜体', 'jio')} icon="fa fas fa-italic" ready={ready} onClick={() => onCommand('Italic')} />
|
||||
<ToolButton label={__('下划线', 'jio')} icon="fa fas fa-underline" ready={ready} onClick={() => onCommand('Underline')} />
|
||||
</div>
|
||||
<span className="jio-creator__toolbar-divider" aria-hidden="true" />
|
||||
|
||||
<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')} />
|
||||
<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')} />
|
||||
<ToolButton label={__('公式', 'jio')} icon="fa fas fa-square-root-alt" ready={ready} onClick={() => onInsertBlock('math')} />
|
||||
<ToolButton label={__('表格', 'jio')} icon="fa fas fa-table" ready={ready} onClick={() => onInsertBlock('table')} />
|
||||
</div>
|
||||
<span className="jio-creator__toolbar-divider" aria-hidden="true" />
|
||||
|
||||
<div className="jio-creator__toolbar-group" role="group" aria-label={__('媒体和链接', 'jio')}>
|
||||
{capabilities.canUploadImage && (
|
||||
<button type="button" className="jio-creator__tool-btn jio-creator__tool-labeled" disabled={!ready} onClick={() => onOpenMedia('image')} title={__('插入图片', 'jio')}>
|
||||
<i className="fa far fa-image" aria-hidden="true" /><span>{__('图片', 'jio')}</span>
|
||||
</button>
|
||||
<ToolButton label={__('图片', 'jio')} icon="fa far fa-image" ready={ready} onClick={() => onOpenMedia('image')} />
|
||||
)}
|
||||
{(capabilities.canUploadVideo || capabilities.canEmbed) && (
|
||||
<button type="button" className="jio-creator__tool-btn jio-creator__tool-labeled" disabled={!ready} onClick={() => onOpenMedia('video')} title={__('插入视频', 'jio')}>
|
||||
<i className="fa fas fa-video" aria-hidden="true" /><span>{__('视频', 'jio')}</span>
|
||||
</button>
|
||||
<ToolButton label={__('视频', 'jio')} icon="fa fas fa-video" ready={ready} onClick={() => onOpenMedia('video')} />
|
||||
)}
|
||||
<ToolButton label={__('链接', 'jio')} icon="fa fas fa-link" ready={ready} onClick={onOpenLink} />
|
||||
{capabilities.canUploadFile && (
|
||||
<ToolButton label={__('附件', 'jio')} icon="fa fas fa-paperclip" ready={ready} onClick={() => onOpenMedia('file')} />
|
||||
)}
|
||||
<button type="button" className="jio-creator__tool-btn jio-creator__tool-labeled" disabled={!ready} onClick={onOpenLink} title={__('插入链接', 'jio')}>
|
||||
<i className="fa fas fa-link" aria-hidden="true" /><span>{__('链接', 'jio')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,18 +7,18 @@
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
import { createRoot, useCallback, useEffect, useMemo, useRef, useState } from '@wordpress/element';
|
||||
import { createRoot, useCallback, useEffect, useRef, useState } from '@wordpress/element';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
|
||||
import { apiPost } from './services/api';
|
||||
import { PublishDrawer } from './panels/publish-drawer';
|
||||
import { EditorToolbar } from './components/editor-toolbar';
|
||||
import { PublishSettings } from './components/publish-settings';
|
||||
import { AuthorRow } from './components/author-row';
|
||||
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 './styles/studio.scss';
|
||||
|
||||
const bootstrap: Record<string, any> = window['jioCreatorStudio'] || {};
|
||||
@@ -184,8 +184,6 @@ function Studio(): JSX.Element {
|
||||
setFormatValue(['h1', 'h2', 'h3', 'blockquote'].includes(name) ? name : 'p');
|
||||
}, []);
|
||||
|
||||
const charCount = useMemo(() => Array.from(plainText(draft.content)).length, [draft.content]);
|
||||
|
||||
const buildPayload = useCallback((extra: Record<string, any> = {}) => {
|
||||
const current = draftRef.current;
|
||||
return {
|
||||
@@ -510,7 +508,31 @@ function Studio(): JSX.Element {
|
||||
}
|
||||
}, [focusEditor]);
|
||||
|
||||
const openMedia = useCallback((type: 'image' | 'video') => {
|
||||
const insertBlock = useCallback((kind: 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) {
|
||||
return;
|
||||
}
|
||||
editor.insertContent(html);
|
||||
editor.nodeChanged();
|
||||
updateContent(editor.getContent());
|
||||
updateHistoryAvailability(editor);
|
||||
}, [focusEditor]);
|
||||
|
||||
const openMedia = useCallback((type: 'image' | 'video' | 'file') => {
|
||||
const editor = editorRef.current;
|
||||
const media = (window as any).JioSubmitMedia;
|
||||
if (!editor || !media || typeof media.open !== 'function') {
|
||||
@@ -535,6 +557,10 @@ function Studio(): JSX.Element {
|
||||
const fullAttribute = fullUrl ? ` data-full-url="${escapeAttribute(fullUrl)}"` : '';
|
||||
return `<p><img src="${escapeAttribute(url)}"${idAttribute} alt="${escapeAttribute(String(item.name || item.title || ''))}"${fullAttribute}></p>`;
|
||||
}
|
||||
if (type === 'file') {
|
||||
const name = String(item.filename || item.name || url.split('/').pop() || '');
|
||||
return `<p><a class="jio-file-link" href="${escapeAttribute(url)}"${idAttribute}><i class="fa fas fa-paperclip" aria-hidden="true"></i> ${escapeAttribute(name)}</a></p>`;
|
||||
}
|
||||
return `<div contenteditable="false" data-video-url="${escapeAttribute(url)}" data-video-name="${escapeAttribute(String(item.filename || item.name || url))}"${idAttribute} class="new-dplayer post-dplayer dplayer"></div><p></p>`;
|
||||
}).join('');
|
||||
if (markup) {
|
||||
@@ -572,8 +598,8 @@ function Studio(): JSX.Element {
|
||||
|
||||
<EditorToolbar
|
||||
ready={editorReady}
|
||||
formatValue={formatValue}
|
||||
onFormatChange={setFormatValue}
|
||||
formatValue={formatValue}
|
||||
onFormatChange={setFormatValue}
|
||||
canUndo={historyAvailability.canUndo}
|
||||
canRedo={historyAvailability.canRedo}
|
||||
capabilities={capabilities}
|
||||
@@ -581,6 +607,7 @@ function Studio(): JSX.Element {
|
||||
onRememberSelection={rememberSelection}
|
||||
onOpenLink={openLink}
|
||||
onOpenMedia={openMedia}
|
||||
onInsertBlock={insertBlock}
|
||||
/>
|
||||
<div className="jio-creator__bar-actions jio-creator__toolbar-extension" data-toolbar-extension aria-label={__('工具扩展区', 'jio')}>
|
||||
<SaveState value={saveState} />
|
||||
@@ -618,8 +645,6 @@ function Studio(): JSX.Element {
|
||||
)}
|
||||
|
||||
<main className="jio-creator__canvas">
|
||||
<AuthorRow author={bootstrap.author || {}} />
|
||||
|
||||
<label className="jio-creator__title">
|
||||
<span className="screen-reader-text">{__('文章标题', 'jio')}</span>
|
||||
<textarea
|
||||
@@ -651,11 +676,6 @@ function Studio(): JSX.Element {
|
||||
onScheduleChange={(enabled, at) => patchDraft({ schedule: { enabled, at } })}
|
||||
/>
|
||||
|
||||
<footer className="jio-creator__footer">
|
||||
<span>{__('字数', 'jio')}:{charCount}</span>
|
||||
<span>{__('约', 'jio')} {Math.max(1, Math.round(charCount / 400))} {__('分钟阅读', 'jio')}</span>
|
||||
</footer>
|
||||
|
||||
{drawerOpen && (
|
||||
<PublishDrawer
|
||||
draft={draft}
|
||||
|
||||
@@ -48,7 +48,7 @@ body.jio-page-submit.has-static-nav {
|
||||
--jc-accent: #{$accent};
|
||||
--jc-accent-strong: #{$accent-strong};
|
||||
--jc-warn: #{$warn};
|
||||
--jc-bar: 60px;
|
||||
--jc-bar: 64px;
|
||||
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
@@ -172,25 +172,34 @@ body.jio-page-submit.has-static-nav {
|
||||
background: var(--jc-line);
|
||||
}
|
||||
|
||||
/* 工具按钮:图标在上、文字在下,一行高度固定,与公众号/知乎一致。 */
|
||||
.jio-creator__tool-btn {
|
||||
display: inline-flex;
|
||||
min-width: 32px;
|
||||
height: 34px;
|
||||
width: auto;
|
||||
min-width: 40px;
|
||||
height: 46px;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
padding: 0 8px;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 5px 6px;
|
||||
border: 0;
|
||||
border-radius: 7px;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--jc-ink-soft);
|
||||
color: #5f6664;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-size: 11px;
|
||||
line-height: 1.2;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&:hover:not(:disabled),
|
||||
&:focus-visible:not(:disabled) {
|
||||
background: #edf3f0;
|
||||
background: #f0f2f1;
|
||||
color: var(--jc-accent-strong);
|
||||
outline: 0;
|
||||
}
|
||||
@@ -201,31 +210,61 @@ body.jio-page-submit.has-static-nav {
|
||||
}
|
||||
}
|
||||
|
||||
.jio-creator__tool-labeled span {
|
||||
.jio-creator__tool-label {
|
||||
max-width: 52px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.jio-creator__format-select {
|
||||
width: 94px;
|
||||
height: 34px;
|
||||
/* 标题按钮沿用「图标 + 文字」外观,内部叠一个原生的级别下拉。 */
|
||||
.jio-creator__format-field {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: auto;
|
||||
min-width: 48px;
|
||||
height: 46px;
|
||||
flex: 0 0 auto;
|
||||
padding: 0 8px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 7px;
|
||||
background: transparent;
|
||||
color: var(--jc-ink-soft);
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
padding: 5px 6px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
border-color: var(--jc-line);
|
||||
background: #fff;
|
||||
outline: 0;
|
||||
i {
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus-within {
|
||||
background: #f0f2f1;
|
||||
color: var(--jc-accent-strong);
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
outline: 2px solid rgba(38, 118, 101, 0.35);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.jio-creator__format-select {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
color: #aebbb5;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +514,7 @@ body.jio-page-submit.has-static-nav {
|
||||
|
||||
.jio-creator__settings {
|
||||
width: min(900px, calc(100% - clamp(24px, 6vw, 72px)));
|
||||
margin: 20px auto 0;
|
||||
margin: 20px auto clamp(48px, 10vh, 140px);
|
||||
padding: clamp(20px, 3vw, 28px) clamp(24px, 4vw, 40px);
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
@@ -638,70 +677,14 @@ body.jio-page-submit.has-static-nav {
|
||||
}
|
||||
}
|
||||
|
||||
.jio-creator__footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: min(900px, calc(100% - clamp(24px, 6vw, 72px)));
|
||||
margin: 16px auto 0;
|
||||
padding: 0 4px 140px;
|
||||
color: #98a19e;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* ---------- 作者行 ---------- */
|
||||
|
||||
.jio-creator__author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 2px 0 20px;
|
||||
color: #8c9a94;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.jio-creator__author-avatar {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.jio-creator__author-avatar.is-placeholder {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #e3ece7;
|
||||
color: #4a5f57;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.jio-creator__author-name {
|
||||
color: #4a5f57;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.jio-creator__author-meta {
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
background: #eef5f2;
|
||||
color: #3c7d6c;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.jio-creator__author-hint {
|
||||
margin-left: auto;
|
||||
color: #b0bcb7;
|
||||
}
|
||||
|
||||
@import './_publish-drawer';
|
||||
|
||||
/* ---------- 手机 / 平板 ---------- */
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.jio-creator {
|
||||
--jc-bar: 56px;
|
||||
/* 图标 + 文字的两行按钮把顶栏撑高,移动端同样保留文字。 */
|
||||
--jc-bar: 60px;
|
||||
}
|
||||
|
||||
.jio-creator__bar {
|
||||
@@ -736,17 +719,22 @@ body.jio-page-submit.has-static-nav {
|
||||
}
|
||||
|
||||
.jio-creator__tool-btn {
|
||||
min-width: 30px;
|
||||
padding: 0 6px;
|
||||
min-width: 36px;
|
||||
padding: 4px 4px;
|
||||
|
||||
i {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.jio-creator__tool-labeled span {
|
||||
display: none;
|
||||
.jio-creator__tool-label {
|
||||
max-width: 42px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.jio-creator__format-select {
|
||||
width: 82px;
|
||||
padding: 0 5px;
|
||||
.jio-creator__format-field {
|
||||
min-width: 42px;
|
||||
padding: 4px 4px;
|
||||
}
|
||||
|
||||
.jio-creator__bar-actions .jio-creator__save {
|
||||
@@ -788,7 +776,7 @@ body.jio-page-submit.has-static-nav {
|
||||
|
||||
.jio-creator__settings {
|
||||
width: calc(100% - 20px);
|
||||
margin-top: 12px;
|
||||
margin: 12px auto calc(40px + env(safe-area-inset-bottom));
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
@@ -800,11 +788,6 @@ body.jio-page-submit.has-static-nav {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.jio-creator__footer {
|
||||
width: calc(100% - 20px);
|
||||
padding-bottom: calc(120px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.jio-creator__editor .mce-edit-area iframe,
|
||||
.jio-creator__editor .tox .tox-edit-area__iframe {
|
||||
min-height: 48vh;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 正文块的 HTML 片段。
|
||||
*
|
||||
* 这些块都由服务端再次清洗(FrontSubmitSave 的 wp_kses_post),因此这里只负责
|
||||
* 生成符合前台渲染习惯的结构:代码块沿用 enlighter 的既有高亮约定,脚注与公式
|
||||
* 使用主题前台已有的渲染口径,目录使用文章目录树识别标题的 data-nav 结构。
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
const escapeHtml = (value: string): string =>
|
||||
value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
|
||||
/** 有序 / 无序列表:直接插入一个空列表块,光标落在第一项。 */
|
||||
export function listMarkup(ordered: boolean): string {
|
||||
const tag = ordered ? 'ol' : 'ul';
|
||||
return `<${tag}><li></li></${tag}><p></p>`;
|
||||
}
|
||||
|
||||
/** 分割线。 */
|
||||
export function dividerMarkup(): string {
|
||||
return '<hr><p></p>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 代码块:沿用前台 enlighter 的高亮标记,未选择语言时按通用代码展示。
|
||||
*/
|
||||
export function codeBlockMarkup(): string {
|
||||
return (
|
||||
'<pre class="enlighter-pre"><code class="gl" data-enlighter-language="generic">' +
|
||||
escapeHtml(__('在此输入代码', 'jio')) +
|
||||
'</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>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 目录:文章页的目录树由前台 section-navs 依据 [data-nav] 生成,
|
||||
* 因此这里只放一个占位标记,读者端不会看到裸露的标记文本。
|
||||
*/
|
||||
export function tocMarkup(): string {
|
||||
return `<div class="jio-toc-placeholder"></div><p></p>`;
|
||||
}
|
||||
|
||||
/** 注释:Markdown 脚注语法,前台由文章渲染流程转成编号脚注。 */
|
||||
export function footnoteMarkup(): string {
|
||||
return '<p>[^1]<br>[^1]: ' + escapeHtml(__('注释内容', 'jio')) + '</p>';
|
||||
}
|
||||
|
||||
/** 公式:行内 LaTeX,包在主题可识别的公式容器里。 */
|
||||
export function mathMarkup(): string {
|
||||
return `<span class="jio-math">E = mc^2</span>`;
|
||||
}
|
||||
@@ -678,6 +678,80 @@ article.article.box-body,
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* ---------- 正文扩展块:公式、脚注、附件、目录 ---------- */
|
||||
|
||||
.jio-math {
|
||||
display: inline-block;
|
||||
padding: 0 2px;
|
||||
font-family: "Cambria Math", "Latin Modern Math", "Times New Roman", serif;
|
||||
font-size: 1.02em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.jio-math--block {
|
||||
display: block;
|
||||
margin: 12px 0 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jio-footnotes {
|
||||
margin-top: 32px;
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.06));
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.jio-footnotes__title {
|
||||
margin: 0 0 10px;
|
||||
color: var(--muted-color, #777);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.jio-footnotes__list {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
color: var(--muted-color, #777);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.jio-footnotes__list li {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.jio-footnote-ref a,
|
||||
.jio-footnote-back {
|
||||
color: var(--theme-color, #267665);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.jio-footnote-ref a:hover,
|
||||
.jio-footnote-back:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 编辑器插入的轻量附件链接 */
|
||||
.wp-posts-content .jio-file-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.08));
|
||||
border-radius: var(--mini-radius, 6px);
|
||||
background: var(--muted-bg-2-color, #f6f7f8);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wp-posts-content .jio-file-link i {
|
||||
color: var(--theme-color, #267665);
|
||||
}
|
||||
|
||||
/* 目录占位渲染成文章目录卡片时,卡片自带间距,这里只补上下留白。 */
|
||||
.wp-posts-content .posts-nav-box {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
/* source: assets/css/frontend/single-post/author-actions.css */
|
||||
/* ===== Content 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
|
||||
@@ -340,3 +340,76 @@ article.article.box-body,
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* ---------- 正文扩展块:公式、脚注、附件、目录 ---------- */
|
||||
|
||||
.jio-math {
|
||||
display: inline-block;
|
||||
padding: 0 2px;
|
||||
font-family: "Cambria Math", "Latin Modern Math", "Times New Roman", serif;
|
||||
font-size: 1.02em;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.jio-math--block {
|
||||
display: block;
|
||||
margin: 12px 0 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.jio-footnotes {
|
||||
margin-top: 32px;
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.06));
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.jio-footnotes__title {
|
||||
margin: 0 0 10px;
|
||||
color: var(--muted-color, #777);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.jio-footnotes__list {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
color: var(--muted-color, #777);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.jio-footnotes__list li {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.jio-footnote-ref a,
|
||||
.jio-footnote-back {
|
||||
color: var(--theme-color, #267665);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.jio-footnote-ref a:hover,
|
||||
.jio-footnote-back:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 编辑器插入的轻量附件链接 */
|
||||
.wp-posts-content .jio-file-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--main-border-color, rgba(50, 50, 50, 0.08));
|
||||
border-radius: var(--mini-radius, 6px);
|
||||
background: var(--muted-bg-2-color, #f6f7f8);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wp-posts-content .jio-file-link i {
|
||||
color: var(--theme-color, #267665);
|
||||
}
|
||||
|
||||
/* 目录占位渲染成文章目录卡片时,卡片自带间距,这里只补上下留白。 */
|
||||
.wp-posts-content .posts-nav-box {
|
||||
margin: 20px 0;
|
||||
}
|
||||
@@ -324,6 +324,9 @@ if (class_exists('\Jio\Core\CreatorRest')) {
|
||||
if (class_exists('\Jio\Core\CreatorStudio')) {
|
||||
\Jio\Core\CreatorStudio::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\Jio\Content\ContentBlocks')) {
|
||||
\Jio\Content\ContentBlocks::get_instance()->init();
|
||||
}
|
||||
if (class_exists('\Jio\Content\PrivateAssets')) {
|
||||
\Jio\Content\PrivateAssets::get_instance()->init();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* 正文扩展块的渲染:目录、脚注、公式。
|
||||
*
|
||||
* 编辑器插入的都是 HTML/文本占位,读者端由这里转换成真正的渲染结果:
|
||||
* - 目录:占位替换成前台 section-navs 识别的 .posts-nav-box 卡片;
|
||||
* - 脚注:[^1] 引用与 [^1]: 定义转换成脚注列表并重新编号;
|
||||
* - 公式:.jio-math 内的 ^ / _ 转成上标下标,给出可读的数学排版。
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
namespace Jio\Content;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class ContentBlocks {
|
||||
|
||||
/** 编辑器插入的目录占位 class。 */
|
||||
public const TOC_CLASS = 'jio-toc-placeholder';
|
||||
|
||||
private static $instance = null;
|
||||
|
||||
public static function get_instance(): self {
|
||||
if (null === self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function init(): void {
|
||||
add_filter('the_content', [$this, 'render_blocks'], 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* 正文里是否插入了目录占位(用于决定是否加载目录脚本)。
|
||||
*/
|
||||
public static function has_toc_placeholder(string $content): bool {
|
||||
return strpos($content, self::TOC_CLASS) !== false;
|
||||
}
|
||||
|
||||
public function render_blocks(string $content): string {
|
||||
if ($content === '' || is_admin()) {
|
||||
return $content;
|
||||
}
|
||||
$content = $this->render_toc($content);
|
||||
$content = $this->render_footnotes($content);
|
||||
return $this->render_math($content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 目录占位 → 文章目录卡片。
|
||||
*/
|
||||
private function render_toc(string $content): string {
|
||||
if (!self::has_toc_placeholder($content)) {
|
||||
return $content;
|
||||
}
|
||||
$box = '<div class="widget-container"><div class="posts-nav-box" data-title="'
|
||||
. esc_attr__('本文目录', 'jio')
|
||||
. '"></div></div>';
|
||||
$pattern = '/<div[^>]*class="[^"]*' . preg_quote(self::TOC_CLASS, '/') . '[^"]*"[^>]*>\s*<\/div>\s*(?:<p>\s*<\/p>)?/i';
|
||||
$replaced = preg_replace($pattern, $box, $content, 1);
|
||||
return is_string($replaced) ? $replaced : $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Markdown 脚注:[^1] 引用与 [^1]: 定义。
|
||||
*/
|
||||
private function render_footnotes(string $content): string {
|
||||
if (strpos($content, '[^') === false) {
|
||||
return $content;
|
||||
}
|
||||
if (!preg_match_all('/\[\^([^\]]+)\]:[ \t]*([^\n<]+)/u', $content, $defs, PREG_SET_ORDER)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$notes = [];
|
||||
foreach ($defs as $def) {
|
||||
$notes[(string) $def[1]] = trim((string) $def[2]);
|
||||
$content = str_replace($def[0], '', $content);
|
||||
}
|
||||
if ($notes === []) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
// 按首次引用顺序编号,而不是按定义顺序。
|
||||
$order = [];
|
||||
$result = preg_replace_callback('/\[\^([^\]]+)\]/u', static function (array $match) use (&$order): string {
|
||||
$key = (string) $match[1];
|
||||
if (!isset($order[$key])) {
|
||||
$order[$key] = count($order) + 1;
|
||||
}
|
||||
$index = $order[$key];
|
||||
return '<sup class="jio-footnote-ref"><a href="#jio-fn-' . $index . '" id="jio-fnref-' . $index . '">'
|
||||
. $index
|
||||
. '</a></sup>';
|
||||
}, $content);
|
||||
if (!is_string($result)) {
|
||||
return $content;
|
||||
}
|
||||
$content = $result;
|
||||
|
||||
$items = '';
|
||||
foreach ($order as $key => $index) {
|
||||
if (!isset($notes[$key])) {
|
||||
continue;
|
||||
}
|
||||
$items .= '<li id="jio-fn-' . $index . '">'
|
||||
. esc_html($notes[$key])
|
||||
. ' <a class="jio-footnote-back" href="#jio-fnref-' . $index . '" aria-label="'
|
||||
. esc_attr__('返回正文', 'jio')
|
||||
. '">↩</a></li>';
|
||||
}
|
||||
if ($items === '') {
|
||||
return $content;
|
||||
}
|
||||
|
||||
return $content
|
||||
. '<section class="jio-footnotes"><h4 class="jio-footnotes__title">'
|
||||
. esc_html__('注释', 'jio')
|
||||
. '</h4><ol class="jio-footnotes__list">' . $items . '</ol></section>';
|
||||
}
|
||||
|
||||
/**
|
||||
* 公式:把 .jio-math 片段里的 ^ 与 _ 转成真正可见的上标下标。
|
||||
*
|
||||
* 主题没有内置 TeX 引擎,这里只做确定性的公式排版,不猜测其它语法。
|
||||
*/
|
||||
private function render_math(string $content): string {
|
||||
if (strpos($content, 'jio-math') === false) {
|
||||
return $content;
|
||||
}
|
||||
$result = preg_replace_callback(
|
||||
'#<span([^>]*\bclass="[^"]*jio-math[^"]*"[^>]*)>(.*?)</span>#is',
|
||||
static function (array $match): string {
|
||||
$inner = esc_html(html_entity_decode($match[2], ENT_QUOTES, 'UTF-8'));
|
||||
$inner = (string) preg_replace('/\^\{([^{}]*)\}|\^(\S)/u', '<sup>$1$2</sup>', $inner);
|
||||
$inner = (string) preg_replace('/_\{([^{}]*)\}|_(\S)/u', '<sub>$1$2</sub>', $inner);
|
||||
return '<span' . $match[1] . '>' . $inner . '</span>';
|
||||
},
|
||||
$content
|
||||
);
|
||||
return is_string($result) ? $result : $content;
|
||||
}
|
||||
}
|
||||
@@ -153,8 +153,6 @@ final class CreatorRest {
|
||||
'titlePlaceholder' => (string) $settings['title_placeholder'],
|
||||
'contentPlaceholder' => (string) $settings['content_placeholder'],
|
||||
],
|
||||
// 作者行数据由服务端给出,前端不自行拼装昵称/头像。
|
||||
'author' => $this->author_payload($editable, $user_id),
|
||||
'privateUploads' => $this->private_upload_rows($user_id, $post_id),
|
||||
'privateStorage' => PrivateAssets::is_available(),
|
||||
'media' => [
|
||||
@@ -566,37 +564,6 @@ final class CreatorRest {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 作者行数据:昵称、头像与身份说明。
|
||||
*
|
||||
* @return array{name:string,avatar:string,meta:string}
|
||||
*/
|
||||
private function author_payload(?\WP_Post $editable, int $user_id): array {
|
||||
$author_id = $editable instanceof \WP_Post ? (int) $editable->post_author : $user_id;
|
||||
$user = $author_id > 0 ? get_userdata($author_id) : null;
|
||||
if (!$user) {
|
||||
return ['name' => '', 'avatar' => '', 'meta' => ''];
|
||||
}
|
||||
|
||||
$avatar = function_exists('jio_get_avatar_url') ? (string) jio_get_avatar_url($author_id, ['size' => 64]) : '';
|
||||
if ($avatar === '' && function_exists('get_avatar_url')) {
|
||||
$avatar = (string) get_avatar_url($author_id, ['size' => 64]);
|
||||
}
|
||||
|
||||
$meta = '';
|
||||
if ($author_id !== $user_id) {
|
||||
$meta = __('原作者', 'jio');
|
||||
} elseif (function_exists('jio_get_user_vip_level') && (int) jio_get_user_vip_level($user_id) > 0) {
|
||||
$meta = sprintf(__('VIP%s', 'jio'), (string) max(1, min(2, (int) jio_get_user_vip_level($user_id))));
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => (string) $user->display_name,
|
||||
'avatar' => $avatar,
|
||||
'meta' => $meta,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $post_meta
|
||||
* @return array<string, mixed>
|
||||
|
||||
@@ -103,7 +103,10 @@ trait ScriptsPart1c {
|
||||
}
|
||||
|
||||
$requirements['is_article'] = $is_article;
|
||||
$requirements['has_toc'] = $is_article && $article_nav_enabled;
|
||||
// 用户在正文里插入了目录块时,即使关闭了侧栏目录树也要加载目录脚本。
|
||||
$has_inline_toc = class_exists('\Jio\Content\ContentBlocks')
|
||||
&& \Jio\Content\ContentBlocks::has_toc_placeholder($content);
|
||||
$requirements['has_toc'] = $is_article && ($article_nav_enabled || $has_inline_toc);
|
||||
$requirements['has_video'] = $has_video;
|
||||
$requirements['has_hidden_content'] = $has_hidden_content || $has_yiyan || $has_inserted_content;
|
||||
$requirements['needs_pay_box'] = $is_paid_content_single || (bool) preg_match('/\[(?:payshow|hidecontent)\b|wp:jio\/paid-fragment/i', $content);
|
||||
|
||||
@@ -150,7 +150,7 @@ test('fixed top toolbar has TinyMCE formatting, media, and extension actions', (
|
||||
assert.match(appSource, /undoManager\.undo\(\)/);
|
||||
assert.match(appSource, /undoManager\.redo\(\)/);
|
||||
assert.match(appSource, /editor\.formatter\.toggle\(value \|\| 'h2'\)/);
|
||||
assert.match(editorToolbar, /value=\{formatValue\}/);
|
||||
assert.match(editorToolbar, /value=\{formatValue === 'blockquote' \? 'p' : formatValue\}/);
|
||||
assert.match(editorToolbar, /canUndo/);
|
||||
assert.match(editorToolbar, /canRedo/);
|
||||
assert.match(editorToolbar, /onCommand\('Bold'\)/);
|
||||
@@ -158,6 +158,19 @@ 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.doesNotMatch(editorToolbar, /<option value="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, /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(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(appSource, /保存草稿/);
|
||||
assert.match(appSource, /<SaveState value=\{saveState\}/);
|
||||
assert.match(appSource, /draftRef\.current\.postId/);
|
||||
@@ -237,7 +250,15 @@ test('scheduled publishing is validated and applied server side', () => {
|
||||
assert.match(rest, /'canSchedule'\s*=>\s*\$cap\('new_post_audit_no'\)/);
|
||||
});
|
||||
|
||||
test('Chinese character count strips markup and counts visible text', () => {
|
||||
assert.match(appSource, /Array\.from\(plainText\(draft\.content\)\)\.length/);
|
||||
assert.match(appSource, /container\.textContent/);
|
||||
test('the writing card has no author strip above the title', () => {
|
||||
assert.doesNotMatch(appSource, /AuthorRow/);
|
||||
assert.doesNotMatch(appSource, /原创内容/);
|
||||
assert.doesNotMatch(read('assets/creator-studio/src/styles/studio.scss'), /jio-creator__author/);
|
||||
});
|
||||
|
||||
test('the writing surface has no character or reading-time footer', () => {
|
||||
assert.doesNotMatch(appSource, /jio-creator__footer/);
|
||||
assert.doesNotMatch(appSource, /分钟阅读/);
|
||||
assert.doesNotMatch(appSource, /charCount/);
|
||||
assert.doesNotMatch(read('assets/creator-studio/src/styles/studio.scss'), /\.jio-creator__footer/);
|
||||
});
|
||||
@@ -708,7 +708,7 @@ test('single articles enqueue only content and settings driven assets', () => {
|
||||
assert.match(singleBlock, new RegExp(`singular_assets\\['${requirement}'\\]`));
|
||||
}
|
||||
|
||||
assert.match(runtime, /\$requirements\['has_toc'\] = \$is_article && \$article_nav_enabled/);
|
||||
assert.match(runtime, /\$requirements\['has_toc'\] = \$is_article && \(\$article_nav_enabled \|\| \$has_inline_toc\)/);
|
||||
assert.match(runtime, /data-jio-video/);
|
||||
assert.match(runtime, /post_password_required\(\$post_id\)/);
|
||||
assert.match(runtime, /comment_settings\['close_comments'\]/);
|
||||
|
||||
@@ -43,7 +43,10 @@ test('TOC script enqueue follows article visibility settings and per-post overri
|
||||
assert.match(scripts, /\$article_nav_enabled = !array_key_exists\('article_nav', \$settings\) \|\| !empty\(\$settings\['article_nav'\]\)/);
|
||||
assert.match(scripts, /get_post_meta\(\$post_id, DomainModel::POST_META_KEY, true\)/);
|
||||
assert.match(scripts, /empty\(\$extension_meta\['no_article-navs'\]\)/);
|
||||
assert.match(scripts, /\$requirements\['has_toc'\] = \$is_article && \$article_nav_enabled/);
|
||||
// 正文里插入目录块时也要加载脚本,因此条件是「设置开启」或「正文含占位」。
|
||||
assert.match(scripts, /\$has_inline_toc = class_exists\(/);
|
||||
assert.match(scripts, /ContentBlocks::has_toc_placeholder\(\$content\)/);
|
||||
assert.match(scripts, /\$requirements\['has_toc'\] = \$is_article && \(\$article_nav_enabled \|\| \$has_inline_toc\)/);
|
||||
assert.doesNotMatch(scripts, /preg_match_all\('\/<h\[1-4\]/);
|
||||
assert.match(scripts, /\$singular_assets\['has_toc'\][\s\S]*?'jio-section-navs'/);
|
||||
|
||||
|
||||
Reference in new issue
Block a user