「MediaWiki:MobileUI.js」の版間の差分
表示
編集の要約なし タグ: モバイル編集 モバイルウェブ編集 |
編集の要約なし タグ: モバイル編集 モバイルウェブ編集 |
||
| 2行目: | 2行目: | ||
/** | /** | ||
* @typedef { | * @typedef MediaWiki | ||
* @prop {readonly Map<string, any>} config | |||
*/ | */ | ||
| 32行目: | 33行目: | ||
* @typedef MobileUI | * @typedef MobileUI | ||
* @prop {MenuDataGroup[]} leftGroups | * @prop {MenuDataGroup[]} leftGroups | ||
* @prop {MenuDataGroup[]} rightGroups | * @prop {MenuDataGroup[]} rightGroups | ||
*/ | */ | ||
| 38行目: | 38行目: | ||
class MobileUIBuilder { | class MobileUIBuilder { | ||
/** | /** | ||
* @param { | * @param {MediaWiki} mw | ||
*/ | */ | ||
constructor(mw | constructor(mw) { | ||
this.mw = mw | // this.mw = mw; | ||
/** | /** | ||
| 59行目: | 57行目: | ||
*/ | */ | ||
this.wgArticleId = mw.config.get("wgArticleId"); | this.wgArticleId = mw.config.get("wgArticleId"); | ||
/** | |||
* @type {boolean} | |||
*/ | |||
this.wgIsArticle = mw.config.get("wgIsArticle"); | |||
/** | |||
* @type {boolean} | |||
*/ | |||
this.wgIsMainPage = mw.config.get("wgIsMainPage"); | |||
} | } | ||
| 84行目: | 92行目: | ||
/** | /** | ||
* @param {MenuDataItemComponent} component | * @param {MenuDataItemComponent} component | ||
* @returns { | * @returns {HTMLAnchorElement} | ||
*/ | */ | ||
menuDataItemComponent(component) { | menuDataItemComponent(component) { | ||
const href = this.hrefForMenuDataItemComponent(component); | const href = this.hrefForMenuDataItemComponent(component); | ||
const element = window.document.createElement("a"); | |||
element.text = component.text || href; | |||
element.href = encodeURI(href); | |||
element.id = component.id || ""; | |||
element.classList.add(...[].concat(component.class)); | |||
element.setAttribute("data-event-name", component.dataEventName || ""); | |||
return element; | |||
} | } | ||
/** | /** | ||
* @param {MenuDataItem} item | * @param {MenuDataItem} item | ||
* @returns { | * @returns {HTMLLIElement} | ||
*/ | */ | ||
menuDataItem(item) { | menuDataItem(item) { | ||
/** | /** | ||
* @type { | * @type {HTMLAnchorElement[]} | ||
*/ | */ | ||
const children = []; | const children = []; | ||
| 112行目: | 121行目: | ||
} | } | ||
const element = window.document.createElement("li"); | |||
element.id = item.id || ""; | |||
element.classList.add(...[].concat(item.class)); | |||
element.append(...children); | |||
return element; | |||
} | } | ||
/** | /** | ||
* @param {MenuDataGroup} group | * @param {MenuDataGroup} group | ||
* @returns { | * @returns {HTMLUListElement} | ||
*/ | */ | ||
menuDataGroup(group) { | menuDataGroup(group) { | ||
/** | /** | ||
* @type { | * @type {HTMLLIElement[]} | ||
*/ | */ | ||
const children = []; | const children = []; | ||
| 131行目: | 141行目: | ||
for (const item of group.items) { | for (const item of group.items) { | ||
children.push(this.menuDataItem(item)); | children.push(this.menuDataItem(item)); | ||
Function.call | |||
} | } | ||
const element = window.document.createElement("ul"); | |||
element.id = group.id || ""; | |||
element.classList.add(...[].concat(group.class)); | |||
element.append(...children); | |||
return element; | |||
} | } | ||
/** | /** | ||
* @param {MenuDataGroup[]} groups | * @param {MenuDataGroup[]} groups | ||
* @returns { | * @returns {HTMLUListElement[]} | ||
*/ | */ | ||
menuDataGroups(groups) { | menuDataGroups(groups) { | ||
/** | /** | ||
* @type { | * @type {HTMLUListElement[]} | ||
*/ | */ | ||
const children = []; | const children = []; | ||
| 180行目: | 192行目: | ||
/** | /** | ||
* @returns { | * @returns {HTMLDivElement | null} | ||
*/ | */ | ||
get menuElement() { | get menuElement() { | ||
return /** @type { | return /** @type {HTMLDivElement} */ ( | ||
this.navElement.getElementsByClassName("menu").item(0) | this.navElement.getElementsByClassName("menu").item(0) | ||
); | ); | ||
| 193行目: | 205行目: | ||
*/ | */ | ||
modify(mobileUI, menuElement) { | modify(mobileUI, menuElement) { | ||
const navElement = this.navElement; | |||
if (this.wgIsArticle && !this.wgIsMainPage) { | |||
navElement.classList.add("mobile-ui-article"); | |||
} | |||
const firstGroup = menuElement.getElementsByTagName("ul").item(0); | |||
if (firstGroup) { | |||
firstGroup.style.display = "none"; | |||
} | |||
const leftGroups = this.menuDataGroups(mobileUI.leftGroups); | const leftGroups = this.menuDataGroups(mobileUI.leftGroups); | ||
menuElement.prepend(...leftGroups); | |||
const rightMenuElement = window.document.createElement("div"); | |||
rightMenuElement.classList.add(...menuElement.classList, "menu-right"); | |||
const rightGroups = this.menuDataGroups(mobileUI.rightGroups); | |||
rightMenuElement.append(...rightGroups); | |||
navElement.append(rightMenuElement); | |||
} | } | ||
2019年6月26日 (水) 09:02時点における版
// @ts-check
/**
* @typedef MediaWiki
* @prop {readonly Map<string, any>} config
*/
/**
* @typedef MenuDataItemComponent
* @prop {string} [text]
* @prop {string} [title]
* @prop {string} [href]
* @prop {string} [id]
* @prop {string | string[]} [class]
* @prop {string} [dataEventName]
*/
/**
* @typedef MenuDataItem
* @prop {string} [id]
* @prop {string | string[]} [class]
* @prop {MenuDataItemComponent[]} components
*/
/**
* @typedef MenuDataGroup
* @prop {string} [id]
* @prop {string | string[]} [class]
* @prop {MenuDataItem[]} items
*/
/**
* @typedef MobileUI
* @prop {MenuDataGroup[]} leftGroups
* @prop {MenuDataGroup[]} rightGroups
*/
class MobileUIBuilder {
/**
* @param {MediaWiki} mw
*/
constructor(mw) {
// this.mw = mw;
/**
* @type {string}
*/
this.wgServer = mw.config.get("wgServer");
/**
* @type {string}
*/
this.wgScriptPath = mw.config.get("wgScriptPath");
/**
* @type {number}
*/
this.wgArticleId = mw.config.get("wgArticleId");
/**
* @type {boolean}
*/
this.wgIsArticle = mw.config.get("wgIsArticle");
/**
* @type {boolean}
*/
this.wgIsMainPage = mw.config.get("wgIsMainPage");
}
/**
* @param {MenuDataItemComponent} component
* @returns {string}
*/
hrefForMenuDataItemComponent(component) {
switch (component.id) {
case "mobile-ui-page-short-url":
return (
this.wgServer
+ this.wgScriptPath
+ "?curid="
+ String(this.wgArticleId)
);
default:
return component.title
? (this.wgScriptPath + "/" + component.title)
: component.href;
}
}
/**
* @param {MenuDataItemComponent} component
* @returns {HTMLAnchorElement}
*/
menuDataItemComponent(component) {
const href = this.hrefForMenuDataItemComponent(component);
const element = window.document.createElement("a");
element.text = component.text || href;
element.href = encodeURI(href);
element.id = component.id || "";
element.classList.add(...[].concat(component.class));
element.setAttribute("data-event-name", component.dataEventName || "");
return element;
}
/**
* @param {MenuDataItem} item
* @returns {HTMLLIElement}
*/
menuDataItem(item) {
/**
* @type {HTMLAnchorElement[]}
*/
const children = [];
for (const component of item.components) {
children.push(this.menuDataItemComponent(component));
}
const element = window.document.createElement("li");
element.id = item.id || "";
element.classList.add(...[].concat(item.class));
element.append(...children);
return element;
}
/**
* @param {MenuDataGroup} group
* @returns {HTMLUListElement}
*/
menuDataGroup(group) {
/**
* @type {HTMLLIElement[]}
*/
const children = [];
for (const item of group.items) {
children.push(this.menuDataItem(item));
Function.call
}
const element = window.document.createElement("ul");
element.id = group.id || "";
element.classList.add(...[].concat(group.class));
element.append(...children);
return element;
}
/**
* @param {MenuDataGroup[]} groups
* @returns {HTMLUListElement[]}
*/
menuDataGroups(groups) {
/**
* @type {HTMLUListElement[]}
*/
const children = [];
for (const group of groups) {
children.push(this.menuDataGroup(group));
}
return children;
}
/**
* @param {Node} node
* @returns {HTMLElement | null}
*/
elementIfTarget(node) {
const element = /** @type {HTMLElement} */ (node);
const { classList } = element;
if (!classList) return null;
if (!classList.contains("menu")) return null;
if (classList.contains("menu-right")) return null;
return element;
}
/**
* @returns {HTMLElement}
*/
get navElement() {
return window.document.getElementById("mw-mf-page-left");
}
/**
* @returns {HTMLDivElement | null}
*/
get menuElement() {
return /** @type {HTMLDivElement} */ (
this.navElement.getElementsByClassName("menu").item(0)
);
}
/**
* @param {MobileUI} mobileUI
* @param {HTMLElement} menuElement
*/
modify(mobileUI, menuElement) {
const navElement = this.navElement;
if (this.wgIsArticle && !this.wgIsMainPage) {
navElement.classList.add("mobile-ui-article");
}
const firstGroup = menuElement.getElementsByTagName("ul").item(0);
if (firstGroup) {
firstGroup.style.display = "none";
}
const leftGroups = this.menuDataGroups(mobileUI.leftGroups);
menuElement.prepend(...leftGroups);
const rightMenuElement = window.document.createElement("div");
rightMenuElement.classList.add(...menuElement.classList, "menu-right");
const rightGroups = this.menuDataGroups(mobileUI.rightGroups);
rightMenuElement.append(...rightGroups);
navElement.append(rightMenuElement);
}
/**
* @param {MobileUI} mobileUI
*/
build(mobileUI) {
const element = this.menuElement;
if (element) {
this.modify(mobileUI, element);
return;
}
const observer = new MutationObserver((mutations, observer) => {
for (const record of mutations) {
for (const node of record.addedNodes) {
const element = this.elementIfTarget(node);
if (element) {
observer.disconnect();
this.modify(mobileUI, element);
return;
}
}
}
});
observer.observe(this.navElement, { childList: true });
}
}