MediaWiki:Common.js: Difference between revisions
Fix training-materials upload errors; library append messaging |
Training materials: admin-only Delete file; purge after library edits |
||
| Line 3,353: | Line 3,353: | ||
trimmed.indexOf('[[File:' + filename + '|') !== -1; | trimmed.indexOf('[[File:' + filename + '|') !== -1; | ||
if (already) { | if (already) { | ||
return { edit: { result: 'Success', nochange: true } }; | return purgeLibraryPages(api).then(function () { | ||
return { edit: { result: 'Success', nochange: true } }; | |||
}); | |||
} | } | ||
if (!trimmed || trimmed.indexOf(PLACEHOLDER) !== -1) { | if (!trimmed || trimmed.indexOf(PLACEHOLDER) !== -1) { | ||
| Line 3,375: | Line 3,377: | ||
return api.postWithToken('csrf', params).then(function (editData) { | return api.postWithToken('csrf', params).then(function (editData) { | ||
if (editData.edit && editData.edit.result === 'Success') { | if (editData.edit && editData.edit.result === 'Success') { | ||
return editData; | return purgeLibraryPages(api).then(function () { | ||
return editData; | |||
}); | |||
} | } | ||
if (editData.error && editData.error.code === 'editconflict') { | if (editData.error && editData.error.code === 'editconflict') { | ||
| Line 3,403: | Line 3,407: | ||
return 'Upload failed (connection dropped or timed out). Try again, or use Special:Upload.'; | return 'Upload failed (connection dropped or timed out). Try again, or use Special:Upload.'; | ||
} | } | ||
function purgeLibraryPages(api) { | |||
return api.postWithToken('csrf', { | |||
action: 'purge', | |||
titles: 'Training materials|' + LIBRARY, | |||
forcelinkupdate: 1, | |||
format: 'json' | |||
}); | |||
} | |||
function readLibraryText(api) { | |||
return api.get({ | |||
action: 'query', | |||
prop: 'revisions|info', | |||
titles: LIBRARY, | |||
rvprop: 'content|timestamp', | |||
rvslots: 'main', | |||
curtimestamp: 1, | |||
formatversion: 2, | |||
maxage: 0, | |||
smaxage: 0 | |||
}).then(function (data) { | |||
var page = data.query && data.query.pages && data.query.pages[0]; | |||
var rev = page && page.revisions && page.revisions[0]; | |||
return { | |||
text: (rev && rev.slots && rev.slots.main && rev.slots.main.content) || '', | |||
basetimestamp: rev && rev.timestamp, | |||
starttimestamp: data.curtimestamp | |||
}; | |||
}); | |||
} | |||
function writeLibraryText(api, next, summary, basets, startts) { | |||
var params = { | |||
action: 'edit', | |||
title: LIBRARY, | |||
text: next, | |||
summary: summary, | |||
format: 'json' | |||
}; | |||
if (basets) { | |||
params.basetimestamp = basets; | |||
} | |||
if (startts) { | |||
params.starttimestamp = startts; | |||
} | |||
return api.postWithToken('csrf', params).then(function (editData) { | |||
if (editData.edit && editData.edit.result === 'Success') { | |||
return purgeLibraryPages(api).then(function () { | |||
return editData; | |||
}); | |||
} | |||
if (editData.error && editData.error.code === 'editconflict') { | |||
throw new Error('editconflict'); | |||
} | |||
throw new Error((editData.error && editData.error.info) || 'Could not update library list'); | |||
}); | |||
} | |||
function removeFromLibrary(api, filename) { | |||
return readLibraryText(api).then(function (cur) { | |||
var lines = String(cur.text || '').split(/\r?\n/); | |||
var nextLines = lines.filter(function (line) { | |||
return line.indexOf('[[Media:' + filename + '|') === -1 && | |||
line.indexOf('[[File:' + filename + '|') === -1 && | |||
line.indexOf(filename) === -1; | |||
}); | |||
var next = $.trim(nextLines.join('\n')); | |||
if (!next) { | |||
next = PLACEHOLDER; | |||
} else { | |||
next = next + '\n'; | |||
} | |||
return writeLibraryText( | |||
api, | |||
next, | |||
'Remove from Training materials library: ' + filename, | |||
cur.basetimestamp, | |||
cur.starttimestamp | |||
).then(null, function (err) { | |||
if (String(err && err.message || err) === 'editconflict') { | |||
return removeFromLibrary(api, filename); | |||
} | |||
throw err; | |||
}); | |||
}); | |||
} | |||
function deleteLibraryFile(api, filename) { | |||
return api.postWithToken('csrf', { | |||
action: 'delete', | |||
title: 'File:' + filename, | |||
reason: 'Removed from Training materials library', | |||
format: 'json' | |||
}).then(function (data) { | |||
if (data.error) { | |||
throw new Error(data.error.info || 'Could not delete file'); | |||
} | |||
return removeFromLibrary(api, filename); | |||
}); | |||
} | |||
function filenameFromHref(href) { | |||
if (!href) { | |||
return ''; | |||
} | |||
try { | |||
href = decodeURIComponent(href); | |||
} catch (e) { /* ignore */ } | |||
var m = href.match(/\/wiki\/(?:File|Media):([^?#]+)/i) || | |||
href.match(/[?&]title=(?:File|Media):([^&#]+)/i); | |||
if (!m) { | |||
return ''; | |||
} | |||
return m[1].replace(/_/g, ' ').trim().replace(/ /g, '_'); | |||
} | |||
function enhanceLibraryManage() { | |||
var canEdit = !!mw.config.get('wgIsProbablyEditable') || !!mw.config.get('wgUserName'); | |||
if (!canEdit) { | |||
return; | |||
} | |||
var $heading = $('.mw-parser-output .mw-heading, .mw-parser-output h2').filter(function () { | |||
return $(this).text().replace(/\s*\[edit\].*$/i, '').trim() === 'Library'; | |||
}).first(); | |||
if (!$heading.length) { | |||
return; | |||
} | |||
var $list = $heading.nextAll('ul').first(); | |||
if (!$list.length) { | |||
$list = $heading.parent().nextAll('ul').first(); | |||
} | |||
if (!$list.length || $list.data('rbCampManualManage')) { | |||
return; | |||
} | |||
$list.data('rbCampManualManage', 1); | |||
var api = new mw.Api(); | |||
var canDeleteFile = false; | |||
api.get({ | |||
action: 'query', | |||
meta: 'userinfo', | |||
uiprop: 'groups', | |||
formatversion: 2 | |||
}).then(function (data) { | |||
var groups = (data.query && data.query.userinfo && data.query.userinfo.groups) || []; | |||
/* Delete file is administrators (sysop) only. */ | |||
canDeleteFile = groups.indexOf('sysop') !== -1; | |||
}).always(function () { | |||
$list.children('li').each(function () { | |||
var $li = $(this); | |||
if ($li.find('.camp-manual-lib-actions').length) { | |||
return; | |||
} | |||
var $a = $li.find('a[href*="File:"], a[href*="Media:"]').first(); | |||
var filename = filenameFromHref($a.attr('href')); | |||
if (!filename && $a.length) { | |||
filename = String($a.text() || '').replace(/ /g, '_'); | |||
if (!/\.[a-z0-9]+$/i.test(filename)) { | |||
filename = ''; | |||
} | |||
} | |||
if (!filename) { | |||
return; | |||
} | |||
var $actions = $('<span>', { 'class': 'camp-manual-lib-actions' }); | |||
var $remove = $('<button>', { | |||
type: 'button', | |||
'class': 'camp-manual-lib-remove', | |||
text: 'Remove' | |||
}).attr('title', 'Remove from this list (file stays on the wiki)'); | |||
$actions.append($remove); | |||
if (canDeleteFile) { | |||
$actions.append($('<button>', { | |||
type: 'button', | |||
'class': 'camp-manual-lib-delete', | |||
text: 'Delete file' | |||
}).attr('title', 'Delete the file from the wiki')); | |||
} | |||
$li.append($actions); | |||
$remove.on('click', function () { | |||
if (!window.confirm('Remove “' + filename.replace(/_/g, ' ') + '” from the Library list?\n(The file will remain on the wiki.)')) { | |||
return; | |||
} | |||
$actions.find('button').prop('disabled', true); | |||
removeFromLibrary(api, filename).done(function () { | |||
window.location.reload(); | |||
}).fail(function (code, data) { | |||
window.alert(failMessage(code, data)); | |||
$actions.find('button').prop('disabled', false); | |||
}); | |||
}); | |||
$actions.on('click', '.camp-manual-lib-delete', function () { | |||
if (!window.confirm('Permanently delete file “' + filename.replace(/_/g, ' ') + '” from the wiki?')) { | |||
return; | |||
} | |||
$actions.find('button').prop('disabled', true); | |||
deleteLibraryFile(api, filename).done(function () { | |||
window.location.reload(); | |||
}).fail(function (code, data) { | |||
window.alert(failMessage(code, data)); | |||
$actions.find('button').prop('disabled', false); | |||
}); | |||
}); | |||
}); | |||
}); | |||
} | |||
enhanceLibraryManage(); | |||
$form.on('submit', function (evt) { | $form.on('submit', function (evt) { | ||