MediaWiki:Gadget-AdvancedSiteNotices.js:修订间差异
外观
删除的内容 添加的内容
创建页面,内容为“// <nowiki> /* * **************************************************************************** * * >>>>> 小工具导入者:导入前请阅读 <<<<< * * * 请确保通告页面 (Template:AdvancedSiteNotices/ajax) * * * 在您的维基上已受到适当保护。 * * * 虽然已尽力正确处理 JavaScript 表达式, * * *…” |
无编辑摘要 |
||
| 第1行: | 第1行: | ||
/** |
|||
// <nowiki> |
|||
* SPDX-License-Identifier: CC-BY-SA-4.0 |
|||
/* |
|||
* _addText: '{{Gadget Header|license=CC-BY-SA-4.0}}' |
|||
* **************************************************************************** |
|||
* * >>>>> 小工具导入者:导入前请阅读 <<<<< * |
|||
* * 请确保通告页面 ([[Template:AdvancedSiteNotices/ajax]]) * |
|||
* * 在您的维基上已受到适当保护。 * |
|||
* * 虽然已尽力正确处理 JavaScript 表达式, * |
|||
* * 但它们未经充分的实战测试。 * |
|||
* **************************************************************************** |
|||
* |
* |
||
* @base {@link https://zh.wikipedia.org/wiki/MediaWiki:Gadget-AdvancedSiteNotices.js} |
|||
* @source {@link https://git.qiuwen.net.cn/InterfaceAdmin/QiuwenGadgets/src/branch/master/src/AdvancedSiteNotices} |
|||
* @license CC-BY-SA-4.0 {@link https://www.qiuwenbaike.cn/wiki/H:CC-BY-SA-4.0} |
|||
*/ |
*/ |
||
/** |
|||
* +------------------------------------------------------------+ |
|||
* | === WARNING: GLOBAL GADGET FILE === | |
|||
* +------------------------------------------------------------+ |
|||
* | All changes should be made in the repository, | |
|||
* | otherwise they will be lost. | |
|||
* +------------------------------------------------------------+ |
|||
* | Changes to this page may affect many users. | |
|||
* | Please discuss changes by opening an issue before editing. | |
|||
* +------------------------------------------------------------+ |
|||
*/ |
|||
/* <nowiki> */ |
|||
(() => { |
|||
Promise.all([ |
|||
$.ready |
|||
]).then(() => { |
|||
const geo = null; // 明确设为 null,兼容后续逻辑 |
|||
if ( |
|||
$("#siteNotice").length < 0 || |
|||
mw.config.get("wgAction") === "edit" || |
|||
mw.config.get("wgAction") === "submit" |
|||
) { |
|||
return; |
|||
} |
|||
"use strict"; |
|||
const { conv } = require("ext.gadget.HanAssist"); |
|||
var __getOwnPropNames = Object.getOwnPropertyNames; |
|||
var __commonJS = (cb, mod) => function __require() { |
|||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; |
|||
}; |
|||
// node_modules/.pnpm/broadcastchannel-polyfill@1.0.1/node_modules/broadcastchannel-polyfill/index.js |
|||
let customASNInterval = window.customASNInterval || 15; |
|||
var require_broadcastchannel_polyfill = __commonJS({ |
|||
const COOKIE_NAME = "dismissASN"; |
|||
"node_modules/.pnpm/broadcastchannel-polyfill@1.0.1/node_modules/broadcastchannel-polyfill/index.js"() { |
|||
let cookieVal = Number.parseInt(mw.cookie.get(COOKIE_NAME) || "-1", 10); |
|||
(function(global) { |
|||
let revisionId = 0; |
|||
var channels = []; |
|||
function BroadcastChannel2(channel) { |
|||
var $this = this; |
|||
let $asnRoot = $("<div>", { id: "asn-dismissable-notice" }); |
|||
channel = String(channel); |
|||
var id = "$BroadcastChannel$" + channel + "$"; |
|||
id: "advancedSiteNotices", |
|||
channels[id] = channels[id] || []; |
|||
channels[id].push(this); |
|||
}); |
|||
this._name = channel; |
|||
let $asnClose = $("<button>", { |
|||
this._id = id; |
|||
title: conv({ hans: "关闭", hant: "關閉" }), |
|||
this._closed = false; |
|||
"aria-label": conv({ hans: "关闭", hant: "關閉" }), |
|||
this._mc = new MessageChannel(); |
|||
this._mc.port1.start(); |
|||
}); |
|||
this._mc.port2.start(); |
|||
global.addEventListener("storage", function(e) { |
|||
$asnRoot.append($asnBody, $asnClose); |
|||
if (e.storageArea !== global.localStorage) return; |
|||
$asnClose.click(() => { |
|||
if (e.newValue == null || e.newValue === "") return; |
|||
$asnClose.prop("disabled", true); |
|||
if (e.key.substring(0, id.length) !== id) return; |
|||
mw.cookie.set(COOKIE_NAME, revisionId, { |
|||
var data = JSON.parse(e.newValue); |
|||
$this._mc.port2.postMessage(data); |
|||
secure: true, |
|||
}); |
}); |
||
} |
|||
clearTimeout(timeoutId); |
|||
BroadcastChannel2.prototype = { |
|||
// BroadcastChannel API |
|||
get name() { |
|||
return this._name; |
|||
}); |
|||
}, |
|||
postMessage: function(message) { |
|||
/** |
|||
var $this = this; |
|||
* @typedef {Object} TokenizeStatus |
|||
if (this._closed) { |
|||
* @property {string} expression |
|||
var e = new Error(); |
|||
* @property {RegExp} startTokenRe |
|||
e.name = "InvalidStateError"; |
|||
* @property {number} index |
|||
throw e; |
|||
* @property {((item: any) => void) & { __orig__?: (item: any) => void }} append |
|||
} |
|||
var value = JSON.stringify(message); |
|||
var key = this._id + String(Date.now()) + "$" + String(Math.random()); |
|||
/** |
|||
global.localStorage.setItem(key, value); |
|||
* 条件解析器。仅支持 JavaScript 的一个小子集。 |
|||
setTimeout(function() { |
|||
*/ |
|||
global.localStorage.removeItem(key); |
|||
class CriteriaExecutor { |
|||
}, 500); |
|||
channels[this._id].forEach(function(bc) { |
|||
* @param {Record<string, (...args: any[]) => any>} functions |
|||
if (bc === $this) return; |
|||
bc._mc.port2.postMessage(JSON.parse(value)); |
|||
constructor(functions = {}) { |
|||
}); |
|||
}, |
|||
close: function() { |
|||
if (this._closed) return; |
|||
this._closed = true; |
|||
this._mc.port1.close(); |
|||
this._mc.port2.close(); |
|||
var index = channels[this._id].indexOf(this); |
|||
channels[this._id].splice(index, 1); |
|||
}, |
|||
// EventTarget API |
|||
get onmessage() { |
|||
return this._mc.port1.onmessage; |
|||
}, |
|||
set onmessage(value) { |
|||
this._mc.port1.onmessage = value; |
|||
}, |
|||
addEventListener: function() { |
|||
return this._mc.port1.addEventListener.apply(this._mc.port1, arguments); |
|||
}, |
|||
removeEventListener: function() { |
|||
return this._mc.port1.removeEventListener.apply(this._mc.port1, arguments); |
|||
}, |
|||
dispatchEvent: function() { |
|||
return this._mc.port1.dispatchEvent.apply(this._mc.port1, arguments); |
|||
} |
} |
||
}; |
|||
global.BroadcastChannel = global.BroadcastChannel || BroadcastChannel2; |
|||
})(self); |
|||
} |
|||
}); |
|||
// dist/AdvancedSiteNotices/AdvancedSiteNotices.js |
|||
/** |
|||
require_broadcastchannel_polyfill(); |
|||
* 将表达式解析为标记 |
|||
function asyncGeneratorStep(n, t, e, r, o, a, c) { |
|||
* |
|||
try { |
|||
* @param {string} expression |
|||
var i = n[a](c), u = i.value; |
|||
} catch (n2) { |
|||
return void e(n2); |
|||
_tokenizeExpression(expression) { |
|||
} |
|||
expression = expression.trim(); |
|||
i.done ? t(u) : Promise.resolve(u).then(r, o); |
|||
if (!expression) { |
|||
} |
|||
return []; |
|||
function _asyncToGenerator(n) { |
|||
} |
|||
return function() { |
|||
var t = this, e = arguments; |
|||
return new Promise(function(r, o) { |
|||
/** @type {TokenizeStatus} */ |
|||
var a = n.apply(t, e); |
|||
function _next(n2) { |
|||
expression, |
|||
asyncGeneratorStep(a, r, o, _next, _throw, "next", n2); |
|||
startTokenRe: /\|\||&&|\b(?:true|false)\b|[('"!\s]/g, |
|||
} |
|||
function _throw(n2) { |
|||
asyncGeneratorStep(a, r, o, _next, _throw, "throw", n2); |
|||
result.push(item); |
|||
} |
|||
_next(void 0); |
|||
}); |
|||
let match = status.startTokenRe.exec(expression); |
|||
}; |
|||
} |
|||
const skipSpace = () => { |
|||
function _createForOfIteratorHelper(r, e) { |
|||
while (/\s/.test(expression[status.index] || "")) { |
|||
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; |
|||
status.index++; |
|||
if (!t) { |
|||
} |
|||
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { |
|||
}; |
|||
t && (r = t); |
|||
var n = 0, F = function() { |
|||
}; |
|||
const token = match[0]; |
|||
return { s: F, n: function() { |
|||
return n >= r.length ? { done: true } : { done: false, value: r[n++] }; |
|||
if (status.startTokenRe.lastIndex - token.length !== status.index) { |
|||
}, e: function(r2) { |
|||
throw r2; |
|||
// 处理函数调用 |
|||
}, f: F }; |
|||
const parsed = this._handleFunctionCall(status); |
|||
if (!parsed) { |
|||
break; |
|||
} |
|||
} else { |
|||
break; |
|||
} |
|||
} else { |
|||
let parsed = false; |
|||
switch (token) { |
|||
case "(": |
|||
parsed = this._handleExpressionStatement(status); |
|||
break; |
|||
case "||": |
|||
case "&&": |
|||
parsed = this._handleLogicalOperator(status, token); |
|||
break; |
|||
case "!": |
|||
parsed = this._handleUnaryExpression(status, token); |
|||
break; |
|||
case "'": |
|||
case '"': |
|||
parsed = this._handleStringLiteral(status, token); |
|||
break; |
|||
case "true": |
|||
case "false": |
|||
parsed = this._handleBooleanLiteral(status, token); |
|||
break; |
|||
} |
|||
if (!parsed) { |
|||
break; |
|||
} |
|||
} |
|||
skipSpace(); |
|||
status.startTokenRe.lastIndex = status.index; |
|||
match = status.startTokenRe.exec(expression); |
|||
} |
|||
if (status.index < expression.length - 1) { |
|||
throw new SyntaxError("Unexpected token."); |
|||
} |
|||
return result; |
|||
} |
|||
/** |
|||
* 处理函数调用 |
|||
* |
|||
* @param {TokenizeStatus} status |
|||
* @return {boolean} |
|||
*/ |
|||
_handleFunctionCall(status) { |
|||
const functionName = status.expression |
|||
.slice(status.index, status.startTokenRe.lastIndex - 1) |
|||
.trim(); |
|||
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(functionName)) { |
|||
return false; |
|||
} |
|||
const argsStartIndex = status.startTokenRe.lastIndex - 1; |
|||
const endIndex = this._findNextBalanceItem( |
|||
status.expression, |
|||
"(", |
|||
")", |
|||
argsStartIndex |
|||
); |
|||
if (endIndex === -1) { |
|||
throw new SyntaxError("Unbalanced parentheses in function call"); |
|||
} |
|||
const rawArgs = status.expression |
|||
.slice(argsStartIndex + 1, endIndex) |
|||
.trim(); |
|||
const args = !rawArgs.length |
|||
? [] |
|||
: this._balanceSplit(rawArgs, ",", "(", ")"); |
|||
status.append({ |
|||
type: "FunctionCall", |
|||
functionName, |
|||
args: args.map((arg) => this._tokenizeExpression(arg)), |
|||
}); |
|||
status.index = endIndex + 1; |
|||
return true; |
|||
} |
|||
/** |
|||
* 处理逻辑运算符 |
|||
* |
|||
* @param {TokenizeStatus} status |
|||
* @param {string} token |
|||
* @return {boolean} |
|||
*/ |
|||
_handleLogicalOperator(status, token) { |
|||
status.append({ |
|||
type: "LogicalExpression", |
|||
operator: token, |
|||
}); |
|||
status.index = status.startTokenRe.lastIndex; |
|||
return true; |
|||
} |
|||
/** |
|||
* 处理一元表达式 |
|||
* |
|||
* @param {TokenizeStatus} status |
|||
* @param {string} token |
|||
* @return {boolean} |
|||
*/ |
|||
_handleUnaryExpression(status, token) { |
|||
const item = { |
|||
type: "UnaryExpression", |
|||
operator: token, |
|||
argument: undefined, |
|||
}; |
|||
status.append(item); |
|||
const origAppend = status.append.__orig__ || status.append; |
|||
status.append = function (nextItem) { |
|||
if (nextItem.type === "LogicalExpression") { |
|||
throw new SyntaxError("Unexpected LogicalExpression"); |
|||
} |
|||
item.argument = nextItem; |
|||
status.append = origAppend; |
|||
}; |
|||
status.append.__orig__ = origAppend; |
|||
status.index = status.startTokenRe.lastIndex; |
|||
return true; |
|||
} |
|||
/** |
|||
* 处理布尔值字面量 |
|||
* |
|||
* @param {TokenizeStatus} status |
|||
* @param {string} token |
|||
* @return {boolean} |
|||
*/ |
|||
_handleBooleanLiteral(status, token) { |
|||
status.append({ |
|||
type: "BooleanLiteral", |
|||
value: token === "true", |
|||
}); |
|||
status.index = status.startTokenRe.lastIndex; |
|||
return true; |
|||
} |
|||
/** |
|||
* 处理表达式语句 |
|||
* |
|||
* @param {TokenizeStatus} status |
|||
* @return {boolean} |
|||
*/ |
|||
_handleExpressionStatement(status) { |
|||
const endIndex = this._findNextBalanceItem( |
|||
status.expression, |
|||
"(", |
|||
")", |
|||
status.index |
|||
); |
|||
if (endIndex === -1) { |
|||
throw new SyntaxError("Unbalanced parentheses in expression."); |
|||
} |
|||
status.append({ |
|||
type: "ExpressionStatement", |
|||
expression: this._tokenizeExpression( |
|||
status.expression.slice(status.index + 1, endIndex) |
|||
), |
|||
}); |
|||
status.index = endIndex + 1; |
|||
return true; |
|||
} |
|||
/** |
|||
* 处理字符串字面量 |
|||
* |
|||
* @param {TokenizeStatus} status |
|||
* @param {string} delimiter |
|||
* @return {boolean} |
|||
*/ |
|||
_handleStringLiteral(status, delimiter) { |
|||
const endIndex = this._findNextTokenWithoutEscape( |
|||
status.expression, |
|||
delimiter, |
|||
status.index + 1 |
|||
); |
|||
if (endIndex === -1) { |
|||
throw new SyntaxError("Unterminated string literal."); |
|||
} |
|||
status.append({ |
|||
type: "StringLiteral", |
|||
value: this._parseString( |
|||
status.expression.slice(status.index + 1, endIndex) |
|||
), |
|||
}); |
|||
status.index = endIndex + 1; |
|||
return true; |
|||
} |
|||
/** |
|||
* 查找下一个平衡项 |
|||
* |
|||
* @param {string} expression |
|||
* @param {string} startToken |
|||
* @param {string} endToken |
|||
* @param {number} currentIndex |
|||
* @return {number} |
|||
*/ |
|||
_findNextBalanceItem(expression, startToken, endToken, currentIndex) { |
|||
let count = 1; |
|||
while (++currentIndex < expression.length) { |
|||
if (expression[currentIndex] === startToken) { |
|||
count++; |
|||
} else if (expression[currentIndex] === endToken) { |
|||
count--; |
|||
} |
|||
if (count === 0) { |
|||
return currentIndex; |
|||
} |
|||
} |
|||
return -1; |
|||
} |
|||
/** |
|||
* 分割平衡子表达式 |
|||
* |
|||
* @param {string} expression |
|||
* @param {string} startToken |
|||
* @param {string} endToken |
|||
* @param {string} splitToken |
|||
* @return {string[]} |
|||
*/ |
|||
_balanceSplit(expression, splitToken, startToken, endToken) { |
|||
const result = []; |
|||
let balance = 0; |
|||
let current = ""; |
|||
for (const char of expression) { |
|||
if (char === startToken) { |
|||
balance++; |
|||
} else if (char === endToken) { |
|||
balance--; |
|||
} |
|||
if (char === splitToken && balance === 0) { |
|||
result.push(current.trim()); |
|||
current = ""; |
|||
} else { |
|||
current += char; |
|||
} |
|||
} |
|||
if (current) { |
|||
result.push(current.trim()); |
|||
} |
|||
return result; |
|||
} |
|||
/** |
|||
* 查找未转义的标记 |
|||
* |
|||
* @param {string} expression |
|||
* @param {string} token |
|||
* @param {number} currentIndex |
|||
* @return {number} |
|||
*/ |
|||
_findNextTokenWithoutEscape(expression, token, currentIndex) { |
|||
while (currentIndex < expression.length) { |
|||
const foundIndex = expression.indexOf(token, currentIndex); |
|||
if (foundIndex === -1) { |
|||
return -1; |
|||
} else if (expression[foundIndex - 1] !== "\\") { |
|||
return foundIndex; |
|||
} |
|||
currentIndex = foundIndex + 1; |
|||
} |
|||
return -1; |
|||
} |
|||
/** |
|||
* 解析字符串并处理转义字符 |
|||
* |
|||
* @param {string} input |
|||
* @return {string} |
|||
*/ |
|||
_parseString(input) { |
|||
return input.replace( |
|||
/\\(n|t|r|b|f|x[0-9A-Fa-f]{2}|u\{[0-9A-Fa-f]+\}|u[0-9A-Fa-f]{4}|.)/g, |
|||
(_, esc) => { |
|||
switch (esc[0]) { |
|||
case "n": |
|||
return "\n"; |
|||
case "t": |
|||
return "\t"; |
|||
case "r": |
|||
return "\r"; |
|||
case "b": |
|||
return "\b"; |
|||
case "f": |
|||
return "\f"; |
|||
case "x": |
|||
if (esc === "x") { |
|||
throw new SyntaxError("Invalid hexadecimal escape sequence."); |
|||
} |
|||
return String.fromCharCode(parseInt(esc.slice(1), 16)); |
|||
case "u": |
|||
if (esc === "u") { |
|||
throw new SyntaxError("Invalid Unicode escape sequence."); |
|||
} else if (esc[1] === "{") { |
|||
const codePoint = Number.parseInt(esc.slice(2, -1), 16); |
|||
if (codePoint > 0x10ffff) { |
|||
throw new SyntaxError( |
|||
`Undefined Unicode code-point: \\${esc}.` |
|||
); |
|||
} |
|||
return String.fromCodePoint(codePoint); |
|||
} |
|||
return String.fromCharCode(Number.parseInt(esc.slice(1), 16)); |
|||
default: |
|||
return esc; |
|||
} |
|||
} |
|||
); |
|||
} |
|||
/** |
|||
* @param {any[]} tokens |
|||
*/ |
|||
_tokensToAst(tokens) { |
|||
if (!tokens.length) { |
|||
throw new TypeError("Token list is empty."); |
|||
} |
|||
// 先處理 && 再處理 || |
|||
let logicalItemIndex = tokens.findIndex((t) => t.type === "LogicalExpression" && t.operator === '&&'); |
|||
if (logicalItemIndex === -1) { |
|||
logicalItemIndex = tokens.findIndex((t) => t.type === "LogicalExpression" && t.operator === '||'); |
|||
} |
|||
if (logicalItemIndex === -1) { |
|||
if (tokens.length === 1) { |
|||
return this._tokenToAst(tokens[0]); |
|||
} |
|||
throw new SyntaxError(`Unexpected ${tokens[1].type}.`); |
|||
} else if (logicalItemIndex === 0) { |
|||
throw new SyntaxError(`Unexpected LogicalExpression`); |
|||
} else { |
|||
const left = this._tokensToAst(tokens.slice(0, logicalItemIndex)); |
|||
const right = this._tokensToAst(tokens.slice(logicalItemIndex + 1)); |
|||
return { |
|||
type: "LogicalExpression", |
|||
operator: tokens[logicalItemIndex].operator, |
|||
left, |
|||
right, |
|||
}; |
|||
} |
|||
} |
|||
/** |
|||
* @param {any} token |
|||
*/ |
|||
_tokenToAst(token) { |
|||
if (!token) { |
|||
throw new TypeError('token is undefined or null.'); |
|||
} |
|||
const result = Object.assign({}, token); |
|||
if (token.type === "UnaryExpression") { |
|||
if (!result.argument) { |
|||
throw new SyntaxError("Unexpected UnaryExpression."); |
|||
} |
|||
result.argument = this._tokenToAst(token.argument); |
|||
} else if (token.type === "FunctionCall") { |
|||
result.args = token.args.map((argument) => |
|||
this._tokensToAst(argument) |
|||
); |
|||
} else if (token.type === "ExpressionStatement") { |
|||
result.expression = this._tokensToAst(token.expression); |
|||
} |
|||
return result; |
|||
} |
|||
/** |
|||
* @param {string} expression |
|||
*/ |
|||
toAst(expression) { |
|||
const tokenizes = this._tokenizeExpression(expression); |
|||
return this._tokensToAst(tokenizes); |
|||
} |
|||
/** |
|||
* @param {string} functionName |
|||
* @param {any[]} args |
|||
*/ |
|||
_executeFunction(functionName, args) { |
|||
if (!Object.prototype.hasOwnProperty.call(this.functions, functionName)) { |
|||
throw new Error(`Function ${functionName} is not allowed.`); |
|||
} |
|||
return this.functions[functionName](...args); |
|||
} |
|||
/** |
|||
* @param {any} ast |
|||
*/ |
|||
evaluate(ast) { |
|||
switch (ast.type) { |
|||
case "ExpressionStatement": |
|||
return this.evaluate(ast.expression); |
|||
case "UnaryExpression": |
|||
if (ast.operator === "!") { |
|||
return !this.evaluate(ast.argument); |
|||
} |
|||
throw new Error(`Unknown UnaryExpression operator: ${ast.operator}`); |
|||
case "LogicalExpression": |
|||
if (ast.operator === "&&" || ast.operator === "||") { |
|||
const left = this.evaluate(ast.left); |
|||
if (ast.operator === "&&" && !left) { |
|||
return false; |
|||
} else if (ast.operator === "||" && left) { |
|||
return true; |
|||
} |
|||
return this.evaluate(ast.right); |
|||
} |
|||
throw new Error(`Unknown LogicalExpression operator: ${ast.operator}`); |
|||
case "FunctionCall": |
|||
const args = ast.args.map((arg) => this.evaluate(arg)); |
|||
let returnValue = this._executeFunction(ast.functionName, args); |
|||
if (typeof returnValue === 'undefined' || returnValue === null) { |
|||
returnValue = false; |
|||
} else if (typeof returnValue !== 'string' && typeof returnValue !== 'boolean') { |
|||
console.warn( |
|||
'[AdvancedSiteNotices]: The return type %s of the function %s() is unsafe and has been forcibly converted to a string.', |
|||
typeof returnValue, |
|||
ast.functionName |
|||
); |
|||
returnValue = String(returnValue); |
|||
} |
|||
return returnValue; |
|||
case "StringLiteral": |
|||
case "BooleanLiteral": |
|||
return ast.value; |
|||
default: |
|||
throw new Error(`Unknown AST node type: ${ast.type}`); |
|||
} |
|||
} |
|||
} |
} |
||
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); |
|||
} |
|||
const functions = {}; |
|||
var o, a = true, u = false; |
|||
return { s: function() { |
|||
// 帶參數 |
|||
t = t.call(r); |
|||
}, n: function() { |
|||
functions.in_country = (...counties) => counties.includes(geo.country); |
|||
var r2 = t.next(); |
|||
functions.in_region = (...regions) => regions.includes(geo.region); |
|||
return a = r2.done, r2; |
|||
functions.in_city = (...cities) => cities.includes(geo.city); |
|||
}, e: function(r2) { |
|||
u = true, o = r2; |
|||
// 无 geoIP 时,跳过地区筛选(或改为 return false 禁用) |
|||
}, f: function() { |
|||
functions.in_country = functions.in_region = functions.in_city = (...args) => args.length ? true : false; |
|||
try { |
|||
a || null == t.return || t.return(); |
|||
} finally { |
|||
if (u) throw o; |
|||
} |
} |
||
} }; |
|||
const configs = mw.config.get(["wgUserGroups", "wgUserLanguage"]); |
|||
} |
|||
functions.in_group = (...groups) => groups.some(group => configs.wgUserGroups.includes(group)); |
|||
function _unsupportedIterableToArray(r, a) { |
|||
functions.in_group_every = (...groups) => groups.every(group => configs.wgUserGroups.includes(group)); |
|||
if (r) { |
|||
functions.in_lang = (...useLangs) => useLangs.includes(configs.wgUserLanguage); |
|||
if ("string" == typeof r) return _arrayLikeToArray(r, a); |
|||
var t = {}.toString.call(r).slice(8, -1); |
|||
// 不帶參數 |
|||
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; |
|||
// 錯誤示範: |
|||
} |
|||
// functions.is_anon = mw.user.isAnon; // 不安全,無法確定 mw.user.isAnon 是否有被修改過可以傳奇怪的值進去 |
|||
} |
|||
// functions.is_anon = () => mw.user.isAnon(); // 不安全,返回值為 boolean 的,只要無法保證一定返回 boolean,就應該全部強制轉換成 boolean |
|||
function _arrayLikeToArray(r, a) { |
|||
functions.is_anon = () => !!mw.user.isAnon(); |
|||
(null == a || a > r.length) && (a = r.length); |
|||
// 支持较旧的 MediaWiki 版本(通常是其他维基) |
|||
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; |
|||
// mw.user.isTemp 和 mw.user.isNamed 在 MediaWiki 1.40 中添加 |
|||
return n; |
|||
functions.is_temp = () => typeof mw.user.isTemp === "function" ? !!mw.user.isTemp() : false; |
|||
} |
|||
functions.is_named = () => typeof mw.user.isNamed === "function" ? !!mw.user.isNamed() : !mw.user.isAnon(); |
|||
var __create = Object.create; |
|||
var __defProp = Object.defineProperty; |
|||
const parser = new CriteriaExecutor(functions); |
|||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; |
|||
var __getOwnPropNames2 = Object.getOwnPropertyNames; |
|||
const cache = new WeakMap(); |
|||
var __getProtoOf = Object.getPrototypeOf; |
|||
function getCache($element, key) { |
|||
var __hasOwnProp = Object.prototype.hasOwnProperty; |
|||
const element = $element.get(0); |
|||
var __copyProps = (to, from, except, desc) => { |
|||
if (cache.has(element)) { |
|||
if (from && typeof from === "object" || typeof from === "function") { |
|||
return cache.get(element)[key]; |
|||
var _iterator = _createForOfIteratorHelper(__getOwnPropNames2(from)), _step; |
|||
} |
|||
try { |
|||
for (_iterator.s(); !(_step = _iterator.n()).done; ) { |
|||
let key = _step.value; |
|||
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { |
|||
get: () => from[key], |
|||
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable |
|||
}); |
|||
} |
|||
} catch (err) { |
|||
_iterator.e(err); |
|||
} finally { |
|||
_iterator.f(); |
|||
} |
} |
||
} |
|||
function setCache($element, key, value) { |
|||
return to; |
|||
const element = $element.get(0); |
|||
}; |
|||
if (cache.has(element)) { |
|||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( |
|||
cache.get(element)[key] = value; |
|||
// If the importer is in node compatibility mode or this is not an ESM |
|||
} else { |
|||
// file that has been converted to a CommonJS file using a Babel- |
|||
cache.set(element, { |
|||
// compatible transform (i.e. "__esModule" has not been set), then set |
|||
[key]: value, |
|||
// "default" to the CommonJS "module.exports" for node compatibility. |
|||
}); |
|||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { |
|||
} |
|||
value: mod, |
|||
enumerable: true |
|||
}) : target, |
|||
mod |
|||
)); |
|||
//! src/AdvancedSiteNotices/options.json |
|||
var ajaxPageTitle = "Template:AdvancedSiteNotices/ajax"; |
|||
var mountPointSelector = "#siteNotice"; |
|||
var storageKey = "ext.gadget.AdvancedSiteNotices_dismissASN"; |
|||
var cacheKey = "ext.gadget.AdvancedSiteNotices_cache"; |
|||
var version = "4.0"; |
|||
//! src/AdvancedSiteNotices/AdvancedSiteNotices.ts |
|||
var import_ext_gadget7 = require("ext.gadget.Util"); |
|||
//! src/AdvancedSiteNotices/modules/api.ts |
|||
var import_ext_gadget = require("ext.gadget.Util"); |
|||
var api = (0, import_ext_gadget.initMwApi)("AdvancedSiteNotices/".concat(version)); |
|||
//! src/AdvancedSiteNotices/modules/util/queryApi.ts |
|||
var { |
|||
wgUserLanguage |
|||
} = mw.config.get(); |
|||
var parameters = { |
|||
action: "parse", |
|||
format: "json", |
|||
formatversion: "2", |
|||
prop: "text", |
|||
page: ajaxPageTitle, |
|||
uselang: wgUserLanguage, |
|||
variant: wgUserLanguage, |
|||
smaxage: 600, |
|||
maxage: 600 |
|||
}; |
|||
var queryApi = /* @__PURE__ */ (function() { |
|||
var _ref = _asyncToGenerator(function* () { |
|||
try { |
|||
let response; |
|||
if (mw.storage.getObject(cacheKey)) { |
|||
response = mw.storage.getObject(cacheKey); |
|||
} else { |
|||
response = yield api.get(parameters); |
|||
mw.storage.setObject(cacheKey, response, 60 * 10); |
|||
} |
|||
return response; |
|||
} catch (error) { |
|||
console.error("[AdvancedSiteNotices] Ajax error:", error); |
|||
return {}; |
|||
} |
} |
||
}); |
|||
return function queryApi2() { |
|||
return _ref.apply(this, arguments); |
|||
let cache = getCache($noticeItem, "asn-cache"); |
|||
}; |
|||
if (cache !== undefined) { |
|||
})(); |
|||
return cache; |
|||
//! src/AdvancedSiteNotices/modules/loadRemoteNotices.tsx |
|||
} |
|||
var import_ext_gadget2 = __toESM(require("ext.gadget.JSX"), 1); |
|||
let criteria = $noticeItem.attr("data-asn-criteria"); |
|||
var loadRemoteNotices = /* @__PURE__ */ (function() { |
|||
let result; |
|||
var _ref2 = _asyncToGenerator(function* () { |
|||
if (criteria !== undefined) { |
|||
const response = yield queryApi(); |
|||
if (criteria === "") { |
|||
const responseParse = response["parse"]; |
|||
result = true; |
|||
if (!(responseParse !== null && responseParse !== void 0 && responseParse.text)) { |
|||
} else { |
|||
return {}; |
|||
criteria = decodeURIComponent(criteria.replace(/\+/g, "%20")).trim(); |
|||
const ast = parser.toAst(criteria); |
|||
result = !!parser.evaluate(ast); |
|||
} catch (error) { |
|||
console.warn( |
|||
'[AdvancedSiteNotices]: Fail to parse or evaluate criteria "%s":', |
|||
criteria, |
|||
error |
|||
); |
|||
result = false; |
|||
} |
|||
} |
|||
} else { |
|||
const testList = []; |
|||
if ($noticeItem.hasClass("only_sysop")) { |
|||
testList.push(functions.in_group("sysop")); |
|||
} |
|||
if ( |
|||
$noticeItem.hasClass("only_logged_in") || |
|||
$noticeItem.hasClass("only_logged") /* 已弃用 */ || |
|||
$noticeItem.hasClass("is_named") |
|||
) { |
|||
testList.push(functions.is_named()); |
|||
} |
|||
if ( |
|||
$noticeItem.hasClass("only_logged_out") || |
|||
$noticeItem.hasClass("only_anon") /* 已弃用 */ |
|||
) { |
|||
testList.push(!functions.is_named()); |
|||
} |
|||
if ($noticeItem.hasClass("is_temp")) { |
|||
testList.push(functions.is_temp()); |
|||
} |
|||
if ($noticeItem.hasClass("is_anon")) { |
|||
testList.push(functions.is_anon()); |
|||
} |
|||
if ($noticeItem.hasClass("only_zh_cn")) { |
|||
testList.push(functions.in_lang("zh-cn")); |
|||
} |
|||
if ($noticeItem.hasClass("only_zh_hk")) { |
|||
testList.push(functions.in_lang("zh-hk")); |
|||
} |
|||
if ($noticeItem.hasClass("only_zh_sg")) { |
|||
testList.push(functions.in_lang("zh-sg")); |
|||
} |
|||
if ($noticeItem.hasClass("only_zh_tw")) { |
|||
testList.push(functions.in_lang("zh-tw")); |
|||
} |
|||
result = !testList.length || testList.every((v) => !!v); |
|||
} |
|||
setCache($noticeItem, "asn-cache", result); |
|||
return result; |
|||
} |
} |
||
const remoteNotice = (/* @__PURE__ */ import_ext_gadget2.default.createElement("div", { |
|||
innerHTML: responseParse.text |
|||
function getNoticeElement($noticeItem) { |
|||
})).querySelector("ul.sitents"); |
|||
let $cache = getCache($noticeItem, "asn-element"); |
|||
if (!remoteNotice) { |
|||
return {}; |
|||
} |
|||
$cache = $("<div>").append($noticeItem.contents().clone()); |
|||
setCache($noticeItem, "asn-element", $cache); |
|||
return $cache; |
|||
} |
} |
||
const $remoteNotices = $(remoteNotice); |
|||
const $notices2 = $remoteNotices.find("li"); |
|||
let isSetMinHeightCalled = false; |
|||
const remoteNoticesVersion = $remoteNotices.data("asn-version").toString(); |
|||
return { |
|||
$notices: $notices2, |
|||
* 设置 ASN 的高度为所有条目的最大高度,以防止在切换时出现大量布局偏移。 |
|||
version: remoteNoticesVersion |
|||
*/ |
|||
}; |
|||
function setMinHeight($noticeList) { |
|||
}); |
|||
let minHeight = -1; |
|||
return function loadRemoteNotices2() { |
|||
$noticeList.each((_, nt) => { |
|||
return _ref2.apply(this, arguments); |
|||
let $nt = $(nt); |
|||
}; |
|||
$asnBody.replaceWith($nt); |
|||
})(); |
|||
minHeight = Math.max(minHeight, $nt.height()); |
|||
//! src/AdvancedSiteNotices/modules/constant.ts |
|||
$nt.replaceWith($asnBody); |
|||
var CLASS_NAME = "gadget-advanced_site_notices"; |
|||
}); |
|||
var CLASS_NAME_DISMISS = "".concat(CLASS_NAME, "__dismiss"); |
|||
var CLASS_NAME_NOTICE = "".concat(CLASS_NAME, "__notice"); |
|||
$asnRoot.css("min-height", `${minHeight}px`); |
|||
var CLASS_NAME_NOTICE_CONTENT = "".concat(CLASS_NAME_NOTICE, "__content"); |
|||
var CLASS_NAME_TITLE = "".concat(CLASS_NAME, "__title"); |
|||
if (!isSetMinHeightCalled) { |
|||
//! src/AdvancedSiteNotices/modules/showNotice.tsx |
|||
isSetMinHeightCalled = true; |
|||
var import_ext_gadget5 = __toESM(require("ext.gadget.JSX"), 1); |
|||
window.addEventListener( |
|||
//! src/AdvancedSiteNotices/modules/util/generateArea.tsx |
|||
"resize", |
|||
var import_ext_gadget4 = __toESM(require("ext.gadget.JSX"), 1); |
|||
mw.util.debounce(() => setMinHeight($noticeList), 300) |
|||
//! src/AdvancedSiteNotices/modules/i18n.ts |
|||
); |
|||
var import_ext_gadget3 = require("ext.gadget.i18n"); |
|||
} |
|||
var getI18nMessages = () => { |
|||
return { |
|||
Dismiss: (0, import_ext_gadget3.localize)({ |
|||
en: "Turn off this notice", |
|||
ja: "ASNをオフにする", |
|||
"zh-hans": "关闭公告", |
|||
"zh-hant": "關閉公告" |
|||
}), |
|||
DismissNotice: (0, import_ext_gadget3.localize)({ |
|||
en: "You have chosen to turn off Advanced Site Notices for the next 30 days. <br>If the site-wide announcement is not updated within the next 30 days, it will no longer be displayed; however, if the site-wide announcement is updated, it will be displayed again.", |
|||
ja: "今後30日間、ASNをオフにすることを選択しました。<br>サイト全体の通知が今後30日以内に更新されない場合、表示されなくなります。ただし、サイト全体の通知が更新される場合は、再び表示されます。", |
|||
"zh-hans": "您已选择在接下来30日内关闭“高级站点通告”。<br>若接下来30日内全站公告未有更新,则不再显示;但是,若全站公告内容更新,则将重新显示。", |
|||
"zh-hant": "您已選擇在接下來30日內關閉「高級站點通告」。<br>若接下來30日內全站公告未有更新,則不再顯示;但是,若全站公告內容更新,則將重新顯示。" |
|||
}), |
|||
Title: (0, import_ext_gadget3.localize)({ |
|||
en: "Announcement", |
|||
ja: "通知", |
|||
zh: "公告" |
|||
}) |
|||
}; |
|||
}; |
|||
var i18nMessages = getI18nMessages(); |
|||
var getMessage = (key) => { |
|||
return i18nMessages[key] || key; |
|||
}; |
|||
//! src/AdvancedSiteNotices/modules/util/generateArea.tsx |
|||
var generateArea = () => { |
|||
const area = /* @__PURE__ */ import_ext_gadget4.default.createElement("div", { |
|||
className: [CLASS_NAME, "noprint"] |
|||
}, /* @__PURE__ */ import_ext_gadget4.default.createElement("div", { |
|||
className: CLASS_NAME_TITLE |
|||
}, getMessage("Title")), /* @__PURE__ */ import_ext_gadget4.default.createElement("div", { |
|||
className: CLASS_NAME_NOTICE |
|||
}, /* @__PURE__ */ import_ext_gadget4.default.createElement("div", { |
|||
className: [CLASS_NAME_NOTICE_CONTENT, "center"] |
|||
})), /* @__PURE__ */ import_ext_gadget4.default.createElement("div", { |
|||
className: CLASS_NAME_DISMISS |
|||
}, /* @__PURE__ */ import_ext_gadget4.default.createElement("a", { |
|||
role: "button", |
|||
"aria-label": getMessage("Dismiss") |
|||
}))); |
|||
return $(area); |
|||
}; |
|||
//! src/AdvancedSiteNotices/modules/util/matchCriteria.ts |
|||
var { |
|||
wgUserGroups, |
|||
wgGlobalGroups, |
|||
wgUserLanguage: wgUserLanguage2 |
|||
} = mw.config.get(); |
|||
var in_group = (group) => { |
|||
return !!(wgUserGroups !== null && wgUserGroups !== void 0 && wgUserGroups.includes(group) || wgGlobalGroups !== null && wgGlobalGroups !== void 0 && wgGlobalGroups.includes(group)); |
|||
}; |
|||
var only_for = (userLanguage) => { |
|||
return userLanguage === wgUserLanguage2; |
|||
}; |
|||
var matchCriteria = ($notice) => { |
|||
var _$notice$data; |
|||
const cache = $notice.data("asn-cache"); |
|||
if (cache !== void 0) { |
|||
return cache; |
|||
} |
|||
const testCriteria = (criteria) => { |
|||
try { |
|||
return window.eval(criteria); |
|||
} catch { |
|||
return false; |
|||
} |
} |
||
}; |
|||
let result = false; |
|||
function loadNotice($noticeList, pos) { |
|||
const criteriaData = ((_$notice$data = $notice.data("asn-criteria")) !== null && _$notice$data !== void 0 ? _$notice$data : "").trim(); |
|||
const $noticeItem = $noticeList.eq(pos); |
|||
if (criteriaData) { |
|||
let nextPos = pos + 1; |
|||
try { |
|||
if (nextPos === $noticeList.length) { |
|||
result = testCriteria(decodeURIComponent(criteriaData.replace(/\+/g, "%20"))); |
|||
nextPos = 0; |
|||
} catch { |
|||
result = true; |
|||
const $noticeElement = getNoticeElement($noticeItem); |
|||
if ($asnBody.children().length) { |
|||
$asnBody.stop().fadeOut(() => { |
|||
$asnBody.empty().append($noticeElement); |
|||
// 动画 try/catch 以避免 TypeError: (Animation.tweeners[prop]||[]).concat is not a function 错误在生产环境中出现 |
|||
try { |
|||
$asnBody.fadeIn(); |
|||
} catch (_) { } |
|||
}); |
|||
} else { |
|||
$asnBody.append($noticeElement).fadeIn(); |
|||
} |
|||
if ($noticeList.length > 1) { |
|||
timeoutId = setTimeout(() => { |
|||
loadNotice($noticeList, nextPos); |
|||
}, customASNInterval * 1000); |
|||
} |
|||
} |
} |
||
} else if ($notice.attr("class")) { |
|||
let criteria; |
|||
function initialNotices($noticeList) { |
|||
if ($notice.hasClass("only_sysop")) { |
|||
if (!$asnRoot.length || !$noticeList.length || revisionId === cookieVal) { |
|||
criteria || (criteria = in_group("sysop")); |
|||
} |
|||
if ($notice.hasClass("only_logged")) { |
|||
criteria || (criteria = in_group("user")); |
|||
mw.cookie.set(COOKIE_NAME, null); |
|||
} |
|||
$asnRoot.appendTo($("#siteNotice")); |
|||
if ($notice.hasClass("only_anon")) { |
|||
setMinHeight($noticeList); |
|||
criteria || (criteria = !in_group("user")); |
|||
loadNotice($noticeList, Math.floor(Math.random() * $noticeList.length)); |
|||
} |
} |
||
if ($notice.hasClass("only_zh_cn")) { |
|||
criteria || (criteria = only_for("zh-cn")); |
|||
} |
|||
if ($notice.hasClass("only_zh_sg")) { |
|||
criteria || (criteria = only_for("zh-sg")); |
|||
} |
|||
if ($notice.hasClass("only_zh_my")) { |
|||
criteria || (criteria = only_for("zh-my")); |
|||
} |
|||
if ($notice.hasClass("only_zh_hk")) { |
|||
criteria || (criteria = only_for("zh-hk")); |
|||
} |
|||
if ($notice.hasClass("only_zh_mo")) { |
|||
criteria || (criteria = only_for("zh-mo")); |
|||
} |
|||
if ($notice.hasClass("only_zh_tw")) { |
|||
criteria || (criteria = only_for("zh-tw")); |
|||
} |
|||
if (criteria === void 0) { |
|||
criteria = true; |
|||
} |
|||
result = criteria; |
|||
} else { |
|||
result = true; |
|||
} |
|||
$notice.data("asn-cache", result); |
|||
return result; |
|||
}; |
|||
//! src/AdvancedSiteNotices/modules/showNotice.tsx |
|||
var import_ext_gadget6 = require("ext.gadget.Tippy"); |
|||
var broadcastChannel = new BroadcastChannel(storageKey); |
|||
var currentVersion = "0"; |
|||
var localVersion = mw.storage.get(storageKey); |
|||
var timer; |
|||
var $area = generateArea(); |
|||
var $currentNotice = $area.find(".".concat(CLASS_NAME_NOTICE_CONTENT)); |
|||
var $dismiss = $area.find(".".concat(CLASS_NAME_DISMISS)).find("a"); |
|||
var closeNotices = () => { |
|||
broadcastChannel.postMessage("close"); |
|||
broadcastChannel.close(); |
|||
clearTimeout(timer); |
|||
$area.remove(); |
|||
mw.storage.set(storageKey, currentVersion, 60 * 60 * 24 * 30); |
|||
}; |
|||
broadcastChannel.addEventListener("message", closeNotices); |
|||
$dismiss.on("click", () => { |
|||
closeNotices(); |
|||
void mw.notify($(/* @__PURE__ */ import_ext_gadget5.default.createElement("span", { |
|||
innerHTML: getMessage("DismissNotice") |
|||
})), { |
|||
tag: "AdvancedSiteNotices" |
|||
}); |
|||
}); |
|||
(0, import_ext_gadget6.tippy)($dismiss.get(0), { |
|||
arrow: true, |
|||
content: $dismiss.attr("aria-label"), |
|||
placement: "bottom" |
|||
}); |
|||
var $notices; |
|||
var noticeStyles = []; |
|||
var showNotices = ($mountPoint, index, remoteNotices) => { |
|||
var _remoteNotices$versio; |
|||
currentVersion = (_remoteNotices$versio = remoteNotices === null || remoteNotices === void 0 ? void 0 : remoteNotices.version) !== null && _remoteNotices$versio !== void 0 ? _remoteNotices$versio : currentVersion; |
|||
if (currentVersion === localVersion) { |
|||
return; |
|||
} |
|||
if (remoteNotices !== null && remoteNotices !== void 0 && remoteNotices.$notices) { |
|||
({ |
|||
$notices |
|||
} = remoteNotices); |
|||
} |
|||
const noticesLength = $notices.length; |
|||
const nextNoticeIndex = (index + 1) % noticesLength; |
|||
let $notice = $(); |
|||
let i = 0; |
|||
while (i++ < noticesLength) { |
|||
$notice = $notices.eq(index); |
|||
if (!matchCriteria($notice)) { |
|||
showNotices($mountPoint, nextNoticeIndex); |
|||
return; |
|||
} |
|||
index = index++ % noticesLength; |
|||
} |
|||
if (typeof $notice.data("asn-html") === "string") { |
|||
$notice.data("asn-html-raw", decodeURIComponent($notice.data("asn-html").replace(/\+/g, "%20"))); |
|||
$notice.data("asn-html", null); |
|||
} |
|||
if (typeof $notice.data("asn-style") === "string") { |
|||
$notice.data("asn-style-id", noticeStyles.length); |
|||
const style = mw.loader.addStyleTag(decodeURIComponent($notice.data("asn-style").replace(/\+/g, "%20"))); |
|||
style.disabled = true; |
|||
noticeStyles[noticeStyles.length] = style; |
|||
$notice.data("asn-style", null); |
|||
} |
|||
const noticeHtml = $notice.data("asn-html-raw") || $notice.html(); |
|||
const noticeStyleId = $notice.data("asn-style-id"); |
|||
const currentNoticeHtml = $currentNotice.html(); |
|||
if (currentNoticeHtml && currentNoticeHtml !== noticeHtml) { |
|||
$currentNotice.stop().fadeOut(() => { |
|||
for (var _i = 0, _noticeStyles = noticeStyles; _i < _noticeStyles.length; _i++) { |
|||
const style = _noticeStyles[_i]; |
|||
style.disabled = true; |
|||
} |
|||
const noticeStyle = noticeStyles[noticeStyleId]; |
|||
if (noticeStyle) { |
|||
noticeStyle.disabled = false; |
|||
} |
|||
$currentNotice.html(noticeHtml); |
|||
try { |
|||
$currentNotice.fadeIn(); |
|||
} catch { |
|||
} |
|||
}); |
|||
} else if (!currentNoticeHtml) { |
|||
$mountPoint.append($area); |
|||
const noticeStyle = noticeStyles[noticeStyleId]; |
|||
if (noticeStyle) { |
|||
noticeStyle.disabled = false; |
|||
} |
|||
$currentNotice.html(noticeHtml).fadeIn(); |
|||
} |
|||
timer = setTimeout(() => { |
|||
showNotices($mountPoint, nextNoticeIndex); |
|||
}, 7 * 1e3); |
|||
}; |
|||
//! src/AdvancedSiteNotices/AdvancedSiteNotices.ts |
|||
(function() { |
|||
var _advancedSiteNotices = _asyncToGenerator(function* () { |
|||
var _remoteNotices$$notic; |
|||
const $body = yield (0, import_ext_gadget7.getBody)(); |
|||
const $mountPoint = $body.find(mountPointSelector); |
|||
if (!$mountPoint.length) { |
|||
return; |
|||
} |
|||
const remoteNotices = yield loadRemoteNotices(); |
|||
if (!((_remoteNotices$$notic = remoteNotices.$notices) !== null && _remoteNotices$$notic !== void 0 && _remoteNotices$$notic.length)) { |
|||
return; |
|||
} |
|||
const randomIndex = Math.floor(Math.random() * remoteNotices.$notices.length); |
|||
showNotices($mountPoint, randomIndex, remoteNotices); |
|||
}); |
|||
function advancedSiteNotices() { |
|||
return _advancedSiteNotices.apply(this, arguments); |
|||
} |
|||
return advancedSiteNotices; |
|||
})()(); |
|||
})(); |
|||
new mw.Api({ |
|||
ajax: { |
|||
headers: { |
|||
"Api-User-Agent": "w:zh:MediaWiki:Gadget-AdvancedSiteNotices.js", |
|||
}, |
|||
}, |
|||
}) |
|||
.parse(new mw.Title("Template:AdvancedSiteNotices/ajax"), { |
|||
variant: mw.config.get("wgUserVariant"), |
|||
maxage: 3600, |
|||
smaxage: 3600, |
|||
}) |
|||
.then((html) => { |
|||
let $json = $("ul.sitents", $.parseHTML(html)); |
|||
let $noticeList = $("li", $json).filter((_, li) => matchCriteria($(li))); |
|||
revisionId = $json.data("asn-version"); |
|||
/* </nowiki> */ |
|||
initialNotices($noticeList); |
|||
}) |
|||
.catch((e) => { |
|||
console.error("[AdvancedSiteNotices]: error ", e); |
|||
}); |
|||
}); |
|||
// </nowiki> |
|||
2026年6月14日 (日) 13:35的最新版本
/**
* SPDX-License-Identifier: CC-BY-SA-4.0
* _addText: '{{Gadget Header|license=CC-BY-SA-4.0}}'
*
* @base {@link https://zh.wikipedia.org/wiki/MediaWiki:Gadget-AdvancedSiteNotices.js}
* @source {@link https://git.qiuwen.net.cn/InterfaceAdmin/QiuwenGadgets/src/branch/master/src/AdvancedSiteNotices}
* @license CC-BY-SA-4.0 {@link https://www.qiuwenbaike.cn/wiki/H:CC-BY-SA-4.0}
*/
/**
* +------------------------------------------------------------+
* | === WARNING: GLOBAL GADGET FILE === |
* +------------------------------------------------------------+
* | All changes should be made in the repository, |
* | otherwise they will be lost. |
* +------------------------------------------------------------+
* | Changes to this page may affect many users. |
* | Please discuss changes by opening an issue before editing. |
* +------------------------------------------------------------+
*/
/* <nowiki> */
(() => {
"use strict";
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
// node_modules/.pnpm/broadcastchannel-polyfill@1.0.1/node_modules/broadcastchannel-polyfill/index.js
var require_broadcastchannel_polyfill = __commonJS({
"node_modules/.pnpm/broadcastchannel-polyfill@1.0.1/node_modules/broadcastchannel-polyfill/index.js"() {
(function(global) {
var channels = [];
function BroadcastChannel2(channel) {
var $this = this;
channel = String(channel);
var id = "$BroadcastChannel$" + channel + "$";
channels[id] = channels[id] || [];
channels[id].push(this);
this._name = channel;
this._id = id;
this._closed = false;
this._mc = new MessageChannel();
this._mc.port1.start();
this._mc.port2.start();
global.addEventListener("storage", function(e) {
if (e.storageArea !== global.localStorage) return;
if (e.newValue == null || e.newValue === "") return;
if (e.key.substring(0, id.length) !== id) return;
var data = JSON.parse(e.newValue);
$this._mc.port2.postMessage(data);
});
}
BroadcastChannel2.prototype = {
// BroadcastChannel API
get name() {
return this._name;
},
postMessage: function(message) {
var $this = this;
if (this._closed) {
var e = new Error();
e.name = "InvalidStateError";
throw e;
}
var value = JSON.stringify(message);
var key = this._id + String(Date.now()) + "$" + String(Math.random());
global.localStorage.setItem(key, value);
setTimeout(function() {
global.localStorage.removeItem(key);
}, 500);
channels[this._id].forEach(function(bc) {
if (bc === $this) return;
bc._mc.port2.postMessage(JSON.parse(value));
});
},
close: function() {
if (this._closed) return;
this._closed = true;
this._mc.port1.close();
this._mc.port2.close();
var index = channels[this._id].indexOf(this);
channels[this._id].splice(index, 1);
},
// EventTarget API
get onmessage() {
return this._mc.port1.onmessage;
},
set onmessage(value) {
this._mc.port1.onmessage = value;
},
addEventListener: function() {
return this._mc.port1.addEventListener.apply(this._mc.port1, arguments);
},
removeEventListener: function() {
return this._mc.port1.removeEventListener.apply(this._mc.port1, arguments);
},
dispatchEvent: function() {
return this._mc.port1.dispatchEvent.apply(this._mc.port1, arguments);
}
};
global.BroadcastChannel = global.BroadcastChannel || BroadcastChannel2;
})(self);
}
});
// dist/AdvancedSiteNotices/AdvancedSiteNotices.js
require_broadcastchannel_polyfill();
function asyncGeneratorStep(n, t, e, r, o, a, c) {
try {
var i = n[a](c), u = i.value;
} catch (n2) {
return void e(n2);
}
i.done ? t(u) : Promise.resolve(u).then(r, o);
}
function _asyncToGenerator(n) {
return function() {
var t = this, e = arguments;
return new Promise(function(r, o) {
var a = n.apply(t, e);
function _next(n2) {
asyncGeneratorStep(a, r, o, _next, _throw, "next", n2);
}
function _throw(n2) {
asyncGeneratorStep(a, r, o, _next, _throw, "throw", n2);
}
_next(void 0);
});
};
}
function _createForOfIteratorHelper(r, e) {
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (!t) {
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) {
t && (r = t);
var n = 0, F = function() {
};
return { s: F, n: function() {
return n >= r.length ? { done: true } : { done: false, value: r[n++] };
}, e: function(r2) {
throw r2;
}, f: F };
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var o, a = true, u = false;
return { s: function() {
t = t.call(r);
}, n: function() {
var r2 = t.next();
return a = r2.done, r2;
}, e: function(r2) {
u = true, o = r2;
}, f: function() {
try {
a || null == t.return || t.return();
} finally {
if (u) throw o;
}
} };
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
}
}
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames2 = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
var _iterator = _createForOfIteratorHelper(__getOwnPropNames2(from)), _step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done; ) {
let key = _step.value;
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: () => from[key],
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target,
mod
));
//! src/AdvancedSiteNotices/options.json
var ajaxPageTitle = "Template:AdvancedSiteNotices/ajax";
var mountPointSelector = "#siteNotice";
var storageKey = "ext.gadget.AdvancedSiteNotices_dismissASN";
var cacheKey = "ext.gadget.AdvancedSiteNotices_cache";
var version = "4.0";
//! src/AdvancedSiteNotices/AdvancedSiteNotices.ts
var import_ext_gadget7 = require("ext.gadget.Util");
//! src/AdvancedSiteNotices/modules/api.ts
var import_ext_gadget = require("ext.gadget.Util");
var api = (0, import_ext_gadget.initMwApi)("AdvancedSiteNotices/".concat(version));
//! src/AdvancedSiteNotices/modules/util/queryApi.ts
var {
wgUserLanguage
} = mw.config.get();
var parameters = {
action: "parse",
format: "json",
formatversion: "2",
prop: "text",
page: ajaxPageTitle,
uselang: wgUserLanguage,
variant: wgUserLanguage,
smaxage: 600,
maxage: 600
};
var queryApi = /* @__PURE__ */ (function() {
var _ref = _asyncToGenerator(function* () {
try {
let response;
if (mw.storage.getObject(cacheKey)) {
response = mw.storage.getObject(cacheKey);
} else {
response = yield api.get(parameters);
mw.storage.setObject(cacheKey, response, 60 * 10);
}
return response;
} catch (error) {
console.error("[AdvancedSiteNotices] Ajax error:", error);
return {};
}
});
return function queryApi2() {
return _ref.apply(this, arguments);
};
})();
//! src/AdvancedSiteNotices/modules/loadRemoteNotices.tsx
var import_ext_gadget2 = __toESM(require("ext.gadget.JSX"), 1);
var loadRemoteNotices = /* @__PURE__ */ (function() {
var _ref2 = _asyncToGenerator(function* () {
const response = yield queryApi();
const responseParse = response["parse"];
if (!(responseParse !== null && responseParse !== void 0 && responseParse.text)) {
return {};
}
const remoteNotice = (/* @__PURE__ */ import_ext_gadget2.default.createElement("div", {
innerHTML: responseParse.text
})).querySelector("ul.sitents");
if (!remoteNotice) {
return {};
}
const $remoteNotices = $(remoteNotice);
const $notices2 = $remoteNotices.find("li");
const remoteNoticesVersion = $remoteNotices.data("asn-version").toString();
return {
$notices: $notices2,
version: remoteNoticesVersion
};
});
return function loadRemoteNotices2() {
return _ref2.apply(this, arguments);
};
})();
//! src/AdvancedSiteNotices/modules/constant.ts
var CLASS_NAME = "gadget-advanced_site_notices";
var CLASS_NAME_DISMISS = "".concat(CLASS_NAME, "__dismiss");
var CLASS_NAME_NOTICE = "".concat(CLASS_NAME, "__notice");
var CLASS_NAME_NOTICE_CONTENT = "".concat(CLASS_NAME_NOTICE, "__content");
var CLASS_NAME_TITLE = "".concat(CLASS_NAME, "__title");
//! src/AdvancedSiteNotices/modules/showNotice.tsx
var import_ext_gadget5 = __toESM(require("ext.gadget.JSX"), 1);
//! src/AdvancedSiteNotices/modules/util/generateArea.tsx
var import_ext_gadget4 = __toESM(require("ext.gadget.JSX"), 1);
//! src/AdvancedSiteNotices/modules/i18n.ts
var import_ext_gadget3 = require("ext.gadget.i18n");
var getI18nMessages = () => {
return {
Dismiss: (0, import_ext_gadget3.localize)({
en: "Turn off this notice",
ja: "ASNをオフにする",
"zh-hans": "关闭公告",
"zh-hant": "關閉公告"
}),
DismissNotice: (0, import_ext_gadget3.localize)({
en: "You have chosen to turn off Advanced Site Notices for the next 30 days. <br>If the site-wide announcement is not updated within the next 30 days, it will no longer be displayed; however, if the site-wide announcement is updated, it will be displayed again.",
ja: "今後30日間、ASNをオフにすることを選択しました。<br>サイト全体の通知が今後30日以内に更新されない場合、表示されなくなります。ただし、サイト全体の通知が更新される場合は、再び表示されます。",
"zh-hans": "您已选择在接下来30日内关闭“高级站点通告”。<br>若接下来30日内全站公告未有更新,则不再显示;但是,若全站公告内容更新,则将重新显示。",
"zh-hant": "您已選擇在接下來30日內關閉「高級站點通告」。<br>若接下來30日內全站公告未有更新,則不再顯示;但是,若全站公告內容更新,則將重新顯示。"
}),
Title: (0, import_ext_gadget3.localize)({
en: "Announcement",
ja: "通知",
zh: "公告"
})
};
};
var i18nMessages = getI18nMessages();
var getMessage = (key) => {
return i18nMessages[key] || key;
};
//! src/AdvancedSiteNotices/modules/util/generateArea.tsx
var generateArea = () => {
const area = /* @__PURE__ */ import_ext_gadget4.default.createElement("div", {
className: [CLASS_NAME, "noprint"]
}, /* @__PURE__ */ import_ext_gadget4.default.createElement("div", {
className: CLASS_NAME_TITLE
}, getMessage("Title")), /* @__PURE__ */ import_ext_gadget4.default.createElement("div", {
className: CLASS_NAME_NOTICE
}, /* @__PURE__ */ import_ext_gadget4.default.createElement("div", {
className: [CLASS_NAME_NOTICE_CONTENT, "center"]
})), /* @__PURE__ */ import_ext_gadget4.default.createElement("div", {
className: CLASS_NAME_DISMISS
}, /* @__PURE__ */ import_ext_gadget4.default.createElement("a", {
role: "button",
"aria-label": getMessage("Dismiss")
})));
return $(area);
};
//! src/AdvancedSiteNotices/modules/util/matchCriteria.ts
var {
wgUserGroups,
wgGlobalGroups,
wgUserLanguage: wgUserLanguage2
} = mw.config.get();
var in_group = (group) => {
return !!(wgUserGroups !== null && wgUserGroups !== void 0 && wgUserGroups.includes(group) || wgGlobalGroups !== null && wgGlobalGroups !== void 0 && wgGlobalGroups.includes(group));
};
var only_for = (userLanguage) => {
return userLanguage === wgUserLanguage2;
};
var matchCriteria = ($notice) => {
var _$notice$data;
const cache = $notice.data("asn-cache");
if (cache !== void 0) {
return cache;
}
const testCriteria = (criteria) => {
try {
return window.eval(criteria);
} catch {
return false;
}
};
let result = false;
const criteriaData = ((_$notice$data = $notice.data("asn-criteria")) !== null && _$notice$data !== void 0 ? _$notice$data : "").trim();
if (criteriaData) {
try {
result = testCriteria(decodeURIComponent(criteriaData.replace(/\+/g, "%20")));
} catch {
result = true;
}
} else if ($notice.attr("class")) {
let criteria;
if ($notice.hasClass("only_sysop")) {
criteria || (criteria = in_group("sysop"));
}
if ($notice.hasClass("only_logged")) {
criteria || (criteria = in_group("user"));
}
if ($notice.hasClass("only_anon")) {
criteria || (criteria = !in_group("user"));
}
if ($notice.hasClass("only_zh_cn")) {
criteria || (criteria = only_for("zh-cn"));
}
if ($notice.hasClass("only_zh_sg")) {
criteria || (criteria = only_for("zh-sg"));
}
if ($notice.hasClass("only_zh_my")) {
criteria || (criteria = only_for("zh-my"));
}
if ($notice.hasClass("only_zh_hk")) {
criteria || (criteria = only_for("zh-hk"));
}
if ($notice.hasClass("only_zh_mo")) {
criteria || (criteria = only_for("zh-mo"));
}
if ($notice.hasClass("only_zh_tw")) {
criteria || (criteria = only_for("zh-tw"));
}
if (criteria === void 0) {
criteria = true;
}
result = criteria;
} else {
result = true;
}
$notice.data("asn-cache", result);
return result;
};
//! src/AdvancedSiteNotices/modules/showNotice.tsx
var import_ext_gadget6 = require("ext.gadget.Tippy");
var broadcastChannel = new BroadcastChannel(storageKey);
var currentVersion = "0";
var localVersion = mw.storage.get(storageKey);
var timer;
var $area = generateArea();
var $currentNotice = $area.find(".".concat(CLASS_NAME_NOTICE_CONTENT));
var $dismiss = $area.find(".".concat(CLASS_NAME_DISMISS)).find("a");
var closeNotices = () => {
broadcastChannel.postMessage("close");
broadcastChannel.close();
clearTimeout(timer);
$area.remove();
mw.storage.set(storageKey, currentVersion, 60 * 60 * 24 * 30);
};
broadcastChannel.addEventListener("message", closeNotices);
$dismiss.on("click", () => {
closeNotices();
void mw.notify($(/* @__PURE__ */ import_ext_gadget5.default.createElement("span", {
innerHTML: getMessage("DismissNotice")
})), {
tag: "AdvancedSiteNotices"
});
});
(0, import_ext_gadget6.tippy)($dismiss.get(0), {
arrow: true,
content: $dismiss.attr("aria-label"),
placement: "bottom"
});
var $notices;
var noticeStyles = [];
var showNotices = ($mountPoint, index, remoteNotices) => {
var _remoteNotices$versio;
currentVersion = (_remoteNotices$versio = remoteNotices === null || remoteNotices === void 0 ? void 0 : remoteNotices.version) !== null && _remoteNotices$versio !== void 0 ? _remoteNotices$versio : currentVersion;
if (currentVersion === localVersion) {
return;
}
if (remoteNotices !== null && remoteNotices !== void 0 && remoteNotices.$notices) {
({
$notices
} = remoteNotices);
}
const noticesLength = $notices.length;
const nextNoticeIndex = (index + 1) % noticesLength;
let $notice = $();
let i = 0;
while (i++ < noticesLength) {
$notice = $notices.eq(index);
if (!matchCriteria($notice)) {
showNotices($mountPoint, nextNoticeIndex);
return;
}
index = index++ % noticesLength;
}
if (typeof $notice.data("asn-html") === "string") {
$notice.data("asn-html-raw", decodeURIComponent($notice.data("asn-html").replace(/\+/g, "%20")));
$notice.data("asn-html", null);
}
if (typeof $notice.data("asn-style") === "string") {
$notice.data("asn-style-id", noticeStyles.length);
const style = mw.loader.addStyleTag(decodeURIComponent($notice.data("asn-style").replace(/\+/g, "%20")));
style.disabled = true;
noticeStyles[noticeStyles.length] = style;
$notice.data("asn-style", null);
}
const noticeHtml = $notice.data("asn-html-raw") || $notice.html();
const noticeStyleId = $notice.data("asn-style-id");
const currentNoticeHtml = $currentNotice.html();
if (currentNoticeHtml && currentNoticeHtml !== noticeHtml) {
$currentNotice.stop().fadeOut(() => {
for (var _i = 0, _noticeStyles = noticeStyles; _i < _noticeStyles.length; _i++) {
const style = _noticeStyles[_i];
style.disabled = true;
}
const noticeStyle = noticeStyles[noticeStyleId];
if (noticeStyle) {
noticeStyle.disabled = false;
}
$currentNotice.html(noticeHtml);
try {
$currentNotice.fadeIn();
} catch {
}
});
} else if (!currentNoticeHtml) {
$mountPoint.append($area);
const noticeStyle = noticeStyles[noticeStyleId];
if (noticeStyle) {
noticeStyle.disabled = false;
}
$currentNotice.html(noticeHtml).fadeIn();
}
timer = setTimeout(() => {
showNotices($mountPoint, nextNoticeIndex);
}, 7 * 1e3);
};
//! src/AdvancedSiteNotices/AdvancedSiteNotices.ts
(function() {
var _advancedSiteNotices = _asyncToGenerator(function* () {
var _remoteNotices$$notic;
const $body = yield (0, import_ext_gadget7.getBody)();
const $mountPoint = $body.find(mountPointSelector);
if (!$mountPoint.length) {
return;
}
const remoteNotices = yield loadRemoteNotices();
if (!((_remoteNotices$$notic = remoteNotices.$notices) !== null && _remoteNotices$$notic !== void 0 && _remoteNotices$$notic.length)) {
return;
}
const randomIndex = Math.floor(Math.random() * remoteNotices.$notices.length);
showNotices($mountPoint, randomIndex, remoteNotices);
});
function advancedSiteNotices() {
return _advancedSiteNotices.apply(this, arguments);
}
return advancedSiteNotices;
})()();
})();
/* </nowiki> */