'use strict'; const ParsoidExtApi = module.parent.parent.require('./extapi.js').versionCheck('^0.11.0'); const { ContentUtils, DOMDataUtils, WTUtils, Promise } = ParsoidExtApi; /** * Simple token transform version of the Ref extension tag. * * @class */ class Ref { static toDOM(state, content, args) { // Drop nested refs entirely, unless we've explicitly allowed them if (state.parseContext.extTag === 'ref' && !(state.parseContext.extTagOpts && state.parseContext.extTagOpts.allowNestedRef) ) { return null; } // The one supported case for nested refs is from the {{#tag:ref}} parser // function. However, we're overly permissive here since we can't // distinguish when that's nested in another template. // The php preprocessor did our expansion. const allowNestedRef = state.parseContext.inTemplate && state.parseContext.extTag !== 'ref'; return ParsoidExtApi.parseTokenContentsToDOM(state, args, '', content, { // NOTE: sup's content model requires it only contain phrasing // content, not flow content. However, since we are building an // in-memory DOM which is simply a tree data structure, we can // nest flow content in a tag. wrapperTag: 'sup', pipelineOpts: { extTag: 'ref', extTagOpts: { allowNestedRef: !!allowNestedRef, }, inTemplate: state.parseContext.inTemplate, // FIXME: One-off PHP parser state leak. // This needs a better solution. inPHPBlock: true, }, }); } static lintHandler(ref, env, tplInfo, domLinter) { // Don't lint the content of ref in ref, since it can lead to cycles // using named refs if (WTUtils.fromExtensionContent(ref, 'references')) { return ref.nextSibling; } var linkBackId = ref.firstChild.getAttribute('href').replace(/[^#]*#/, ''); var refNode = ref.ownerDocument.getElementById(linkBackId); if (refNode) { // Ex: Buggy input wikitext without ref content domLinter(refNode.lastChild, env, tplInfo.isTemplated ? tplInfo : null); } return ref.nextSibling; } } Ref.serialHandler = { handle: Promise.async(function *(node, state, wrapperUnmodified) { var startTagSrc = yield state.serializer.serializeExtensionStartTag(node, state); var dataMw = DOMDataUtils.getDataMw(node); var env = state.env; var html; if (!dataMw.body) { return startTagSrc; // We self-closed this already. } else if (typeof dataMw.body.html === 'string') { // First look for the extension's content in data-mw.body.html html = dataMw.body.html; } else if (typeof dataMw.body.id === 'string') { // If the body isn't contained in data-mw.body.html, look if // there's an element pointed to by body.id. var bodyElt = node.ownerDocument.getElementById(dataMw.body.id); if (!bodyElt && env.page.editedDoc) { // Try to get to it from the main page. // This can happen when the is inside another // extension, most commonly inside a . // The recursive call to serializeDOM puts us inside // inside a new document. bodyElt = env.page.editedDoc.getElementById(dataMw.body.id); } if (bodyElt) { // n.b. this is going to drop any diff markers but since // the dom differ doesn't traverse into extension content // none should exist anyways. DOMDataUtils.visitAndStoreDataAttribs(bodyElt); html = ContentUtils.toXML(bodyElt, { innerXML: true }); DOMDataUtils.visitAndLoadDataAttribs(bodyElt); } else { // Some extra debugging for VisualEditor var extraDebug = ''; var firstA = node.querySelector('a[href]'); if (firstA && /^#/.test(firstA.getAttribute('href') || '')) { var href = firstA.getAttribute('href') || ''; try { var ref = node.ownerDocument.querySelector(href); if (ref) { extraDebug += ' [own doc: ' + ref.outerHTML + ']'; } ref = env.page.editedDoc.querySelector(href); if (ref) { extraDebug += ' [main doc: ' + ref.outerHTML + ']'; } } catch (e) { } // eslint-disable-line if (!extraDebug) { extraDebug = ' [reference ' + href + ' not found]'; } } env.log('error/' + dataMw.name, 'extension src id ' + dataMw.body.id + ' points to non-existent element for:', node.outerHTML, '. More debug info: ', extraDebug); return ''; // Drop it! } } else { env.log('error', 'Ref body unavailable for: ' + node.outerHTML); return ''; // Drop it! } var src = yield state.serializer.serializeHTML({ env: state.env, extName: dataMw.name, // FIXME: One-off PHP parser state leak. // This needs a better solution. inPHPBlock: true, }, html); return startTagSrc + src + ''; }), }; module.exports = Ref;