mediawiki-extensions-Cite/lib/ext/Cite/Ref.js
C. Scott Ananian b84b71af22 Gallery: shift TSRs in the DOM, rather than fibbing about srcOffset
Passing srcOffsets which don't actually correspond to actual regions of
the source wikitext cause problems in the token offset conversion code.
Instead, parse the wikitext as itself, then adjust the TSRs in the DOM
tree.

Since Gallery isn't ported to PHP (yet), update the
automatically-generated Gallery/index.php.  The newly-added
ContentUtils::shiftDSR() was ported, however.

Change-Id: I28f3d3398930733ae2bcf9759e49c45f93bc7190
2019-06-28 14:10:16 +00:00

132 lines
4.6 KiB
JavaScript

'use strict';
const ParsoidExtApi = module.parent.parent.require('./extapi.js').versionCheck('^0.10.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 <sup> 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 <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;