Skip to content

Commit

Permalink
Migrate wcf.messageTabMenu (jQuery Widget) to Typescript
Browse files Browse the repository at this point in the history
HTML and CSS are still a mess and should be overhauled with a future update.

Closes #6088
Closes #6089
  • Loading branch information
BurntimeX committed Nov 26, 2024
1 parent 392c188 commit 0d8d5b3
Show file tree
Hide file tree
Showing 22 changed files with 341 additions and 143 deletions.
2 changes: 1 addition & 1 deletion com.woltlab.wcf/templates/__messageFormPoll.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
});
</script>

<div id="poll" class="jsOnly messageTabMenuContent">
<div id="poll-{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}" class="jsOnly messageTabMenuContent">
<dl{if $errorField == 'pollOptions'} class="formError"{/if}>
<dt>
<label for="pollQuestion">{lang}wcf.poll.question{/lang}</label>
Expand Down
2 changes: 1 addition & 1 deletion com.woltlab.wcf/templates/__messageFormPollInline.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
});
</script>

<div class="jsOnly messageTabMenuContent">
<div id="poll-{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}" class="jsOnly messageTabMenuContent">
<dl>
<dt>
<label for="{$wysiwygSelector}pollQuestion">{lang}wcf.poll.question{/lang}</label>
Expand Down
18 changes: 6 additions & 12 deletions com.woltlab.wcf/templates/messageFormTabs.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,23 @@
<div class="messageTabMenu" data-preselect="{if $preselectTabMenu|isset}{$preselectTabMenu}{else}true{/if}" data-wysiwyg-container-id="{if $wysiwygContainerID|isset}{$wysiwygContainerID}{else}text{/if}">
<nav class="messageTabMenuNavigation jsOnly">
<ul>
{if MODULE_SMILEY && !$smileyCategories|empty}<li data-name="smilies"><a>{icon name='face-smile'} <span>{lang}wcf.message.smilies{/lang}</span></a></li>{/if}
{if MODULE_SMILEY && !$smileyCategories|empty}<li data-name="smilies"><button type="button">{icon name='face-smile'} <span>{lang}wcf.message.smilies{/lang}</span></button></li>{/if}
{if !$attachmentHandler|empty && $attachmentHandler->canUpload()}
<li data-name="attachments"><a>{icon name='paperclip'} <span>{lang}wcf.attachment.attachments{/lang}</span></a></li>
<li data-name="attachments"><button type="button">{icon name='paperclip'} <span>{lang}wcf.attachment.attachments{/lang}</span></button></li>
{/if}
{if $__messageFormSettings}<li data-name="settings"><a>{icon name='gear'} <span>{lang}wcf.message.settings{/lang}</span></a></li>{/if}
{if $__showPoll|isset && $__showPoll}<li data-name="poll"><a>{icon name='chart-bar'} <span>{lang}wcf.poll.management{/lang}</span></a></li>{/if}
{if $__messageFormSettings}<li data-name="settings"><button type="button">{icon name='gear'} <span>{lang}wcf.message.settings{/lang}</span></button></li>{/if}
{if $__showPoll|isset && $__showPoll}<li data-name="poll"><button type="button">{icon name='chart-bar'} <span>{lang}wcf.poll.management{/lang}</span></button></li>{/if}
{event name='tabMenuTabs'}
</ul>
</nav>

{if MODULE_SMILEY && !$smileyCategories|empty}{include file='messageFormSmilies'}{/if}
{if MODULE_SMILEY && !$smileyCategories|empty}{include file='shared_messageFormSmileyTab'}{/if}
{if !$attachmentHandler|empty && $attachmentHandler->canUpload()}
{include file='shared_messageFormAttachments'}
{/if}

{if $__messageFormSettings}{@$__messageFormSettings}{/if}
{if $__messageFormSettings}{unsafe:$__messageFormSettings}{/if}
{include file='__messageFormPoll'}

{event name='tabMenuContents'}
</div>

