「MediaWiki:MobileUI.js」の版間の差分
表示
編集の要約なし タグ: モバイル編集 モバイルウェブ編集 |
編集の要約なし タグ: モバイル編集 モバイルウェブ編集 |
||
| 85行目: | 85行目: | ||
hrefForMenuDataItemComponent(component) { | hrefForMenuDataItemComponent(component) { | ||
switch (component.id) { | switch (component.id) { | ||
case | case "mobile-ui-page-short-url": | ||
return ( | return ( | ||
this.wgServer | this.wgServer | ||
| 112行目: | 112行目: | ||
element.id = component.id || ""; | element.id = component.id || ""; | ||
element.classList.add(...[].concat(component.class || [])); | element.classList.add(...[].concat(component.class || [])); | ||
element.setAttribute( | element.setAttribute("data-event-name", component.dataEventName || ""); | ||
return element; | return element; | ||
| 176行目: | 176行目: | ||
return children; | return children; | ||
} | |||
/** | |||
* @param {boolean} isVisible | |||
*/ | |||
static setRightMenuVisible(isVisible) { | |||
MobileUIBuilder | |||
.menuContainerElement | |||
.setAttribute("data-mobile-ui-right-menu-visible", String(Boolean(isVisible))); | |||
} | } | ||
| 181行目: | 190行目: | ||
* @returns {HTMLElement} | * @returns {HTMLElement} | ||
*/ | */ | ||
get menuContainerElement() { | static get menuContainerElement() { | ||
return window.document.getElementById( | return window.document | ||
.getElementById("mw-mf-page-left"); | |||
} | } | ||
| 188行目: | 198行目: | ||
* @returns {HTMLDivElement | null} | * @returns {HTMLDivElement | null} | ||
*/ | */ | ||
get menuElement() { | static get menuElement() { | ||
return /** @type {HTMLDivElement} */ ( | return /** @type {HTMLDivElement} */ ( | ||
MobileUIBuilder | |||
.menuContainerElement | |||
.getElementsByClassName(MobileUIBuilder.menuClass) | |||
.item(0) | |||
); | |||
} | |||
/** | |||
* @returns {HTMLBodyElement | null} | |||
*/ | |||
static get authenticatedBody() { | |||
return /** @type {HTMLBodyElement} */ ( | |||
window.document | |||
.getElementsByClassName("is-authenticated") | |||
.item(0) | |||
); | |||
} | |||
/** | |||
* @returns {HTMLFormElement} | |||
*/ | |||
static get headerElement() { | |||
return /** @type {HTMLFormElement} */ ( | |||
window.document | |||
.getElementsByClassName("header") | |||
.item(0) | |||
); | |||
} | |||
/** | |||
* @returns {HTMLAnchorElement | null} | |||
*/ | |||
static get userButton() { | |||
return /** @type {HTMLAnchorElement} */ ( | |||
MobileUIBuilder | |||
.headerElement | |||
.getElementsByClassName("user-button") | |||
.item(0) | |||
); | |||
} | |||
/** | |||
* @returns {HTMLButtonElement | null} | |||
*/ | |||
static get rightMenuButtonElement() { | |||
return /** @type {HTMLButtonElement} */ ( | |||
window.document | |||
.getElementById(MobileUIBuilder.rightMenuButtonID) | |||
); | ); | ||
} | |||
setNotArticleClass() { | |||
if (this.wgIsArticle && !this.wgIsMainPage) return; | |||
MobileUIBuilder | |||
.menuContainerElement | |||
.classList | |||
.add("mobile-ui-not-article"); | |||
} | |||
assembleRightMenuButton() { | |||
if (!this.wgUserId) return; | |||
if (!MobileUIBuilder.authenticatedBody) return; | |||
if (MobileUIBuilder.rightMenuButtonElement) return; | |||
const userButton = MobileUIBuilder.userButton; | |||
if (userButton && !userButton.text.trim()) { | |||
userButton.parentElement.classList.add(MobileUIBuilder.hiddenClass); | |||
} | |||
const button = window.document.createElement("button"); | |||
button.id = MobileUIBuilder.rightMenuButtonID; | |||
button.classList.add( | |||
"mw-ui-icon", | |||
"mw-ui-icon-element", | |||
"mobile-ui-icon-puzzle", | |||
); | |||
button.addEventListener("click", function (event) { | |||
event.preventDefault(); | |||
const body = MobileUIBuilder.authenticatedBody; | |||
if (!body) return; | |||
if (body.classList.contains(MobileUIBuilder.navigationEnabledClass)) return; | |||
MobileUIBuilder.setRightMenuVisible(true); | |||
body.classList.add( | |||
MobileUIBuilder.navigationEnabledClass, | |||
MobileUIBuilder.secondaryNavigationEnabledClass, | |||
); | |||
}); | |||
const buttonWrapper = window.document.createElement("div"); | |||
buttonWrapper.append(button); | |||
MobileUIBuilder.headerElement.append(buttonWrapper); | |||
} | |||
prebuild() { | |||
this.setNotArticleClass(); | |||
this.assembleRightMenuButton(); | |||
} | } | ||
| 198行目: | 305行目: | ||
*/ | */ | ||
modify(mobileUI) { | modify(mobileUI) { | ||
const leftMenu = | const leftMenu = MobileUIBuilder.menuElement; | ||
if (leftMenu.classList.contains(MobileUIBuilder.leftMenuClass)) return; | if (leftMenu.classList.contains(MobileUIBuilder.leftMenuClass)) return; | ||
MobileUIBuilder.setRightMenuVisible(false); | |||
menuContainer | const menuContainer = MobileUIBuilder.menuContainerElement; | ||
menuContainer.addEventListener("transitionend", function () { | menuContainer.addEventListener("transitionend", function () { | ||
if ( | const body = MobileUIBuilder.authenticatedBody; | ||
if (!body || body.classList.contains(MobileUIBuilder.secondaryNavigationEnabledClass)) return; | |||
MobileUIBuilder.setRightMenuVisible(false); | |||
}); | }); | ||
const firstGroup = leftMenu.getElementsByTagName("ul").item(0); | const firstGroup = leftMenu.getElementsByTagName("ul").item(0); | ||
| 234行目: | 339行目: | ||
*/ | */ | ||
build(mobileUI) { | build(mobileUI) { | ||
if ( | if (MobileUIBuilder.menuElement) { | ||
this.modify(mobileUI); | this.modify(mobileUI); | ||
return; | return; | ||
| 252行目: | 357行目: | ||
}); | }); | ||
observer.observe( | observer.observe(MobileUIBuilder.menuContainerElement, { childList: true }); | ||
} | } | ||
} | } | ||
MobileUIBuilder. | MobileUIBuilder.rightMenuButtonID = "mobile-ui-right-menu-button"; | ||
MobileUIBuilder.menuClass = "menu"; | MobileUIBuilder.menuClass = "menu"; | ||
MobileUIBuilder.leftMenuClass = "mobile-ui-left-menu"; | MobileUIBuilder.leftMenuClass = "mobile-ui-left-menu"; | ||
MobileUIBuilder.rightMenuClass = "mobile-ui-right-menu"; | MobileUIBuilder.rightMenuClass = "mobile-ui-right-menu"; | ||
MobileUIBuilder.hiddenClass = "mobile-ui-hidden"; | MobileUIBuilder.hiddenClass = "mobile-ui-hidden"; | ||
MobileUIBuilder.navigationEnabledClass = "navigation-enabled"; | MobileUIBuilder.navigationEnabledClass = "navigation-enabled"; | ||
MobileUIBuilder.secondaryNavigationEnabledClass = "secondary-navigation-enabled"; | MobileUIBuilder.secondaryNavigationEnabledClass = "secondary-navigation-enabled"; | ||
2019年6月27日 (木) 22:01時点における版
// @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");
/**
* @type {number}
*/
this.wgUserId = mw.config.get("wgUserId");
/**
* @type {string}
*/
this.wgUserName = mw.config.get("wgUserName");
}
/**
* @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));
}
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 {boolean} isVisible
*/
static setRightMenuVisible(isVisible) {
MobileUIBuilder
.menuContainerElement
.setAttribute("data-mobile-ui-right-menu-visible", String(Boolean(isVisible)));
}
/**
* @returns {HTMLElement}
*/
static get menuContainerElement() {
return window.document
.getElementById("mw-mf-page-left");
}
/**
* @returns {HTMLDivElement | null}
*/
static get menuElement() {
return /** @type {HTMLDivElement} */ (
MobileUIBuilder
.menuContainerElement
.getElementsByClassName(MobileUIBuilder.menuClass)
.item(0)
);
}
/**
* @returns {HTMLBodyElement | null}
*/
static get authenticatedBody() {
return /** @type {HTMLBodyElement} */ (
window.document
.getElementsByClassName("is-authenticated")
.item(0)
);
}
/**
* @returns {HTMLFormElement}
*/
static get headerElement() {
return /** @type {HTMLFormElement} */ (
window.document
.getElementsByClassName("header")
.item(0)
);
}
/**
* @returns {HTMLAnchorElement | null}
*/
static get userButton() {
return /** @type {HTMLAnchorElement} */ (
MobileUIBuilder
.headerElement
.getElementsByClassName("user-button")
.item(0)
);
}
/**
* @returns {HTMLButtonElement | null}
*/
static get rightMenuButtonElement() {
return /** @type {HTMLButtonElement} */ (
window.document
.getElementById(MobileUIBuilder.rightMenuButtonID)
);
}
setNotArticleClass() {
if (this.wgIsArticle && !this.wgIsMainPage) return;
MobileUIBuilder
.menuContainerElement
.classList
.add("mobile-ui-not-article");
}
assembleRightMenuButton() {
if (!this.wgUserId) return;
if (!MobileUIBuilder.authenticatedBody) return;
if (MobileUIBuilder.rightMenuButtonElement) return;
const userButton = MobileUIBuilder.userButton;
if (userButton && !userButton.text.trim()) {
userButton.parentElement.classList.add(MobileUIBuilder.hiddenClass);
}
const button = window.document.createElement("button");
button.id = MobileUIBuilder.rightMenuButtonID;
button.classList.add(
"mw-ui-icon",
"mw-ui-icon-element",
"mobile-ui-icon-puzzle",
);
button.addEventListener("click", function (event) {
event.preventDefault();
const body = MobileUIBuilder.authenticatedBody;
if (!body) return;
if (body.classList.contains(MobileUIBuilder.navigationEnabledClass)) return;
MobileUIBuilder.setRightMenuVisible(true);
body.classList.add(
MobileUIBuilder.navigationEnabledClass,
MobileUIBuilder.secondaryNavigationEnabledClass,
);
});
const buttonWrapper = window.document.createElement("div");
buttonWrapper.append(button);
MobileUIBuilder.headerElement.append(buttonWrapper);
}
prebuild() {
this.setNotArticleClass();
this.assembleRightMenuButton();
}
/**
* @param {MobileUI} mobileUI
*/
modify(mobileUI) {
const leftMenu = MobileUIBuilder.menuElement;
if (leftMenu.classList.contains(MobileUIBuilder.leftMenuClass)) return;
MobileUIBuilder.setRightMenuVisible(false);
const menuContainer = MobileUIBuilder.menuContainerElement;
menuContainer.addEventListener("transitionend", function () {
const body = MobileUIBuilder.authenticatedBody;
if (!body || body.classList.contains(MobileUIBuilder.secondaryNavigationEnabledClass)) return;
MobileUIBuilder.setRightMenuVisible(false);
});
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.classList.add(...leftMenu.classList, MobileUIBuilder.rightMenuClass);
const rightGroups = this.menuDataGroups(mobileUI.rightGroups);
rightMenu.append(...rightGroups);
menuContainer.append(rightMenu);
leftMenu.classList.add(MobileUIBuilder.leftMenuClass);
}
/**
* @param {MobileUI} mobileUI
*/
build(mobileUI) {
if (MobileUIBuilder.menuElement) {
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(MobileUIBuilder.menuContainerElement, { childList: true });
}
}
MobileUIBuilder.rightMenuButtonID = "mobile-ui-right-menu-button";
MobileUIBuilder.menuClass = "menu";
MobileUIBuilder.leftMenuClass = "mobile-ui-left-menu";
MobileUIBuilder.rightMenuClass = "mobile-ui-right-menu";
MobileUIBuilder.hiddenClass = "mobile-ui-hidden";
MobileUIBuilder.navigationEnabledClass = "navigation-enabled";
MobileUIBuilder.secondaryNavigationEnabledClass = "secondary-navigation-enabled";