匿名
未登录
中文(中国大陆)
创建账号
登录
Limbo Wiki
搜索
这是测试站点!!!!
不要保存重要内容!不要保存重要内容!不要保存重要内容!不要保存重要内容!不要保存重要内容!
查看“︁微件:PD卡牌编辑器”︁的源代码
来自Limbo Wiki
命名空间
微件
讨论
更多
更多
页面操作
中文(中国大陆)
阅读
查看源代码
历史
←
微件:PD卡牌编辑器
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于这些用户组的用户执行:
管理员
、
邮箱已验证用户
、FullUser
您没有权限编辑
微件
命名空间内的页面。
您可以查看和复制此页面的源代码。
<script> /* PD workbench source operations: no DOM, network or external dependencies. */ (function (root) { 'use strict'; function fail(message, code) { var e = new Error(message); e.code = code || 'validation'; throw e; } function norm(s) { return s.trim().replace(/_/g, ' ').replace(/^(?:Template|模板):/i, '').toLowerCase(); } function scan(text, tolerant) { var stack = [], templates = [], comments = [], topPipes = [], ranges = []; for (var i = 0; i < text.length;) { if (text.slice(i,i+4) === '<!--') { var end = text.indexOf('-->',i+4); if(end<0)fail('注释没有闭合'); comments.push({start:i,end:end+3,text:text.slice(i,end+3),depth:stack.length}); i=end+3; continue; } var tag = text.slice(i).match(/^<(nowiki|pre|source|syntaxhighlight|math)\b[^>]*>/i); if(tag) { var protectedStart=i; if(/\/\s*>$/.test(tag[0])) {i+=tag[0].length;ranges.push({start:protectedStart,end:i});continue;} var close=new RegExp('</'+tag[1]+'\\s*>','ig');close.lastIndex=i+tag[0].length;var match=close.exec(text);if(!match)fail(tag[1]+' 没有闭合');i=match.index+match[0].length;ranges.push({start:protectedStart,end:i});continue; } var top=stack[stack.length-1], token=''; if(text.slice(i,i+3)==='{{{')token='arg';else if(text.slice(i,i+2)==='{{')token='template';else if(text.slice(i,i+2)==='[[')token='link'; if(token) {var node={kind:token,start:i,parent:top||null,pipes:[],equals:[]};stack.push(node);i+=token==='arg'?3:2;continue;} if(top&&((top.kind==='arg'&&text.slice(i,i+3)==='}}}')||(top.kind==='template'&&text.slice(i,i+2)==='}}')||(top.kind==='link'&&text.slice(i,i+2)===']]'))) { var len=top.kind==='arg'?3:2;top.close=i;top.end=i+len;stack.pop();ranges.push(top);if(top.kind==='template')templates.push(top);i+=len;continue; } if(text.slice(i,i+2)==='}}'||text.slice(i,i+2)===']]') {if(!tolerant)fail('存在没有对应起始符的模板或链接结束符');i+=2;continue;} if(text[i]==='|') {if(top)top.pipes.push(i);else topPipes.push(i);} if(text[i]==='='&&top)top.equals.push(i); i++; } if(stack.length)fail('模板或链接没有闭合'); templates.sort(function(a,b){return a.start-b.start;}); templates.forEach(function(t){ t.name=text.slice(t.start+2,t.pipes.length?t.pipes[0]:t.close).trim(); t.params=[]; t.pipes.forEach(function(p,j){var end=j+1<t.pipes.length?t.pipes[j+1]:t.close;var eq=t.equals.find(function(e){return e>p&&e<end;}); t.params.push({name:eq===undefined?String(j+1):text.slice(p+1,eq).trim(),value:text.slice(eq===undefined?p+1:eq+1,end),start:p,end:end,valueStart:eq===undefined?p+1:eq+1});}); }); return {templates:templates,comments:comments,topPipes:topPipes,ranges:ranges}; } function param(t,n) {var p=t.params.filter(function(p){return p.name===n;});if(p.length>1)fail('参数重复:'+n);return p[0];} function val(t,n){var p=param(t,n);return p?p.value.trim():'';} function sections(source,parsed) { var starts={},ends={},list=[]; parsed.comments.filter(function(c){return c.depth===0;}).forEach(function(c){var m=c.text.match(/^<!-- pd-workbench:section:([a-z0-9-]+):(start|end) -->$/);if(!m)return;var map=m[2]==='start'?starts:ends;if(map[m[1]])fail('栏目定位标记重复');map[m[1]]=c;}); Object.keys(starts).forEach(function(id){var s=starts[id],e=ends[id];if(!e||e.start<=s.end)fail('栏目边界损坏');list.push({id:id,start:s.end,end:e.start});}); if(Object.keys(ends).length!==list.length)fail('栏目边界不完整'); list.sort(function(a,b){return a.start-b.start;});for(var i=1;i<list.length;i++)if(list[i].start<list[i-1].end)fail('栏目边界重叠'); return list; } function inventory(source) { var parsed=scan(source,true), secs=sections(source,parsed), packs=[], ids={}; var headings=[], re=/^(={2,6})\s*(.*?)\s*\1\s*$/gm,m; while((m=re.exec(source))) {if(parsed.templates.some(function(t){return t.start<m.index&&t.end>m.index;}))continue;headings.push({start:m.index,level:m[1].length,text:m[2]});} function path(pos){var stack=[];headings.filter(function(h){return h.start<pos;}).forEach(function(h){while(stack.length&&stack[stack.length-1].level>=h.level)stack.pop();stack.push(h);});return stack.map(function(h){return h.text;}).join(' › ');} parsed.templates.filter(function(t){return norm(t.name)==='pd卡包';}).forEach(function(t){ var id=val(t,'标识');if(!id)fail('卡包缺少稳定标识,请联系维护者登记');if(ids[id])fail('卡包标识重复:'+id);ids[id]=true; var cp=param(t,'卡牌');if(!cp)fail('卡包缺少卡牌参数'); var cards=parsed.templates.filter(function(c){return norm(c.name)==='pd卡牌'&&c.start>=cp.valueStart&&c.end<=cp.end&&c.parent===t;}); var number=val(t,'编号'),series=val(t,'系列')||(/^PHIORD\./.test(number)?'PHIORD':/^SUBSPC\./.test(number)?'SUBSPC':'BDRGCP'); packs.push({id:id,number:number,name:val(t,'名称'),series:series,path:path(t.start),node:t,cards:cards,param:cp}); }); secs.forEach(function(s){s.label=path(s.start);s.series=/^series-(phiord|subspc|bdrgcp)$/.test(s.id)?s.id.slice(7).toUpperCase():'';}); return {source:source,parsed:parsed,packs:packs,sections:secs,cards:parsed.templates.filter(function(t){return norm(t.name)==='pd卡牌';})}; } function validateField(value,label,plain) { value=String(value||'');var p; try {p=scan(value);}catch(e){fail(label+':'+e.message);} if(p.topPipes.length)fail(label+':裸竖线请写成 {{!}}'); var visible=value.replace(/<nowiki\b[^>]*>[\s\S]*?<\/nowiki\s*>|<nowiki\s*\/>/gi,''); if(/<!--\s*pd-workbench:|__NOTOC__|__TOC__|\{\||\|\}|^\s*={2,}/im.test(visible))fail(label+':不能包含栏目、表格或工作台控制标记'); if(p.templates.some(function(t){return /^(?:pd卡牌|pd卡包|#widget\s*:|#tag\s*:)/i.test(norm(t.name));}))fail(label+':不能嵌入卡牌、卡包或微件'); if(plain&&/[<>\[\]{}\r\n]/.test(value))fail(label+':此项请填写单行文字'); var clean=value.replace(/<!--[\s\S]*?-->/g,'').replace(/<nowiki\b[^>]*>[\s\S]*?<\/nowiki\s*>|<nowiki\s*\/>/gi,''); var tags=[], r=/<\/?([a-z][\w:-]*)\b[^>]*>/gi,mt; while((mt=r.exec(clean))) { var name=mt[1].toLowerCase();if(!['br','b','strong','i','em','u','s','del','small','big','span','ruby','rt','rp','sub','sup','code'].includes(name))fail(label+':不支持标签 '+name); if(name==='br'||/\/\s*>$/.test(mt[0]))continue; if(/^<\//.test(mt[0])){if(tags.pop()!==name)fail(label+':HTML标签不成对');}else tags.push(name); } if(tags.length)fail(label+':HTML标签没有闭合'); if(/<\s*\/?(?:script|style|div|includeonly|noinclude|onlyinclude|iframe)\b/i.test(value))fail(label+':不支持结构或脚本标签'); return value; } function suggestion(inv,number,kind,derived) { if(derived)return '';var m=number.match(/^(PHIORD|SUBSPC|BDRGCP)\.(\d+)$/);if(!m)return ''; var prefix=({PHIORD:'P',SUBSPC:'S',BDRGCP:'B'}[m[1]])+m[2].padStart(2,'0'),k={角色:'C',置物:'P',行动:'A',反击:'T'}[kind],max=-1; inv.packs.filter(function(p){return p.number===number;}).forEach(function(p){p.cards.forEach(function(c){ var n=val(c,'编号'),a=n.match(new RegExp('^'+prefix+'-'+k+'(\\d+)$')),b=n.match(new RegExp('^'+k+'-'+prefix+'(\\d+)$')); if(a||b)max=Math.max(max,Number((a||b)[1])); var malformed=n.match(new RegExp('^'+prefix+'-(\\d+)$'));if(malformed&&val(c,'类型')===kind)max=Math.max(max,Number(malformed[1])); });});return prefix+'-'+k+String(max+1).padStart(2,'0'); } function entryText(d,key,label) { if(!Array.isArray(d[key+'Items']))return (d[key]||'').replace(/\r?\n/g,'<br>'); return d[key+'Items'].filter(function(v){return v.trim();}).map(function(v,i){ validateField(v,label+' '+(i+1));var parsed=scan(v),ranges=parsed.templates.concat(parsed.comments); var protectedTags=/<(nowiki|pre|source|syntaxhighlight|math)\b[^>]*>[\s\S]*?<\/\1\s*>/gi,m; while((m=protectedTags.exec(v)))ranges.push({start:m.index,end:m.index+m[0].length}); return v.replace(/\r?\n/g,function(line,index){return ranges.some(function(r){return r.start<=index&&index<r.end;})?line:'<br>';}); }).join('<br>'); } function createCard(d) { if(!d.name.trim())fail('请填写卡名');if(!['角色','置物','行动','反击'].includes(d.kind))fail('请选择卡片类型');if(!['C','B','A','S'].includes(d.rarity))fail('请选择稀有度'); if(!/^pd-wb-[a-z0-9-]+$/.test(d.anchor))fail('投稿标识不合法'); var fields={类型:d.kind,名称:d.name,稀有度:d.rarity,编号:d.number,KB:d.kb,锚点:d.anchor}; if(d.derived)fields.异想='是'; var noninstant=['角色','置物'].includes(d.kind); if(noninstant){fields.EP=d.ep;fields.属性=entryText(d,'attributes','属性');if(d.kind==='角色'){fields.BP=d.bp;fields.援护=entryText(d,'support','援护对象');}else {fields.承伤=d.ic;fields.移动=d.movement;} var skills=[];(d.skills||[]).forEach(function(s,i){if(!s.type.trim()||!s.description.trim())fail('技能 '+(i+1)+':类型与描述必填');var sf={类型:s.type,名称:s.name,描述:s.description,KB:s.kb};Object.keys(sf).forEach(function(k){validateField(sf[k],'技能 '+(i+1)+' '+k,k==='类型');});skills.push(template('PD技能',sf));}); validateField(d.preface||'','技能前置说明');fields.技能=(d.preface||'')+(skills.length?'\n'+skills.join('\n'):''); }else fields.效果=d.effect; fields.背景=d.background; Object.keys(fields).filter(function(k){return k!=='技能';}).forEach(function(k){validateField(fields[k],k,['类型','编号','稀有度','EP','BP','承伤','KB','锚点','移动'].includes(k));}); var text=template('PD卡牌',fields),parsed=scan(text);if(parsed.templates.filter(function(t){return norm(t.name)==='pd卡牌';}).length!==1||parsed.templates.some(function(t){return norm(t.name)==='pd卡包';}))fail('卡牌结构校验失败');return text; } function template(name,fields){return '{{'+name+'\n'+Object.keys(fields).filter(function(k){return fields[k]!==undefined&&String(fields[k]).trim()!=='';}).map(function(k){return '|'+k+'='+fields[k];}).join('\n')+'\n}}';} function propose(inv,d,allowed) { if(inv.cards.some(function(c){return val(c,'锚点')===d.anchor;}))return {already:true}; var card=createCard(d), pack,group; if(d.mode==='new') { validateField(d.packName,'卡包名称',true);validateField(d.packNumber,'卡包编号',true);if(!d.packName.trim())fail('请填写新卡包名称'); if(d.packNumber&&inv.packs.some(function(p){return p.number.toLowerCase()===d.packNumber.trim().toLowerCase();}))fail('该卡包编号已存在','pack-conflict'); if(inv.packs.some(function(p){return p.name&&p.name===d.packName.trim();}))fail('该卡包名称已存在','pack-conflict'); if(!allowed.includes(d.section))fail('该栏目不允许新建卡包');group=inv.sections.find(function(s){return s.id===d.section;});if(!group)fail('目标栏目已消失'); if(group.series&&d.packNumber){var ns=/^PHIORD\./.test(d.packNumber)?'PHIORD':/^SUBSPC\./.test(d.packNumber)?'SUBSPC':'BDRGCP';if(ns!==group.series)fail('卡包编号与所选系列不一致');} pack={id:d.anchor+'-pack',number:d.packNumber,name:d.packName,series:group.series||'',cards:[]}; }else {pack=inv.packs.find(function(p){return p.id===d.packId;});if(!pack)fail('目标卡包已消失');} var related=pack.number?inv.packs.filter(function(p){return p.number===pack.number;}):[pack],cs=related.flatMap(function(p){return p.cards;}); if(d.number&&cs.some(function(c){return val(c,'编号').toLowerCase()===d.number.trim().toLowerCase();}))fail('编号已被占用,请重新预览并确认编号','number-conflict'); if(cs.some(function(c){return val(c,'名称')===d.name.trim()&&val(c,'类型')===d.kind;})&&!d.duplicateConfirmed)fail('同包已有同名同类型卡;如需创建变体,请勾选确认','duplicate-name'); var insert,position; if(d.mode==='new'){position=group.end;insert='\n'+template('PD卡包',{标识:pack.id,系列:pack.series,编号:d.packNumber,名称:d.packName,卡牌:'\n'+card+'\n'})+'\n';} else {position=pack.param.end;insert='\n'+card+'\n';} var source=inv.source.slice(0,position)+insert+inv.source.slice(position),next=inventory(source); if(next.cards.length!==inv.cards.length+1||next.packs.length!==inv.packs.length+(d.mode==='new'?1:0))fail('插入后卡牌或卡包数量异常'); return {source:source,card:card,pack:pack,position:position,insert:insert,count:next.cards.length}; } // Editing is deliberately separate from new-card generation: unchanged source is never regenerated. var cardFields={name:'名称',kind:'类型',rarity:'稀有度',derived:'异想',number:'编号',ep:'EP',bp:'BP',ic:'承伤',kb:'KB',movement:'移动',attributes:'属性',support:'援护',effect:'效果',background:'背景'}; function splitEntries(text) { if(!text.trim())return []; var p=scan(text),ranges=p.ranges.concat(p.comments),re=/<\/?([a-z][\w:-]*)\b[^>]*>/gi,m,depth=0,last=0,out=[]; while((m=re.exec(text))){if(ranges.some(function(r){return r.start<=m.index&&m.index<r.end;}))continue; var name=m[1].toLowerCase();if(name==='br'){if(!depth){out.push(text.slice(last,m.index));last=re.lastIndex;}}else if(!/\/\s*>$/.test(m[0]))depth=Math.max(0,depth+(/^<\//.test(m[0])?-1:1)); }out.push(text.slice(last));return out; } function readCard(raw) { var p=scan(raw),node=p.templates.find(function(t){return !t.parent&&norm(t.name)==='pd卡牌';}); if(!node||node.start!==0||node.end!==raw.length)fail('卡牌不是独立的PD卡牌模板,请使用源码编辑器。','unsupported'); node.params.forEach(function(x){param(node,x.name);}); var d={skills:[],preface:''};Object.keys(cardFields).forEach(function(k){d[k]=val(node,cardFields[k]);}); d.derived=d.derived==='是'; if(!['角色','置物','行动','反击'].includes(d.kind)||(!d.derived&&val(node,'异想')))fail('此卡类型或异想结构暂不能无损回填,请使用源码编辑器。','unsupported'); ['attributes','support'].forEach(function(k){d[k+'Items']=splitEntries(d[k]);}); var sp=param(node,'技能'); if(sp){var text=sp.value.trim(),q=scan(text),skills=q.templates.filter(function(t){return !t.parent&&norm(t.name)==='pd技能';}); d.preface=text.slice(0,skills.length?skills[0].start:text.length).trim(); skills.forEach(function(t,i){t.params.forEach(function(x){param(t,x.name);});var tail=text.slice(t.end,i+1<skills.length?skills[i+1].start:text.length);if(tail.trim())fail('技能之间或末尾包含独立文字,请使用源码编辑器避免丢失。','unsupported'); d.skills.push({type:val(t,'类型'),name:val(t,'名称'),description:val(t,'描述'),kb:val(t,'KB'),_source:text.slice(t.start,t.end)}); }); }return d; } function patchTemplate(raw,changes) { var t=scan(raw).templates[0],patches=[],extra=[]; Object.keys(changes).forEach(function(n){var value=changes[n],p=param(t,n);if(value===null){if(p)patches.push({start:p.start,end:p.end,text:''});return;} if(!p){if(String(value).trim())extra.push('|'+n+'='+value+'\n');return;} var lead=p.value.match(/^\s*/)[0],trail=p.value.match(/\s*$/)[0];if(!p.value.trim()){lead='';trail=/\n\s*$/.test(p.value)?'\n':'';} patches.push({start:p.valueStart,end:p.end,text:lead+value+trail}); });if(extra.length)patches.push({start:t.close,end:t.close,text:'\n'+extra.join('')}); patches.sort(function(a,b){return b.start-a.start;}).forEach(function(p){raw=raw.slice(0,p.start)+p.text+raw.slice(p.end);});return raw; } function skillValues(s){return [s.type||'',s.name||'',s.description||'',s.kb||''];} function updateCard(raw,d,fixedAnchor) { var old=readCard(raw),changes={},labels=[],removed=[],non=['角色','置物'].includes(d.kind); var applicable=['name','kind','rarity','derived','number','kb','background'].concat(non?['ep','attributes']:['effect']).concat(d.kind==='角色'?['bp','support']:d.kind==='置物'?['ic','movement']:[]); if(!d.name.trim()||!['角色','置物','行动','反击'].includes(d.kind))fail('请填写卡名并选择卡牌类型'); Object.keys(cardFields).forEach(function(k){var field=cardFields[k],a=k==='derived'?(old.derived?'是':''):old[k],b=k==='derived'?(d.derived?'是':''):(d[k]||'');if(['attributes','support'].includes(k))b=JSON.stringify(d[k+'Items'])===JSON.stringify(old[k+'Items'])?a:entryText(d,k,k==='attributes'?'属性':'援护对象'); if(!applicable.includes(k)){if(old.kind!==d.kind&&a.trim()){changes[field]=null;removed.push(field);}return;} if(a!==b){validateField(b,field,['类型','编号','稀有度','EP','BP','承伤','KB','移动'].includes(field));changes[field]=b;labels.push(field);} }); var skillChanged=old.preface!==(d.preface||'')||JSON.stringify(old.skills.map(skillValues))!==JSON.stringify((d.skills||[]).map(skillValues)); if(non&&skillChanged){var parts=(d.skills||[]).map(function(s){var known=old.skills.find(function(o){return o._source&&o._source===s._source;}),sf={类型:s.type,名称:s.name,描述:s.description,KB:s.kb}; if(!known){Object.keys(sf).forEach(function(k){validateField(sf[k],'技能 '+k);});return template('PD技能',sf);}var edits={};['type','name','description','kb'].forEach(function(k){if((known[k]||'')!==(s[k]||'')){validateField(s[k],'技能 '+k);edits[({type:'类型',name:'名称',description:'描述',kb:'KB'})[k]]=s[k]||'';}});return patchTemplate(known._source,edits); });if(old.preface!==(d.preface||''))validateField(d.preface||'','技能前置说明');changes.技能=(d.preface||'')+(parts.length?'\n'+parts.join('\n'):'');labels.push('技能'); }else if(!non&&old.kind!==d.kind&&val(scan(raw).templates[0],'技能')){changes.技能=null;removed.push('技能');} if(!Object.keys(changes).length)return {card:raw,noop:true,fields:[],removed:[]}; if(!fixedAnchor)fail('无法确认此卡现有锚点,请重新载入或使用源码编辑器。','anchor'); var explicit=val(scan(raw).templates[0],'锚点');if(explicit&&explicit!==fixedAnchor)fail('卡牌锚点不一致','anchor'); if(!explicit)changes.锚点=fixedAnchor; var card=patchTemplate(raw,changes);readCard(card); return {card:card,noop:false,fields:labels,removed:removed}; } function locateCard(inv,identity) { var pack=inv.packs.find(function(p){return p.id===identity.packId;});if(!pack)fail('目标卡包已消失,请重新载入。','target-changed'); var matches=pack.cards.filter(function(c){return identity.explicitAnchor?val(c,'锚点')===identity.explicitAnchor:inv.source.slice(c.start,c.end)===identity.original;}); if(matches.length!==1)fail('目标卡已变化、消失或无法唯一识别;草稿已保留,请重新载入。','target-changed'); var node=matches[0];if(inv.source.slice(node.start,node.end)!==identity.original)fail('目标卡已被他人修改,请重新载入;不会覆盖他人内容。','target-changed'); return {pack:pack,node:node}; } function proposeUpdate(inv,d,identity) { var found=locateCard(inv,identity),result=updateCard(identity.original,d,identity.anchor);result.pack=found.pack; if(result.noop)return result; var peers=(found.pack.number?inv.packs.filter(function(p){return p.number===found.pack.number;}):[found.pack]).flatMap(function(p){return p.cards;}).filter(function(c){return c!==found.node;}); if(d.number&&d.number.trim().toLowerCase()!==val(found.node,'编号').toLowerCase()&&peers.some(function(c){return val(c,'编号').toLowerCase()===d.number.trim().toLowerCase();}))fail('编号已被其他卡牌占用,请修改或清空。','number-conflict'); if(peers.some(function(c){return val(c,'名称')===d.name.trim()&&val(c,'类型')===d.kind;})&&!d.duplicateConfirmed)fail('同包已有同名同类型卡;如需规则变体,请勾选确认。','duplicate-name'); result.source=inv.source.slice(0,found.node.start)+result.card+inv.source.slice(found.node.end);var next=inventory(result.source); if(next.cards.length!==inv.cards.length||next.packs.length!==inv.packs.length||next.cards.filter(function(c){return val(c,'锚点')===identity.anchor;}).length!==1)fail('修改后卡牌、卡包数量或锚点异常。','integrity'); return result; } var api={readCard:readCard,updateCard:updateCard,proposeUpdate:proposeUpdate,locateCard:locateCard,splitEntries:splitEntries,scan:scan,norm:norm,val:val,inventory:inventory,suggestion:suggestion,createCard:createCard,propose:propose,validateField:validateField,template:template}; if(typeof module!=='undefined'&&module.exports)module.exports=api;else root.PDWorkbenchCore=api; })(typeof window==='undefined'?this:window); (function(root){ 'use strict'; function error(message,code){var e=new Error(message);e.code=code;return e;} function extraIssues(before,after){var counts={};before.forEach(function(x){counts[x]=(counts[x]||0)+1;});return after.filter(function(x){if(counts[x]){counts[x]--;return false;}return true;});} function factory(core,io){ return {publish:async function(target,d,allowed,approved){ for(var attempt=0;attempt<2;attempt++){ var current=await io.read(target),inv=core.inventory(current.source); if(inv.cards.some(function(c){return core.val(c,'锚点')===d.anchor;}))return {already:true,revid:current.revid}; if(!current.canEdit)throw error('当前账号不能编辑目标页面;请登录或使用有权限的账号。','permission'); var plan; try{plan=core.propose(inv,d,allowed);}catch(e){ if(e.code==='number-conflict'&&d.numberMode==='auto'){var p=inv.packs.find(function(x){return x.id===d.packId;});e.suggested=core.suggestion(inv,p?p.number:d.packNumber,d.kind,d.derived);}throw e; } var fingerprint=d.mode==='new'?d.section:plan.pack.id+'|'+plan.pack.number+'|'+plan.pack.name+'|'+plan.pack.path; if(approved!==fingerprint)throw error('目标卡包位置或名称已变化,请重新预览。','target-changed'); var before=await io.parse(current.source,target),after=await io.parse(plan.source,target); if(before.count!==inv.cards.length)throw error('原页面解析卡牌数量与源码不一致,请联系维护者检查。','baseline-parse'); var issues=extraIssues(before.issues,after.issues);if(issues.length)throw error('此次提交新增解析异常:'+issues.join(';'),'parse-error'); if(after.count!==before.count+1||after.anchors.filter(function(a){return a===d.anchor;}).length!==1)throw error('完整页面预检未得到预期新卡,可能存在解析容量或模板问题。','parse-error'); try{ var saved=await io.edit(target,plan.source,current.revid,'制卡工作台:'+(d.mode==='new'?'创建卡包并添加':'添加')+' '+(plan.pack.number||plan.pack.name||plan.pack.id)+' / '+d.name); return {revid:saved.revid,oldrevid:current.revid,warnings:before.issues}; }catch(e){ if(e.code==='editconflict'){if(attempt===0)continue;throw error('页面再次发生编辑冲突,请重新预览后提交。','editconflict');} if(e.code==='network'||e.code==='timeout'){ try{var check=await io.read(target),found=core.inventory(check.source).cards.some(function(c){return core.val(c,'锚点')===d.anchor;});if(found)return {revid:check.revid,recovered:true};} catch(checkError){throw error('保存结果暂时无法确认。请保留本页,稍后再确认发布;系统将先检查投稿标识,避免重复添加。','uncertain');} throw error('已核对最新页面,尚未发现本次投稿。可保留草稿后重试。','network'); } throw e; } } } ,update:async function(target,d,identity){ var intended=core.updateCard(identity.original,d,identity.anchor); for(var attempt=0;attempt<2;attempt++){ var current=await io.read(target),inv=core.inventory(current.source); var completed=inv.packs.filter(function(p){return p.id===identity.packId;}).flatMap(function(p){return p.cards;}).filter(function(c){return core.val(c,'锚点')===identity.anchor&¤t.source.slice(c.start,c.end)===intended.card;}); if(!intended.noop&&completed.length===1)return {revid:current.revid,card:intended.card,recovered:true}; if(!current.canEdit)throw error('当前账号不能编辑目标页面,请登录或使用源码编辑器。','permission'); var plan=core.proposeUpdate(inv,d,identity); if(plan.noop)return {noop:true,revid:current.revid,card:identity.original}; var before=await io.parse(current.source,target),after=await io.parse(plan.source,target); if(before.count!==inv.cards.length)throw error('原页面解析卡牌数量与源码不一致。','baseline-parse'); var issues=extraIssues(before.issues,after.issues);if(issues.length)throw error('此次修改新增解析异常:'+issues.join(';'),'parse-error'); if(after.count!==before.count||after.anchors.filter(function(a){return a===identity.anchor;}).length!==1)throw error('修改预检的卡牌数量或锚点异常,未保存。','parse-error'); // Full-page parsing can take seconds. Recheck the target after that wait. var finalCheck=await io.read(target); if(finalCheck.revid!==current.revid){core.locateCard(core.inventory(finalCheck.source),identity);if(!attempt)continue;throw error('页面再次发生变化,请重新预览。','editconflict');} try{ var saved=await io.edit(target,plan.source,current.revid,'制卡工作台:修改 '+(plan.pack.number||plan.pack.name||plan.pack.id)+' / '+d.name); return {revid:saved.revid,oldrevid:current.revid,card:plan.card,warnings:before.issues}; }catch(e){ if(e.code==='editconflict'){if(!attempt)continue;throw error('页面再次发生编辑冲突,请重新预览。','editconflict');} if(e.code==='network'||e.code==='timeout'){ var check;try{check=await io.read(target);}catch(_){throw error('保存结果无法确认,请保留本页后再次确认;系统会先核对目标源码。','uncertain');} var cards=core.inventory(check.source).packs.filter(function(p){return p.id===identity.packId;}).flatMap(function(p){return p.cards;}); var savedCards=cards.filter(function(c){return core.val(c,'锚点')===identity.anchor&&check.source.slice(c.start,c.end)===plan.card;}); if(savedCards.length===1)return {revid:check.revid,recovered:true,card:plan.card}; try{core.locateCard(core.inventory(check.source),identity);}catch(_){throw error('保存后目标源码出现其他变化,请保留草稿并重新载入,不会自动覆盖。','target-changed');} throw error('已核对目标源码尚未修改,可保留草稿后重试。','network'); }throw e; } } } }; } var api={factory:factory,extraIssues:extraIssues};if(typeof module!=='undefined'&&module.exports)module.exports=api;else root.PDWorkbenchService=api; })(typeof window==='undefined'?this:window); (function(){ 'use strict'; function init(host){ if(host.dataset.ready)return;host.dataset.ready='1'; var C=window.PDWorkbenchCore,target=host.dataset.targetEncoded!==undefined?decodeURIComponent(host.dataset.targetEncoded.replace(/\+/g,' ')):host.dataset.target,allowed=(host.dataset.sections||'').split(',').filter(Boolean),inv=null,permission=false,busy=false,approval=null,fields={},skillRows=[],numberMode='auto'; var storageKey='pd-workbench-v1:'+target,serial=0,anchor=unique(),baselineNotice='',entryGroups={},workMode='create',editIdentity=null,editBaseline='',editStale=false,loadedRevision=0,anchorCache=null,modeDrafts={}; function unique(){return 'pd-wb-'+(window.crypto&&crypto.randomUUID?crypto.randomUUID():Date.now().toString(36)+'-'+Math.random().toString(36).slice(2));} function el(tag,text,cls){var n=document.createElement(tag);if(text!==undefined)n.textContent=text;if(cls)n.className=cls;return n;} function btn(text,fn){var b=el('button',text);b.type='button';b.addEventListener('click',fn);return b;} function pageUrl(title,params){var url=window.mw&&mw.util?mw.util.getUrl(title,params||{}):'/index.php?'+new URLSearchParams(Object.assign({title:title},params||{}));return url;} var rsd=document.querySelector('link[rel="EditURI"]'),endpoint=(window.mw&&mw.config?(mw.config.get('wgScriptPath')||''):'')+'/api.php'; if(rsd){var discovered=new URL(rsd.href,location.href);if(discovered.origin===location.origin){discovered.search='';discovered.hash='';endpoint=discovered.href;}} async function api(params){ var controller=new AbortController(),timer=setTimeout(function(){controller.abort();},120000),r; try{r=await fetch(endpoint,{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'},body:new URLSearchParams(Object.assign({format:'json',formatversion:2},params)),signal:controller.signal});if(!r.ok)throw Object.assign(new Error('API请求失败:HTTP '+r.status),{code:'network'});var data=await r.json();if(data.error)throw Object.assign(new Error(data.error.info||data.error.code),{code:data.error.code});return data;} catch(e){if(!e.code||e.name==='AbortError')e.code=e.name==='AbortError'?'timeout':'network';throw e;}finally{clearTimeout(timer);} } async function read(title){var d=await api({action:'query',prop:'revisions|info',rvprop:'ids|content',rvslots:'main',intestactions:'edit',titles:title,meta:'userinfo',uiprop:'rights'}),p=d.query.pages[0];if(!p.revisions)throw Error('目标页面不存在或无法读取');return {source:p.revisions[0].slots.main.content,revid:p.revisions[0].revid,canEdit:!d.query.userinfo.anon&&p.actions&&p.actions.edit===true,user:d.query.userinfo.name};} function parseDocument(html){return new DOMParser().parseFromString(html,'text/html');} async function parse(source,title){var d=await api({action:'parse',text:source,title:title,contentmodel:'wikitext',prop:'text|parsewarnings|limitreportdata',disableeditsection:1}),p=d.parse,doc=parseDocument(p.text),issues=[]; (p.parsewarnings||[]).forEach(function(w){issues.push(typeof w==='string'?w:JSON.stringify(w));});doc.querySelectorAll('.error,.mw-ext-cite-error').forEach(function(n){issues.push(n.textContent.trim());}); (p.limitreportdata||[]).forEach(function(x){if(['limitreport-postexpandincludesize','limitreport-templateargumentsize','limitreport-ppvisitednodes','limitreport-expansiondepth'].includes(x.name)&&Number(x[0])>=Number(x[1]))issues.push(x.name+'达到上限');}); return {resolved:window.pdCards&&window.pdCards.resolveAnchors?window.pdCards.resolveAnchors(doc):null,html:p.text,count:doc.querySelectorAll('.pd-card-ui-card').length,anchors:Array.from(doc.querySelectorAll('.pd-card-ui-card[id]')).map(function(n){return n.id;}),issues:issues}; } async function edit(title,text,rev,summary){var token=await api({action:'query',meta:'tokens'});var d=await api({action:'edit',title:title,text:text,baserevid:rev,token:token.query.tokens.csrftoken,summary:summary,assert:'user',nocreate:1});if(!d.edit||d.edit.result!=='Success')throw Object.assign(new Error(d.edit&&d.edit.captcha?'本站要求验证码,请使用下方原生编辑入口。':'保存被站点拒绝,请检查权限、过滤器或使用原生编辑。'),{code:'edit-rejected'});return {revid:d.edit.newrevid};} var service=window.PDWorkbenchService.factory(C,{read:read,parse:parse,edit:edit}); host.replaceChildren();var status=el('p','正在读取卡包……','pd-wb-status');status.setAttribute('role','status');status.setAttribute('aria-live','polite');host.appendChild(status); var native=el('a','打开目标页面的源码编辑器');native.href=pageUrl(target,{action:'edit'});native.className='pd-wb-native'; var form=el('form');form.addEventListener('submit',function(e){e.preventDefault();});host.appendChild(form); function group(title){var box=el('fieldset',undefined,'pd-wb-group');box.appendChild(el('legend',title));form.appendChild(box);return box;} function row(parent,cls){var n=el('div',undefined,'pd-wb-row '+cls);parent.appendChild(n);return n;} function required(input,on){input.required=on;input.setAttribute('aria-required',String(on));var label=form.querySelector('label[for="'+input.id+'"]'),mark=label&&label.querySelector('.pd-wb-required-marker');if(on&&label&&!mark){mark=el('span',' *','pd-wb-required-marker');mark.setAttribute('aria-hidden','true');label.appendChild(mark);}else if(!on&&mark)mark.remove();if(!on){input.setCustomValidity('');input.removeAttribute('aria-invalid');}} function validate(){var inputs=form.querySelectorAll('input,select,textarea');for(var i=0;i<inputs.length;i++){var n=inputs[i];n.setCustomValidity('');n.removeAttribute('aria-invalid');if(n.required&&!n.value.trim()){var label=form.querySelector('label[for="'+n.id+'"]'),text='请填写'+(label?label.textContent.replace(/\s*\*\s*$/,''):n.getAttribute('aria-label')||'必填项');n.setCustomValidity(text);n.setAttribute('aria-invalid','true');n.focus();if(n.reportValidity)n.reportValidity();throw Error(text);}}} function fieldHeading(wrap,label,input){var head=el('div',undefined,'pd-wb-field-head');head.appendChild(label);wrap.append(head,input);if(input.tagName==='TEXTAREA'){var bar=el('div',undefined,'pd-wb-format');[['加粗',"'''","'''"],['链接','[[',']]'],['换行','<br>','']].forEach(function(x){bar.appendChild(btn(x[0],function(){var a=input.selectionStart,b=input.selectionEnd;input.setRangeText(x[1]+input.value.slice(a,b)+x[2],a,b,'end');input.focus();changed();}));});head.appendChild(bar);}} function addField(parent,key,label,type,options){var wrap=el('div',undefined,'pd-wb-field pd-wb-field-'+key),id='pd-wb-input-'+(++serial)+'-'+anchor;var lab=el('label',label);lab.htmlFor=id;var input; if(type==='select'){input=el('select');(options||[]).forEach(function(o){var opt=el('option',o[1]);opt.value=o[0];input.appendChild(opt);});} else if(type==='textarea'){input=el('textarea');input.rows=['preface','attributes','support'].includes(key)?2:3;}else{input=el('input');input.type=type||'text';} input.id=id;input.dataset.field=key;var hints={packName:'例如:你好像素塔',ep:'例如:1000',bp:'例如:400',kb:'例如:+3 或 -6',preface:'例如:以下技能仅在叠载期间生效。'};if(hints[key])input.placeholder=hints[key];if(type==='checkbox'){wrap.classList.add('pd-wb-check');wrap.append(input,lab);}else fieldHeading(wrap,lab,input);parent.appendChild(wrap);fields[key]=input; return input; } var modeBar=el('div',undefined,'pd-wb-mode');modeBar.setAttribute('role','group');modeBar.setAttribute('aria-label','工作模式');var modeInputs={};[['create','发布'],['edit','修改']].forEach(function(x){var label=el('label'),input=el('input');input.type='radio';input.id='pd-wb-mode-'+x[0]+'-'+anchor;label.htmlFor=input.id;input.name='pd-wb-mode-'+anchor;input.value=x[0];input.checked=x[0]==='create';input.addEventListener('change',function(){if(input.checked)switchMode(x[0]);});label.append(input,document.createTextNode(x[1]));modeBar.appendChild(label);modeInputs[x[0]]=input;});form.appendChild(modeBar); var destination=group('发布位置'),destRow=row(destination,'pd-wb-destination');addField(destRow,'mode','操作','select',[['existing','向已有卡包添加'],['new','新建卡包并添加']]);addField(destRow,'packId','已有卡包','select',[]);addField(destRow,'section','新卡包所属系列','select',[]);var newPackRow=row(destination,'pd-wb-new-pack');addField(newPackRow,'packNumber','新卡包编号');addField(newPackRow,'packName','新卡包名称'); var editRow=row(destination,'pd-wb-edit-target');addField(editRow,'editCard','卡牌','select',[['','请选择卡牌']]);var reloadButton=btn('重新载入卡牌',function(){selectEdit(true);});editRow.appendChild(reloadButton);editRow.hidden=true; var basic=group('卡牌信息'),basicRow=row(basic,'pd-wb-basic');addField(basicRow,'name','卡名');addField(basicRow,'kind','类型','select',[['角色','角色'],['置物','置物'],['行动','行动'],['反击','反击']]);addField(basicRow,'rarity','稀有度','select',Array.from('CBAS').map(function(x){return [x,x];}));addField(basicRow,'derived','异想卡','checkbox');var numberRow=row(basic,'pd-wb-number-row');addField(numberRow,'number','卡牌编号');numberRow.appendChild(btn('重新建议编号',function(){numberMode='auto';suggest();changed();})); var stats=row(basic,'pd-wb-stats');['ep','bp','ic','kb'].forEach(function(k){addField(stats,k,{ep:'EP',bp:'BP',ic:'承伤 IC',kb:'KB'}[k]);});addField(stats,'movement','是否可移动','select',[['','未填写'],['Y','Y'],['N','N']]); var skillBox=group('技能');addField(skillBox,'preface','技能前置说明','textarea');var rows=el('div',undefined,'pd-wb-skills');skillBox.appendChild(rows); function skill(data){data=data||{};var box=el('div',undefined,'pd-wb-skill'),inputs={},head=row(box,'pd-wb-skill-head');box.setAttribute('role','group');box.setAttribute('aria-label','技能'); var basicType=data.type||'被动',active=['主动','双面主动','游标主动','双面游标主动'].includes(basicType),variants=el('div',undefined,'pd-wb-skill-variants'),checks={};if(active)basicType='主动'; ['name','type','kb','description'].forEach(function(k){var wrap=el('div',undefined,'pd-wb-field pd-wb-skill-'+k),id='skill-'+(++serial)+'-'+anchor,label=el('label',({type:'类型',name:'技能名',kb:'KB',description:'完整描述'})[k]);label.htmlFor=id;var n=el(k==='description'?'textarea':k==='type'?'select':'input');if(k==='type'){['登场','退场','被动','主动','特殊'].forEach(function(t){var o=el('option',t);o.value=t;n.appendChild(o);});if(!Array.from(n.options).some(function(o){return o.value===basicType;})){var old=el('option','原草稿:'+basicType);old.value=basicType;n.appendChild(old);}}else if(k!=='description')n.type='text';else n.rows=3;n.id=id;n.value=k==='type'?basicType:(k==='kb'&&data.kbDraft!==undefined?data.kbDraft:(data[k]||''));n.dataset.skillField=k;var hints={name:'例如:渔枪击发',kb:'例如:-6;无消耗填 0',description:'选中文本之后可点击右上角一键加粗等'};if(hints[k])n.placeholder=hints[k];fieldHeading(wrap,label,n);(k==='description'?box:head).appendChild(wrap);inputs[k]=n; if(k==='type')wrap.appendChild(variants); }); ['双面','游标'].forEach(function(v){var label=el('label',undefined,'pd-wb-check'),n=el('input');n.type='checkbox';n.id='skill-'+(++serial)+'-'+anchor;label.htmlFor=n.id;n.dataset.skillVariant=v;n.checked=data.variants?!!data.variants[v]:(data.type||'').includes(v);label.append(n,document.createTextNode(v));variants.appendChild(label);checks[v]=n;});function updateVariants(){var active=inputs.type.value==='主动';variants.hidden=!active;inputs.kb.parentElement.hidden=!active;head.dataset.active=String(active);}inputs.type.addEventListener('input',updateVariants);inputs.type.addEventListener('change',updateVariants);updateVariants(); var entry={box:box,inputs:inputs,checks:checks,source:data._source};var controls=el('div',undefined,'pd-wb-skill-actions');controls.append(btn('上移',function(){var i=skillRows.indexOf(entry);if(i>0){rows.insertBefore(box,skillRows[i-1].box);skillRows.splice(i,1);skillRows.splice(i-1,0,entry);changed();}}),btn('下移',function(){var i=skillRows.indexOf(entry);if(i<skillRows.length-1){rows.insertBefore(skillRows[i+1].box,box);skillRows.splice(i,1);skillRows.splice(i+1,0,entry);changed();}}),btn('删除技能',function(){box.remove();skillRows.splice(skillRows.indexOf(entry),1);changed();}));head.appendChild(controls);rows.appendChild(box);skillRows.push(entry); } skillBox.appendChild(btn('添加技能',function(){skill();changed();})); var info=group('正文'),infoPair=row(info,'pd-wb-info-pair'); function entryGroup(key,title){var wrap=el('div',undefined,'pd-wb-field pd-wb-entry-group'),heading=el('div',title,'pd-wb-entry-heading'),list=el('div',undefined,'pd-wb-entries'),items=[];wrap.dataset.entryGroup=key;wrap.append(heading,list);infoPair.appendChild(wrap); function add(value){var box=el('div',undefined,'pd-wb-entry'),input=el('input');input.type='text';input.id='entry-'+(++serial)+'-'+anchor;input.value=(value||'').replace(/\r?\n/g,'<br>');input.dataset.entryField=key;input.setAttribute('aria-label',title+'条目');box.appendChild(input);var item={box:box,input:input,original:value||'',initial:input.value},controls=el('div',undefined,'pd-wb-entry-actions');controls.append(btn('上移',function(){var i=items.indexOf(item);if(i>0){list.insertBefore(box,items[i-1].box);items.splice(i,1);items.splice(i-1,0,item);changed();}}),btn('下移',function(){var i=items.indexOf(item);if(i<items.length-1){list.insertBefore(items[i+1].box,box);items.splice(i,1);items.splice(i+1,0,item);changed();}}),btn('删除',function(){box.remove();items.splice(items.indexOf(item),1);changed();}));box.appendChild(controls);list.appendChild(box);items.push(item);return input;} wrap.appendChild(btn('添加'+title,function(){add('').focus();changed();}));entryGroups[key]={wrap:wrap,values:function(){return items.map(function(i){return i.input.value===i.initial?i.original:i.input.value;});},restore:function(values){items=[];list.replaceChildren();(values.length?values:['']).forEach(add);}};entryGroups[key].restore([]); } entryGroup('attributes','属性');entryGroup('support','援护对象');['effect','background'].forEach(function(k){addField(info,k,{effect:'效果',background:'背景/备注'}[k],'textarea');}); addField(form,'duplicateConfirmed','确认仍要创建同名同类型的规则变体','checkbox'); var actions=el('div',undefined,'pd-wb-actions'),previewButton=btn('预览',preview),publishButton=btn('确认发布',publish),copyButton=btn('复制生成源码',copy),newButton=btn('填写下一张',reset);publishButton.disabled=true;newButton.hidden=true;actions.append(previewButton,publishButton,copyButton,newButton,native);form.appendChild(actions); var generated=el('textarea');generated.className='pd-wb-source';generated.readOnly=true;generated.rows=8;generated.hidden=true;generated.setAttribute('aria-label','生成的卡牌源码');host.appendChild(generated); var previewTitle=el('p',undefined,'pd-wb-preview-title'),previewHost=el('div',undefined,'pd-wb-preview');host.append(previewTitle,previewHost); function data(){var d={anchor:anchor,numberMode:numberMode,skills:skillRows.map(function(s){var r={_source:s.source};Object.keys(s.inputs).forEach(function(k){r[k]=s.inputs[k].value;});r.variants={双面:s.checks['双面'].checked,游标:s.checks['游标'].checked};r.kbDraft=r.kb;if(r.type!=='主动')r.kb='';if(r.type==='主动')r.type=(r.variants['双面']?'双面':'')+(r.variants['游标']?'游标':'')+'主动';return r;})};Object.keys(fields).forEach(function(k){d[k]=fields[k].type==='checkbox'?fields[k].checked:fields[k].value;});Object.keys(entryGroups).forEach(function(k){d[k+'Items']=entryGroups[k].values();d[k]=d[k+'Items'].filter(function(v){return v.trim();}).join('<br>');});return d;} function show(k,on){(entryGroups[k]?entryGroups[k].wrap:fields[k].parentElement).hidden=!on;} function placeholders(){var d=data(),p=inv&&inv.packs.find(function(p){return p.id===d.packId;}),section=inv&&inv.sections.find(function(s){return s.id===d.section;}),series=d.mode==='new'?(section&§ion.series):(p&&p.series),examples={PHIORD:'PHIORD.16',SUBSPC:'SUBSPC.10',BDRGCP:'BDRGCP.02'},code=({PHIORD:'P',SUBSPC:'S',BDRGCP:'B'})[series]||'P',pack=d.mode==='new'?d.packNumber:(p&&p.number)||'',match=pack.match(/^(?:PHIORD|SUBSPC|BDRGCP)\.(\d+)$/);fields.packNumber.placeholder='例如:'+(examples[series]||examples.PHIORD);fields.number.placeholder='例如:'+code+(match?match[1].padStart(2,'0'):'01')+'-'+({角色:'C',置物:'P',行动:'A',反击:'T'})[d.kind]+'01';} function display(){var d=data(),editing=workMode==='edit',non=['角色','置物'].includes(d.kind);show('mode',!editing);show('packId',editing||d.mode==='existing');show('editCard',editing);editRow.hidden=!editing;destination.querySelector('legend').textContent=editing?'修改对象':'发布位置';previewButton.textContent=editing?'预览修改':'预览';publishButton.textContent=editing?'确认保存':'确认发布';['section','packName','packNumber'].forEach(function(k){show(k,!editing&&d.mode==='new');});newPackRow.hidden=editing||d.mode!=='new';show('ep',non);show('bp',d.kind==='角色');show('ic',d.kind==='置物');show('movement',d.kind==='置物');stats.dataset.kind=d.kind;show('attributes',non);show('support',d.kind==='角色');infoPair.hidden=!non;infoPair.classList.toggle('pd-wb-single',d.kind==='置物');show('effect',!non);skillBox.hidden=!non;Object.keys(fields).forEach(function(k){var on=['mode','packId','editCard','section','packNumber','packName','name','kind','rarity','ep','bp','ic','kb','movement','effect'].includes(k)&&!fields[k].parentElement.hidden;required(fields[k],on);});skillRows.forEach(function(s){Object.keys(s.inputs).forEach(function(k){required(s.inputs[k],non&&(k!=='kb'||s.inputs.type.value==='主动'));});});} function suggest(){if(workMode==='edit'&&!editIdentity)return;if(numberMode!=='auto'||!inv)return;var d=data(),p=inv.packs.find(function(p){return p.id===d.packId;});fields.number.value=C.suggestion(inv,d.mode==='new'?d.packNumber:p?p.number:'',d.kind,d.derived);} function stash(){try{var d=data(),record=workMode==='edit'?{data:d,identity:editIdentity,baseline:editBaseline,stale:editStale}:d;modeDrafts[workMode]=record;sessionStorage.setItem(workMode==='edit'?editStorageKey():storageKey,JSON.stringify(record));}catch(e){/* optional browser storage */}} function invalidate(){approval=null;publishButton.disabled=true;previewTitle.textContent='';if(window.pdCards&&window.pdCards.dispose)window.pdCards.dispose(previewHost);previewHost.replaceChildren();} function changed(event){if(busy)return;if(event&&event.target.type==='radio')return;if(workMode==='edit'&&event&&[fields.packId,fields.editCard].includes(event.target)){if(event.type==='change'||event.target.value!==(event.target===fields.packId?selectedPack:selectedCard))selectEdit(false,event.target===fields.packId);return;}if(event&&event.target.setCustomValidity){event.target.setCustomValidity('');event.target.removeAttribute('aria-invalid');}if(event&&event.target===fields.number)numberMode='manual';if(event&&[fields.packId,fields.kind,fields.derived,fields.packNumber,fields.mode].includes(event.target)){if(workMode!=='edit'&&numberMode==='auto')suggest();}display();placeholders();invalidate();stash();} form.addEventListener('input',changed);form.addEventListener('change',changed); function lock(on){busy=on;form.querySelectorAll('input,select,textarea,button').forEach(function(n){n.disabled=on;});publishButton.disabled=on||!approval||!permission;if(!on&&workMode==='edit'){var blocked=!editIdentity||editStale;[basic,skillBox,info].forEach(function(box){box.disabled=blocked;});previewButton.disabled=blocked;copyButton.disabled=!editIdentity;if(blocked)publishButton.disabled=true;}else if(!on)[basic,skillBox,info].forEach(function(box){box.disabled=false;});} function message(text){status.textContent=text;} function options(){var keep=fields.packId.value,section=fields.section.value,groups={};fields.packId.replaceChildren();inv.packs.forEach(function(p){var series=p.series||'BDRGCP';if(!groups[series]){groups[series]=el('optgroup');groups[series].label=series+'系列';fields.packId.appendChild(groups[series]);}var o=el('option',[p.number,p.name].filter(Boolean).join(' ')||'单卡收录');o.value=p.id;groups[series].appendChild(o);});if(keep){if(!inv.packs.some(function(p){return p.id===keep;})){var lost=el('option','原目标卡包已合并或移除,请重新选择');lost.value=keep;fields.packId.appendChild(lost);}fields.packId.value=keep;} fields.section.replaceChildren();inv.sections.filter(function(s){return allowed.includes(s.id);}).forEach(function(s){var o=el('option',s.series?s.series+'系列':s.label);o.value=s.id;fields.section.appendChild(o);});if(section){if(!Array.from(fields.section.options).some(function(o){return o.value===section;})){var missing=el('option','原目标栏目已消失,请重新选择');missing.value=section;fields.section.appendChild(missing);}fields.section.value=section;}} async function load(){var r=await read(target);if(!inv||inv.source!==r.source)anchorCache=null;inv=C.inventory(r.source);loadedRevision=r.revid;permission=r.canEdit;options();return r;} function sanitized(html){var doc=parseDocument(html);doc.querySelectorAll('script,iframe,object,embed,link,meta,base').forEach(function(n){n.remove();});doc.querySelectorAll('*').forEach(function(n){Array.from(n.attributes).forEach(function(a){if(/^on/i.test(a.name)||a.name==='srcdoc'||(['href','src','action','formaction','xlink:href'].includes(a.name.toLowerCase())&&/^\s*(?:javascript|data|vbscript):/i.test(a.value)))n.removeAttribute(a.name);});});var fragment=document.createDocumentFragment();Array.from(doc.body.childNodes).forEach(function(n){fragment.appendChild(document.importNode(n,true));});return fragment;} async function preview(){if(busy)return;try{validate();}catch(e){invalidate();message(e.message);return;}invalidate();lock(true);message('正在读取最新卡包并解析预览……');try{ await load();var d=data(),plan=workMode==='edit'?C.proposeUpdate(inv,d,editIdentity):C.propose(inv,d,allowed);if(plan.noop){message('没有需要保存的修改。');return;}if(plan.already){success({already:true},d);return;} var text=C.template('PD卡包',{编号:plan.pack.number,名称:plan.pack.name,卡牌:'\n'+plan.card+'\n'}),r=await parse(text,target); if(r.count!==1||r.issues.length)throw Error('卡面解析失败:'+r.issues.join(';')); previewHost.appendChild(sanitized(r.html));if(!window.pdCards)throw Error('卡牌模块未加载,请检查 Widget:PD卡牌');window.pdCards.init(); previewTitle.textContent='将发布到 '+target+' / '+(d.mode==='new'?fields.section.selectedOptions[0].textContent:plan.pack.path)+' / '+(plan.pack.number||plan.pack.name||'未编号卡包')+';卡号:'+(d.number||'未编号'); if(workMode==='edit')previewTitle.textContent='将保存到 '+target+' / '+(plan.pack.number||plan.pack.name)+';改动:'+(plan.fields.join('、')||'无')+(plan.removed.length?';将移除不适用字段:'+plan.removed.join('、'):''); var baseline=await parse(inv.source,target);baselineNotice=baseline.issues.length?' 原页面已有解析提示:'+baseline.issues.join(';'):''; approval={snapshot:JSON.stringify(d),fingerprint:d.mode==='new'?d.section:plan.pack.id+'|'+plan.pack.number+'|'+plan.pack.name+'|'+plan.pack.path}; message((permission?(workMode==='edit'?'修改预览完成。确认后即可保存。':'预览完成。确认内容后即可立即发布。'):'预览完成;当前未登录或没有目标页面编辑权限,不能发布。')+baselineNotice); }catch(e){message(e.message);if(e.code==='number-conflict'&&numberMode==='auto'){suggest();stash();message('编号已被占用,已重新建议编号,请再次预览。');}}finally{lock(false);}} function success(result,d){approval=null;newButton.hidden=false;previewButton.disabled=true;publishButton.disabled=true;message(result.recovered?'已确认此前保存成功,没有重复投稿。':result.already?'该投稿已在页面中,无需重复发布。':'发布成功。');var link=el('a','查看卡牌');link.href=pageUrl(target)+'#'+encodeURIComponent(d.anchor);status.append(' ',link);if(result.revid){var diff=el('a','查看编辑记录');diff.href=pageUrl(target,{diff:result.revid,oldid:result.oldrevid||'prev'});status.append(' · ',diff);}try{sessionStorage.removeItem(storageKey);}catch(e){}host.dataset.published=d.anchor;} async function publish(){if(busy||!approval)return;try{validate();}catch(e){invalidate();message(e.message);return;}var d=data();if(approval.snapshot!==JSON.stringify(d)){invalidate();message('内容已变化,请重新预览。');return;}lock(true);message('正在检查最新页面、完整解析并保存,请勿关闭本页……');try{var result=workMode==='edit'?await service.update(target,d,editIdentity):await service.publish(target,d,allowed,approval.fingerprint);if(workMode==='edit')await editSuccess(result,d);else success(result,d);} catch(e){if(e.suggested!==undefined){fields.number.value=e.suggested;invalidate();stash();}if(['editconflict','target-changed','pack-conflict'].includes(e.code))invalidate();message(e.message);} finally{lock(false);if(workMode==='create'&&host.dataset.published){previewButton.disabled=true;publishButton.disabled=true;}} } async function copy(){try{validate();generated.value=workMode==='edit'?C.updateCard(editIdentity.original,data(),editIdentity.anchor).card:C.createCard(data());generated.hidden=false;try{await navigator.clipboard.writeText(generated.value);message('卡牌源码已复制。');}catch(e){generated.focus();generated.select();message('请复制下方已选中的源码。');}}catch(e){message(e.message);}} function reset(){delete host.dataset.published;anchor=unique();skillRows=[];rows.replaceChildren();['name','number','ep','bp','ic','kb','effect','background','preface'].forEach(function(k){fields[k].value='';});Object.keys(entryGroups).forEach(function(k){entryGroups[k].restore([]);});fields.derived.checked=false;fields.duplicateConfirmed.checked=false;numberMode='auto';newButton.hidden=true;previewButton.disabled=false;invalidate();suggest();display();placeholders();stash();message('可填写下一张卡牌。');} var selectedPack='',selectedCard='',cardChoices=[]; function editStorageKey(){return 'pd-workbench-edit-v1:'+target;} function fill(d){d=d||{};skillRows=[];rows.replaceChildren();Object.keys(fields).forEach(function(k){var v=d[k];if(k==='editCard')return;if(fields[k].type==='checkbox')fields[k].checked=!!v;else {if(fields[k].tagName==='SELECT'&&v!==undefined&&!Array.from(fields[k].options).some(function(o){return o.value===v;})){var o=el('option','原值:'+v);o.value=v;fields[k].appendChild(o);}fields[k].value=v===undefined?(['kind','rarity','mode','packId','section'].includes(k)?fields[k].options[0].value:''):v;}});(d.skills||[]).forEach(skill);Object.keys(entryGroups).forEach(function(k){entryGroups[k].restore(Array.isArray(d[k+'Items'])?d[k+'Items']:(d[k]?[d[k]]:[]));});numberMode=d.numberMode||'manual';if(d.anchor)anchor=d.anchor;} function cardOptions(value){cardChoices=[];fields.editCard.replaceChildren();var empty=el('option','请选择卡牌');empty.value='';fields.editCard.appendChild(empty);var p=inv&&inv.packs.find(function(p){return p.id===fields.packId.value;});if(p)p.cards.forEach(function(c,i){cardChoices[i]=inv.source.slice(c.start,c.end);var o=el('option',[C.val(c,'编号')||'未编号',C.val(c,'名称'),C.val(c,'类型')].join(' · '));o.value=String(i);fields.editCard.appendChild(o);});fields.editCard.value=value===undefined?'':value;} function editSnapshot(){var d=data();['packId','editCard','mode','section','packNumber','packName'].forEach(function(k){delete d[k];});return JSON.stringify(d);} function dirty(){return !!editIdentity&&editSnapshot()!==editBaseline;} async function anchorFor(card){if(!anchorCache){var parsed=await parse(inv.source,target);if(!parsed.resolved||parsed.resolved.length!==inv.cards.length)throw Error('卡牌模块无法提供完整锚点映射,请更新Widget:PD卡牌或使用源码编辑器。');anchorCache=parsed.resolved;}var index=inv.cards.indexOf(card),id=anchorCache[index];if(!id)throw Error('此卡缺少可确认的锚点。');return id;} async function selectEdit(force,packChanged){ var packId=fields.packId.value,choice=packChanged?'':fields.editCard.value,chosenRaw=packChanged?'':cardChoices[choice]; if(!force&&packId===selectedPack&&choice===selectedCard)return; if(dirty()&&!window.confirm('此卡有未保存修改,是否放弃这些修改并载入所选卡牌?')){fields.packId.value=selectedPack;cardOptions(selectedCard);return;} invalidate();lock(true);try{ await load();fields.packId.value=packId;cardOptions(choice);selectedPack=packId;selectedCard=choice;editIdentity=null;editBaseline='';editStale=false;delete host.dataset.published; if(choice===''){fill({mode:'existing',packId:packId});fields.editCard.value='';message('请选择要修改的卡牌。');return;} var pack=inv.packs.find(function(p){return p.id===packId;}),matches=pack&&pack.cards.filter(function(c){return inv.source.slice(c.start,c.end)===chosenRaw;}),card=matches&&matches.length===1?matches[0]:null;if(!card)throw Error('所选卡牌已变化或消失,请重新选择。');choice=String(pack.cards.indexOf(card));selectedCard=choice;fields.editCard.value=choice; var raw=inv.source.slice(card.start,card.end),d=C.readCard(raw),id=await anchorFor(card);editIdentity={packId:packId,original:raw,anchor:id,explicitAnchor:C.val(card,'锚点'),loadedRevision:loadedRevision}; fill(Object.assign(d,{mode:'existing',packId:packId,numberMode:'manual',anchor:id}));fields.editCard.value=choice;editBaseline=editSnapshot();message('卡牌已载入,修改后先预览再保存。');stash(); }catch(e){editStale=true;message(e.message+' 请使用源码编辑入口或重新载入。');}finally{display();placeholders();lock(false);} } async function switchMode(next){if(busy||next===workMode)return;stash();invalidate();delete host.dataset.published;workMode=next;modeInputs[next].checked=true;newButton.hidden=true;previewButton.disabled=false; if(next==='create'){fill(modeDrafts.create||{});display();placeholders();lock(false);message('发布模式:预览后可立即发布。');return;} var saved=modeDrafts.edit;try{saved=saved||JSON.parse(sessionStorage.getItem(editStorageKey())||'null');}catch(_){}editIdentity=null;editBaseline='';editStale=false; lock(true);try{await load();if(saved&&saved.identity){editIdentity=saved.identity;try{C.locateCard(inv,editIdentity);}catch(e){editStale=true;}fill(saved.data);selectedPack=editIdentity.packId;fields.packId.value=selectedPack;var p=inv.packs.find(function(p){return p.id===selectedPack;}),index=p?p.cards.findIndex(function(c){return inv.source.slice(c.start,c.end)===editIdentity.original;}):-1;selectedCard=index<0?'':String(index);cardOptions(selectedCard);editBaseline=saved.baseline||'';message(editStale?'修改草稿对应的原卡已变化,草稿已保留;请复制源码或重新载入后编辑。':'已恢复修改草稿,并核对目标卡未变化。');} else{fill({mode:'existing',packId:fields.packId.value});selectedPack=fields.packId.value;selectedCard='';cardOptions();message('请选择要修改的卡牌。');} }catch(e){message(e.message);}finally{display();placeholders();lock(false);} } async function editSuccess(result,d){approval=null;if(result.noop){message('没有需要保存的修改。');return;}editIdentity.original=result.card;editIdentity.explicitAnchor=editIdentity.anchor;editIdentity.loadedRevision=result.revid;anchor=editIdentity.anchor;numberMode='manual'; // Re-read the saved source as the new baseline, including skill source tokens. fill(Object.assign(C.readCard(result.card),{mode:'existing',packId:editIdentity.packId,numberMode:'manual',anchor:anchor}));fields.editCard.value=selectedCard;if(fields.editCard.selectedOptions[0])fields.editCard.selectedOptions[0].textContent=[d.number||'未编号',d.name,d.kind].join(' · ');cardChoices[selectedCard]=result.card;editBaseline=editSnapshot();stash();message(result.recovered?'已核对修改保存成功,没有重复保存。':'修改已保存。');var link=el('a','查看卡牌');link.href=pageUrl(target)+'#'+encodeURIComponent(anchor);status.append(' ',link);if(result.revid){var diff=el('a','查看编辑记录');diff.href=pageUrl(target,{diff:result.revid,oldid:result.oldrevid||'prev'});status.append(' · ',diff);}previewButton.disabled=false; } async function boot(){lock(true);try{await load();var saved;try{saved=JSON.parse(sessionStorage.getItem(storageKey)||'null');}catch(e){}if(saved){anchor=/^pd-wb-[a-z0-9-]+$/.test(saved.anchor)?saved.anchor:anchor;numberMode=saved.numberMode==='manual'?'manual':'auto';Object.keys(fields).forEach(function(k){if(saved[k]!==undefined){if(fields[k].type==='checkbox')fields[k].checked=!!saved[k];else fields[k].value=saved[k];}});(saved.skills||[]).forEach(skill);Object.keys(entryGroups).forEach(function(k){entryGroups[k].restore(Array.isArray(saved[k+'Items'])?saved[k+'Items']:(saved[k]?[saved[k]]:[]));});}else suggest();display();placeholders();message((saved?'已恢复本标签页草稿。':'卡包已载入。')+(permission?'预览后可立即发布。':'当前未登录或没有编辑权限,可填写和预览。'));} catch(e){message('工作台初始化失败:'+e.message+'。请使用源码编辑器。');previewButton.disabled=true;}finally{lock(false);if(!inv){previewButton.disabled=true;publishButton.disabled=true;}} } boot(); } function start(){document.querySelectorAll('.pd-wb').forEach(init);} if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});else start(); })(); </script>
返回
微件:PD卡牌编辑器
。
导航
导航
首页
最近更改
随机页面
操作申请
帮助
入门指南
编辑指南
写作指南
随机
官方
碎数研
谜题保管所
wiki工具
wiki工具
页面工具
页面工具
用户页面工具
更多
链入页面
相关更改
页面信息
页面日志