<script data-relocate="true">
$(function() {
$('.messageTabMenu').messageTabMenu();
});
</script>
16 changes: 5 additions & 11 deletions com.woltlab.wcf/templates/messageFormTabsInline.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
<div class="messageTabMenu"{if $preselectTabMenu|isset} data-preselect="{$preselectTabMenu}"{/if} data-wysiwyg-container-id="{$wysiwygSelector}">
<nav class="messageTabMenuNavigation jsOnly">
<ul>
{if MODULE_SMILEY && !$smileyCategories|empty}<li data-name="smilies"><a>{icon name='face-smile'} <span>{lang}wcf.message.smilies{/lang}</span></a></li>{/if}
{if MODULE_SMILEY && !$smileyCategories|empty}<li data-name="smilies"><button type="button">{icon name='face-smile'} <span>{lang}wcf.message.smilies{/lang}</span></button></li>{/if}
{if !$attachmentHandler|empty && $attachmentHandler->canUpload()}
<li data-name="attachments"><a>{icon name='paperclip'} <span>{lang}wcf.attachment.attachments{/lang}</span></a></li>
<li data-name="attachments"><button type="button">{icon name='paperclip'} <span>{lang}wcf.attachment.attachments{/lang}</span></button></li>
{/if}
{if $__messageFormSettingsInlineContent}<li data-name="settings"><a>{icon name='gear'} <span>{lang}wcf.message.settings{/lang}</span></a></li>{/if}
{if $__showPoll|isset && $__showPoll}<li data-name="poll"><a>{icon name='chart-bar'} <span>{lang}wcf.poll.management{/lang}</span></a></li>{/if}
{if $__messageFormSettingsInlineContent}<li data-name="settings"><button type="button">{icon name='gear'} <span>{lang}wcf.message.settings{/lang}</span></button></li>{/if}
{if $__showPoll|isset && $__showPoll}<li data-name="poll"><button type="button">{icon name='chart-bar'} <span>{lang}wcf.poll.management{/lang}</span></button></li>{/if}
{event name='tabMenuTabs'}
</ul>
</nav>

{if MODULE_SMILEY && !$smileyCategories|empty}{include file='messageFormSmilies'}{/if}
{if MODULE_SMILEY && !$smileyCategories|empty}{include file='shared_messageFormSmileyTab'}{/if}
{if !$attachmentHandler|empty && $attachmentHandler->canUpload()}
{include file='shared_messageFormAttachments'}
{/if}
Expand All @@ -29,9 +29,3 @@

{event name='tabMenuContents'}
</div>

<script data-relocate="true">
$(function() {
$('.messageTabMenu').messageTabMenu();
});
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
{foreach from=$smileyCategories item=smileyCategory}
{assign var=__tabCount value=$__tabCount + 1}
{assign var='__smileyAnchor' value='smilies-'|concat:$smileyCategory->categoryID}
<li data-name="smilies-{@$smileyCategory->categoryID}" data-smiley-category-id="{@$smileyCategory->categoryID}"><a>{$smileyCategory->getTitle()}</a></li>
<li data-name="smilies-{@$smileyCategory->categoryID}" data-smiley-category-id="{@$smileyCategory->categoryID}"><button type="button">{$smileyCategory->getTitle()}</button></li>
{/foreach}
{/capture}

<div class="messageTabMenuContent{if $__tabCount} messageTabMenu{/if}" data-preselect="true" data-collapsible="false" id="smilies-{if $wysiwygSelector|isset}{$wysiwygSelector}{else}text{/if}">
{assign var='__firstSmileyCategory' value=$smileyCategories|reset}
{capture assign=__defaultSmilies}
{if $__firstSmileyCategory->categoryID}
{include file='__messageFormSmilies' smilies=$__wcf->getSmileyCache()->getCategorySmilies($__firstSmileyCategory->categoryID)}
{include file='shared_messageFormSmilies' smilies=$__wcf->getSmileyCache()->getCategorySmilies($__firstSmileyCategory->categoryID)}
{else}
{include file='__messageFormSmilies' smilies=$__wcf->getSmileyCache()->getCategorySmilies()}
{include file='shared_messageFormSmilies' smilies=$__wcf->getSmileyCache()->getCategorySmilies()}
{/if}
{/capture}

Expand All @@ -26,15 +26,13 @@

