375
回編集
細編集の要約なし タグ: モバイル編集 モバイルウェブ編集 |
細編集の要約なし タグ: モバイル編集 モバイルウェブ編集 |
||
(同じ利用者による、間の13版が非表示) | |||
3行目: | 3行目: | ||
/** | /** | ||
* @typedef MediaWiki | * @typedef MediaWiki | ||
* @prop {readonly Map<string, | * @prop {readonly Map<string, never>} config | ||
*/ | |||
/** | |||
* @typedef WGNamespaceIds | |||
* @prop {number} user | |||
* @prop {number} user_talk | |||
* @prop {number} special | |||
*/ | |||
/** | |||
* @typedef {{ [id: string]: string; }} WGFormattedNamespaces | |||
*/ | */ | ||
28行目: | 39行目: | ||
* @prop {string | string[]} [class] | * @prop {string | string[]} [class] | ||
* @prop {MenuDataItem[]} items | * @prop {MenuDataItem[]} items | ||
*/ | |||
/** | |||
* @typedef UserMenuItem | |||
* @prop {string} text | |||
* @prop {string} title | |||
* @prop {readonly string[]} iconClass | |||
*/ | */ | ||
33行目: | 51行目: | ||
* @typedef MobileUI | * @typedef MobileUI | ||
* @prop {MenuDataGroup[]} leftGroups | * @prop {MenuDataGroup[]} leftGroups | ||
* @prop { | * @prop {readonly UserMenuItem[]} userMenuItems | ||
*/ | */ | ||
47行目: | 65行目: | ||
*/ | */ | ||
this.wgServer = mw.config.get("wgServer"); | this.wgServer = mw.config.get("wgServer"); | ||
/** | |||
* @type {string} | |||
*/ | |||
this.wgScript = mw.config.get("wgScript"); | |||
/** | /** | ||
52行目: | 75行目: | ||
*/ | */ | ||
this.wgScriptPath = mw.config.get("wgScriptPath"); | this.wgScriptPath = mw.config.get("wgScriptPath"); | ||
/** | |||
* @type {string} | |||
*/ | |||
this.wgPageName = mw.config.get("wgPageName"); | |||
/** | /** | ||
77行目: | 105行目: | ||
*/ | */ | ||
this.wgUserName = mw.config.get("wgUserName"); | this.wgUserName = mw.config.get("wgUserName"); | ||
/** | |||
* @type {WGNamespaceIds} | |||
*/ | |||
this.wgNamespaceIds = Object.assign({}, mw.config.get("wgNamespaceIds")); | |||
/** | |||
* @type {WGFormattedNamespaces} | |||
*/ | |||
this.wgFormattedNamespaces = Object.assign({}, mw.config.get("wgFormattedNamespaces")); | |||
} | } | ||
86行目: | 124行目: | ||
switch (component.id) { | switch (component.id) { | ||
case "mobile-ui-page-short-url": | case "mobile-ui-page-short-url": | ||
return | return [ | ||
this.wgServer | this.wgServer, | ||
this.wgScriptPath, | |||
"/?curid=", | |||
String(this.wgArticleId), | |||
); | ].join(""); | ||
case "mobile-ui-page-special-user": | |||
return [ | |||
this.wgScriptPath, | |||
"/", | |||
this.wgFormattedNamespaces[this.wgNamespaceIds.user], | |||
":", | |||
this.wgUserName, | |||
].join(""); | |||
case "mobile-ui-page-special-contributions": | |||
return [ | |||
this.wgScriptPath, | |||
"/", | |||
this.wgFormattedNamespaces[this.wgNamespaceIds.special], | |||
":", | |||
"Contributions", | |||
"/", | |||
this.wgUserName, | |||
].join(""); | |||
case "mobile-ui-page-user-talk": | |||
return [ | |||
this.wgScriptPath, | |||
"/", | |||
this.wgFormattedNamespaces[this.wgNamespaceIds.user_talk], | |||
":", | |||
this.wgUserName, | |||
].join(""); | |||
case "mobile-ui-page-special-logout": | |||
return [ | |||
this.wgScript, | |||
"?title=", | |||
this.wgFormattedNamespaces[this.wgNamespaceIds.special], | |||
":", | |||
"Logout", | |||
"&returnto=", | |||
this.wgPageName, | |||
].join(""); | |||
default: | default: | ||
return component.title | return component.title | ||
? (this.wgScriptPath + "/" + component.title) | ? (this.wgScriptPath + "/" + component.title) | ||
: component.href; | : component.href || ""; | ||
} | } | ||
} | } | ||
108行目: | 186行目: | ||
const element = window.document.createElement("a"); | const element = window.document.createElement("a"); | ||
element.id = component.id || ""; | element.id = component.id || ""; | ||
element.classList.add(...[].concat(component.class || [])); | element.classList.add(...[].concat(component.class || [])); | ||
element.setAttribute("data-event-name", component.dataEventName || ""); | element.setAttribute("data-event-name", component.dataEventName || ""); | ||
if (href) { | |||
element.href = encodeURI(href); | |||
} | |||
const textElement = window.document.createElement("span"); | |||
textElement.textContent = component.text || href; | |||
element.append(textElement); | |||
return element; | return element; | ||
176行目: | 260行目: | ||
return children; | return children; | ||
} | } | ||
190行目: | 265行目: | ||
* @returns {HTMLElement} | * @returns {HTMLElement} | ||
*/ | */ | ||
static get | static get leftMenuContainerElement() { | ||
return window.document | return window.document | ||
.getElementById("mw-mf-page-left"); | .getElementById("mw-mf-page-left"); | ||
198行目: | 273行目: | ||
* @returns {HTMLDivElement | null} | * @returns {HTMLDivElement | null} | ||
*/ | */ | ||
static get | static get leftMenuElement() { | ||
return /** @type {HTMLDivElement} */ ( | return /** @type {HTMLDivElement} */ ( | ||
MobileUIBuilder | MobileUIBuilder | ||
. | .leftMenuContainerElement | ||
.getElementsByClassName(MobileUIBuilder.menuClass) | .getElementsByClassName(MobileUIBuilder.menuClass) | ||
.item(0) | .item(0) | ||
208行目: | 283行目: | ||
/** | /** | ||
* @returns { | * @returns {HTMLUListElement | null} | ||
*/ | */ | ||
static get | static get userMenuElement() { | ||
return | return window | ||
.document | |||
.querySelector(".minerva-user-menu > ul.toggle-list__list"); | |||
} | } | ||
258行目: | 298行目: | ||
MobileUIBuilder | MobileUIBuilder | ||
. | .leftMenuContainerElement | ||
.classList | .classList | ||
.add("mobile-ui-not-article"); | .add("mobile-ui-not-article"); | ||
266行目: | 306行目: | ||
/** | /** | ||
* @returns { | * @param {UserMenuItem} item | ||
* @returns {HTMLLIElement} | |||
*/ | */ | ||
createUserMenuItemElement(item) { | |||
const li = window.document.createElement("li"); | |||
li.classList.add("toggle-list-item"); | |||
const | const a = window.document.createElement("a"); | ||
a.classList.add("toggle-list-item__anchor"); | |||
a.href = [this.wgScriptPath, item.title].join("/"); | |||
li.append(a); | |||
const outerSpan = window.document.createElement("span"); | |||
outerSpan.classList.add("toggle-list-item__icon", "mw-ui-icon", "mw-ui-icon-before", ...item.iconClass); | |||
a.append(outerSpan); | |||
const | const spacingSpan = window.document.createElement("span"); | ||
spacingSpan.textContent = " "; | |||
outerSpan.append(spacingSpan); | |||
const innerSpan = window.document.createElement("span"); | |||
innerSpan.classList.add("toggle-list-item__label"); | |||
innerSpan.textContent = item.text; | |||
outerSpan.append(innerSpan); | |||
return li; | |||
} | |||
/** | |||
* @param {readonly UserMenuItem[]} items | |||
* @returns {this} | |||
*/ | |||
extendUserMenu(items) { | |||
const menu = MobileUIBuilder.userMenuElement; | |||
const | if (menu) { | ||
const lastItem = menu.lastElementChild; | |||
if (lastItem) { | |||
for (const item of items) { | |||
const li = this.createUserMenuItemElement(item); | |||
menu.insertBefore(li, lastItem); | |||
} | |||
} | |||
} | |||
return this; | return this; | ||
315行目: | 360行目: | ||
return this | return this | ||
.setNotArticleClass() | .setNotArticleClass() | ||
; | |||
} | } | ||
/** | /** | ||
* @param {MobileUI} mobileUI | * @param {MobileUI} mobileUI | ||
* @param {HTMLDivElement} leftMenu | |||
* @returns {this} | * @returns {this} | ||
*/ | */ | ||
modify(mobileUI | modify(mobileUI, leftMenu) { | ||
const firstGroup = leftMenu.getElementsByTagName("ul").item(0); | const firstGroup = leftMenu.getElementsByTagName("ul").item(0); | ||
if (firstGroup) { | if (firstGroup) { | ||
342行目: | 376行目: | ||
const customLeftGroups = this.menuDataGroups(mobileUI.leftGroups); | const customLeftGroups = this.menuDataGroups(mobileUI.leftGroups); | ||
leftMenu.prepend(...customLeftGroups); | leftMenu.prepend(...customLeftGroups); | ||
return this; | return this; | ||
360行目: | 385行目: | ||
*/ | */ | ||
build(mobileUI) { | build(mobileUI) { | ||
if (MobileUIBuilder. | if (mobileUI.userMenuItems) { | ||
this.modify(mobileUI); | this.extendUserMenu(mobileUI.userMenuItems); | ||
} | |||
const leftMenuContainer = window.document.getElementById("mw-mf-page-left"); | |||
const leftMenu = /** @type {HTMLDivElement} */ ( | |||
leftMenuContainer.getElementsByClassName(MobileUIBuilder.menuClass).item(0) | |||
); | |||
if (MobileUIBuilder.leftMenuElement) { | |||
this.modify(mobileUI, leftMenu); | |||
return this; | return this; | ||
} | } | ||
368行目: | 403行目: | ||
for (const record of mutations) { | for (const record of mutations) { | ||
for (const node of record.addedNodes) { | for (const node of record.addedNodes) { | ||
const | const maybeLeftMenu = /** @type {HTMLDivElement} */ (node); | ||
const { classList } = maybeLeftMenu; | |||
if (classList && classList.contains(MobileUIBuilder.menuClass)) { | if (classList && classList.contains(MobileUIBuilder.menuClass)) { | ||
observer.disconnect(); | observer.disconnect(); | ||
this.modify(mobileUI); | this.modify(mobileUI, maybeLeftMenu); | ||
return; | return; | ||
} | } | ||
378行目: | 414行目: | ||
}); | }); | ||
observer.observe( | observer.observe(leftMenuContainer, { childList: true }); | ||
return this; | return this; | ||
386行目: | 422行目: | ||
MobileUIBuilder.rightMenuButtonID = "mobile-ui-right-menu-button"; | MobileUIBuilder.rightMenuButtonID = "mobile-ui-right-menu-button"; | ||
MobileUIBuilder.menuClass = "menu"; | MobileUIBuilder.menuClass = "menu"; | ||
MobileUIBuilder.hiddenClass = "mobile-ui-hidden"; | MobileUIBuilder.hiddenClass = "mobile-ui-hidden"; | ||
MobileUIBuilder.navigationEnabledClass = "navigation-enabled"; | MobileUIBuilder.navigationEnabledClass = "navigation-enabled"; | ||