Skip to content

Commit e42dfd5

Browse files
committed
DEV: Extract CustomHeaderIcon component for header link rendering
Refactor header icon link initialization by delegating rendering logic to a new `CustomHeaderIcon` component. This modularizes `isLastLink`, styling, and rendering logic, improving maintainability and separation of concerns.
1 parent 270f8b2 commit e42dfd5

File tree

2 files changed

+81
-74
lines changed

2 files changed

+81
-74
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import Component from "@glimmer/component";
2+
import { service } from "@ember/service";
3+
import { htmlSafe } from "@ember/template";
4+
import { eq } from "truth-helpers";
5+
import concatClass from "discourse/helpers/concat-class";
6+
import icon from "discourse/helpers/d-icon";
7+
import dasherize from "discourse/helpers/dasherize";
8+
import { escapeExpression } from "discourse/lib/utilities";
9+
import isValidUrl from "../lib/isValidUrl";
10+
11+
export default class CustomHeaderIcon extends Component {
12+
static shouldRender(args, context) {
13+
return context.site.mobileView
14+
? args.link.view === "vmo" || args.link.view === "vdm"
15+
: args.link.view === "vdo" || args.link.view === "vdm";
16+
}
17+
18+
@service site;
19+
20+
get className() {
21+
return `header-icon-${dasherize(this.args.link.title)}`;
22+
}
23+
24+
get isLastLink() {
25+
const visibleLinks = this.args.links.filter((item) =>
26+
this.site.mobileView
27+
? item.view === "vmo" || item.view === "vdm"
28+
: item.view === "vdo" || item.view === "vdm"
29+
);
30+
31+
return this.args.link === visibleLinks.at(-1);
32+
}
33+
34+
get style() {
35+
const numericWidth = Number(this.args.link.width);
36+
return Number.isFinite(numericWidth)
37+
? htmlSafe(`width: ${escapeExpression(numericWidth)}px`)
38+
: undefined;
39+
}
40+
41+
<template>
42+
<li
43+
class={{concatClass
44+
"custom-header-icon-link"
45+
this.className
46+
@link.view
47+
(if this.isLastLink "last-custom-icon")
48+
}}
49+
>
50+
<a
51+
class="btn no-text icon btn-flat"
52+
href={{@link.url}}
53+
title={{@link.title}}
54+
target={{if (eq @link.target "blank") "_blank"}}
55+
rel={{if @link.target "noopener"}}
56+
style={{this.style}}
57+
>
58+
{{#if (isValidUrl @link.icon)}}
59+
<img src={{@link.icon}} aria-hidden="true" />
60+
<span class="sr-only">{{@link.title}}</span>
61+
{{else}}
62+
{{icon @link.icon label=@link.title}}
63+
{{/if}}
64+
</a>
65+
</li>
66+
</template>
67+
}

javascripts/discourse/initializers/initialize-for-header-icon-links.gjs

Lines changed: 14 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,9 @@
1-
import Component from "@glimmer/component";
2-
import { service } from "@ember/service";
3-
import { dasherize } from "@ember/string";
4-
import { htmlSafe } from "@ember/template";
5-
import concatClass from "discourse/helpers/concat-class";
6-
import icon from "discourse/helpers/d-icon";
1+
import curryComponent from "ember-curry-component";
2+
import { getOwnerWithFallback } from "discourse/lib/get-owner";
73
import { withPluginApi } from "discourse/lib/plugin-api";
8-
import { escapeExpression } from "discourse/lib/utilities";
9-
import isValidUrl from "../lib/isValidUrl";
4+
import CustomHeaderIcon from "../components/custom-header-icon";
105

11-
const beforeIcon = ["chat", "search", "hamburger", "user-menu"];
12-
13-
function buildIconTemplate(iconNameOrImageUrl, title) {
14-
if (isValidUrl(iconNameOrImageUrl)) {
15-
return <template>
16-
<img src={{iconNameOrImageUrl}} aria-hidden="true" />
17-
<span class="sr-only">{{title}}</span>
18-
</template>;
19-
}
20-
21-
return <template>{{icon iconNameOrImageUrl label=title}}</template>;
22-
}
6+
const BEFORE_ICONS = ["chat", "search", "hamburger", "user-menu"];
237

248
export default {
259
name: "header-icon-links",
@@ -29,61 +13,17 @@ export default {
2913
const links = settings.header_links || [];
3014

3115
links.forEach((link) => {
32-
const iconTemplate = buildIconTemplate(link.icon, link.title);
33-
const className = `header-icon-${dasherize(link.title)}`;
34-
const target = link.target === "blank" ? "_blank" : "";
35-
const rel = link.target ? "noopener" : "";
36-
37-
const numericWidth = Number(link.width);
38-
const style = Number.isFinite(numericWidth)
39-
? htmlSafe(`width: ${escapeExpression(numericWidth)}px`)
40-
: undefined;
41-
42-
const iconComponent = class extends Component {
43-
static shouldRender(args, context) {
44-
return context.site.mobileView
45-
? link.view === "vmo" || link.view === "vdm"
46-
: link.view === "vdo" || link.view === "vdm";
47-
}
48-
49-
@service site;
50-
51-
get isLastLink() {
52-
const visibleLinks = links.filter((item) =>
53-
this.site.mobileView
54-
? item.view === "vmo" || item.view === "vdm"
55-
: item.view === "vdo" || item.view === "vdm"
56-
);
57-
58-
return link === visibleLinks.at(-1);
16+
api.headerIcons.add(
17+
link.title,
18+
curryComponent(
19+
CustomHeaderIcon,
20+
{ link, links },
21+
getOwnerWithFallback()
22+
),
23+
{
24+
before: BEFORE_ICONS,
5925
}
60-
61-
<template>
62-
<li
63-
class={{concatClass
64-
"custom-header-icon-link"
65-
className
66-
link.view
67-
(if this.isLastLink "last-custom-icon")
68-
}}
69-
>
70-
<a
71-
class="btn no-text icon btn-flat"
72-
href={{link.url}}
73-
title={{link.title}}
74-
target={{target}}
75-
rel={{rel}}
76-
style={{style}}
77-
>
78-
{{iconTemplate}}
79-
</a>
80-
</li>
81-
</template>
82-
};
83-
84-
api.headerIcons.add(link.title, iconComponent, {
85-
before: beforeIcon,
86-
});
26+
);
8727
});
8828
} catch (error) {
8929
// eslint-disable-next-line no-console

0 commit comments

Comments
 (0)