「MediaWiki:MobileUI.js」の版間の差分
表示
細編集の要約なし タグ: モバイル編集 モバイルウェブ編集 |
編集の要約なし タグ: モバイル編集 モバイルウェブ編集 |
||
| 75行目: | 75行目: | ||
hrefForMenuDataItemComponent(component) { | hrefForMenuDataItemComponent(component) { | ||
switch (component.id) { | switch (component.id) { | ||
case | case MobileUIBuilder.pageShortURLID: | ||
return ( | return ( | ||
this.wgServer | this.wgServer | ||
| 101行目: | 101行目: | ||
element.href = encodeURI(href); | element.href = encodeURI(href); | ||
element.id = component.id || ""; | element.id = component.id || ""; | ||
element.classList.add(...[].concat(component.class)); | element.classList.add(...[].concat(component.class || [])); | ||
element.setAttribute( | element.setAttribute(MobileUIBuilder.dataEventNameAttr, component.dataEventName || ""); | ||
return element; | return element; | ||
| 123行目: | 123行目: | ||
const element = window.document.createElement("li"); | const element = window.document.createElement("li"); | ||
element.id = item.id || ""; | element.id = item.id || ""; | ||
element.classList.add(...[].concat(item.class)); | element.classList.add(...[].concat(item.class || [])); | ||
element.append(...children); | element.append(...children); | ||
| 141行目: | 141行目: | ||
for (const item of group.items) { | for (const item of group.items) { | ||
children.push(this.menuDataItem(item)); | children.push(this.menuDataItem(item)); | ||
} | } | ||
const element = window.document.createElement("ul"); | const element = window.document.createElement("ul"); | ||
element.id = group.id || ""; | element.id = group.id || ""; | ||
element.classList.add(...[].concat(group.class)); | element.classList.add(...[].concat(group.class || [])); | ||
element.append(...children); | element.append(...children); | ||
| 167行目: | 166行目: | ||
return children; | return children; | ||
} | } | ||
| 187行目: | 171行目: | ||
* @returns {HTMLElement} | * @returns {HTMLElement} | ||
*/ | */ | ||
get | get menuContainerElement() { | ||
return window.document.getElementById( | return window.document.getElementById(MobileUIBuilder.menuContainerID); | ||
} | } | ||
| 196行目: | 180行目: | ||
get menuElement() { | get menuElement() { | ||
return /** @type {HTMLDivElement} */ ( | return /** @type {HTMLDivElement} */ ( | ||
this. | this.menuContainerElement.getElementsByClassName(MobileUIBuilder.menuClass).item(0) | ||
); | ); | ||
} | } | ||
| 202行目: | 186行目: | ||
/** | /** | ||
* @param {MobileUI} mobileUI | * @param {MobileUI} mobileUI | ||
*/ | */ | ||
modify(mobileUI | modify(mobileUI) { | ||
const | const leftMenu = this.menuElement; | ||
if (leftMenu.id === MobileUIBuilder.leftMenuID) return; | |||
leftMenu.id = MobileUIBuilder.leftMenuID; | |||
const menuContainer = this.menuContainerElement; | |||
if (!this.wgIsArticle || this.wgIsMainPage) { | if (!this.wgIsArticle || this.wgIsMainPage) { | ||
menuContainer.classList.add(MobileUIBuilder.notArticleID); | |||
} | } | ||
const firstGroup = | const firstGroup = leftMenu.getElementsByTagName("ul").item(0); | ||
if (firstGroup) { | if (firstGroup) { | ||
firstGroup. | firstGroup.classList.add(MobileUIBuilder.hiddenClass); | ||
} | } | ||
const | const customLeftGroups = this.menuDataGroups(mobileUI.leftGroups); | ||
leftMenu.prepend(...customLeftGroups); | |||
const | const rightMenu = window.document.createElement("div"); | ||
rightMenu.id = MobileUIBuilder.rightMenuID; | |||
rightMenu.classList.add(...leftMenu.classList); | |||
const rightGroups = this.menuDataGroups(mobileUI.rightGroups); | const rightGroups = this.menuDataGroups(mobileUI.rightGroups); | ||
rightMenu.append(...rightGroups); | |||
menuContainer.append(rightMenu); | |||
} | } | ||
| 233行目: | 221行目: | ||
if (element) { | if (element) { | ||
this.modify(mobileUI | this.modify(mobileUI); | ||
return; | return; | ||
} | } | ||
| 240行目: | 228行目: | ||
for (const record of mutations) { | for (const record of mutations) { | ||
for (const node of record.addedNodes) { | for (const node of record.addedNodes) { | ||
const | const { classList } = /** @type {HTMLDivElement} */ (node); | ||
if ( | if (classList && classList.contains(MobileUIBuilder.menuClass)) { | ||
observer.disconnect(); | observer.disconnect(); | ||
this.modify(mobileUI | this.modify(mobileUI); | ||
return; | return; | ||
} | } | ||
| 250行目: | 238行目: | ||
}); | }); | ||
observer.observe(this. | observer.observe(this.menuContainerElement, { childList: true }); | ||
} | } | ||
static menuContainerID = "mw-mf-page-left"; | |||
static menuClass = "menu"; | |||
static dataEventNameAttr = "data-event-name"; | |||
static leftMenuID = "mobile-ui-left-menu"; | |||
static rightMenuID = "mobile-ui-right-menu"; | |||
static notArticleID = "mobile-ui-not-article"; | |||
static pageShortURLID = "mobile-ui-page-short-url"; | |||
static hiddenClass = "mobile-ui-hidden"; | |||
} | } | ||
2019年6月26日 (水) 22:54時点における版
// @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 MobileUIBuilder.pageShortURLID:
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(MobileUIBuilder.dataEventNameAttr, 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));
}
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;
}
/**
* @returns {HTMLElement}
*/
get menuContainerElement() {
return window.document.getElementById(MobileUIBuilder.menuContainerID);
}
/**
* @returns {HTMLDivElement | null}
*/
get menuElement() {
return /** @type {HTMLDivElement} */ (
this.menuContainerElement.getElementsByClassName(MobileUIBuilder.menuClass).item(0)
);
}
/**
* @param {MobileUI} mobileUI
*/
modify(mobileUI) {
const leftMenu = this.menuElement;
if (leftMenu.id === MobileUIBuilder.leftMenuID) return;
leftMenu.id = MobileUIBuilder.leftMenuID;
const menuContainer = this.menuContainerElement;
if (!this.wgIsArticle || this.wgIsMainPage) {
menuContainer.classList.add(MobileUIBuilder.notArticleID);
}
const firstGroup = leftMenu.getElementsByTagName("ul").item(0);
if (firstGroup) {
firstGroup.classList.add(MobileUIBuilder.hiddenClass);
}
const customLeftGroups = this.menuDataGroups(mobileUI.leftGroups);
leftMenu.prepend(...customLeftGroups);
const rightMenu = window.document.createElement("div");
rightMenu.id = MobileUIBuilder.rightMenuID;
rightMenu.classList.add(...leftMenu.classList);
const rightGroups = this.menuDataGroups(mobileUI.rightGroups);
rightMenu.append(...rightGroups);
menuContainer.append(rightMenu);
}
/**
* @param {MobileUI} mobileUI
*/
build(mobileUI) {
const element = this.menuElement;
if (element) {
this.modify(mobileUI);
return;
}
const observer = new MutationObserver((mutations, observer) => {
for (const record of mutations) {
for (const node of record.addedNodes) {
const { classList } = /** @type {HTMLDivElement} */ (node);
if (classList && classList.contains(MobileUIBuilder.menuClass)) {
observer.disconnect();
this.modify(mobileUI);
return;
}
}
}
});
observer.observe(this.menuContainerElement, { childList: true });
}
static menuContainerID = "mw-mf-page-left";
static menuClass = "menu";
static dataEventNameAttr = "data-event-name";
static leftMenuID = "mobile-ui-left-menu";
static rightMenuID = "mobile-ui-right-menu";
static notArticleID = "mobile-ui-not-article";
static pageShortURLID = "mobile-ui-page-short-url";
static hiddenClass = "mobile-ui-hidden";
}