Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump.
This code will be executed when previewing this page.
This code will be executed when previewing this page.
This user script seems to have a documentation page at User:DVRTed/script-ctl.
mw.loader.using([
"vue",
"@wikimedia/codex",
"mediawiki.api",
"mediawiki.util"
]).then(function() {
const require = mw.loader.require;
(function(vue, _wikimedia_codex) {
//#region node_modules/@wikimedia/codex-icons/dist/codex-icons.js
var M = "<path d=\"M11 9V4H9v5H4v2h5v5h2v-5h5V9z\"/>", g = "<path d=\"M11.53 2.3A1.85 1.85 0 0010 1.21 1.85 1.85 0 008.48 2.3L.36 16.36C-.48 17.81.21 19 1.88 19h16.24c1.67 0 2.36-1.19 1.52-2.64zM11 16H9v-2h2zm0-4H9V6h2z\"/>", D = "<path d=\"M10 1a9 9 0 109 9 9 9 0 00-9-9m5 10H5V9h10z\"/>", v1 = "<path d=\"M7 14.17 2.83 10l-1.41 1.41L7 17 19 5l-1.41-1.42z\"/>", z1 = "<path d=\"m4.34 2.93 12.73 12.73-1.41 1.41L2.93 4.35z\"/><path d=\"M17.07 4.34 4.34 17.07l-1.41-1.41L15.66 2.93z\"/>", f1 = "<circle cx=\"10\" cy=\"10\" r=\"2\"/><circle cx=\"3\" cy=\"10\" r=\"2\"/><circle cx=\"17\" cy=\"10\" r=\"2\"/>", W2 = "<path d=\"m4 10 9 9 1.4-1.5L7 10l7.4-7.5L13 1z\"/>", _2 = "<path d=\"M15.65 4.35A8 8 0 1017.4 13h-2.22a6 6 0 11-1-7.22L11 9h7V2z\"/>", t5 = "<path d=\"M12.2 13.6a7 7 0 111.4-1.4l5.4 5.4-1.4 1.4zM3 8a5 5 0 1010 0A5 5 0 003 8\"/>", N5 = "<path d=\"M17 2h-3.5l-1-1h-5l-1 1H3v2h14zM4 17a2 2 0 002 2h8a2 2 0 002-2V5H4z\"/>", C3 = M, F3 = g, Y3 = D, n4 = v1, r4 = z1, F4 = f1, C7 = {
ltr: W2,
shouldFlip: !0
}, W7 = _2, P7 = t5, y8 = N5;
//#endregion
//#region src/helpers.js
function build_raw_url(pagename, oldid) {
const url = new URL(mw.config.get("wgScriptPath") + "/index.php", location.origin);
url.searchParams.set("title", pagename);
url.searchParams.set("action", "raw");
url.searchParams.set("ctype", "text/javascript");
if (oldid) url.searchParams.set("oldid", String(oldid));
return url.toString();
}
function fetch_page_info(pagename) {
return new mw.Api().get({
action: "query",
prop: "revisions|info",
titles: pagename,
rvprop: "ids",
rvlimit: 1,
formatversion: 2
}).then((data) => {
const page = data.query.pages[0];
return {
exists: !page.missing,
oldid: page.missing ? null : page.revisions[0].revid,
normalized_title: page.title,
content_model: page.contentmodel
};
});
}
async function fetch_latest_revisions(pagenames) {
const result = {};
if (!pagenames.length) return result;
for (let i = 0; i < pagenames.length; i += 50) {
const chunk = pagenames.slice(i, i + 50);
try {
const data = await new mw.Api().get({
action: "query",
prop: "revisions",
titles: chunk.join("|"),
rvprop: "ids",
formatversion: 2
});
for (const page of data.query.pages || []) if (!page.missing && page.revisions?.[0]) result[page.title] = page.revisions[0].revid;
} catch (e) {
console.error("Failed to fetch revisions batch", e);
}
}
return result;
}
function load_script(pagename, oldid) {
return mw.loader.getScript(build_raw_url(pagename, oldid));
}
async function search_scripts(query) {
if (!query || query.length < 3) return [];
try {
return ((await new mw.Api().get({
action: "query",
list: "search",
srsearch: `intitle:${query} intitle:.js`,
srnamespace: "2|4|8",
srlimit: 10,
format: "json"
}))?.query?.search || []).map((r) => r.title);
} catch (e) {
return [];
}
}
//#endregion
//#region \0plugin-vue:export-helper
var _plugin_vue_export_helper_default = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) target[key] = val;
return target;
};
//#endregion
//#region src/components/InstallForm.vue
var _hoisted_1$2 = { class: "smgr-install-view" };
var _hoisted_2$2 = { class: "smgr-card smgr-install-card" };
var _hoisted_3$2 = { class: "smgr-install-row" };
var _hoisted_4$2 = { class: "smgr-browse-footer" };
var InstallForm_default = /* @__PURE__ */ _plugin_vue_export_helper_default({
__name: "InstallForm",
props: { busy: {
type: Boolean,
default: false
} },
emits: ["install", "cancel"],
setup(__props, { expose: __expose, emit: __emit }) {
const props = __props;
const emit = __emit;
const install_input = (0, vue.ref)("");
const selected_script = (0, vue.ref)(null);
const suggestions = (0, vue.ref)([]);
let timeout = null;
const menu_items = (0, vue.computed)(() => {
return suggestions.value.map((s) => ({
value: s,
label: s
}));
});
const on_input = (val) => {
if (timeout) clearTimeout(timeout);
if (!val || val.length < 3) {
suggestions.value = [];
return;
}
timeout = setTimeout(async () => {
suggestions.value = await search_scripts(val);
}, 300);
};
const submit = () => {
if (props.busy) return;
const val = selected_script.value || install_input.value;
if (val) emit("install", typeof val === "string" ? val : val.value);
};
const open_browse = () => {
window.open(mw.util.getUrl("Wikipedia:User scripts/List"), "_blank");
};
__expose({
clear_input: () => {
install_input.value = "";
selected_script.value = null;
suggestions.value = [];
},
set_input: (val) => {
install_input.value = val;
selected_script.value = null;
}
});
return (_ctx, _cache) => {
return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$2, [(0, vue.createElementVNode)("div", _hoisted_2$2, [
_cache[3] || (_cache[3] = (0, vue.createElementVNode)("label", { class: "smgr-label" }, "Script Page Name", -1)),
(0, vue.createElementVNode)("div", _hoisted_3$2, [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxLookup), {
selected: selected_script.value,
"onUpdate:selected": _cache[0] || (_cache[0] = ($event) => selected_script.value = $event),
"input-value": install_input.value,
"onUpdate:inputValue": _cache[1] || (_cache[1] = ($event) => install_input.value = $event),
placeholder: "User:Foo/bar.js",
class: "smgr-input",
"menu-items": menu_items.value,
onInput: on_input,
disabled: __props.busy,
onKeyup: (0, vue.withKeys)(submit, ["enter"])
}, null, 8, [
"selected",
"input-value",
"menu-items",
"disabled"
]), (0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxButton), {
action: "progressive",
weight: "primary",
onClick: submit,
disabled: __props.busy
}, {
default: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(__props.busy ? "Installing..." : "Install"), 1)]),
_: 1
}, 8, ["disabled"])]),
(0, vue.createElementVNode)("div", _hoisted_4$2, [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxButton), {
weight: "quiet",
action: "progressive",
onClick: open_browse
}, {
default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxIcon), { icon: (0, vue.unref)(P7) }, null, 8, ["icon"]), _cache[2] || (_cache[2] = (0, vue.createTextVNode)("Browse community userscripts ", -1))]),
_: 1
})])
])]);
};
}
}, [["__scopeId", "data-v-c419cbd9"]]);
//#endregion
//#region src/components/ScriptList.vue
var _hoisted_1$1 = { class: "smgr-scripts-view" };
var _hoisted_2$1 = {
key: 0,
class: "smgr-state"
};
var _hoisted_3$1 = ["href"];
var _hoisted_4$1 = {
key: 0,
class: "smgr-update-chip"
};
var _hoisted_5$1 = {
key: 1,
style: { "color": "#72777d" }
};
var _hoisted_6$1 = {
key: 2,
class: "smgr-state"
};
var ScriptList_default = /* @__PURE__ */ _plugin_vue_export_helper_default({
__name: "ScriptList",
props: {
scripts: {
type: Array,
required: true
},
updates: {
type: Object,
required: true
},
timestamps: {
type: Object,
default: () => ({})
},
loading: {
type: Boolean,
required: true
},
busy_idx: {
type: Number,
default: null
}
},
emits: [
"toggle",
"update",
"uninstall",
"add_new"
],
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const columns = [
{
id: "script",
label: "Script"
},
{
id: "status",
label: "Status"
},
{
id: "version",
label: "Version"
},
{
id: "actions",
label: "Action",
align: "end"
}
];
const tableData = (0, vue.computed)(() => props.scripts.map((s, idx) => ({
...s,
_idx: idx
})));
function get_menu_items(row) {
return [
{
label: "Update",
value: "update",
icon: W7,
disabled: !props.updates[row.pagename]
},
{
label: row.status === "enabled" ? "Disable" : "Enable",
value: "toggle",
icon: row.status === "enabled" ? Y3 : n4
},
{
label: "Remove",
value: "uninstall",
icon: y8,
action: "destructive"
}
];
}
function on_select(value, idx) {
if (value === "update") emit("update", idx);
if (value === "toggle") emit("toggle", {
idx,
current_status: props.scripts[idx].status
});
if (value === "uninstall") emit("uninstall", idx);
}
const get_url = (pagename, oldid) => {
if (typeof mw === "undefined" || !mw.util) return "#";
return mw.util.getUrl(pagename, oldid ? { oldid } : {});
};
return (_ctx, _cache) => {
return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1$1, [__props.loading ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2$1, [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxProgressBar), { "aria-label": "Loading scripts..." })])) : ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_wikimedia_codex.CdxTable), {
key: 1,
caption: "Installed scripts",
columns,
data: tableData.value
}, {
"item-script": (0, vue.withCtx)(({ row }) => [(0, vue.createElementVNode)("div", { class: (0, vue.normalizeClass)(["smgr-script-cell", { "smgr-dimmed": row.status === "disabled" }]) }, [
(0, vue.createElementVNode)("span", { class: (0, vue.normalizeClass)(["smgr-stripe", row.status === "enabled" ? "smgr-stripe--on" : "smgr-stripe--off"]) }, null, 2),
(0, vue.createElementVNode)("a", {
href: get_url(row.pagename, row.oldid),
target: "_blank",
class: "smgr-pagename"
}, (0, vue.toDisplayString)(row.pagename), 9, _hoisted_3$1),
__props.updates[row.pagename] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_4$1, [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxIcon), { icon: (0, vue.unref)(F3) }, null, 8, ["icon"]), _cache[0] || (_cache[0] = (0, vue.createTextVNode)(" Update available ", -1))])) : (0, vue.createCommentVNode)("", true)
], 2)]),
"item-status": (0, vue.withCtx)(({ row }) => [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxInfoChip), { status: row.status === "enabled" ? "success" : "warning" }, {
default: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(row.status === "enabled" ? "Enabled" : "Disabled"), 1)]),
_: 2
}, 1032, ["status"])]),
"item-version": (0, vue.withCtx)(({ row }) => [__props.timestamps[row.pagename] ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [(0, vue.createTextVNode)((0, vue.toDisplayString)(__props.timestamps[row.pagename]), 1), row.oldid ? ((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, { key: 0 }, [(0, vue.createTextVNode)(" (rev " + (0, vue.toDisplayString)(row.oldid) + ")", 1)], 64)) : (0, vue.createCommentVNode)("", true)], 64)) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_5$1, "—"))]),
"item-actions": (0, vue.withCtx)(({ row }) => [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxMenuButton), {
selected: null,
"menu-items": get_menu_items(row),
disabled: __props.busy_idx === row._idx,
"aria-label": "Actions for " + row.pagename,
"onUpdate:selected": ($event) => on_select($event, row._idx)
}, {
default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxIcon), { icon: (0, vue.unref)(F4) }, null, 8, ["icon"])]),
_: 1
}, 8, [
"menu-items",
"disabled",
"aria-label",
"onUpdate:selected"
])]),
_: 1
}, 8, ["data"])), !__props.loading && !__props.scripts.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6$1, " No scripts installed yet. ")) : (0, vue.createCommentVNode)("", true)]);
};
}
}, [["__scopeId", "data-v-1b27191a"]]);
//#endregion
//#region src/script_store.js
var BLOCK_RE = /\/\* scriptmanager:begin !DO NOT EDIT THIS LINE MANUALLY!\*\/([\s\S]*?)\/\* scriptmanager:end !DO NOT EDIT THIS LINE MANUALLY!\*\//;
var COMMON_JS = "User:" + mw.config.get("wgUserName") + "/common.js";
var SCRIPT_TAG = " (using [[User:DVRTed/script-ctl.js|script-ctl]])";
var script_store = {
async load() {
const page = (await new mw.Api().get({
action: "query",
prop: "revisions",
titles: COMMON_JS,
rvprop: "content",
rvslots: "main",
formatversion: 2
})).query.pages[0];
if (page.missing) return [];
const match = page.revisions[0].slots.main.content.match(BLOCK_RE);
if (!match) return [];
try {
const match_without_comments = match[1].replace(/^\s*\/\/.*$/gm, "");
return JSON.parse(match_without_comments.trim());
} catch {
return [];
}
},
async save(list, summary = "Updated script list") {
const api = new mw.Api();
const page = (await api.get({
action: "query",
prop: "revisions",
titles: COMMON_JS,
rvprop: "content",
rvslots: "main",
formatversion: 2
})).query.pages[0];
const old_content = page.missing ? "" : page.revisions[0].slots.main.content;
const block = `/* scriptmanager:begin !DO NOT EDIT THIS LINE MANUALLY!*/\n[\n${list.map((item) => {
return `\t// backlink: [[${item.pagename}]]\n\t${JSON.stringify(item)}`;
}).join(",\n\n")}\n]\n/* scriptmanager:end !DO NOT EDIT THIS LINE MANUALLY!*/`;
const new_content = old_content.match(BLOCK_RE) ? old_content.replace(BLOCK_RE, block) : old_content + (old_content ? "\n\n" : "") + block;
return api.postWithToken("csrf", {
action: "edit",
title: COMMON_JS,
text: new_content,
summary: summary + SCRIPT_TAG
});
},
async add(pagename, oldid) {
const list = await this.load();
list.push({
pagename,
oldid,
status: "enabled"
});
await this.save(list, `Installed [[${pagename}]]`);
},
async update(idx, oldid) {
const list = await this.load();
const old_oldid = list[idx].oldid;
list[idx].oldid = oldid;
await this.save(list, `Updated [[${list[idx].pagename}]] (${old_oldid} -> ${oldid})`);
},
async remove(idx) {
const list = await this.load();
const removed = list.splice(idx, 1)[0];
await this.save(list, `Removed [[${removed.pagename}]]`);
return removed;
},
async set_status(idx, status) {
const list = await this.load();
list[idx].status = status;
const action = status === "enabled" ? "Enabled" : "Disabled";
await this.save(list, `${action} [[${list[idx].pagename}]]`);
}
};
//#endregion
//#region src/App.vue
var _hoisted_1 = {
key: 0,
class: "smgr-header-nav"
};
var _hoisted_2 = {
key: 1,
class: "smgr-header-text"
};
var _hoisted_3 = { class: "smgr-header-actions" };
var _hoisted_4 = { class: "smgr-body" };
var _hoisted_5 = { key: 1 };
var _hoisted_6 = { key: 2 };
var _hoisted_7 = ["href"];
var _hoisted_8 = { key: 0 };
var _hoisted_9 = { key: 1 };
var _hoisted_10 = { style: {
"display": "flex",
"gap": "8px",
"justify-content": "flex-end"
} };
var App_default = /* @__PURE__ */ _plugin_vue_export_helper_default({
__name: "App",
setup(__props, { expose: __expose }) {
const is_open = (0, vue.ref)(false);
const notice = (0, vue.ref)({
text: "",
type: "notice"
});
const busy_install = (0, vue.ref)(false);
const show_warning = (0, vue.ref)(false);
const pending_action = (0, vue.ref)({
type: "install",
name: "",
idx: null,
oldid: null
});
const busy_idx = (0, vue.ref)(null);
const loading = (0, vue.ref)(false);
const scripts = (0, vue.ref)([]);
const updates = (0, vue.ref)({});
const timestamps = (0, vue.ref)({});
const current_view = (0, vue.ref)("list");
const install_form_ref = (0, vue.ref)(null);
const open = () => {
notice.value = {
text: "",
type: "notice"
};
is_open.value = true;
current_view.value = "list";
if (install_form_ref.value) install_form_ref.value.clear_input();
render_list();
};
const open_install = async (pagename) => {
notice.value = {
text: "",
type: "notice"
};
is_open.value = true;
current_view.value = "install";
if (pagename) {
on_install_requested(pagename);
await (0, vue.nextTick)();
if (install_form_ref.value) install_form_ref.value.set_input(pagename);
}
render_list();
};
__expose({
open,
open_install
});
const get_url = (pagename) => {
return mw.util.getUrl(pagename);
};
const on_dialog_update = (val) => {
is_open.value = val;
};
const set_notice = (text, type) => {
notice.value = {
text,
type
};
};
const clear_notice = () => {
set_notice("");
};
const check_updates_and_timestamps = async (all_scripts) => {
const current_scripts = all_scripts.filter((s) => s.status === "enabled");
if (!current_scripts.length) return;
const new_updates = {};
const new_timestamps = {};
const oldids = current_scripts.map((s) => s.oldid).filter((id) => id);
if (oldids.length > 0) for (let i = 0; i < oldids.length; i += 50) {
const chunk = oldids.slice(i, i + 50);
try {
const data2 = await new mw.Api().get({
action: "query",
prop: "revisions",
revids: chunk.join("|"),
rvprop: "timestamp|ids",
formatversion: 2
});
for (const page of data2.query.pages || []) for (const rev of page.revisions || []) {
const ts = new Date(rev.timestamp).toLocaleDateString(void 0, {
year: "numeric",
month: "short",
day: "numeric"
});
const script = current_scripts.find((s) => s.oldid === rev.revid);
if (script) new_timestamps[script.pagename] = ts;
}
} catch (e) {}
}
const pagenames = current_scripts.map((s) => s.pagename);
try {
const latest_map = await fetch_latest_revisions(pagenames);
for (const s of current_scripts) {
const latest = latest_map[s.pagename];
if (latest && latest !== s.oldid) new_updates[s.pagename] = true;
}
} catch (e) {
console.error("Failed to check for updates", e);
}
updates.value = new_updates;
timestamps.value = new_timestamps;
};
const render_list = async () => {
loading.value = true;
scripts.value = await script_store.load();
loading.value = false;
check_updates_and_timestamps(scripts.value);
};
const on_install_requested = async (pagename_input) => {
const pagename = pagename_input.trim();
if (!pagename) return set_notice("Enter a page name.", "error");
busy_install.value = true;
clear_notice();
try {
const info = await fetch_page_info(pagename);
if (!info.exists) set_notice(`Page not found: ${pagename}`, "error");
else if (info.content_model !== "javascript") set_notice(`Script must have javascript content model (got: ${info.content_model})`, "error");
else {
if ((await script_store.load()).some((s) => s.pagename === info.normalized_title)) return set_notice(`${info.normalized_title} is already installed.`, "warning");
pending_action.value = {
type: "install",
name: info.normalized_title,
oldid: info.oldid,
idx: null
};
show_warning.value = true;
}
} catch {
set_notice(`Failed to fetch script info for ${pagename}.`, "error");
} finally {
busy_install.value = false;
}
};
const confirm_action = async () => {
if (pending_action.value.type === "install") await perform_install();
else await perform_update();
};
const perform_install = async () => {
const pagename = pending_action.value.name;
const oldid = pending_action.value.oldid;
show_warning.value = false;
busy_install.value = true;
clear_notice();
try {
await load_script(pagename, oldid);
await script_store.add(pagename, oldid);
if (install_form_ref.value) install_form_ref.value.clear_input();
set_notice(`Installed ${pagename} (oldid ${oldid}).`, "success");
current_view.value = "list";
await render_list();
} catch {
set_notice(`Failed to install ${pagename}.`, "error");
} finally {
busy_install.value = false;
}
};
const on_update = async (idx) => {
pending_action.value = {
type: "update",
name: (await script_store.load())[idx].pagename,
idx
};
show_warning.value = true;
};
const perform_update = async () => {
const idx = pending_action.value.idx;
show_warning.value = false;
const script = (await script_store.load())[idx];
busy_idx.value = idx;
clear_notice();
try {
const info = await fetch_page_info(script.pagename);
if (!info.exists) set_notice(`Page not found: ${script.pagename}`, "error");
else if (info.oldid === script.oldid) {
set_notice(`${script.pagename} is already up to date.`, "notice");
await render_list();
} else {
await load_script(script.pagename, info.oldid);
await script_store.update(idx, info.oldid);
set_notice(`Updated ${script.pagename} to the latest version (oldid ${info.oldid}).`, "success");
await render_list();
}
} catch {
set_notice(`Failed to update ${script.pagename}.`, "error");
} finally {
busy_idx.value = null;
}
};
const on_toggle = async (idx, current_status) => {
const new_status = current_status === "enabled" ? "disabled" : "enabled";
busy_idx.value = idx;
clear_notice();
try {
await script_store.set_status(idx, new_status);
await render_list();
} catch {
set_notice("Failed to update status.", "error");
} finally {
busy_idx.value = null;
}
};
const on_uninstall = async (idx) => {
busy_idx.value = idx;
clear_notice();
try {
set_notice(`Removed ${(await script_store.remove(idx)).pagename}.`, "warning");
await render_list();
} catch {
set_notice("Failed to remove script.", "error");
} finally {
busy_idx.value = null;
}
};
return (_ctx, _cache) => {
return (0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxDialog), {
open: is_open.value,
"onUpdate:open": [_cache[6] || (_cache[6] = ($event) => is_open.value = $event), on_dialog_update],
class: "smgr-dialog",
"close-button-label": "Close",
"use-close-button": true
}, {
header: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", { class: (0, vue.normalizeClass)(["smgr-header-content", { "smgr-header-content--install": current_view.value === "install" }]) }, [current_view.value === "install" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_1, [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxButton), {
onClick: _cache[0] || (_cache[0] = ($event) => current_view.value = "list"),
action: "progressive",
weight: "quiet",
class: "smgr-back-btn"
}, {
default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxIcon), { icon: (0, vue.unref)(C7) }, null, 8, ["icon"]), _cache[10] || (_cache[10] = (0, vue.createTextVNode)("Back ", -1))]),
_: 1
}), _cache[11] || (_cache[11] = (0, vue.createElementVNode)("div", { class: "smgr-header-text" }, [(0, vue.createElementVNode)("span", { class: "cdx-dialog__header__title" }, "Install New Script")], -1))])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_2, [..._cache[12] || (_cache[12] = [(0, vue.createElementVNode)("span", { class: "cdx-dialog__header__title" }, "script-ctl", -1), (0, vue.createElementVNode)("span", { class: "cdx-dialog__header__subtitle" }, "A security-focused user script manager", -1)])])), (0, vue.createElementVNode)("div", _hoisted_3, [current_view.value === "list" ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(_wikimedia_codex.CdxButton), {
key: 0,
action: "progressive",
weight: "primary",
onClick: _cache[1] || (_cache[1] = ($event) => current_view.value = "install")
}, {
default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxIcon), { icon: (0, vue.unref)(C3) }, null, 8, ["icon"]), _cache[13] || (_cache[13] = (0, vue.createTextVNode)("Add script ", -1))]),
_: 1
})) : (0, vue.createCommentVNode)("", true), (0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxButton), {
weight: "quiet",
onClick: _cache[2] || (_cache[2] = ($event) => is_open.value = false),
"aria-label": "Close"
}, {
default: (0, vue.withCtx)(() => [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxIcon), { icon: (0, vue.unref)(r4) }, null, 8, ["icon"]), _cache[14] || (_cache[14] = (0, vue.createTextVNode)("Close ", -1))]),
_: 1
})])], 2)]),
default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", _hoisted_4, [notice.value.text ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
key: 0,
class: (0, vue.normalizeClass)(["smgr-notice", `smgr-notice--${notice.value.type}`])
}, (0, vue.toDisplayString)(notice.value.text), 3)) : (0, vue.createCommentVNode)("", true), current_view.value === "install" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_5, [(0, vue.createVNode)(InstallForm_default, {
ref_key: "install_form_ref",
ref: install_form_ref,
busy: busy_install.value,
onInstall: on_install_requested,
onCancel: _cache[3] || (_cache[3] = ($event) => current_view.value = "list")
}, null, 8, ["busy"])])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6, [(0, vue.createVNode)(ScriptList_default, {
scripts: scripts.value,
updates: updates.value,
timestamps: timestamps.value,
loading: loading.value,
busy_idx: busy_idx.value,
onToggle: _cache[4] || (_cache[4] = ($event) => on_toggle($event.idx, $event.current_status)),
onUpdate: on_update,
onUninstall: on_uninstall,
onAdd_new: _cache[5] || (_cache[5] = ($event) => current_view.value = "install")
}, null, 8, [
"scripts",
"updates",
"timestamps",
"loading",
"busy_idx"
])]))])]),
_: 1
}, 8, ["open"]), (0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxDialog), {
open: show_warning.value,
"onUpdate:open": [_cache[8] || (_cache[8] = ($event) => show_warning.value = $event), _cache[9] || (_cache[9] = ($event) => show_warning.value = $event)],
title: "Security Warning",
"close-button-label": "Cancel",
"use-close-button": true
}, {
footer: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("div", _hoisted_10, [(0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxButton), { onClick: _cache[7] || (_cache[7] = ($event) => show_warning.value = false) }, {
default: (0, vue.withCtx)(() => [..._cache[21] || (_cache[21] = [(0, vue.createTextVNode)("Cancel", -1)])]),
_: 1
}), (0, vue.createVNode)((0, vue.unref)(_wikimedia_codex.CdxButton), {
action: "destructive",
weight: "primary",
onClick: confirm_action,
disabled: busy_install.value
}, {
default: (0, vue.withCtx)(() => [(0, vue.createTextVNode)((0, vue.toDisplayString)(pending_action.value.type === "install" ? "I Understand, Install" : "I Understand, Update"), 1)]),
_: 1
}, 8, ["disabled"])])]),
default: (0, vue.withCtx)(() => [(0, vue.createElementVNode)("p", null, [
_cache[15] || (_cache[15] = (0, vue.createElementVNode)("strong", null, "WARNING:", -1)),
_cache[16] || (_cache[16] = (0, vue.createTextVNode)(" Userscripts could contain malicious content capable of compromising your account. You are expected to review the source code of the script before installing it: ", -1)),
(0, vue.createElementVNode)("a", {
href: get_url(pending_action.value.name),
target: "_blank"
}, [(0, vue.createElementVNode)("strong", null, (0, vue.toDisplayString)(pending_action.value.name), 1)], 8, _hoisted_7)
]), pending_action.value.type === "install" ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_8, [
_cache[17] || (_cache[17] = (0, vue.createTextVNode)(" Are you absolutely sure you want to install and trust ", -1)),
(0, vue.createElementVNode)("code", null, (0, vue.toDisplayString)(pending_action.value.name), 1),
_cache[18] || (_cache[18] = (0, vue.createTextVNode)("? ", -1))
])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("p", _hoisted_9, [
_cache[19] || (_cache[19] = (0, vue.createTextVNode)(" Are you absolutely sure you want to update and trust the new version of ", -1)),
(0, vue.createElementVNode)("code", null, (0, vue.toDisplayString)(pending_action.value.name), 1),
_cache[20] || (_cache[20] = (0, vue.createTextVNode)("? ", -1))
]))]),
_: 1
}, 8, ["open"])], 64);
};
}
}, [["__scopeId", "data-v-bafc0596"]]);
//#endregion
//#region src/init.js
async function init() {
(await script_store.load()).filter((s) => s.status === "enabled").forEach((s) => load_script(s.pagename, s.oldid));
let app_instance = null;
const ensure_app_instance = () => {
if (!app_instance) {
const app_mount = document.createElement("div");
app_mount.id = "script-manager-vue-root";
document.body.appendChild(app_mount);
app_instance = (0, vue.createApp)(App_default).mount(app_mount);
}
return app_instance;
};
const link = mw.util.addPortletLink("p-personal", "#", "Script Manager", "pt-scriptmanager", "Install and manage userscripts");
if (link) link.addEventListener("click", (e) => {
e.preventDefault();
const inst = ensure_app_instance();
if (inst && typeof inst.open === "function") inst.open();
});
if (mw.config.get("wgPageName") === "Wikipedia:User_scripts/List") document.querySelectorAll("span.scriptInstallerLink").forEach((span) => {
const id = span.id;
if (id && id.endsWith(".js")) {
const install_link = document.createElement("a");
install_link.textContent = " [install]";
install_link.href = "#";
install_link.style.cursor = "pointer";
install_link.className = "script-ctl-install-link";
install_link.addEventListener("click", (e) => {
e.preventDefault();
const inst = ensure_app_instance();
if (inst && typeof inst.open_install === "function") inst.open_install(id);
});
span.after(install_link);
}
});
}
init();
//#endregion
})(require("vue"), require("@wikimedia/codex"));
});
(function() {
//#region \0vite/all-css
try {
if (typeof document != "undefined") {
var elementStyle = document.createElement("style");
elementStyle.appendChild(document.createTextNode(".smgr-install-view[data-v-c419cbd9] {\n display: flex;\n flex-direction: column;\n gap: 16px;\n}\n.smgr-card[data-v-c419cbd9] {\n background: #fff;\n border: 1px solid #c8ccd1;\n border-radius: 4px;\n padding: 16px;\n}\n.skin-theme-clientpref-night .smgr-card[data-v-c419cbd9] {\n background: #202122;\n border-color: #54595d;\n}\n.smgr-install-card[data-v-c419cbd9] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.smgr-label[data-v-c419cbd9] {\n font-weight: bold;\n font-size: 0.9em;\n}\n.smgr-install-row[data-v-c419cbd9] {\n display: flex;\n gap: 10px;\n}\n.smgr-input[data-v-c419cbd9] {\n flex: 1;\n}\n.smgr-browse-footer[data-v-c419cbd9] {\n margin-top: 4px;\n display: flex;\n justify-content: flex-start;\n}\n\n.smgr-state[data-v-1b27191a] {\r\n padding: 24px 0;\r\n text-align: center;\r\n color: #72777d;\n}\n.smgr-script-cell[data-v-1b27191a] {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n margin-left: -12px;\n}\n.smgr-dimmed[data-v-1b27191a] {\r\n opacity: 0.45;\n}\n.smgr-pagename[data-v-1b27191a] {\r\n font-weight: 600;\r\n color: #0645ad;\r\n text-decoration: none;\n}\n.smgr-pagename[data-v-1b27191a]:hover {\r\n text-decoration: underline;\n}\n.smgr-update-chip[data-v-1b27191a] {\r\n display: inline-flex;\r\n align-items: center;\r\n gap: 3px;\r\n font-size: 0.72em;\r\n font-weight: 600;\r\n color: #3366cc;\r\n background: #eaf3fb;\r\n padding: 1px 6px;\r\n border-radius: 3px;\n}\n.smgr-update-chip .cdx-icon[data-v-1b27191a] {\r\n width: 12px;\r\n height: 12px;\n}\r\n\n.smgr-body[data-v-bafc0596] {\r\n display: flex;\r\n flex-direction: column;\r\n gap: 20px;\n}\n.smgr-header-content[data-v-bafc0596] {\r\n display: flex;\r\n justify-content: space-between;\r\n align-items: center;\r\n width: 100%;\n}\n.smgr-header-nav[data-v-bafc0596] {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n margin-left: -8px;\n}\n.smgr-header-text[data-v-bafc0596] {\r\n display: flex;\r\n flex-direction: column;\n}\n.smgr-header-actions[data-v-bafc0596] {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\n}\n.smgr-header-content--install .cdx-dialog__header__title[data-v-bafc0596] {\r\n font-size: 1rem;\n}\n.smgr-notice[data-v-bafc0596] {\r\n display: flex;\r\n align-items: center;\r\n gap: 8px;\r\n padding: 10px 14px;\r\n border-radius: 4px;\r\n border-left: 4px solid;\r\n font-size: 0.9rem;\r\n font-weight: 500;\n}\n.smgr-notice--success[data-v-bafc0596] {\r\n background: #f0fdf4;\r\n border-color: #22c55e;\r\n color: #15803d;\n}\n.smgr-notice--error[data-v-bafc0596] {\r\n background: #fef2f2;\r\n border-color: #ef4444;\r\n color: #b91c1c;\n}\n.smgr-notice--warning[data-v-bafc0596] {\r\n background: #fffbeb;\r\n border-color: #f59e0b;\r\n color: #b45309;\n}\n.smgr-notice--notice[data-v-bafc0596] {\r\n background: #eff6ff;\r\n border-color: #3b82f6;\r\n color: #1d4ed8;\n}\r\n\n.smgr-dialog.cdx-dialog__window,\r\n.smgr-dialog .cdx-dialog__window,\r\n.smgr-dialog {\r\n width: 800px !important;\r\n max-width: 90vw !important;\n}\r\n/*$vite$:1*/"));
document.head.appendChild(elementStyle);
}
} catch (e) {
console.error("vite-plugin-css-injected-by-js", e);
}
//#endregion
})();