2019-04-02 22:06:21 +00:00
|
|
|
'use strict';
|
|
|
|
|
2020-01-11 08:13:13 +00:00
|
|
|
const ParsoidExtApi = module.parent.parent.require('./extapi.js').versionCheck('^0.11.0');
|
2019-04-02 22:06:21 +00:00
|
|
|
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 <sup> tag.
|
|
|
|
wrapperTag: 'sup',
|
2019-04-19 19:38:27 +00:00
|
|
|
pipelineOpts: {
|
|
|
|
extTag: 'ref',
|
|
|
|
extTagOpts: {
|
|
|
|
allowNestedRef: !!allowNestedRef,
|
|
|
|
},
|
|
|
|
inTemplate: state.parseContext.inTemplate,
|
|
|
|
// FIXME: One-off PHP parser state leak.
|
|
|
|
// This needs a better solution.
|
|
|
|
inPHPBlock: true,
|
2019-04-02 22:06:21 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
static lintHandler(ref, env, tplInfo, domLinter) {
|
|
|
|
// Don't lint the content of ref in ref, since it can lead to cycles
|
|
|
|
// using named refs
|
2019-05-28 16:04:49 +00:00
|
|
|
if (WTUtils.fromExtensionContent(ref, 'references')) { return ref.nextSibling; }
|
2019-04-02 22:06:21 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
2019-05-28 16:04:49 +00:00
|
|
|
return ref.nextSibling;
|
2019-04-02 22:06:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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 <ref> is inside another
|
|
|
|
// extension, most commonly inside a <references>.
|
|
|
|
// 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 + '</' + dataMw.name + '>';
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = Ref;
|