375
回編集
編集の要約なし タグ: モバイル編集 モバイルウェブ編集 |
編集の要約なし タグ: モバイル編集 モバイルウェブ編集 |
||
2行目: | 2行目: | ||
/** | /** | ||
* @typedef | * @typedef {import("jquery")} _JQueryStatic | ||
* @prop {string} title | */ | ||
* @prop {string} | |||
/** | |||
* @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 {string | string[]} [class] | ||
* @prop {MenuDataItemComponent[]} components | |||
*/ | */ | ||
/** | /** | ||
* @typedef | * @typedef MenuDataGroup | ||
* @prop { | * @prop {string} [id] | ||
* @prop {string | string[]} [class] | |||
* @prop {MenuDataItem[]} items | |||
*/ | */ | ||
/** | /** | ||
* @ | * @typedef MobileUI | ||
* @ | * @prop {MenuDataGroup[]} leftGroups | ||
* @ | * @prop {MenuDataGroup} leftShortURLGroup | ||
* @prop {MenuDataGroup[]} rightGroups | |||
*/ | */ | ||
class MobileUIBuilder { | |||
/** | |||
* @param {any} mw | |||
* @param {JQueryStatic} $ | |||
*/ | |||
constructor(mw, $) { | |||
this.mw = mw; | |||
this.$ = $; | |||
/** | |||
* @type {string} | |||
*/ | |||
this.wgServer = mw.config.get("wgServer"); | |||
/** | |||
* @type {string} | |||
*/ | |||
this.wgScriptPath = mw.config.get("wgScriptPath"); | |||
/** | |||
* @type {number} | |||
*/ | |||
this.wgArticleId = mw.config.get("wgArticleId"); | |||
} | |||
/** | /** | ||
* @ | * @param {MenuDataItemComponent} component | ||
* @returns {string} | |||
*/ | */ | ||
hrefForMenuDataItemComponent(component) { | |||
switch (component.id) { | |||
case "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 {JQuery<HTMLElement>} | |||
*/ | |||
menuDataItemComponent(component) { | |||
const href = this.hrefForMenuDataItemComponent(component); | |||
return this | |||
.$("<a>") | |||
.text(component.text || href) | |||
.attr("href", href) | |||
.attr("id", component.id) | |||
.addClass(component.class) | |||
.attr("data-event-name", component.dataEventName); | |||
} | |||
/** | |||
. | * @param {MenuDataItem} item | ||
. | * @returns {JQuery<HTMLElement>} | ||
.attr(" | */ | ||
. | menuDataItem(item) { | ||
/** | |||
* @type {JQuery<HTMLElement>[]} | |||
*/ | |||
const children = []; | |||
for (const component of item.components) { | |||
children.push(this.menuDataItemComponent(component)); | |||
} | |||
return this | |||
.$("<li>") | |||
.attr("id", item.id) | |||
.addClass(item.class) | |||
.append(children); | |||
} | } | ||
/** | |||
* @param {MenuDataGroup} group | |||
* @returns {JQuery<HTMLElement>} | |||
*/ | |||
menuDataGroup(group) { | |||
/** | |||
* @type {JQuery<HTMLElement>[]} | |||
*/ | |||
const children = []; | |||
const | for (const item of group.items) { | ||
children.push(this.menuDataItem(item)); | |||
} | |||
$("< | return this | ||
. | .$("<ul>") | ||
. | .attr("id", group.id) | ||
.addClass(group.class) | |||
. | .append(children); | ||
} | |||
const | /** | ||
. | * @param {MenuDataGroup[]} groups | ||
* @returns {JQuery<HTMLElement>[]} | |||
*/ | |||
menuDataGroups(groups) { | |||
/** | |||
* @type {JQuery<HTMLElement>[]} | |||
*/ | |||
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 {HTMLElement | null} | |||
*/ | |||
get menuElement() { | |||
return /** @type {HTMLElement} */ ( | |||
this.navElement.getElementsByClassName("menu").item(0) | |||
); | |||
} | |||
/** | |||
* @param {MobileUI} mobileUI | |||
* @param {HTMLElement} menuElement | |||
*/ | |||
modify(mobileUI, menuElement) { | |||
const leftGroups = this.menuDataGroups(mobileUI.leftGroups); | |||
const rightGroups = []; | |||
const jqMenu = this.$(menuElement); | |||
jqMenu.find("ul").first().remove(); | |||
jqMenu.prepend(leftGroups); | |||
this | |||
.$("<div>") | |||
.addClass(["menu", "menu-right"]) | |||
.append(rightGroups) | |||
.insertAfter(jqMenu); | |||
} | } | ||
/** | |||
* @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 }); | |||
} | |||
} | } |