Rework the nav for projects 2.0

This commit is contained in:
Ben Visness 2021-10-20 21:21:24 -05:00
parent 307699af4c
commit ccdbad8978
12 changed files with 357 additions and 270 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
src/config/config.go
.vscode
.idea
vendor/
dbclones/
coverage.out

View File

@ -1377,7 +1377,7 @@ img, video {
.bw0 {
border-width: 0; }
.bw1 {
.bw1, header .submenu {
border-width: 0.125rem; }
.bw2 {
@ -4581,7 +4581,7 @@ code, .code {
.pa1 {
padding: 0.25rem; }
.pa2, header .menu-bar .items a, .tab {
.pa2, .tab, header .root-item > a, header .submenu > a {
padding: 0.5rem; }
.pa3, header #login-popup {
@ -4707,7 +4707,7 @@ code, .code {
padding-top: 0.25rem;
padding-bottom: 0.25rem; }
.pv2, header .menu-bar .items a.project-logo, .tab-bar .tab-button,
.pv2, .tab-bar .tab-button,
button,
.button,
input[type=button],
@ -5053,7 +5053,7 @@ input[type=submit], .notice {
.optionbar .options input[type=submit] {
padding-top: 0.5rem;
padding-bottom: 0.5rem; }
.pv3-ns, header .menu-bar .items a {
.pv3-ns {
padding-top: 1rem;
padding-bottom: 1rem; }
.pv4-ns {
@ -5112,7 +5112,7 @@ input[type=submit], .notice {
margin-left: 0; }
.ml1-ns {
margin-left: 0.25rem; }
.ml2-ns, header .menu-bar .items a:first-child {
.ml2-ns {
margin-left: 0.5rem; }
.ml3-ns {
margin-left: 1rem; }
@ -5593,7 +5593,7 @@ input[type=submit], .notice {
.ph2-l {
padding-left: 0.5rem;
padding-right: 0.5rem; }
.ph3-l, header .menu-bar .items a {
.ph3-l, header .root-item > a, header .submenu > a {
padding-left: 1rem;
padding-right: 1rem; }
.ph4-l {
@ -5630,7 +5630,7 @@ input[type=submit], .notice {
margin-left: 0.25rem; }
.ml2-l {
margin-left: 0.5rem; }
.ml3-l, header .menu-bar .items a:first-child {
.ml3-l {
margin-left: 1rem; }
.ml4-l {
margin-left: 2rem; }
@ -7381,7 +7381,7 @@ article code {
color: #ccc;
color: var(--theme-color-dimmest); }
.b--dimmest, header #login-popup, .optionbar, blockquote, .post-content th, .post-content td {
.b--dimmest, .optionbar, blockquote, .post-content th, .post-content td, header #login-popup {
border-color: #bbb;
border-color: var(--dimmest-color); }
@ -7578,6 +7578,7 @@ article code {
.svgicon svg {
fill: currentColor;
stroke: currentColor;
width: 1em;
height: 1em; }
@ -7588,64 +7589,6 @@ article code {
margin-right: auto;
margin-left: auto; }
header .hmn-logo {
height: 3.75rem;
width: 100%;
text-transform: uppercase;
font-family: 'MohaveHMN', sans-serif;
font-size: 2rem;
display: flex;
align-items: center;
justify-content: center;
color: white !important; }
@media screen and (min-width: 30em) {
header .hmn-logo {
width: 11.25rem; } }
header .menu-bar {
width: 100%;
z-index: 10; }
header .menu-bar .items a {
font-weight: bold; }
header .menu-bar .items a.patreon {
float: right;
height: 30px;
padding-top: 18px;
display: inline-block; }
header .menu-bar .items a h1 {
display: inline; }
header .user-options {
position: relative; }
header .login, header .register {
text-align: center; }
header #login-popup {
background-color: #fbfbfb;
background-color: var(--login-popup-background);
color: black;
color: var(--fg-font-color);
border-width: 1px;
border-style: dashed;
visibility: hidden;
position: absolute;
z-index: 12;
margin-top: 10px;
right: 0px;
top: 20px;
width: 290px;
max-height: 0px;
overflow: hidden;
opacity: 0;
transition: all 0.2s; }
header #login-popup.open {
max-height: 170px;
opacity: 1;
visibility: visible; }
header #login-popup label {
padding-right: 10px; }
@media screen and (min-width: 30em) {
footer .list li:not(:last-child)::after {
content: ' / '; } }
@ -8762,6 +8705,102 @@ div.mark_as_read_toplevel_blog {
.bbtable tbody tr:nth-child(even) {
background: rgba(0, 0, 0, 0.05); }
header .hmn-logo {
height: 3.75rem;
width: 100%;
text-transform: uppercase;
font-family: 'MohaveHMN', sans-serif;
font-size: 2rem;
display: flex;
align-items: center;
justify-content: center;
color: white !important; }
@media screen and (min-width: 30em) {
header .hmn-logo {
width: 11.25rem; } }
header .items {
position: relative; }
@media screen and (min-width: 30em) {
header .root-item {
position: relative;
height: 3.75rem; } }
header .root-item:not(:hover):not(.clicked) > .submenu {
display: none; }
header .root-item.clicked .svgicon {
transform: rotate(180deg); }
header .root-item > a {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-weight: bold; }
header .root-item .svgicon {
font-size: 0.7em; }
header .submenu {
background-color: #f8f8f8;
background-color: var(--content-background);
display: flex;
flex-direction: column;
position: absolute;
left: 0;
right: 0;
z-index: 1;
min-width: 10rem;
border-top-style: solid;
border-bottom-style: solid; }
@media screen and (min-width: 30em) {
header .submenu {
border-top-style: none;
border-left-style: solid;
border-right-style: solid;
left: initial;
right: initial; } }
header .submenu > a {
display: block;
white-space: nowrap;
z-index: 1;
font-weight: bold;
text-align: center; }
@media screen and (min-width: 30em) {
header .submenu > a {
text-align: left; } }
header .menu-bar {
width: 100%;
z-index: 10; }
header #login-popup {
background-color: #fbfbfb;
background-color: var(--login-popup-background);
color: black;
color: var(--fg-font-color);
border-width: 1px;
border-style: dashed;
visibility: hidden;
position: absolute;
z-index: 12;
margin-top: 10px;
right: 0px;
top: 20px;
width: 290px;
max-height: 0px;
overflow: hidden;
opacity: 0;
transition: all 0.2s; }
header #login-popup.open {
max-height: 170px;
opacity: 1;
visibility: visible; }
header #login-popup label {
padding-right: 10px; }
@font-face {
font-family: icons;
src: url("/public/icons.ttf?v=4"); }

View File

@ -408,6 +408,7 @@ article code {
.svgicon {
svg {
fill: currentColor;
stroke: currentColor;
width: 1em;
height: 1em;
}
@ -424,100 +425,6 @@ article code {
margin-left: auto;
}
header {
.hmn-logo {
height: px2rem(60px);
width: 100%;
text-transform: uppercase;
font-family: 'MohaveHMN', sans-serif;
font-size: 2rem;
display: flex;
align-items: center;
justify-content: center;
color: white !important;
@media #{$breakpoint-not-small} {
width: px2rem(180px);
}
}
.menu-bar {
width: 100%;
z-index: 10;
.items a {
@extend .pa2;
@extend .pv3-ns;
@extend .ph3-l;
font-weight: bold;
&:first-child {
@extend .ml2-ns;
@extend .ml3-l;
}
&.patreon {
float:right;
height:30px;
padding-top:18px;
display:inline-block;
}
&.project-logo {
@extend .pv2;
}
h1 {
display: inline;
}
}
}
.user-options {
position: relative;
}
.login, .register {
text-align:center;
}
#login-popup {
@include usevar(background-color, login-popup-background);
@include usevar(color, fg-font-color);
@extend .pa3;
border-width: 1px;
border-style: dashed;
@extend .b--dimmest;
visibility: hidden;
position: absolute;
z-index: 12;
margin-top: 10px;
right: 0px;
top: 20px;
width: 290px;
max-height: 0px;
overflow: hidden;
opacity: 0;
transition: all 0.2s;
&.open {
max-height: 170px;
opacity: 1;
visibility: visible;
}
label {
padding-right:10px;
}
}
}
footer {
.list li:not(:last-child)::after {
@extend .c--dimmer;

View File

@ -0,0 +1,135 @@
header {
$logo-height: px2rem(60px);
.hmn-logo {
height: $logo-height;
width: 100%;
text-transform: uppercase;
font-family: 'MohaveHMN', sans-serif;
font-size: 2rem;
display: flex;
align-items: center;
justify-content: center;
color: white !important;
@media #{$breakpoint-not-small} {
width: px2rem(180px);
}
}
.items {
position: relative; // will be used on mobile, when .root-item is not relative
}
.root-item {
@media #{$breakpoint-not-small} {
& {
position: relative; // makes submenus align to this item instead of the screen
height: $logo-height;
}
}
&:not(:hover):not(.clicked) > .submenu {
display: none;
}
&.clicked .svgicon {
transform: rotate(180deg);
}
> a {
@extend .pa2, .ph3-l;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-weight: bold;
}
.svgicon {
font-size: 0.7em;
}
}
.submenu {
@extend .bw1;
@include usevar(background-color, content-background);
display: flex;
flex-direction: column;
position: absolute;
left: 0;
right: 0;
z-index: 1;
min-width: 10rem;
border-top-style: solid;
border-bottom-style: solid;
@media #{$breakpoint-not-small} {
& {
border-top-style: none;
border-left-style: solid;
border-right-style: solid;
left: initial;
right: initial;
}
}
> a {
@extend .pa2, .ph3-l;
display: block;
white-space: nowrap;
z-index: 1;
font-weight: bold;
text-align: center;
@media #{$breakpoint-not-small} {
& {
text-align: left;
}
}
}
}
.menu-bar {
width: 100%;
z-index: 10;
}
#login-popup {
@include usevar(background-color, login-popup-background);
@include usevar(color, fg-font-color);
@extend .pa3;
border-width: 1px;
border-style: dashed;
@extend .b--dimmest;
visibility: hidden;
position: absolute;
z-index: 12;
margin-top: 10px;
right: 0px;
top: 20px;
width: 290px;
max-height: 0px;
overflow: hidden;
opacity: 0;
transition: all 0.2s;
&.open {
max-height: 170px;
opacity: 1;
visibility: visible;
}
label {
padding-right:10px;
}
}
}

View File

@ -14,6 +14,7 @@
@import 'episodes';
@import 'forms';
@import 'forum';
@import 'header';
@import 'icons';
@import 'irc';
@import 'landing';

View File

@ -1,4 +1,4 @@
<header class="mb3 bb bw1 b--theme-dark">
<header id="site-header" class="mb3 bb bw1 b--theme-dark">
<div class="user-options flex justify-center justify-end-ns">
{{ if .User }}
{{ if .User.IsStaff }}
@ -30,52 +30,73 @@
<a href="{{ .Header.HMNHomepageUrl }}" class="hmn-logo bg-theme-dark">
Handmade
</a>
<div class="items flex items-center justify-center justify-start-ns">
{{ if .IsProjectPage }}
<a class="project-logo" href="{{ .Header.ProjectHomepageUrl }}">
<h1>{{ .Project.Name }}</h1>
</a>
{{ end }}
{{ if not .IsProjectPage }}
<a href="{{ .Header.ProjectIndexUrl }}" class="projects">Projects</a>
{{ end }}
{{ if .Project.HasBlog }}
<a href="{{ .Header.BlogUrl }}" class="blog">{{ if .IsProjectPage }}Blog{{ else }}News{{ end }}</a>
{{ end }}
{{ if .Project.HasForum }}
<a href="{{ .Header.ForumsUrl }}" class="forums">Forums</a>
{{ end }}
{{ if .Project.HasLibrary }}
<a href="{{ .Header.LibraryUrl }}" class="library">Library</a>
{{ end }}
{{ if .Project.IsHMN }}
<a href="{{ .Header.ManifestoUrl }}" class="misson">Mission</a>
{{ end }}
{{ if .Header.EpisodeGuideUrl }}
<a href="{{ .Header.EpisodeGuideUrl }}" class="annotations">Episode Guide</a>
{{ end }}
{{ if .Header.EditUrl }}
<a class="edit" href="{{ .Header.EditUrl }}" title="Edit {{ .Project.Name }}"><span class="icon">0</span>&nbsp;Settings</a>
{{ end }}
<div class="items flex items-center justify-center justify-start-ns ml2-ns ml3-l">
<div class="root-item">
<a href="{{ .Header.ProjectIndexUrl }}">Projects</a>
</div>
<div class="root-item">
<a>Media <div class="dib svgicon ml1">{{ svg "chevron-down-thick" }}</div></a>
<div class="submenu b--theme-dark">
<a href="{{ .Header.PodcastUrl }}">Podcast</a>
<a href="https://handmadedev.show/" target="_blank">Handmade Dev Show</a>
</div>
</div>
<div class="root-item">
<a href="{{ .Header.ForumsUrl }}">Forums</a>
</div>
<div class="root-item">
<a>Resources <div class="dib svgicon ml1">{{ svg "chevron-down-thick" }}</div></a>
<div class="submenu b--theme-dark">
<a href="{{ .Header.LibraryUrl }}">Library</a>
</div>
</div>
</div>
</div>
<form onsubmit="this.querySelector('input[name=q]').value = this.querySelector('#searchstring').value + ' site:handmade.network';" class="dn ma0 flex-l flex-column justify-center items-end" method="GET" action="{{ .Header.SearchActionUrl }}" target="_blank">
<input type="hidden" name="q" />
<input class="site-search bn lite pa2 fira" type="text" id="searchstring" value="" placeholder="Search with DuckDuckGo" size="18" />
<input id="search_button_homepage" type="submit" value="Go"/>
</form>
</div>
</header>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function() {
var loginPopup = document.getElementById("login-popup");
var loginLink = document.getElementById("login-link");
const header = document.querySelector('#site-header');
// set up dropdown stuff for mobile / touch
{
const rootItems = header.querySelectorAll('.root-item');
function clearDropdowns() {
for (const item of rootItems) {
item.classList.remove('clicked');
}
}
function clickDropdown(el) {
if (el.classList.contains('clicked')) {
clearDropdowns();
} else {
clearDropdowns();
el.classList.add('clicked');
}
}
for (const item of rootItems) {
if (item.querySelector('.submenu')) {
item.addEventListener('click', e => {
clickDropdown(item);
e.stopPropagation();
});
}
}
}
// set up login form
{
const loginPopup = document.getElementById("login-popup");
const loginLink = document.getElementById("login-link");
if (loginPopup !== null) {
loginLink.removeAttribute("href");
loginLink.onclick = function() {
loginLink.onclick = () => {
loginPopup.classList.toggle("open");
}
}
@ -84,5 +105,6 @@
const d = new Date(Date.parse(time.dateTime));
time.title = d.toLocaleString();
}
}
});
</script>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 186 186" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<path d="M16,57L93,129L170,57" style="fill:none;stroke-width:32px;"/>
</svg>

After

Width:  |  Height:  |  Size: 549 B

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 186 186" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(6.12323e-17,1,-1,6.12323e-17,185.342,0.00025)">
<path d="M51.707,185.343C48.966,185.343 46.214,184.299 44.114,182.194C39.92,178 39.92,171.213 44.114,167.019L118.466,92.672L44.114,18.32C39.92,14.126 39.92,7.333 44.114,3.145C48.308,-1.049 55.101,-1.049 59.294,3.145L141.228,85.079C145.422,89.273 145.422,96.066 141.228,100.254L59.294,182.193C57.201,184.293 54.454,185.343 51.707,185.343Z" style="fill-rule:nonzero;"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 910 B

View File

@ -71,32 +71,8 @@ func names(ts []*template.Template) []string {
return result
}
//go:embed svg/close.svg
var SVGClose string
//go:embed svg/chevron-left.svg
var SVGChevronLeft string
//go:embed svg/chevron-right.svg
var SVGChevronRight string
//go:embed svg/appleinc.svg
var SVGAppleInc string
//go:embed svg/google.svg
var SVGGoogle string
//go:embed svg/spotify.svg
var SVGSpotify string
var SVGMap = map[string]string{
"close": SVGClose,
"chevron-left": SVGChevronLeft,
"chevron-right": SVGChevronRight,
"appleinc": SVGAppleInc,
"google": SVGGoogle,
"spotify": SVGSpotify,
}
//go:embed svg/*
var SVGs embed.FS
var HMNTemplateFuncs = template.FuncMap{
"add": func(a int, b ...int) int {
@ -185,8 +161,8 @@ var HMNTemplateFuncs = template.FuncMap{
}
},
"svg": func(name string) template.HTML {
contents, found := SVGMap[name]
if !found {
contents, err := SVGs.ReadFile(fmt.Sprintf("svg/%s.svg", name))
if err != nil {
panic("SVG not found: " + name)
}
return template.HTML(contents)

View File

@ -44,16 +44,12 @@ type Header struct {
LogoutActionUrl string
ForgotPasswordUrl string
RegisterUrl string
HMNHomepageUrl string
ProjectHomepageUrl string
ProjectIndexUrl string
BlogUrl string
PodcastUrl string
ForumsUrl string
LibraryUrl string
ManifestoUrl string
EpisodeGuideUrl string
EditUrl string
SearchActionUrl string
}
type Footer struct {

View File

@ -364,7 +364,8 @@ func ProjectHomepage(c *RequestContext) ResponseData {
projectHomepageData.BaseData = getBaseData(c, project.Name, nil)
if canEdit {
projectHomepageData.BaseData.Header.EditUrl = hmnurl.BuildProjectEdit(project.Slug, "")
// TODO: Move to project-specific navigation
// projectHomepageData.BaseData.Header.EditURL = hmnurl.BuildProjectEdit(project.Slug, "")
}
projectHomepageData.BaseData.OpenGraphItems = append(projectHomepageData.BaseData.OpenGraphItems, templates.OpenGraphItem{
Property: "og:description",

View File

@ -304,11 +304,12 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc
}
}
episodeGuideUrl := ""
defaultTopic, hasAnnotations := config.Config.EpisodeGuide.Projects[c.CurrentProject.Slug]
if hasAnnotations {
episodeGuideUrl = hmnurl.BuildEpisodeList(c.CurrentProject.Slug, defaultTopic)
}
// TODO: move to project-specific navigation
// episodeGuideUrl := ""
// defaultTopic, hasAnnotations := config.Config.EpisodeGuide.Projects[c.CurrentProject.Slug]
// if hasAnnotations {
// episodeGuideUrl = hmnurl.BuildEpisodeList(c.CurrentProject.Slug, defaultTopic)
// }
baseData := templates.BaseData{
Theme: c.Theme,
@ -336,16 +337,12 @@ func getBaseData(c *RequestContext, title string, breadcrumbs []templates.Breadc
LogoutActionUrl: hmnurl.BuildLogoutAction(c.FullUrl()),
ForgotPasswordUrl: hmnurl.BuildRequestPasswordReset(),
RegisterUrl: hmnurl.BuildRegister(),
HMNHomepageUrl: hmnurl.BuildHomepage(),
ProjectHomepageUrl: hmnurl.BuildProjectHomepage(c.CurrentProject.Slug),
ProjectIndexUrl: hmnurl.BuildProjectIndex(1),
BlogUrl: hmnurl.BuildBlog(c.CurrentProject.Slug, 1),
PodcastUrl: hmnurl.BuildPodcast(c.CurrentProject.Slug),
ForumsUrl: hmnurl.BuildForum(c.CurrentProject.Slug, nil, 1),
LibraryUrl: hmnurl.BuildLibrary(c.CurrentProject.Slug),
ManifestoUrl: hmnurl.BuildManifesto(),
EpisodeGuideUrl: episodeGuideUrl,
EditUrl: "",
SearchActionUrl: "https://duckduckgo.com",
},
Footer: templates.Footer{
HomepageUrl: hmnurl.BuildHomepage(),