{/if}
</script>
+<script src="{$__wcf->getPath()}js/WoltLabSuite/WebComponent/fa-icon.js?v={TIME_NOW}"></script>
+
{js application='wcf' file='require' bundle='WoltLabSuite.Core' core='true' hasTiny=true}
{js application='wcf' file='require.config' bundle='WoltLabSuite.Core' core='true' hasTiny=true}
{js application='wcf' file='require.linearExecution' bundle='WoltLabSuite.Core' core='true' hasTiny=true}
aria-haspopup="true"
aria-expanded="false"
>
- <span class="icon icon32 fa-bell-o"></span> <span>{lang}wcf.user.notification.notifications{/lang}</span>{if $__wcf->getUserNotificationHandler()->getNotificationCount()} <span class="badge badgeUpdate">{#$__wcf->getUserNotificationHandler()->getNotificationCount()}</span>{/if}
+ {icon size=32 name='bell' type='solid'}
+ {icon size=32 name='bell' type='regular'}
+ <span class="icon icon32 fa-bell-o"></span>
+ {icon size=32 name='500px' type='brand'}
+ <span>{lang}wcf.user.notification.notifications{/lang}</span>{if $__wcf->getUserNotificationHandler()->getNotificationCount()} <span class="badge badgeUpdate">{#$__wcf->getUserNotificationHandler()->getNotificationCount()}</span>{/if}
</a>
{if !OFFLINE || $__wcf->session->getPermission('admin.general.canViewPageDuringOfflineMode')}
<script data-relocate="true">
--- /dev/null
+const Sizes = [16, 24, 32, 48, 64, 96, 128, 144];
+const HeightMap = new Map<number, number>([
+ [16, 14],
+ [24, 18],
+ [32, 28],
+ [48, 42],
+ [64, 56],
+ [96, 84],
+ [128, 112],
+ [144, 130],
+]);
+
+class FaIcon extends HTMLElement {
+ constructor() {
+ performance.mark("icon-init-start");
+ super();
+
+ performance.mark("icon-init-end");
+ performance.measure("iconInit", "icon-init-start", "icon-init-end");
+ }
+
+ connectedCallback() {
+ performance.mark("icon-start");
+
+ this.validate();
+
+ const root = this.prepareRoot();
+ if (this.brand) {
+ const slot = document.createElement("slot");
+ slot.name = "svg";
+ root.append(slot);
+ } else {
+ root.append("\uf0f3");
+ }
+
+ performance.mark("icon-end");
+ performance.measure("iconRendered", "icon-start", "icon-end");
+ }
+
+ private validate(): void {
+ if (this.size === 0) {
+ throw new TypeError("Must provide an icon size.");
+ } else if (!Sizes.includes(this.size)) {
+ throw new TypeError("Must provide a valid icon size.");
+ }
+
+ if (this.brand) {
+ if (this.name !== null) {
+ throw new TypeError("Cannot provide a name for brand icons.");
+ }
+ } else {
+ if (this.name === null) {
+ throw new TypeError("Must provide the name of the icon.");
+ }
+ }
+ }
+
+ private prepareRoot(): ShadowRoot {
+ const size = this.size;
+ const iconHeight = HeightMap.get(size)!;
+
+ const root = this.attachShadow({ mode: "closed" });
+ const style = document.createElement("style");
+ style.textContent = `
+ ::slotted(svg) {
+ fill: currentColor;
+ height: ${iconHeight}px;
+ shape-rendering: geometricprecision;
+ }
+ `;
+ root.append(style);
+
+ return root;
+ }
+
+ get brand(): boolean {
+ return this.hasAttribute("brand");
+ }
+
+ get name(): string | null {
+ return this.getAttribute("name");
+ }
+
+ get size(): number {
+ const size = this.getAttribute("size");
+ if (size === null) {
+ return 0;
+ }
+
+ return parseInt(size);
+ }
+}
+
+window.customElements.define("fa-icon", FaIcon);
--- /dev/null
+{
+ "extends": "../../../tsconfig.json",
+ "include": [
+ "*.ts"
+ ],
+ "exclude": [],
+ "compilerOptions": {
+ "composite": false,
+ "module": "ES2020"
+ }
+}
"global.d.ts",
"ts/**/*"
],
+ "exclude": [
+ "ts/WoltLabSuite/WebComponent/*.ts"
+ ],
+ "references": [
+ {
+ "path": "ts/WoltLabSuite/WebComponent"
+ }
+ ],
"compilerOptions": {
"allowJs": true,
"target": "es2019",
--- /dev/null
+const Sizes = [16, 24, 32, 48, 64, 96, 128, 144];
+const HeightMap = new Map([
+ [16, 14],
+ [24, 18],
+ [32, 28],
+ [48, 42],
+ [64, 56],
+ [96, 84],
+ [128, 112],
+ [144, 130],
+]);
+class FaIcon extends HTMLElement {
+ constructor() {
+ performance.mark("icon-init-start");
+ super();
+ performance.mark("icon-init-end");
+ performance.measure("iconInit", "icon-init-start", "icon-init-end");
+ }
+ connectedCallback() {
+ performance.mark("icon-start");
+ this.validate();
+ const root = this.prepareRoot();
+ if (this.brand) {
+ const slot = document.createElement("slot");
+ slot.name = "svg";
+ root.append(slot);
+ }
+ else {
+ root.append("\uf0f3");
+ }
+ performance.mark("icon-end");
+ performance.measure("iconRendered", "icon-start", "icon-end");
+ }
+ validate() {
+ if (this.size === 0) {
+ throw new TypeError("Must provide an icon size.");
+ }
+ else if (!Sizes.includes(this.size)) {
+ throw new TypeError("Must provide a valid icon size.");
+ }
+ if (this.brand) {
+ if (this.name !== null) {
+ throw new TypeError("Cannot provide a name for brand icons.");
+ }
+ }
+ else {
+ if (this.name === null) {
+ throw new TypeError("Must provide the name of the icon.");
+ }
+ }
+ }
+ prepareRoot() {
+ const size = this.size;
+ const iconHeight = HeightMap.get(size);
+ const root = this.attachShadow({ mode: "closed" });
+ const style = document.createElement("style");
+ style.textContent = `
+ ::slotted(svg) {
+ fill: currentColor;
+ height: ${iconHeight}px;
+ shape-rendering: geometricprecision;
+ }
+ `;
+ root.append(style);
+ return root;
+ }
+ get brand() {
+ return this.hasAttribute("brand");
+ }
+ get name() {
+ return this.getAttribute("name");
+ }
+ get size() {
+ const size = this.getAttribute("size");
+ if (size === null) {
+ return 0;
+ }
+ return parseInt(size);
+ }
+}
+window.customElements.define("fa-icon", FaIcon);
namespace wcf\system\template\plugin;
-use wcf\system\exception\SystemException;
use wcf\system\template\TemplateEngine;
/**
{
private const SIZES = [16, 24, 32, 48, 64, 96, 128, 144];
+ private const TYPES = ['brand', 'regular', 'solid'];
+
/**
* @inheritDoc
*/
{
$size = \intval($tagArgs['size'] ?? 0);
$name = $tagArgs['name'] ?? '';
+ $type = $tagArgs['type'] ?? '';
if (!\in_array($size, self::SIZES)) {
throw new \InvalidArgumentException("An unsupported size `{$size}` was requested.");
}
if ($name === '') {
- throw new \InvalidArgumentException("The `name` attribute must be present and non-empty");
+ throw new \InvalidArgumentException("The `name` attribute must be present and non-empty.");
+ }
+
+ if ($type === '') {
+ throw new \InvalidArgumentException("The `type` attribute must be present and non-empty.");
+ } else if (!\in_array($type, self::TYPES)) {
+ throw new \InvalidArgumentException("An unsupported type `${type}` was specified.");
}
- $svgFile = \WCF_DIR . "icon/font-awesome/v6/brands/{$name}.svg";
- if (\file_exists($svgFile)) {
+ if ($type === 'brand') {
+ $svgFile = \WCF_DIR . "icon/font-awesome/v6/brands/{$name}.svg";
+ if (!\file_exists($svgFile)) {
+ throw new \InvalidArgumentException("Unable to locate the icon for brand `${name}`.");
+ }
+
$content = \file_get_contents($svgFile);
+ $content = \preg_replace('~^<svg~', '<svg slot="svg"', $content);
return <<<HTML
<fa-icon size="{$size}" brand>{$content}</fa-icon>
HTML;
}
return <<<HTML
- <fa-icon size="{$size}" name="{$name}"></fa-icon>
+ <fa-icon size="{$size}" name="{$name}" {$type}></fa-icon>
HTML;
}
}
}
}
+fa-icon {
+ align-items: center;
+ display: flex;
+ height: var(--icon-size);
+ justify-content: center;
+ width: calc(var(--icon-size) * 1.25);
+
+ &:not(:upgraded) {
+ visibility: hidden !important;
+ }
+
+ &[hidden] {
+ display: none;
+ }
+
+ &:not([brand]) {
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+
+ font-family: "Font Awesome 6 Free";
+ font-size: var(--font-size);
+ font-style: normal;
+ font-variant: normal;
+ line-height: 1;
+ text-rendering: auto;
+ }
+
+ &[solid] {
+ font-weight: 900;
+ }
+
+ &[regular] {
+ font-weight: 400;
+ }
+
+ &[size="16"] {
+ --font-size: 14px;
+ --icon-size: 16px;
+ }
+
+ &[size="24"] {
+ --font-size: 18px;
+ --icon-size: 24px;
+ }
+
+ &[size="32"] {
+ --font-size: 28px;
+ --icon-size: 32px;
+ }
+
+ &[size="48"] {
+ --font-size: 42px;
+ --icon-size: 48px;
+ }
+
+ &[size="64"] {
+ --font-size: 56px;
+ --icon-size: 64px;
+ }
+
+ &[size="96"] {
+ --font-size: 84px;
+ --icon-size: 96px;
+ }
+
+ &[size="128"] {
+ --font-size: 112px;
+ --icon-size: 128px;
+ }
+
+ &[size="144"] {
+ --font-size: 130px;
+ --icon-size: 144px;
+ }
+}
+
/* Default icon sizes */
.icon {
&.icon16 {