MediaWiki:Gadget-templating.js: Difference between revisions
Appearance
No edit summary |
No edit summary |
||
| Line 11: | Line 11: | ||
}; | }; | ||
function doSave(toolbar, pid, prevCount) { | |||
// Try the controller API first | |||
try { | |||
if (toolbar && toolbar._controller && typeof toolbar._controller.stopEditing === 'function') { | |||
// ✅ true = save on most installs (false often means cancel) | |||
toolbar._controller.stopEditing(true); | |||
} else { | |||
// Fallback: click the save/publish button in the toolbar DOM | |||
var $root = toolbar && toolbar.$element ? toolbar.$element : $(document); | |||
var $btn = $root.find('.wikibase-toolbarbutton-save, .wikibase-toolbarbutton-publish'); | |||
if ($btn.length) { $btn.click(); } | |||
} | |||
} catch (e) { | |||
console.warn('doSave: toolbar save failed, trying button fallback', e); | |||
try { | |||
var $root2 = toolbar && toolbar.$element ? toolbar.$element : $(document); | |||
var $btn2 = $root2.find('.wikibase-toolbarbutton-save, .wikibase-toolbarbutton-publish'); | |||
if ($btn2.length) { $btn2.click(); } | |||
} catch (e2) {} | |||
} | |||
// Wait until the statement list shows one more row for this property | |||
return waitForStatementRendered(pid, prevCount, (window.WB_TIMING && WB_TIMING.renderTimeout) || 1200); | |||
} | |||
function sleep(ms){ return new Promise(function(res){ setTimeout(res, ms); }); } | function sleep(ms){ return new Promise(function(res){ setTimeout(res, ms); }); } | ||
| Line 412: | Line 436: | ||
// Save once and wait for the statement to re-render | // Save once and wait for the statement to re-render | ||
.then(function () { | .then(function () { | ||
doSave(toolbar, pid, prevCount).then(resolve); | |||
}) | }) | ||
.then(resolve) | .then(resolve) | ||
.catch(function () { | .catch(function () { | ||
// fallback: save anyway so the queue advances | // fallback: save anyway so the queue advances | ||
doSave(toolbar, pid, prevCount).then(resolve); | |||
}); | }); | ||
}); | }); | ||
| Line 486: | Line 510: | ||
// save now and wait for visible render (+1) | // save now and wait for visible render (+1) | ||
doSave(toolbar, pid, prevCount).then(resolve); | |||
}).catch(function () { | }).catch(function () { | ||
// Even if widget didn’t show, try to save the stub so the queue advances | // Even if widget didn’t show, try to save the stub so the queue advances | ||
doSave(toolbar, pid, prevCount).then(resolve); | |||
}); | }); | ||
}); | }); | ||
Revision as of 17:59, 19 September 2025
/* global mw, $ */
mw.loader.using(['mediawiki.api']).done(function () {
// ---- timing knobs (ms) ----
var WB_TIMING = {
pollInterval: 50, // how often we poll for readiness
valueViewTimeout: 1200, // wait for value view to exist
widgetTimeout: 1800, // wait for input widget/$input
renderTimeout: 1200, // wait for statement list to reflect the save (+1)
settleDelay: 0, // optional pause between claims (0 = off)
commitDelay: 120 // short pause to let the widget commit before next step
};
function doSave(toolbar, pid, prevCount) {
// Try the controller API first
try {
if (toolbar && toolbar._controller && typeof toolbar._controller.stopEditing === 'function') {
// ✅ true = save on most installs (false often means cancel)
toolbar._controller.stopEditing(true);
} else {
// Fallback: click the save/publish button in the toolbar DOM
var $root = toolbar && toolbar.$element ? toolbar.$element : $(document);
var $btn = $root.find('.wikibase-toolbarbutton-save, .wikibase-toolbarbutton-publish');
if ($btn.length) { $btn.click(); }
}
} catch (e) {
console.warn('doSave: toolbar save failed, trying button fallback', e);
try {
var $root2 = toolbar && toolbar.$element ? toolbar.$element : $(document);
var $btn2 = $root2.find('.wikibase-toolbarbutton-save, .wikibase-toolbarbutton-publish');
if ($btn2.length) { $btn2.click(); }
} catch (e2) {}
}
// Wait until the statement list shows one more row for this property
return waitForStatementRendered(pid, prevCount, (window.WB_TIMING && WB_TIMING.renderTimeout) || 1200);
}
function sleep(ms){ return new Promise(function(res){ setTimeout(res, ms); }); }
// ===== 0) CONFIG =====
var SEARCH_QUERY = 'haswbstatement:P1=Q196450'; // exactly what works in Special:Search
var WbTemplates = {};
var api = new mw.Api();
var userLang = mw.config.get('wgUserLanguage') || 'en';
// Serialize all UI writes so nothing overlaps
var WriteQueue = (function () {
var chain = Promise.resolve();
function push(taskFn) {
chain = chain.then(function () {
return taskFn();
}).catch(function (e) {
console.error('Write failed:', e);
// swallow so the queue continues
});
return chain;
}
return { push: push };
})();
// ===== 1) Small helpers (ES5-safe) =====
function chunk(arr, n) {
var out = [], i;
for (i = 0; i < arr.length; i += n) out.push(arr.slice(i, i + n));
return out;
}
// Robustly select a wikibase entity in an EntitySelector (plugin optional)
function pickEntity(vw, qid, commitDelay) {
return new Promise(function (resolve) {
var committed = false;
try {
// Preferred: official OOUI widget API (if exposed)
if (vw.plugin && typeof vw.plugin.setValue === 'function') {
vw.plugin.setValue(qid);
committed = true;
}
// Common fallback on many builds
else if (vw.plugin && typeof vw.plugin._select === 'function') {
vw.plugin._select({ id: qid, label: qid });
committed = true;
}
} catch (e) {}
// Last resort: simulate typing + Enter + blur on the raw input
if (!committed) {
try {
vw.$input
.val(qid)
.trigger('input')
.trigger('change')
.trigger({ type: 'keydown', which: 13, keyCode: 13 })
.trigger({ type: 'keyup', which: 13, keyCode: 13 })
.blur();
committed = true;
} catch (e2) {}
}
// Let the widget finish internal commits
sleep(typeof commitDelay === 'number' ? commitDelay : 120).then(resolve);
});
}
function getBestLabel(labels) {
if (!labels) return undefined;
if (labels[userLang] && labels[userLang].value) return labels[userLang].value;
for (var k in labels) {
if (labels[k] && labels[k].value) return labels[k].value;
}
return undefined;
}
function isPlaceholderSnak(snak) {
if (!snak || snak.snaktype !== 'value' || !snak.datavalue) return false;
var dt = snak.datatype;
var v = snak.datavalue.value;
if (dt === 'string' || dt === 'external-id' || dt === 'url') return v === '_';
if (dt === 'monolingualtext') return v && v.text === '_';
return false;
}
function cleanTemplateLabel(raw) {
if (!raw) return '';
return raw.replace(/^RDA\s*/i, '').replace(/\s*Template$/i, '').trim();
}
function slugify(s) {
if (!s) return '';
// simple slug (ASCII only)
return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
}
// ===== 2) API: search → entities → property meta =====
function searchTemplates(query) {
return api.get({
action: 'query',
list: 'search',
srsearch: query,
srbackend: 'CirrusSearch',
srqiprofile: 'wikibase',
srnamespace: 120,
srlimit: 50,
formatversion: 2
}).then(function (res) {
var hits = (res.query && res.query.search) || [];
var ids = [];
for (var i = 0; i < hits.length; i++) {
var m = /Q\d+$/.exec(hits[i].title);
if (m) ids.push(m[0]);
}
return ids;
});
}
function fetchEntities(ids) {
var out = {};
var batches = chunk(ids, 50);
var d = $.Deferred();
(function next(i) {
if (i >= batches.length) { d.resolve(out); return; }
api.get({
action: 'wbgetentities',
ids: batches[i].join('|'),
props: 'labels|claims',
languages: userLang
}).then(function (res) {
$.extend(out, res.entities || {});
next(i + 1);
}).fail(d.reject);
})(0);
return d.promise();
}
function fetchPropertyMeta(entityMap) {
var pids = {};
for (var qid in entityMap) {
var claims = entityMap[qid].claims || {};
for (var pid in claims) pids[pid] = true;
}
var ids = Object.keys(pids);
var out = {};
var batches = chunk(ids, 50);
var d = $.Deferred();
(function next(i) {
if (i >= batches.length) { d.resolve(out); return; }
api.get({
action: 'wbgetentities',
ids: batches[i].join('|'),
props: 'labels|datatype',
languages: userLang
}).then(function (res) {
$.extend(out, res.entities || {});
next(i + 1);
}).fail(d.reject);
})(0);
return d.promise().then(function () {
// normalize
var meta = {};
for (var pid in out) {
var ent = out[pid] || {};
meta[pid] = {
id: pid,
label: getBestLabel(ent.labels) || pid,
datatype: ent.datatype
};
}
return meta;
});
}
// ===== 3) Transform to your WbTemplates shape =====
function transformToTemplate(entity, propMeta) {
var rawLabel = getBestLabel(entity.labels) || entity.id;
var label = cleanTemplateLabel(rawLabel);
var prefilledClaims = [];
var userDefinedClaims = [];
var claims = entity.claims || {};
for (var pid in claims) {
var statements = claims[pid];
if (!statements || !statements.length) continue;
var prop = propMeta[pid] || { id: pid, label: pid, datatype: 'string' };
// find first non-placeholder
var firstNonPlaceholder = null;
for (var i = 0; i < statements.length; i++) {
var ms = statements[i] && statements[i].mainsnak;
if (ms && ms.snaktype === 'value' && !isPlaceholderSnak(ms)) {
firstNonPlaceholder = ms;
break;
}
}
if (firstNonPlaceholder && prop.datatype === 'wikibase-item') {
var v = firstNonPlaceholder.datavalue && firstNonPlaceholder.datavalue.value;
prefilledClaims.push({
property: { id: prop.id, label: prop.label, datatype: prop.datatype },
value: { mainVal: { id: v && v.id, datatype: 'wikibase-item' } },
qualifiers: [{datatype: 'string', property: {id: 'P746', label: 'some prop'}, value: {mainVal: {text:'val'}}}]
});
} else {
userDefinedClaims.push({
property: { id: prop.id, label: prop.label },
datatype: prop.datatype
});
}
}
console.log({ key: slugify(label), label: label, prefilledClaims: prefilledClaims, userDefinedClaims: userDefinedClaims });
return { key: slugify(label), label: label, prefilledClaims: prefilledClaims, userDefinedClaims: userDefinedClaims };
}
function buildWbTemplatesFromQuery(query) {
return searchTemplates(query).then(function (qids) {
if (!qids.length) return {};
return fetchEntities(qids).then(function (entities) {
return fetchPropertyMeta(entities).then(function (propMeta) {
var map = {};
for (var eid in entities) {
var tpl = transformToTemplate(entities[eid], propMeta);
map[tpl.key] = {
label: tpl.label,
prefilledClaims: tpl.prefilledClaims,
userDefinedClaims: tpl.userDefinedClaims
};
}
// return { "person": { "label": "Person", "prefilledClaims": [{ "property": {id: "P1", label: "instance of", datatype: "wikibase-item"}, "value": { "mainVal": {id: "Q78", datatype: "wikibase-item"} } },{ "property": {id: "P2", label: "subclass of", datatype: "wikibase-item"}, "value": { "mainVal": {id: "Q79", datatype: "wikibase-item"} } }], "userDefinedClaims": [ { "property": {id: "P162", label: "has authorized access point for person [a/datatype] (RDA:P50411)"}, datatype: "string" }, { "property": {id: "P1087", label: "has surname [a/datatype] (RDA:P50291)"}, datatype: "string" }, { "property": {id: "P579", label: "has given name [a/datatype] (RDA:P50292)"}, datatype: "string" }, { "property": {id: "P1105", label: "has term of rank or honour or office [a/datatype] (RDA:P50110)"}, datatype: "string" }, { "property": {id: "P1893", label: "has place of birth [a/object] (RDA:P50119)"}, datatype: "wikibase-item" }, { "property": {id: "P2251", label: "has profession or occupation [a/object] (RDA:P50104)"}, datatype: "wikibase-item" }, { "property": {id: "P2250", label: "has language of person [a/object] (RDA:P50102)"}, datatype: "wikibase-item" }, { "property": {id: "P1123", label: "has variant access point for person [a/datatype] (RDA:P50412)"}, datatype: "string" }, { "property": {id: "P2007", label: "has related corporate body of person [a/object] (RDA:P50318)"}, datatype: "wikibase-item" }, { "property": {id: "P756", label: "has note on person [a/datatype] (RDA:P50395)"}, datatype: "string" }, ], }, "corporate": { "label": "Corporate Body", "prefilledClaims": [ { "property": {id: "P1", label: "instance of", datatype: "wikibase-item"}, "value": { "mainVal": {id: "Q72", datatype: "wikibase-item"} } } ], "userDefinedClaims": [ { "property": {id: "P10814", label: "has longitude and latitude (P60345)"}, datatype: "globe-coordinate" }, { "property": {id: "P160", label: "has authorized access point for corporate body [a/datatype] (RDA:P50407)"}, datatype: "string" }, { "property": {id: "P1121", label: "has variant access point for corporate body [a/datatype] (RDA:P50408)"}, datatype: "string" }, { "property": {id: "P754", label: "has note on corporate body [a/datatype] (RDA:P50393)"}, datatype: "string" }, ], }, };
return map;
});
});
});
}
// ===== 4) YOUR existing helpers (unchanged) =====
function statementExists(property, value) {
items.toArray().some(function (item) {
return !!$(item).data().propertyId &&
$(item).data().statementgroupview.options.value.getKey() === property &&
$(item).data().statementgroupview.options.value.getItemContainer().toArray().some(function (claimItem) {
return claimItem.getClaim().getMainSnak().getValue().getSerialization() === value;
});
});
}
function getPropertyEntity(property) {
return {
id: String(property.id),
title: 'Property:' + property.id,
datatype: property.datatype,
label: property.label
};
}
function getStatementCount(pid) {
var count = 0;
$('.wikibase-statementgroupview').each(function () {
var d = $(this).data();
if (d && d.propertyId === pid) {
try { count = d.statementgroupview.statementlistview._listview.items().length; } catch (e) {}
}
});
return count;
}
function waitForStatementRendered(pid, prevCount, timeoutMs) {
var start = Date.now(), timeout = timeoutMs || 1000;
return new Promise(function (resolve) {
(function tick() {
var now = getStatementCount(pid);
if (now > prevCount) return resolve();
if (Date.now() - start > timeout) return resolve(); // fail-safe
setTimeout(tick, WB_TIMING.pollInterval);
})();
});
}
function waitForValueView(snak, timeoutMs) {
var start = Date.now();
var timeout = (typeof timeoutMs === 'number') ? timeoutMs : WB_TIMING.valueViewTimeout;
return new Promise(function (resolve, reject) {
(function tick() {
try {
var vv = snak && snak._variation && snak._variation._valueView;
if (vv && vv.element && vv.element.length) return resolve(vv);
} catch (e) {}
if (Date.now() - start > timeout) return reject(new Error('value view not ready'));
setTimeout(tick, WB_TIMING.pollInterval);
})();
});
}
// Count qualifier snaks on a statementview
function getQualifierCount(sv) {
try {
var items = sv._qualifiers && sv._qualifiers.items ? sv._qualifiers.items() : [];
if (!items || !items.length) return 0;
var qslv = $(items[items.length - 1]).data().snaklistview;
return qslv && qslv._listview ? qslv._listview.items().length : 0;
} catch (e) { return 0; }
}
function waitForQualifierRendered(sv, prevCount, timeoutMs) {
var start = Date.now();
var timeout = (typeof timeoutMs === 'number') ? timeoutMs : WB_TIMING.valueViewTimeout;
return new Promise(function (resolve) {
(function tick() {
if (getQualifierCount(sv) > prevCount) return resolve();
if (Date.now() - start > timeout) return resolve(); // fail-safe
setTimeout(tick, WB_TIMING.pollInterval);
})();
});
}
function waitForValueWidget(snak, datatype, timeoutMs) {
var start = Date.now(), timeout = timeoutMs || 10000;
return new Promise(function (resolve, reject) {
(function tick() {
var vv = snak && snak._variation && snak._variation._valueView;
var ex = vv && vv._expert;
var $input = ex && ex.$input;
var plugin = null, jqe = null;
try {
if ($input && $input.length) {
var data = $input.data();
if (datatype === 'wikibase-item') {
plugin = data.entityselector || data['mw.widgets.EntitySelector'] || data.ooWidget || null;
} else {
plugin = data.inputautoexpand || null;
}
jqe = data.jqueryEventSpecialEachchange || null;
}
} catch (e) {}
if ($input && $input.length) return resolve({ $input: $input, plugin: plugin, jqe: jqe });
if (Date.now() - start > timeout) return reject(new Error('value widget not ready'));
setTimeout(tick, WB_TIMING.pollInterval);
})();
});
}
function prefillStatement(claim, onlyProperty) {
return new Promise(function (resolve) {
var pid = claim.property.id;
var prevCount = getStatementCount(pid);
var statementListView = $(".wikibase-statementgrouplistview").first().data().wikibaseStatementgrouplistview;
statementListView.enterNewItem();
var items = statementListView.listview.items();
var item = items.last().data();
var svItems = item.statementgroupview.statementlistview._listview.items();
var sv = $(svItems).data().statementview;
var snak = sv.$mainSnak.data().snakview;
var toolbar = svItems.last().data().edittoolbar;
// Select main property
var es = getPropertyEntity(claim.property);
var selector = snak._getPropertySelector();
selector.element.val(es.label);
selector.element.attr("title", es.label);
selector._trigger('change');
selector._select(es);
// Wait for the value view + widget, set main value (no save yet)
var mainVal = claim.value && claim.value.mainVal;
var dt = claim.property.datatype || (mainVal && mainVal.datatype) || claim.datatype || 'string';
waitForValueView(snak, WB_TIMING.valueViewTimeout)
.then(function () { return waitForValueWidget(snak, dt, WB_TIMING.widgetTimeout); })
.then(function (vw) {
try {
if (dt === 'wikibase-item' && mainVal && mainVal.id) {
if (vw.plugin && typeof vw.plugin._select === 'function') {
return pickEntity(vw, mainVal.id, 120);
} else {
vw.$input.val(mainVal.id).trigger('input').trigger('change');
}
} else if (dt === 'string' || dt === 'external-id' || dt === 'url') {
vw.$input.val(mainVal && (mainVal.text || mainVal.value) || '')
.trigger('input').trigger('change');
} else if (dt === 'monolingualtext') {
vw.$input.val(mainVal && mainVal.text || '')
.trigger('input').trigger('change');
} else if (dt === 'globe-coordinate' || dt === 'globecoordinate') {
var coord = (mainVal && mainVal.latitude != null && mainVal.longitude != null)
? (mainVal.latitude + ',' + mainVal.longitude) : '';
vw.$input.val(coord).trigger('input').trigger('change');
} else if (dt === 'time') {
vw.$input.val(mainVal && mainVal.time || '')
.trigger('input').trigger('change');
} else {
vw.$input.val(mainVal && (mainVal.text || mainVal.value) || '')
.trigger('input').trigger('change');
}
} catch (eFill) { /* continue */ }
// let the widget commit the value
return sleep(WB_TIMING.commitDelay);
})
// Add ALL qualifiers before saving
.then(function () { return addQualifiersSequentially(sv, claim.qualifiers || []); })
// Save once and wait for the statement to re-render
.then(function () {
doSave(toolbar, pid, prevCount).then(resolve);
})
.then(resolve)
.catch(function () {
// fallback: save anyway so the queue advances
doSave(toolbar, pid, prevCount).then(resolve);
});
});
}
function pendingStatement(claim) {
var prop = claim.property;
var pid = prop.id;
var prevCount = getStatementCount(pid);
return new Promise(function (resolve) {
var statementListView = $(".wikibase-statementgrouplistview").first().data().wikibaseStatementgrouplistview;
statementListView.enterNewItem();
var items = statementListView.listview.items();
var item = items.last().data();
var svItems = item.statementgroupview.statementlistview._listview.items();
var sv = $(svItems).data().statementview;
var snak = sv.$mainSnak.data().snakview;
var toolbar = svItems.last().data().edittoolbar;
// Build the full property entity object the selector expects
var es = getPropertyEntity(prop);
var selector = snak._getPropertySelector();
// Promise that resolves when the property selection actually took effect
var propertyReady = new Promise(function (res) {
var done = false;
function ok() { if (!done) { done = true; res(); } }
// either the selector reports selection...
selector.element.one('entityselectorselected', ok);
// ...or the value view is redrawn for that property
$(snak._variation).one('afterdraw', ok);
});
// Select the property (this triggers redraw/selection events)
selector.element.val(es.label);
selector.element.attr('title', es.label);
selector._trigger('change');
selector._select(es);
// When property is ready, poke the value widget to create an empty statement and save
propertyReady.then(function () {
var dt = claim.datatype || prop.datatype || 'string';
// wait until the value widget exists
waitForValueWidget(snak, dt, WB_TIMING.widgetTimeout).then(function (vw) {
try {
if (dt === 'wikibase-item') {
// nudge dirty then clear (use plugin when available, otherwise type)
if (vw.plugin && typeof vw.plugin._select === 'function') {
vw.plugin._select({ id: 'Q80', datatype: 'wikibase-item' });
} else {
vw.$input.val('Q80').trigger('input').trigger('change');
}
vw.$input.val('').trigger('input').trigger('change');
} else if (dt === 'globe-coordinate' || dt === 'globecoordinate') {
vw.$input.val('0,0').trigger('input').trigger('change');
vw.$input.val('').trigger('input').trigger('change');
} else {
vw.$input.val('__').trigger('input').trigger('change');
vw.$input.val('').trigger('input').trigger('change');
}
} catch (e) {
console.warn('value poke failed', e);
}
// save now and wait for visible render (+1)
doSave(toolbar, pid, prevCount).then(resolve);
}).catch(function () {
// Even if widget didn’t show, try to save the stub so the queue advances
doSave(toolbar, pid, prevCount).then(resolve);
});
});
});
}
function addOneQualifierToStatement(sv, q) {
return new Promise(function (resolve) {
try {
var qlistview = sv._qualifiers;
var prev = getQualifierCount(sv);
// Ensure list exists and add a new qualifier row
qlistview.enterNewItem();
var $qlItems = sv._qualifiers.items();
var qslv = $qlItems && $qlItems.length ? $($qlItems[$qlItems.length - 1]).data().snaklistview : null;
if (!qslv || !qslv.enterNewItem) { resolve(); return; }
qslv.enterNewItem();
// Wait until the new qualifier row is actually in the DOM
waitForQualifierRendered(sv, prev).then(function () {
var qsnak = qslv._listview.items().last().data().snakview;
var qprop = getPropertyEntity(q.property);
var qsel = qsnak._getPropertySelector();
// Select the qualifier property (this draws its value widget)
qsel.element.val(qprop.label);
qsel._trigger('change');
qsel._select(qprop);
// Now wait for its widget, then set the value (if provided)
var dt = q.property.datatype || q.datatype || 'string';
waitForValueWidget(qsnak, dt, WB_TIMING.widgetTimeout).then(function (vw) {
var hasValue = q.value && q.value.mainVal;
try {
if (dt === 'wikibase-item') {
if (hasValue && q.value.mainVal.id) {
if (vw.plugin && typeof vw.plugin._select === 'function') {
vw.plugin._select({ id: q.value.mainVal.id, datatype: 'wikibase-item' });
} else {
vw.$input.val(q.value.mainVal.id).trigger('input').trigger('change');
}
} else {
// blank scaffold
vw.$input.val('Q1').trigger('input').trigger('change')
.val('').trigger('input').trigger('change');
}
} else if (dt === 'time') {
var t = hasValue && q.value.mainVal.time || '';
vw.$input.val(t).trigger('input').trigger('change');
} else if (dt === 'globe-coordinate' || dt === 'globecoordinate') {
var v = hasValue && q.value.mainVal.latitude != null ? (q.value.mainVal.latitude + ',' + q.value.mainVal.longitude) : '0,0';
vw.$input.val(v).trigger('input').trigger('change');
if (!hasValue) { vw.$input.val('').trigger('input').trigger('change'); }
} else {
var txt = hasValue ? (q.value.mainVal.text || q.value.mainVal) : '__';
vw.$input.val(txt).trigger('input').trigger('change');
if (!hasValue || txt === '__') { vw.$input.val('').trigger('input').trigger('change'); }
}
} catch (e) { /* ignore, continue */ }
// small settle to let widget commit
return sleep(WB_TIMING.commitDelay);
}).then(resolve).catch(function () { resolve(); });
});
} catch (e) { resolve(); }
});
}
function addQualifiersSequentially(sv, qualifiers) {
qualifiers = qualifiers || [];
var chain = Promise.resolve();
for (var i = 0; i < qualifiers.length; i++) {
(function (q) {
chain = chain.then(function () { return addOneQualifierToStatement(sv, q); });
})(qualifiers[i]);
}
return chain;
}
function getExistingPropertyIds() {
var map = {};
$('.wikibase-statementgroupview').each(function () {
var d = $(this).data();
if (d && d.propertyId) map[d.propertyId] = true;
});
return map;
}
function addDetails(templateName, opts) {
opts = opts || {};
var skipExisting = (typeof opts.skipExisting === 'boolean') ? opts.skipExisting : true;
var tpl = WbTemplates[templateName] || { userDefinedClaims: [] };
var claims = (tpl.userDefinedClaims || []).slice();
var existing = getExistingPropertyIds();
var seq = Promise.resolve();
claims.forEach(function (claim) {
if (skipExisting && existing[claim.property.id]) { return; }
seq = seq.then(function () {
return WriteQueue.push(function () {
return pendingStatement(claim).then(function () {
existing[claim.property.id] = true;
});
});
});
});
return seq;
}
function applyTemplate(templateName) {
var claims = (WbTemplates[templateName].prefilledClaims || []).slice();
var seq = Promise.resolve();
claims.forEach(function (claim) {
seq = seq.then(function () {
return WriteQueue.push(function () {
return prefillStatement(claim, false); // returns a Promise
});
});
});
return seq;
}
// ===== 5) UI (render AFTER templates are loaded) =====
function initTemplatingUI() {
var html = ''
+ '<div class="wikibase-templating-section">'
+ ' <div class="wikibase-templating-section-title"><span class="magic-wand-icon"></span> RDA Entity template</div>'
+ ' <div class="wikibase-templating-help"><span class="settings-text">Select an RDA entity class to apply predefined template entries and additional entity details.</span></div>'
+ ' <div class="wikibase-templating-form oo-ui-labelElement oo-ui-fieldLayout">'
+ ' <div class=""><label for="wbtemplate">Entity class</label></div>'
+ ' <div><select id="wbtemplate" class="cdx-select"></select></div>'
+ ' </div>'
+ ' <div class="mw-htmlform-submit-buttons">'
+ ' <button id="applyTemplateBtn" class="cdx-button">Apply template</button>'
+ ' <button id="addDetailsBtn" class="cdx-button">Add details</button>'
+ ' </div>'
+ '</div>';
$('.wikibase-entitytermsview').append(html);
var key;
for (key in WbTemplates) {
$('#wbtemplate').append($('<option>', {
value: key,
text: WbTemplates[key].label
}));
}
$('#applyTemplateBtn').off('click').on('click', function () {
var $b=$(this).prop('disabled', true);
applyTemplate($('#wbtemplate').val()).then(function(){ $b.prop('disabled', false); });
});
$('#addDetailsBtn').off('click').on('click', function () {
var $b=$(this).prop('disabled', true);
addDetails($('#wbtemplate').val(), { skipExisting: true }).then(function(){ $b.prop('disabled', false); });
});
}
// ===== 6) Build templates, then render UI =====
buildWbTemplatesFromQuery(SEARCH_QUERY)
.then(function (map) {
WbTemplates = map;
window.WbTemplates = WbTemplates; // if you need it elsewhere
if (!WbTemplates || !Object.keys(WbTemplates).length) {
console.warn('No templates found for query:', SEARCH_QUERY);
return;
}
initTemplatingUI();
})
.fail(function (err) {
// If this fails for anonymous users, it might be auth/profile config
// related; try while logged in.
console.error('Template build failed:', err);
});
});