Skip to content

Commit 2bcf695

Browse files
committed
INIT
0 parents commit 2bcf695

File tree

13 files changed

+2424
-0
lines changed

13 files changed

+2424
-0
lines changed

.eslintrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"extends": "eslint-config-discourse",
3+
"globals": {
4+
"settings": "readonly",
5+
"themePrefix": "readonly"
6+
}
7+
}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.discourse-site
2+
node_modules
3+
HELP

.template-lintrc.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
plugins: ["ember-template-lint-plugin-discourse"],
3+
extends: "discourse:recommended",
4+
};

about.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"about_url": null,
3+
"license_url": null,
4+
"assets": {},
5+
"name": "Dropdown Header",
6+
"component": true,
7+
"modifiers": {
8+
"svg_icons": [
9+
"caret-square-down"
10+
]
11+
}
12+
}

common/common.scss

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
@mixin dropdown() {
2+
position: absolute;
3+
background-color: $dropdown_background_color;
4+
border: 1px solid $dropdown_border_color;
5+
box-shadow: 0 12px 12px rgba(0, 0, 0, 0.15);
6+
z-index: 1000;
7+
padding: 0.5em;
8+
top: var(--header-offset);
9+
}
10+
11+
.custom-header-links {
12+
.top-level-links {
13+
list-style: none;
14+
display: flex;
15+
justify-content: center;
16+
align-items: center;
17+
gap: 1rem;
18+
19+
@media screen and (max-width: 712px) {
20+
display: none;
21+
}
22+
23+
.custom-header-link {
24+
color: $main_link_color;
25+
padding-block: 1rem;
26+
cursor: default;
27+
28+
&-icon {
29+
margin-right: 0.5rem;
30+
}
31+
32+
&-caret {
33+
margin-left: 0.5rem;
34+
svg {
35+
transition: transform 0.25s ease;
36+
}
37+
}
38+
39+
&:hover {
40+
color: $main_link_hover_color;
41+
42+
.custom-header-dropdown {
43+
transform: scale(1);
44+
}
45+
46+
.custom-header-link-caret svg {
47+
transform: rotate(90deg);
48+
}
49+
}
50+
51+
&.with-url {
52+
cursor: pointer;
53+
}
54+
}
55+
}
56+
57+
.custom-header-dropdown {
58+
@include dropdown;
59+
min-width: 200px;
60+
max-width: 280px;
61+
list-style: none;
62+
transform: scale(0);
63+
transition: transform 0.2s ease;
64+
65+
&-link {
66+
color: $dropdown_item_color;
67+
padding: 0.5em;
68+
cursor: pointer;
69+
font-weight: bold;
70+
display: flex;
71+
flex-flow: row wrap;
72+
73+
.custom-header-link-desc {
74+
flex-basis: 100%;
75+
margin-top: 0.3em;
76+
font-size: 0.9em;
77+
font-weight: 400;
78+
}
79+
80+
&:hover {
81+
color: $dropdown_item_hover_color;
82+
background: $dropdown_item_hover_background_color;
83+
}
84+
}
85+
}
86+
}
87+
88+
// hide on scroll when nav is positioned to left of header:
89+
.title .custom-header-links.scrolling {
90+
display: none;
91+
}
92+
93+
.mobile-view {
94+
.top-level-links {
95+
@include dropdown;
96+
left: 0;
97+
right: 0;
98+
margin: 0 auto;
99+
flex-direction: column;
100+
align-items: flex-start;
101+
justify-content: flex-start;
102+
}
103+
104+
.custom-header-dropdown {
105+
display: block;
106+
position: initial;
107+
transform: scale(1);
108+
box-shadow: none;
109+
margin-top: 1rem;
110+
min-width: unset;
111+
112+
.custom-header-link-desc {
113+
display: none;
114+
}
115+
}
116+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { apiInitializer } from 'discourse/lib/api';
2+
3+
export default apiInitializer('0.11.1', (api) => {
4+
const headerLinks = JSON.parse(settings.header_links);
5+
6+
const links_position =
7+
settings.links_position === 'right'
8+
? 'header-buttons:before'
9+
: 'home-logo:after';
10+
11+
api.decorateWidget(links_position, (helper) => {
12+
const scrolling = helper.attrs.minimized;
13+
return helper.widget.attach('custom-header-links', {
14+
headerLinks,
15+
scrolling,
16+
});
17+
});
18+
});
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { iconNode } from 'discourse-common/lib/icon-library';
2+
import DiscourseURL from 'discourse/lib/url';
3+
import { createWidget } from 'discourse/widgets/widget';
4+
import { h } from 'virtual-dom';
5+
6+
createWidget('custom-header-dropdown', {
7+
tagName: 'li.custom-header-dropdown-link',
8+
buildKey: (attrs) => `custom-header-dropdown-${attrs.title}`,
9+
10+
html(attrs) {
11+
const icon = attrs.icon ? iconNode(attrs.icon) : null;
12+
const iconHTML = icon ? h('span.custom-header-link-icon', icon) : '';
13+
const titleHTML = h('span.custom-header-link-title', attrs.title);
14+
const descHTML = attrs.description
15+
? h('span.custom-header-link-desc', attrs.description)
16+
: '';
17+
const contents = [iconHTML, titleHTML, descHTML];
18+
return contents;
19+
},
20+
21+
click() {
22+
DiscourseURL.routeTo(this.attrs.url);
23+
},
24+
});
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { iconNode } from 'discourse-common/lib/icon-library';
2+
import { withPluginApi } from 'discourse/lib/plugin-api';
3+
import DiscourseURL from 'discourse/lib/url';
4+
import { createWidget } from 'discourse/widgets/widget';
5+
import { h } from 'virtual-dom';
6+
7+
createWidget('custom-header-link', {
8+
tagName: 'li.custom-header-link',
9+
buildKey: (attrs) => `custom-header-link-${attrs.id}`,
10+
11+
html(attrs) {
12+
const icon = attrs.icon ? iconNode(attrs.icon) : null;
13+
const iconHTML = icon ? h('span.custom-header-link-icon', icon) : '';
14+
const titleHTML = h('span.custom-header-link-title', attrs.title);
15+
const permissions = this.handleLinkPermissions(attrs);
16+
const allDropdownItems = JSON.parse(settings.dropdown_links);
17+
const dropdownLinks = allDropdownItems.filter(
18+
(d) => d.headerLinkId === attrs.id
19+
);
20+
21+
if (!permissions) {
22+
return;
23+
}
24+
25+
const dropdownItems = [];
26+
27+
dropdownLinks.forEach((link) => {
28+
dropdownItems.push(this.attach('custom-header-dropdown', link));
29+
});
30+
31+
const hasDropdown = dropdownItems.length > 0 ? true : false;
32+
33+
const dropdownHTML = hasDropdown
34+
? h('ul.custom-header-dropdown', dropdownItems)
35+
: '';
36+
37+
const contents = [
38+
iconHTML,
39+
titleHTML,
40+
this.caretHTML(hasDropdown),
41+
dropdownHTML,
42+
];
43+
return contents;
44+
},
45+
46+
buildAttributes(attrs) {
47+
return {
48+
title: attrs.title,
49+
};
50+
},
51+
52+
buildClasses(attrs) {
53+
const classes = [];
54+
55+
if (attrs.url) {
56+
classes.push('with-url');
57+
}
58+
59+
if (attrs.hasDropdown) {
60+
classes.push('has-dropdown');
61+
}
62+
63+
return classes;
64+
},
65+
66+
handleLinkPermissions(attrs) {
67+
const permissions = JSON.parse(settings.security);
68+
const getPermissions = permissions
69+
.filter((p) => p.headerLinkId === attrs.id)
70+
.map((p) => p.title);
71+
72+
const currentUser = withPluginApi('1.2.0', (api) => {
73+
return api.getCurrentUser();
74+
});
75+
76+
const currentUserGroups = currentUser?.groups.map((g) => g.name);
77+
78+
if (getPermissions?.length < 1) {
79+
return true;
80+
}
81+
82+
if (getPermissions.length < 0) {
83+
return false;
84+
}
85+
86+
if (!currentUser) {
87+
return false;
88+
}
89+
90+
if (currentUserGroups.includes(getPermissions[0])) {
91+
return true;
92+
}
93+
94+
return false;
95+
},
96+
97+
caretHTML(hasDropdown) {
98+
if (!settings.show_caret_icons || !hasDropdown) {
99+
return;
100+
}
101+
102+
return h('span.custom-header-link-caret', iconNode('caret-down'));
103+
},
104+
105+
click() {
106+
DiscourseURL.routeTo(this.attrs.url);
107+
},
108+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import hbs from 'discourse/widgets/hbs-compiler';
2+
import { createWidget } from 'discourse/widgets/widget';
3+
4+
createWidget('custom-header-links', {
5+
tagName: 'nav.custom-header-links',
6+
buildKey: (attrs) => `custom-header-links-${attrs.id}`,
7+
8+
buildClasses(attrs) {
9+
const { scrolling } = attrs;
10+
11+
if (scrolling) {
12+
return ['scrolling'];
13+
}
14+
},
15+
16+
transform(attrs) {
17+
const { headerLinks } = attrs;
18+
19+
return {
20+
headerLinks,
21+
};
22+
},
23+
24+
defaultState() {
25+
const showLinks = !this.site.mobileView;
26+
const mobileView = this.site.mobileView;
27+
28+
return {
29+
mobileView,
30+
showLinks,
31+
};
32+
},
33+
34+
showHeaderLinks() {
35+
this.state.showLinks = !this.state.showLinks;
36+
},
37+
38+
template: hbs`
39+
{{#if this.state.mobileView}}
40+
{{attach
41+
widget="button"
42+
attrs=(hash
43+
action="showHeaderLinks"
44+
icon="caret-square-down"
45+
)
46+
}}
47+
{{/if}}
48+
{{#if this.state.showLinks}}
49+
<ul class="top-level-links">
50+
{{#each transformed.headerLinks as |item|}}
51+
{{attach
52+
widget="custom-header-link"
53+
attrs=item
54+
}}
55+
{{/each}}
56+
</ul>
57+
{{/if}}
58+
`,
59+
});

locales/en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
en:
2+
theme_metadata:
3+
description: "A dropdown header for Discourse"

0 commit comments

Comments
 (0)