global topbar
This commit is contained in:
@@ -145,13 +145,126 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getWestgateTopbar() {
|
||||||
|
if (!document || typeof document.querySelector !== 'function') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return document.querySelector('[data-wg-topbar]');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeWestgateTopbarPanels(topbar, exceptKey) {
|
||||||
|
if (!topbar || typeof topbar.querySelectorAll !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toArray(topbar.querySelectorAll('[data-wg-panel]')).forEach((panel) => {
|
||||||
|
if (panel.getAttribute('data-wg-panel') !== exceptKey) {
|
||||||
|
panel.classList.remove('is-open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toArray(topbar.querySelectorAll('[data-wg-menu]')).forEach((trigger) => {
|
||||||
|
if (trigger.getAttribute('data-wg-menu') !== exceptKey) {
|
||||||
|
trigger.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeWestgateTopbarDrawer(topbar) {
|
||||||
|
if (!topbar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
topbar.classList.remove('is-drawer-open');
|
||||||
|
const burger = topbar.querySelector('[data-wg-burger]');
|
||||||
|
if (burger) {
|
||||||
|
burger.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initWestgateTopbar() {
|
||||||
|
const topbar = getWestgateTopbar();
|
||||||
|
if (!topbar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.westgateTheme.topbarEventsBound) {
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
const currentTopbar = getWestgateTopbar();
|
||||||
|
if (!currentTopbar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trigger = event.target.closest && event.target.closest('[data-wg-menu]');
|
||||||
|
if (trigger && currentTopbar.contains(trigger)) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const key = trigger.getAttribute('data-wg-menu');
|
||||||
|
const panel = currentTopbar.querySelector(`[data-wg-panel="${key}"]`);
|
||||||
|
const shouldOpen = !!panel && !panel.classList.contains('is-open');
|
||||||
|
closeWestgateTopbarPanels(currentTopbar, shouldOpen ? key : null);
|
||||||
|
if (panel) {
|
||||||
|
panel.classList.toggle('is-open', shouldOpen);
|
||||||
|
}
|
||||||
|
trigger.setAttribute('aria-expanded', String(shouldOpen));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const burger = event.target.closest && event.target.closest('[data-wg-burger]');
|
||||||
|
if (burger && currentTopbar.contains(burger)) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const isOpen = !currentTopbar.classList.contains('is-drawer-open');
|
||||||
|
currentTopbar.classList.toggle('is-drawer-open', isOpen);
|
||||||
|
burger.setAttribute('aria-expanded', String(isOpen));
|
||||||
|
closeWestgateTopbarPanels(currentTopbar, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.target.closest && event.target.closest('[data-wg-panel]')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeWestgateTopbarPanels(currentTopbar, null);
|
||||||
|
if (!currentTopbar.contains(event.target)) {
|
||||||
|
closeWestgateTopbarDrawer(currentTopbar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key !== 'Escape') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTopbar = getWestgateTopbar();
|
||||||
|
closeWestgateTopbarPanels(currentTopbar, null);
|
||||||
|
closeWestgateTopbarDrawer(currentTopbar);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.westgateTheme.topbarEventsBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document)
|
||||||
|
.off('shown.bs.dropdown.westgateTopbar')
|
||||||
|
.on('shown.bs.dropdown.westgateTopbar', '.wg-topbar [component="sidebar/search"]', function () {
|
||||||
|
$(this).find('[component="search/fields"] input[name="query"]').trigger('focus');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
window.westgateTheme = window.westgateTheme || {};
|
window.westgateTheme = window.westgateTheme || {};
|
||||||
window.westgateTheme.wrapWikiTables = wrapWestgateWikiTables;
|
window.westgateTheme.wrapWikiTables = wrapWestgateWikiTables;
|
||||||
|
window.westgateTheme.initTopbar = initWestgateTopbar;
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
wrapWestgateWikiTables(document);
|
wrapWestgateWikiTables(document);
|
||||||
|
initWestgateTopbar();
|
||||||
$(window).on('action:ajaxify.end', function () {
|
$(window).on('action:ajaxify.end', function () {
|
||||||
wrapWestgateWikiTables(document);
|
wrapWestgateWikiTables(document);
|
||||||
|
initWestgateTopbar();
|
||||||
|
closeWestgateTopbarPanels(getWestgateTopbar(), null);
|
||||||
|
closeWestgateTopbarDrawer(getWestgateTopbar());
|
||||||
});
|
});
|
||||||
|
|
||||||
require(['api'], function (api) {
|
require(['api'], function (api) {
|
||||||
|
|||||||
@@ -0,0 +1,418 @@
|
|||||||
|
.wg-layout-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar {
|
||||||
|
top: 0;
|
||||||
|
z-index: 1020;
|
||||||
|
color: var(--wg-text-soft);
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 241, 196, 0.035), transparent 42%),
|
||||||
|
linear-gradient(90deg, rgba(42, 18, 34, 0.96), rgba(15, 13, 18, 0.98) 38%, rgba(9, 8, 11, 0.99));
|
||||||
|
border-bottom: 1px solid rgba(194, 163, 90, 0.2);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 244, 221, 0.04),
|
||||||
|
0 12px 32px rgba(0, 0, 0, 0.34);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__inner {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.9rem;
|
||||||
|
width: min(100%, 1420px);
|
||||||
|
min-height: 64px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0.55rem clamp(0.85rem, 2vw, 1.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__brand {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 0.7rem;
|
||||||
|
color: var(--wg-text);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: #f3ede4;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__brand-mark {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2.3rem;
|
||||||
|
height: 2.3rem;
|
||||||
|
border: 1px solid rgba(194, 163, 90, 0.36);
|
||||||
|
border-radius: 50%;
|
||||||
|
color: var(--wg-gold);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 38% 24%, rgba(255, 238, 190, 0.2), transparent 30%),
|
||||||
|
linear-gradient(145deg, rgba(58, 24, 48, 0.7), rgba(10, 9, 13, 0.98));
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(9, 8, 11, 0.66),
|
||||||
|
0 8px 18px rgba(0, 0, 0, 0.35);
|
||||||
|
font-family: var(--wg-font-display);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__brand-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__brand-name {
|
||||||
|
max-width: 22ch;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--wg-text);
|
||||||
|
font-family: var(--wg-font-display);
|
||||||
|
font-size: 0.96rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__brand-subtitle {
|
||||||
|
margin-top: 0.28rem;
|
||||||
|
color: var(--wg-gold-soft);
|
||||||
|
font-family: var(--wg-font-code);
|
||||||
|
font-size: 0.62rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__nav {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__nav-list,
|
||||||
|
.wg-topbar__utility-list {
|
||||||
|
gap: 0.28rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__nav-list {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar .nav-link,
|
||||||
|
.wg-topbar__nav-link,
|
||||||
|
.wg-topbar__forums-toggle {
|
||||||
|
min-height: 2.35rem;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.42rem 0.62rem;
|
||||||
|
color: var(--wg-text-soft);
|
||||||
|
background: transparent;
|
||||||
|
font-family: var(--wg-font-ui);
|
||||||
|
font-size: 0.86rem;
|
||||||
|
line-height: 1.1;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar .nav-link:hover,
|
||||||
|
.wg-topbar .nav-link:focus,
|
||||||
|
.wg-topbar__nav-link:hover,
|
||||||
|
.wg-topbar__nav-link:focus,
|
||||||
|
.wg-topbar__forums-toggle:hover,
|
||||||
|
.wg-topbar__forums-toggle:focus,
|
||||||
|
.wg-topbar .nav-item.active > .nav-link,
|
||||||
|
.wg-topbar .nav-item.active > .navigation-link {
|
||||||
|
color: #f3ede4;
|
||||||
|
border-color: rgba(194, 163, 90, 0.2);
|
||||||
|
background: rgba(194, 163, 90, 0.075);
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 244, 221, 0.035);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__forums {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__forums-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__utilities {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__utility-list {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__utility-list > .nav-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__utility-list :where(.nav-text, .visible-open) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__utility-list .nav-link {
|
||||||
|
justify-content: center;
|
||||||
|
width: 2.35rem;
|
||||||
|
height: 2.35rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__utility-list [component$="/count"],
|
||||||
|
.wg-topbar__drawer-actions [component$="/count"],
|
||||||
|
.wg-topbar [component="navigation/count"] {
|
||||||
|
background: linear-gradient(to bottom, rgba(142, 52, 56, 0.92), rgba(84, 28, 39, 0.96)) !important;
|
||||||
|
border: 1px solid rgba(216, 194, 138, 0.26);
|
||||||
|
color: #fff0dc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__utility-list .visible-closed {
|
||||||
|
display: inline-block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__skin .dropend {
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__skin .dropdown-menu,
|
||||||
|
.wg-topbar__dropdown,
|
||||||
|
.wg-topbar .search-dropdown,
|
||||||
|
.wg-topbar .notifications-dropdown,
|
||||||
|
.wg-topbar .chats-dropdown,
|
||||||
|
.wg-topbar .drafts-dropdown,
|
||||||
|
.wg-topbar .user-dropdown,
|
||||||
|
.wg-topbar__panel {
|
||||||
|
min-width: min(22rem, calc(100vw - 1.5rem));
|
||||||
|
max-width: calc(100vw - 1.5rem);
|
||||||
|
border: 1px solid rgba(194, 163, 90, 0.22) !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
color: var(--wg-text-soft) !important;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 244, 221, 0.028), transparent 36%),
|
||||||
|
var(--wg-velvet-panel) !important;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 244, 221, 0.035),
|
||||||
|
0 18px 42px rgba(0, 0, 0, 0.42) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar .notifications-dropdown,
|
||||||
|
.wg-topbar .chats-dropdown,
|
||||||
|
.wg-topbar .drafts-dropdown {
|
||||||
|
width: min(24rem, calc(100vw - 1.5rem));
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar .dropdown-item,
|
||||||
|
.wg-topbar .btn.btn-ghost,
|
||||||
|
.wg-topbar .btn.btn-light {
|
||||||
|
color: var(--wg-text-soft) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar .dropdown-item:hover,
|
||||||
|
.wg-topbar .dropdown-item:focus,
|
||||||
|
.wg-topbar .btn.btn-ghost:hover,
|
||||||
|
.wg-topbar .btn.btn-ghost:focus,
|
||||||
|
.wg-topbar .btn.btn-light:hover,
|
||||||
|
.wg-topbar .btn.btn-light:focus {
|
||||||
|
color: var(--wg-text) !important;
|
||||||
|
background: rgba(194, 163, 90, 0.08) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar .btn-primary,
|
||||||
|
.wg-topbar__register {
|
||||||
|
border-color: rgba(194, 163, 90, 0.34) !important;
|
||||||
|
color: #1b1208 !important;
|
||||||
|
background: linear-gradient(to bottom, #d8c28a, var(--wg-gold)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__login,
|
||||||
|
.wg-topbar__register {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 2.35rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.35rem 0.78rem;
|
||||||
|
font-family: var(--wg-font-ui);
|
||||||
|
font-size: 0.86rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__login {
|
||||||
|
color: var(--wg-text-soft);
|
||||||
|
border: 1px solid rgba(194, 163, 90, 0.2);
|
||||||
|
background: rgba(194, 163, 90, 0.055);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__login:hover,
|
||||||
|
.wg-topbar__login:focus {
|
||||||
|
color: var(--wg-text);
|
||||||
|
border-color: rgba(194, 163, 90, 0.3);
|
||||||
|
background: rgba(194, 163, 90, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__panel {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 0.45rem);
|
||||||
|
right: 0;
|
||||||
|
display: none;
|
||||||
|
padding: 0.42rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__panel.is-open {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__panel--forums a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 2.25rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.35rem 0.55rem;
|
||||||
|
color: var(--wg-text-soft);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__panel--forums a:hover,
|
||||||
|
.wg-topbar__panel--forums a:focus {
|
||||||
|
color: var(--wg-text);
|
||||||
|
background: rgba(194, 163, 90, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__burger {
|
||||||
|
display: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2.4rem;
|
||||||
|
height: 2.4rem;
|
||||||
|
border: 1px solid rgba(194, 163, 90, 0.22);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--wg-text-soft);
|
||||||
|
background: rgba(194, 163, 90, 0.055);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__burger:hover,
|
||||||
|
.wg-topbar__burger:focus {
|
||||||
|
color: var(--wg-text);
|
||||||
|
background: rgba(194, 163, 90, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__drawer {
|
||||||
|
display: none;
|
||||||
|
border-top: 1px solid rgba(194, 163, 90, 0.12);
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(42, 18, 34, 0.98), rgba(12, 10, 15, 0.99));
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar.is-drawer-open .wg-topbar__drawer {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__drawer-inner {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.8rem;
|
||||||
|
width: min(100%, 42rem);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__drawer-search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.55rem;
|
||||||
|
border: 1px solid rgba(194, 163, 90, 0.18);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.5rem 0.65rem;
|
||||||
|
background: rgba(9, 8, 11, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__drawer-search input {
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
color: var(--wg-text);
|
||||||
|
background: transparent;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__drawer-nav,
|
||||||
|
.wg-topbar__drawer-actions,
|
||||||
|
.wg-topbar__drawer-auth {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__drawer-link,
|
||||||
|
.wg-topbar__drawer-actions a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.55rem;
|
||||||
|
border: 1px solid rgba(194, 163, 90, 0.12);
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 0.62rem 0.7rem;
|
||||||
|
color: var(--wg-text-soft);
|
||||||
|
background: rgba(255, 244, 221, 0.02);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__drawer-link:hover,
|
||||||
|
.wg-topbar__drawer-link:focus,
|
||||||
|
.wg-topbar__drawer-actions a:hover,
|
||||||
|
.wg-topbar__drawer-actions a:focus {
|
||||||
|
color: var(--wg-text);
|
||||||
|
border-color: rgba(194, 163, 90, 0.22);
|
||||||
|
background: rgba(194, 163, 90, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1199.98px) {
|
||||||
|
.wg-topbar__inner {
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 991.98px) {
|
||||||
|
.wg-topbar__inner {
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__brand-name {
|
||||||
|
max-width: 16ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__brand-subtitle,
|
||||||
|
.wg-topbar__forums,
|
||||||
|
.wg-topbar__utilities {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__burger {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.wg-topbar__brand-mark {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wg-topbar__brand-name {
|
||||||
|
max-width: 13ch;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
</div><!-- /.container#content -->
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="fixed-bottom d-lg-none">
|
||||||
|
<!-- IMPORT partials/topic/navigator-mobile.tpl -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{{ if !isSpider }}}
|
||||||
|
<div>
|
||||||
|
<div component="toaster/tray" class="alert-window fixed-bottom mb-5 mb-md-2 me-2 me-md-5 ms-auto" style="width:300px; z-index: 1090;">
|
||||||
|
<!-- IMPORT partials/reconnect-alert.tpl -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{{ end }}}
|
||||||
|
|
||||||
|
<!-- IMPORT partials/footer/js.tpl -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -25,15 +25,10 @@
|
|||||||
<body class="{bodyClass} skin-{{{if bootswatchSkin}}}{bootswatchSkin}{{{else}}}noskin{{{end}}}">
|
<body class="{bodyClass} skin-{{{if bootswatchSkin}}}{bootswatchSkin}{{{else}}}noskin{{{end}}}">
|
||||||
<a class="visually-hidden-focusable position-absolute top-0 start-0 p-3 m-3 bg-body" style="z-index: 1021;" href="#content">[[global:skip-to-content]]</a>
|
<a class="visually-hidden-focusable position-absolute top-0 start-0 p-3 m-3 bg-body" style="z-index: 1021;" href="#content">[[global:skip-to-content]]</a>
|
||||||
|
|
||||||
{{{ if config.theme.topMobilebar }}}
|
<div class="layout-container wg-layout-container d-flex flex-column pb-4 pb-md-0">
|
||||||
<!-- IMPORT partials/mobile-header.tpl -->
|
<!-- IMPORT partials/header/topbar.tpl -->
|
||||||
{{{ end }}}
|
|
||||||
|
|
||||||
<div class="layout-container d-flex justify-content-between pb-4 pb-md-0">
|
|
||||||
<!-- IMPORT partials/sidebar-left.tpl -->
|
|
||||||
|
|
||||||
<main id="panel" class="d-flex flex-column gap-3 flex-grow-1 mt-3" style="min-width: 0;">
|
<main id="panel" class="d-flex flex-column gap-3 flex-grow-1 mt-3" style="min-width: 0;">
|
||||||
<!-- IMPORT partials/header/brand.tpl -->
|
|
||||||
<div class="container-lg px-md-4 d-flex flex-column gap-3 h-100 mb-5 mb-lg-0" id="content">
|
<div class="container-lg px-md-4 d-flex flex-column gap-3 h-100 mb-5 mb-lg-0" id="content">
|
||||||
<!-- IMPORT partials/noscript/warning.tpl -->
|
<!-- IMPORT partials/noscript/warning.tpl -->
|
||||||
<!-- IMPORT partials/noscript/message.tpl -->
|
<!-- IMPORT partials/noscript/message.tpl -->
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
<header class="wg-topbar sticky-top" data-wg-topbar>
|
||||||
|
<div class="wg-topbar__inner">
|
||||||
|
<a class="wg-topbar__brand" href="{relative_path}/" aria-label="{config.siteTitle}">
|
||||||
|
<span class="wg-topbar__brand-mark" aria-hidden="true">W</span>
|
||||||
|
<span class="wg-topbar__brand-text">
|
||||||
|
<span class="wg-topbar__brand-name">{config.siteTitle}</span>
|
||||||
|
<span class="wg-topbar__brand-subtitle">Shadows Over Westgate</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<nav class="wg-topbar__nav" aria-label="[[global:navigation]]">
|
||||||
|
<ul id="main-nav" class="wg-topbar__nav-list list-unstyled d-flex align-items-center mb-0">
|
||||||
|
{{{ each navigation }}}
|
||||||
|
{{{ if displayMenuItem(@root, @index) }}}
|
||||||
|
<li class="nav-item {./class}{{{ if ./dropdown }}} dropdown{{{ end }}}" title="{./title}">
|
||||||
|
<a class="nav-link navigation-link wg-topbar__nav-link d-flex gap-2 align-items-center {{{ if ./dropdown }}}dropdown-toggle{{{ end }}}" {{{ if ./dropdown }}} href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" {{{ else }}} href="{./route}"{{{ end }}} {{{ if ./id }}}id="{./id}"{{{ end }}}{{{ if ./targetBlank }}} target="_blank"{{{ end }}} {{{ if ./text }}}aria-label="{./text}"{{{ end }}}>
|
||||||
|
{{{ if ./iconClass }}}
|
||||||
|
<i class="fa fa-fw {./iconClass}" data-content="{./content}"></i>
|
||||||
|
{{{ end }}}
|
||||||
|
{{{ if ./text }}}<span class="nav-text text-truncate">{./text}</span>{{{ end }}}
|
||||||
|
<span component="navigation/count" class="badge rounded-1 bg-primary {{{ if !./content }}}hidden{{{ end }}}">{./content}</span>
|
||||||
|
</a>
|
||||||
|
{{{ if ./dropdown }}}
|
||||||
|
<ul class="dropdown-menu wg-topbar__dropdown p-1 shadow" role="menu">
|
||||||
|
{./dropdownContent}
|
||||||
|
</ul>
|
||||||
|
{{{ end }}}
|
||||||
|
</li>
|
||||||
|
{{{ end }}}
|
||||||
|
{{{ end }}}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="wg-topbar__forums dropdown">
|
||||||
|
<button class="wg-topbar__nav-link wg-topbar__forums-toggle" type="button" data-wg-menu="forums" aria-expanded="false" aria-haspopup="true">
|
||||||
|
<span>Forums</span>
|
||||||
|
<i class="fa fa-fw fa-chevron-down" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
<div class="wg-topbar__panel wg-topbar__panel--forums" data-wg-panel="forums" role="menu">
|
||||||
|
<a href="{relative_path}/categories" role="menuitem">[[pages:categories]]</a>
|
||||||
|
<a href="{relative_path}/recent" role="menuitem">[[recent:title]]</a>
|
||||||
|
<a href="{relative_path}/unread" role="menuitem">[[unread:title]]</a>
|
||||||
|
<a href="{relative_path}/popular" role="menuitem">[[pages:popular]]</a>
|
||||||
|
<a href="{relative_path}/tags" role="menuitem">[[tags:tags]]</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wg-topbar__utilities">
|
||||||
|
{{{ if config.loggedIn }}}
|
||||||
|
<ul id="wg-topbar-logged-in-menu" class="wg-topbar__utility-list list-unstyled d-flex align-items-center mb-0">
|
||||||
|
{{{ if (config.searchEnabled && user.privileges.search:content) }}}
|
||||||
|
<li component="sidebar/search" class="nav-item search dropdown position-relative" title="[[global:header.search]]" role="menuitem">
|
||||||
|
<!-- IMPORT partials/sidebar/search.tpl -->
|
||||||
|
</li>
|
||||||
|
{{{ end }}}
|
||||||
|
|
||||||
|
<li component="notifications" class="nav-item notifications dropdown" title="[[global:header.notifications]]" role="menuitem">
|
||||||
|
<!-- IMPORT partials/sidebar/notifications.tpl -->
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{{{ if canChat }}}
|
||||||
|
<li class="nav-item chats dropdown" title="[[global:header.chats]]" role="menuitem">
|
||||||
|
<!-- IMPORT partials/sidebar/chats.tpl -->
|
||||||
|
</li>
|
||||||
|
{{{ end }}}
|
||||||
|
|
||||||
|
<li component="sidebar/drafts" class="nav-item drafts dropdown" title="[[global:header.drafts]]" role="menuitem">
|
||||||
|
<!-- IMPORT partials/sidebar/drafts.tpl -->
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{{{ if !config.disableCustomUserSkins }}}
|
||||||
|
<li class="nav-item wg-topbar__skin">
|
||||||
|
<!-- IMPORT partials/skin-switcher.tpl -->
|
||||||
|
</li>
|
||||||
|
{{{ end }}}
|
||||||
|
|
||||||
|
<li id="user_label" class="nav-item usermenu dropdown" title="{user.username}" role="menuitem">
|
||||||
|
<!-- IMPORT partials/sidebar/user-menu.tpl -->
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{{ else }}}
|
||||||
|
<ul id="wg-topbar-logged-out-menu" class="wg-topbar__utility-list wg-topbar__utility-list--guest list-unstyled d-flex align-items-center mb-0">
|
||||||
|
{{{ if (config.searchEnabled && user.privileges.search:content) }}}
|
||||||
|
<li component="sidebar/search" class="nav-item search dropdown position-relative" title="[[global:header.search]]" role="menuitem">
|
||||||
|
<!-- IMPORT partials/sidebar/search.tpl -->
|
||||||
|
</li>
|
||||||
|
{{{ end }}}
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="wg-topbar__login" href="{relative_path}/login">[[global:login]]</a>
|
||||||
|
</li>
|
||||||
|
{{{ if allowRegistration }}}
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="wg-topbar__register" href="{relative_path}/register">[[global:register]]</a>
|
||||||
|
</li>
|
||||||
|
{{{ end }}}
|
||||||
|
</ul>
|
||||||
|
{{{ end }}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="wg-topbar__burger" type="button" data-wg-burger aria-expanded="false" aria-controls="wg-topbar-drawer" aria-label="[[global:menu]]">
|
||||||
|
<i class="fa fa-fw fa-bars" aria-hidden="true"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="wg-topbar-drawer" class="wg-topbar__drawer">
|
||||||
|
<div class="wg-topbar__drawer-inner">
|
||||||
|
{{{ if (config.searchEnabled && user.privileges.search:content) }}}
|
||||||
|
<form class="wg-topbar__drawer-search" action="{relative_path}/search" method="GET" role="search">
|
||||||
|
<i class="fa fa-fw fa-search" aria-hidden="true"></i>
|
||||||
|
<input autocomplete="off" type="text" name="query" placeholder="[[global:search]]" aria-label="[[search:type-to-search]]">
|
||||||
|
</form>
|
||||||
|
{{{ end }}}
|
||||||
|
|
||||||
|
<nav class="wg-topbar__drawer-nav" aria-label="[[global:navigation]]">
|
||||||
|
{{{ each navigation }}}
|
||||||
|
{{{ if displayMenuItem(@root, @index) }}}
|
||||||
|
<a class="wg-topbar__drawer-link {./class}" href="{./route}" {{{ if ./targetBlank }}}target="_blank"{{{ end }}}>
|
||||||
|
{{{ if ./iconClass }}}<i class="fa fa-fw {./iconClass}" aria-hidden="true"></i>{{{ end }}}
|
||||||
|
{{{ if ./text }}}<span>{./text}</span>{{{ end }}}
|
||||||
|
<span component="navigation/count" class="badge rounded-1 bg-primary {{{ if !./content }}}hidden{{{ end }}}">{./content}</span>
|
||||||
|
</a>
|
||||||
|
{{{ end }}}
|
||||||
|
{{{ end }}}
|
||||||
|
<a class="wg-topbar__drawer-link" href="{relative_path}/categories"><i class="fa fa-fw fa-layer-group" aria-hidden="true"></i><span>[[pages:categories]]</span></a>
|
||||||
|
<a class="wg-topbar__drawer-link" href="{relative_path}/recent"><i class="fa fa-fw fa-clock" aria-hidden="true"></i><span>[[recent:title]]</span></a>
|
||||||
|
<a class="wg-topbar__drawer-link" href="{relative_path}/unread"><i class="fa fa-fw fa-inbox" aria-hidden="true"></i><span>[[unread:title]]</span></a>
|
||||||
|
<a class="wg-topbar__drawer-link" href="{relative_path}/tags"><i class="fa fa-fw fa-tags" aria-hidden="true"></i><span>[[tags:tags]]</span></a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{{{ if config.loggedIn }}}
|
||||||
|
<div class="wg-topbar__drawer-actions">
|
||||||
|
<a href="{relative_path}/notifications"><i class="fa fa-fw fa-bell" aria-hidden="true"></i><span>[[global:header.notifications]]</span><span component="notifications/count" class="badge rounded-1 bg-primary {{{ if !unreadCount.notification }}}hidden{{{ end }}}">{unreadCount.notification}</span></a>
|
||||||
|
{{{ if canChat }}}
|
||||||
|
<a href="{relative_path}/user/{user.userslug}/chats{{{ if user.lastRoomId }}}/{user.lastRoomId}{{{ end }}}"><i class="fa fa-fw fa-comment" aria-hidden="true"></i><span>[[global:header.chats]]</span><span component="chat/count" class="badge rounded-1 bg-primary {{{ if !unreadCount.chat }}}hidden{{{ end }}}">{unreadCount.chat}</span></a>
|
||||||
|
{{{ end }}}
|
||||||
|
<a href="{relative_path}/user/{user.userslug}"><i class="fa fa-fw fa-user" aria-hidden="true"></i><span>[[user:profile]]</span></a>
|
||||||
|
<a href="{relative_path}/user/{user.userslug}/settings"><i class="fa fa-fw fa-gear" aria-hidden="true"></i><span>[[user:settings]]</span></a>
|
||||||
|
</div>
|
||||||
|
{{{ else }}}
|
||||||
|
<div class="wg-topbar__drawer-auth">
|
||||||
|
<a class="wg-topbar__login" href="{relative_path}/login">[[global:login]]</a>
|
||||||
|
{{{ if allowRegistration }}}
|
||||||
|
<a class="wg-topbar__register" href="{relative_path}/register">[[global:register]]</a>
|
||||||
|
{{{ end }}}
|
||||||
|
</div>
|
||||||
|
{{{ end }}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function read(relativePath) {
|
||||||
|
const filePath = path.join(__dirname, '..', relativePath);
|
||||||
|
assert(fs.existsSync(filePath), `${relativePath} should exist`);
|
||||||
|
return fs.readFileSync(filePath, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertIncludes(haystack, needle, message) {
|
||||||
|
assert(
|
||||||
|
haystack.includes(needle),
|
||||||
|
message || `Expected file to include ${needle}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertExcludes(haystack, needle, message) {
|
||||||
|
assert(
|
||||||
|
!haystack.includes(needle),
|
||||||
|
message || `Expected file not to include ${needle}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = read('templates/header.tpl');
|
||||||
|
const footer = read('templates/footer.tpl');
|
||||||
|
const topbar = read('templates/partials/header/topbar.tpl');
|
||||||
|
const theme = read('theme.scss');
|
||||||
|
const stylesheet = read('scss/westgate/_topbar.scss');
|
||||||
|
const client = read('public/client.js');
|
||||||
|
|
||||||
|
assertIncludes(
|
||||||
|
header,
|
||||||
|
'<!-- IMPORT partials/header/topbar.tpl -->',
|
||||||
|
'Header should mount the Westgate topbar partial'
|
||||||
|
);
|
||||||
|
assertExcludes(
|
||||||
|
header,
|
||||||
|
'<!-- IMPORT partials/sidebar-left.tpl -->',
|
||||||
|
"Header should no longer render Harmony's left global sidebar"
|
||||||
|
);
|
||||||
|
assertExcludes(
|
||||||
|
footer,
|
||||||
|
'<!-- IMPORT partials/sidebar-right.tpl -->',
|
||||||
|
"Footer should no longer render Harmony's right global sidebar"
|
||||||
|
);
|
||||||
|
assertIncludes(
|
||||||
|
theme,
|
||||||
|
'@import "./scss/westgate/topbar";',
|
||||||
|
'theme.scss should import the focused topbar partial'
|
||||||
|
);
|
||||||
|
assertIncludes(
|
||||||
|
stylesheet,
|
||||||
|
'.wg-topbar',
|
||||||
|
'Topbar styles should be scoped to .wg-topbar'
|
||||||
|
);
|
||||||
|
assertIncludes(
|
||||||
|
client,
|
||||||
|
'initWestgateTopbar',
|
||||||
|
'Client bundle should initialize Westgate topbar behavior'
|
||||||
|
);
|
||||||
|
|
||||||
|
[
|
||||||
|
'component="sidebar/search"',
|
||||||
|
'component="notifications"',
|
||||||
|
'component="sidebar/drafts"',
|
||||||
|
].forEach((component) => {
|
||||||
|
assertIncludes(topbar, component, `Topbar should preserve live NodeBB hook ${component}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
'<!-- IMPORT partials/sidebar/search.tpl -->',
|
||||||
|
'<!-- IMPORT partials/sidebar/notifications.tpl -->',
|
||||||
|
'<!-- IMPORT partials/sidebar/chats.tpl -->',
|
||||||
|
'<!-- IMPORT partials/sidebar/drafts.tpl -->',
|
||||||
|
'<!-- IMPORT partials/sidebar/user-menu.tpl -->',
|
||||||
|
].forEach((templateImport) => {
|
||||||
|
assertIncludes(topbar, templateImport, `Topbar should reuse Harmony live partial ${templateImport}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
'Velessa Thorne',
|
||||||
|
'Guildmaster Orin',
|
||||||
|
'A letter left at the Undergate',
|
||||||
|
'wg-topbar-demo',
|
||||||
|
"localStorage.setItem('wg-topbar-presence'",
|
||||||
|
].forEach((fixture) => {
|
||||||
|
assertExcludes(topbar, fixture, `Topbar should not ship preview fixture ${fixture}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertIncludes(topbar, '{{{ if config.loggedIn }}}', 'Topbar should branch on the real logged-in state');
|
||||||
|
assertIncludes(topbar, '{{{ if canChat }}}', 'Chat controls should remain permission-gated');
|
||||||
|
assertIncludes(topbar, '{{{ if allowRegistration }}}', 'Registration should remain server-gated');
|
||||||
|
assertIncludes(topbar, '{{{ each navigation }}}', 'Topbar should render ACP Navigation items');
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
@import "./scss/westgate/tokens";
|
@import "./scss/westgate/tokens";
|
||||||
@import "./scss/westgate/wiki-prose";
|
@import "./scss/westgate/wiki-prose";
|
||||||
@import "./scss/westgate/header";
|
@import "./scss/westgate/header";
|
||||||
|
@import "./scss/westgate/topbar";
|
||||||
@import "./scss/westgate/surfaces";
|
@import "./scss/westgate/surfaces";
|
||||||
@import "./scss/westgate/categories";
|
@import "./scss/westgate/categories";
|
||||||
@import "./scss/westgate/topics";
|
@import "./scss/westgate/topics";
|
||||||
|
|||||||
Reference in New Issue
Block a user