{foreach from=$smileyCategories item=smileyCategory}
<div class="messageTabMenuContent" id="smilies-{if $wysiwygSelector|isset}{$wysiwygSelector|encodeJS}{else}text{/if}-{@$smileyCategory->categoryID}">
{if $__firstSmileyCategory->categoryID == $smileyCategory->categoryID}{@$__defaultSmilies}{/if}
{if $__firstSmileyCategory->categoryID == $smileyCategory->categoryID}
{@$__defaultSmilies}
{else}
{include file='shared_messageFormSmilies' smilies=$__wcf->getSmileyCache()->getCategorySmilies($smileyCategory->categoryID)}
{/if}
</div>
{/foreach}

<script data-relocate="true">
$(function() {
new WCF.Message.SmileyCategories('{if $wysiwygSelector|isset}{$wysiwygSelector|encodeJS}{else}text{/if}');
});
</script>
{else}
{@$__defaultSmilies}
{/if}
Expand Down
16 changes: 2 additions & 14 deletions com.woltlab.wcf/templates/shared_wysiwygSmileyFormContainer.tpl
Original file line number Diff line number Diff line change
@@ -1,19 +1,7 @@
{include file='shared_tabTabMenuFormContainer'}

<script data-relocate="true">
$(function() {
{if $container->children()|count > 1}
new WCF.Message.SmileyCategories(
'{@$container->getPrefixedWysiwygId()|encodeJS}',
'{@$container->getPrefixedId()|encodeJS}Container',
true
);
$('#{@$container->getPrefixedId()|encodeJS}Container').messageTabMenu();
{/if}
require(['WoltLabSuite/Core/Ui/Smiley/Insert'], function(UiSmileyInsert) {
new UiSmileyInsert('{@$container->getPrefixedWysiwygId()|encodeJS}');
});
require(['WoltLabSuite/Core/Ui/Smiley/Insert'], function(UiSmileyInsert) {
new UiSmileyInsert('{@$container->getPrefixedWysiwygId()|encodeJS}');
});
</script>
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
{include file='shared_tabMenuFormContainer' __tabMenuCSSClassName='messageTabMenuNavigation'}

<script data-relocate="true">
$(function() {
$('#{@$container->getPrefixedId()|encodeJS}Container').messageTabMenu();
});
</script>
4 changes: 3 additions & 1 deletion ts/WoltLabSuite/Core/Bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ export function setup(options: BoostrapOptions): void {
whenFirstSeen("[data-report-content]", () => {
void import("./Ui/Moderation/Report").then(({ setup }) => setup());
});

whenFirstSeen(".messageTabMenu", () => {
void import("./Component/Message/MessageTabMenu").then(({ setup }) => setup());
});
whenFirstSeen("woltlab-core-pagination", () => {
void import("./Ui/Pagination/JumpToPage").then(({ setup }) => setup());
});
Expand Down
3 changes: 2 additions & 1 deletion ts/WoltLabSuite/Core/Component/Attachment/List.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import WoltlabCoreFileElement from "../File/woltlab-core-file";
import { CkeditorDropEvent } from "../File/Upload";
import { createAttachmentFromFile } from "./Entry";
import { listenToCkeditor } from "../Ckeditor/Event";
import { getTabMenu } from "../Message/MessageTabMenu";

function fileToAttachment(fileList: HTMLElement, file: WoltlabCoreFileElement, editor: HTMLElement): void {
fileList.append(createAttachmentFromFile(file, editor));
Expand Down Expand Up @@ -54,7 +55,7 @@ export function setup(editorId: string): void {
return;
}

window.jQuery(messageTabMenu).messageTabMenu("showTab", "attachments", true);
getTabMenu(editorId)?.setActiveTab("attachments");
})
.collectMetaData((payload) => {
let context: Context | undefined = undefined;
Expand Down
150 changes: 150 additions & 0 deletions ts/WoltLabSuite/Core/Component/Message/MessageTabMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* Provides a specialized tab menu used for message options, integrates better into the editor.
*
* @author Marcel Werk
* @copyright 2001-2024 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.2
* @woltlabExcludeBundle tiny
*/

import { wheneverFirstSeen } from "WoltLabSuite/Core/Helper/Selector";
import * as DomUtil from "WoltLabSuite/Core/Dom/Util";
import { listenToCkeditor } from "../Ckeditor/Event";

class TabMenu {
readonly #tabs: HTMLElement[];
readonly #tabContainers: HTMLElement[];
#activeTabName = "";
#wysiwygContainerId = "";
#collapsible: boolean;

constructor(
tabs: HTMLElement[],
tabContainers: HTMLElement[],
activeTabName: string,
wysiwygContainerId: string,
collapsible: boolean = true,
) {
this.#tabs = tabs;
this.#tabContainers = tabContainers;
this.#wysiwygContainerId = wysiwygContainerId;
this.#collapsible = collapsible;

this.#init();

if (activeTabName) {
this.setActiveTab(activeTabName);
} else {
this.#closeAllTabs();
}
}

setActiveTab(tabName: string): void {
if (this.#activeTabName === tabName) {
if (this.#collapsible) {
this.#activeTabName = "";
this.#closeAllTabs();
}

return;
}

this.#closeAllTabs();

const tab = this.#tabs.find((element) => element.dataset.name === tabName);
if (!tab) {
console.debug(`Unknown tab '${tabName}'.`);
return;
}
const tabIndex = this.#tabs.indexOf(tab);
tab.classList.add("active");
tab.querySelector("button")!.setAttribute("aria-expanded", "true");
this.#tabContainers[tabIndex].hidden = false;
this.#tabContainers[tabIndex].classList.add("active");
this.#activeTabName = tabName;
}

#init(): void {
for (let i = 0; i < this.#tabs.length; i++) {
const tab = this.#tabs[i];
const tabContainer = this.#tabContainers[i];

const anchor = tab.querySelector("a");
if (anchor) {
const buttonReplacement = document.createElement("button");
buttonReplacement.type = "button";
DomUtil.replaceElement(anchor, buttonReplacement);
}

const button = tab.querySelector("button")!;
button.setAttribute("aria-haspopup", "true");
button.setAttribute("aria-expanded", "false");
button.setAttribute("aria-controls", tabContainer.id);
button.addEventListener("click", () => {
this.setActiveTab(tab.dataset.name!);
});
}

if (this.#wysiwygContainerId) {
listenToCkeditor(document.getElementById(this.#wysiwygContainerId)!).reset(() => {
this.#closeAllTabs();
});
}
}

#closeAllTabs(): void {
for (let i = 0; i < this.#tabs.length; i++) {
const tab = this.#tabs[i];
tab.classList.remove("active");
tab.querySelector("button")!.setAttribute("aria-expanded", "false");
this.#tabContainers[i].hidden = true;
this.#tabContainers[i].classList.remove("active");
}
}
}

const tabMenus = new Map<string, TabMenu>();

function initTabMenu(tabMenu: HTMLElement): void {
const tabs = tabMenu.querySelectorAll<HTMLElement>(":scope > nav [data-name]");
const tabContainers = tabMenu.querySelectorAll<HTMLElement>(":scope > .messageTabMenuContent");

if (!tabs.length) {
console.debug(`No tabs found in message tab menu ('${tabMenu.dataset.wysiwygContainerId}').`);
return;
}
if (tabs.length != tabContainers.length) {
console.debug(`Amount of tabs does not equal amount of tab containers ('${tabMenu.dataset.wysiwygContainerId}').`);
return;
}

let activeTabName = "";
if (tabMenu.dataset.preselect) {
if (tabMenu.dataset.preselect === "true") {
activeTabName = tabs[0].dataset.name!;
} else {
activeTabName = tabMenu.dataset.preselect;
}
}

const tabMenuObj = new TabMenu(
Array.from(tabs),
Array.from(tabContainers),
activeTabName,
tabMenu.dataset.wysiwygContainerId ?? "",
tabMenu.dataset.collapsible !== "false",
);

if (tabMenu.dataset.wysiwygContainerId) {
tabMenus.set(tabMenu.dataset.wysiwygContainerId, tabMenuObj);
}
}

export function getTabMenu(identifier: string): TabMenu | undefined {
return tabMenus.get(identifier);
}

export function setup(): void {
wheneverFirstSeen(".messageTabMenu", (tabMenu) => initTabMenu(tabMenu));
}
2 changes: 0 additions & 2 deletions ts/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,6 @@ declare global {

interface JQuery {
sortable(...args: any[]): unknown;

messageTabMenu(...args: any[]): unknown;
}

type ArbitraryObject = Record<string, unknown>;
Expand Down
5 changes: 0 additions & 5 deletions wcfsetup/install/files/acp/templates/__messageFormSmilies.tpl

This file was deleted.

Loading

0 comments on commit 0d8d5b3

Please sign in to comment.