recipe-card/templates.go

310 lines
45 KiB
Go
Raw Normal View History

2017-07-08 01:17:01 -04:00
package main
import (
"fmt"
"html/template"
"io/ioutil"
log "github.com/sirupsen/logrus"
)
const (
minicss = `/*MIT License
Copyright (c) 2016-2017 Angelos Chalaris
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.*/
/* https://github.com/Chalarangelo/mini.css v2.3.2*/
html{font-size:16px}html,*{font-family:-apple-system, BlinkMacSystemFont,"Segoe UI","Roboto", "Droid Sans","Helvetica Neue", Helvetica, Arial, sans-serif;line-height:1.5;-webkit-text-size-adjust:100%}*{font-size:1rem}body{margin:0;color:#212121;background:#f8f8f8}article,aside,section,figcaption,figure,main,details,menu{display:block}summary{display:list-item}abbr[title]{border-bottom:none;text-decoration:underline}audio,video{display:inline-block}svg:not(:root){overflow:hidden}input{overflow:visible}img{max-width:100%;height:auto}dfn{font-style:italic}h1,h2,h3,h4,h5,h6{line-height:1.2em;margin:0.75rem 0.5rem;font-weight:500}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#424242;display:block;margin-top:-.25rem}h1{font-size:2rem}h2{font-size:1.6875rem}h3{font-size:1.4375rem}h4{font-size:1.1875rem}h5{font-size:1rem}h6{font-size:.8125rem}p{margin:.5rem}ol,ul{margin:.5rem;padding-left:1rem}b,strong{font-weight:700}hr{box-sizing:content-box;border:0;overflow:visible;line-height:1.25em;margin:.5rem;height:.0625rem;background:linear-gradient(to right, #bdbdbd, #8c8c8c, #bdbdbd)}blockquote{display:block;position:relative;font-style:italic;background:#eee;margin:.5rem;padding:0.5rem 0.5rem 1.5rem;border-left:.25rem solid #505050;border-radius:0 .125rem .125rem 0}blockquote:after{position:absolute;font-style:normal;font-size:.875rem;color:#505050;left:.625rem;bottom:0;content:"— " attr(cite)}code,kbd,pre,samp{font-family:monospace, monospace}code{border-radius:.125rem;background:#e6e6e6;padding:0.125rem 0.25rem}pre{overflow:auto;border-radius:0 .125rem .125rem 0;background:#e6e6e6;padding:.75rem;margin:.5rem;border-left:.25rem solid #1565c0}kbd{border-radius:.125rem;background:#0c0c0c;color:#fafafa;padding:0.125rem 0.25rem}small,sup,sub{font-size:.75em}sup{top:-.5em}sub{bottom:-.25em}sup,sub{line-height:0;position:relative;vertical-align:baseline}a{color:#0277bd;text-decoration:underline;opacity:1;transition:opacity 0.3s}a:visited{color:#01579b}a:hover,a:focus{opacity:0.75}figcaption{font-size:.8125rem;color:#424242}.container{margin:0 auto;padding:0 .75rem}.row{box-sizing:border-box;display:-webkit-box;-webkit-box-flex:0;-webkit-box-orient:horizontal;-webkit-box-direction:normal;display:-webkit-flex;display:flex;-webkit-flex:0 1 auto;flex:0 1 auto;-webkit-flex-flow:row wrap;flex-flow:row wrap}.col-sm,[class^='col-sm-'],[class^='col-sm-offset-'],.row[class*='cols-sm-']>*{box-sizing:border-box;-webkit-box-flex:0;-webkit-flex:0 0 auto;flex:0 0 auto;padding:0 0.25rem}.col-sm,.row.cols-sm>*{-webkit-box-flex:1;max-width:100%;-webkit-flex-grow:1;flex-grow:1;-webkit-flex-basis:0;flex-basis:0}.col-sm-1,.row.cols-sm-1>*{max-width:8.33333%;-webkit-flex-basis:8.33333%;flex-basis:8.33333%}.col-sm-2,.row.cols-sm-2>*{max-width:16.66667%;-webkit-flex-basis:16.66667%;flex-basis:16.66667%}.col-sm-3,.row.cols-sm-3>*{max-width:25%;-webkit-flex-basis:25%;flex-basis:25%}.col-sm-4,.row.cols-sm-4>*{max-width:33.33333%;-webkit-flex-basis:33.33333%;flex-basis:33.33333%}.col-sm-5,.row.cols-sm-5>*{max-width:41.66667%;-webkit-flex-basis:41.66667%;flex-basis:41.66667%}.col-sm-6,.row.cols-sm-6>*{max-width:50%;-webkit-flex-basis:50%;flex-basis:50%}.col-sm-7,.row.cols-sm-7>*{max-width:58.33333%;-webkit-flex-basis:58.33333%;flex-basis:58.33333%}.col-sm-8,.row.cols-sm-8>*{max-width:66.66667%;-webkit-flex-basis:66.66667%;flex-basis:66.66667%}.col-sm-9,.row.cols-sm-9>*{max-width:75%;-webkit-flex-basis:75%;flex-basis:75%}.col-sm-10,.row.cols-sm-10>*{max-width:83.33333%;-webkit-flex-basis:83.33333%;flex-basis:83.33333%}.col-sm-11,.row.cols-sm-11>*{max-width:91.66667%;-webkit-flex-basis:91.66667%;flex-basis:91.66667%}.col-sm-12,.row.cols-sm-12>*{max-width:100%;-webkit-flex-basis:100%;flex-basis:100%}.col-sm-offset-0{margin-left:0}.col-sm-offset-1{margin-left:8.33333%}.col-sm-offset-2{margin-left:16.66667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333%}.col-sm-offset-5{margin-left:41.66667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333%}.col-sm-offset-8{margin-left:66.66667%}.col-sm-offset-9{margin-lef
transform 0.3s}.tabs>label:not(:first-of-type){border-left:0}.tabs>:checked+label{background:#eee;border-bottom-color:#0277bd}.tabs>:checked+label:hover,.tabs>:checked+label:focus{background:rgba(238,238,238,0.8)}.tabs>:checked+label+div{box-sizing:border-box;position:relative;height:400px;width:100%;overflow:auto;margin:0;-webkit-transform:scaleY(1);transform:scaleY(1);background:#fafafa;border:.0625rem solid #bdbdbd;border-top:0;padding:.5rem;clip:auto;-webkit-clip-path:inset(0%);clip-path:inset(0%)}.tabs.stacked{-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column}.tabs.stacked>label{-webkit-order:initial;order:initial}.tabs.stacked>:checked+label{border:.0625rem solid #bdbdbd}.tabs.stacked>label+div{-webkit-order:initial;order:initial;-webkit-transform-origin:top;transform-origin:top}.tabs.stacked>label:not(:first-of-type){border:.0625rem solid #bdbdbd;border-top:0}.tabs.stacked>:checked+label+div{height:auto}@media screen and (max-width: 767px){.tabs{-webkit-box-orient:vertical;-webkit-flex-direction:column;flex-direction:column}.tabs>label{-webkit-order:initial;order:initial}.tabs>:checked+label{border:.0625rem solid #bdbdbd}.tabs>label+div{-webkit-order:initial;order:initial}.tabs>label:not(:first-of-type){border:.0625rem solid #bdbdbd;border-top:0}}mark{background:#0277bd;color:#fafafa;font-size:.9375em;line-height:1em;border-radius:.125rem;padding:0.125em 0.25em}mark.inline-block{display:inline-block}.toast{position:fixed;background:#424242;bottom:1.5rem;left:50%;transform:translate(-50%, -50%);color:#fafafa;border-radius:2rem;padding:0.75rem 1.5rem;z-index:1111}.tooltip{position:relative;display:inline-block}.tooltip:before,.tooltip:after{position:absolute;opacity:0;clip:rect(0 0 0 0);-webkit-clip-path:inset(100%);clip-path:inset(100%);transition:all 0.3s;z-index:1010;left:50%}.tooltip:not(.bottom):before,.tooltip:not(.bottom):after{bottom:75%}.tooltip.bottom:before,.tooltip.bottom:after{top:75%}.tooltip:hover:before,.tooltip:hover:after,.tooltip:focus:before,.tooltip:focus:after{opacity:1;clip:auto;-webkit-clip-path:inset(0%);clip-path:inset(0%)}.tooltip:before{content:'';background:transparent;border:.5rem solid transparent;left:50%;left:calc(50% - .5rem)}.tooltip:not(.bottom):before{border-top-color:#212121}.tooltip.bottom:before{border-bottom-color:#212121}.tooltip:after{content:attr(aria-label);background:#212121;border-radius:.125rem;color:#fafafa;padding:.5rem;white-space:nowrap;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.tooltip:not(.bottom):after{margin-bottom:1rem}.tooltip.bottom:after{margin-top:1rem}.modal{position:fixed;top:0;left:0;display:none;width:100vw;height:100vh;background:rgba(0,0,0,0.45)}.modal .card{margin:0 auto;max-height:50vh;overflow:auto}.modal .card .close{position:absolute;top:.75rem;right:.25rem;padding:0}:checked+.modal{display:-webkit-box;-webkit-box-flex:0;display:-webkit-flex;display:flex;-webkit-flex:0 1 auto;flex:0 1 auto;z-index:1200}:checked+.modal .card .close{z-index:1211}mark.secondary{background:#e53935}mark.tertiary{background:#689f38}mark.tag{border-radius:200px;padding:0.25em 0.5em}mark.inline-block{font-size:1em;line-height:1.375em;padding:.375em}.toast.small{border-radius:1.5rem;padding:0.5rem 1rem}.toast.large{border-radius:3rem;padding:1.125rem 2.25rem}progress{display:block;vertical-align:baseline;-webkit-appearance:none;-moz-appearance:none;appearance:none;height:.625rem;width:90%;width:calc(100% - 1rem);margin:.5rem .5rem;border:0;border-radius:.125rem;background:#e0e0e0;color:#0277bd}progress::-webkit-progress-value{background:#0277bd;border-top-left-radius:.125rem;border-bottom-left-radius:.125rem}progress::-webkit-progress-bar{background:#e0e0e0}progress::-moz-progress-bar{background:#0277bd;border-top-left-radius:.125rem;border-bottom-left-radius:.125rem}progress[value="1000"]::-webkit-progress-value{border-radius:.125rem}progress[value="1000"]::-moz-progress-bar{border-radius:.125rem}@-webkit-keyframes spinner-donut-anim{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg)}}@keyframes sp
maincss = `h1, h2, h3, h4, h5, h6 {
text-transform: capitalize;
}
img {
image-orientation: from-image;
}
.recipeCardTitle {
text-decoration: none;
color: unset;
}
.recipeCardDesc {
max-height: 200px;
overflow: auto;
}
#recipeImages {
padding-top: 20px;
text-align: center;
}
.recipeImage {
cursor: pointer;
}
.imageModal {
margin-top: 15%;
max-width: 50%;
max-height: 50%;
text-align: right;
}
.recipeCardImageSection {
text-align: center;
}`
templateHeader = `{{ define "header" }}
<!doctype html>
<html lang="en">
<head>
<title>{{ .PageTitle }}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/mini.css">
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<header class="sticky">
<a href="/" class="logo">Recipe Card</a>
<a role="button" href="/recipes">Recipes</a>
</header>
{{ end }}`
templateFooter = `{{ define "footer" }}
</body>
</html>
{{ end }}`
templateSearchForm = `{{ define "searchform" }}
<div class="row cols-sm-4">
<div>
</div>
<form action="/search/" method="post">
<div class="input-group vertical">
<input type="text" value="{{ .SearchValue }}" name="search" id="search" placeholder="search">
</div>
</form>
</div>
{{ end }}`
templateIndex = `{{ define "index" }}
{{ template "header" . }}
{{ template "searchform" . }}
{{ template "footer" . }}
{{ end }}`
templateRecipeCard = `{{ define "recipecard" }}
<div class="card">
{{ if .StockImage }}
<div class="section recipeCardImageSection">
<a href="{{ .URL }}">
<img class="media shadowed rounded" alt="Recipe Image" src="{{ index .StockImage }}">
</a>
</div>
{{ end }}
<div class="section">
<a href="{{ .URL }}" class="recipeCardTitle"><h2>{{ .ID }}</h2></a>
</div>
<div class="section recipeCardDesc">
{{ .Description }}
</div>
</div>
{{ end }}`
templateRecipeCards = `{{ define "recipecards" }}
<div class="row">
{{ range . }}
{{ template "recipecard" . }}
{{ end }}
</div>
{{ end }}`
templateSearch = `{{ define "search" }}
{{ template "header" . }}
{{ template "searchform" . }}
{{ if .Recipes }}
{{ template "recipecards" .Recipes }}
{{ else }}
<h4>Unable to find recipes. :(</h4>
{{ end }}
{{ template "footer" . }}
{{ end }}`
templateRecipes = `{{ define "recipes" }}
{{ template "header" . }}
{{ if .Recipes }}
{{ template "recipecards" .Recipes }}
{{ else }}
<h1>No recipes found :(</h2>
{{ end }}
{{ template "footer" . }}
{{ end }}`
templateRecipe = `{{ define "recipe" }}
{{ template "header" . }}
{{ with index .Recipes 0 }}
<div class="container">
<div class="row">
{{ if or .StockImage .Images }}
<div id="recipeImages" class="col-sm-3">
{{ end }}
{{ if .StockImage }}
<img class="shadowed rounded" src="{{ .StockImage }}" alt="Stock image">
{{ end }}
{{ range $index, $results := .Images }}
<label for="modal-{{ $index }}">
<img class="recipeImage shadowed rounded" src="{{ . }}" alt="Recipe card">
</label>
<input id="modal-{{ $index }}" type="checkbox">
<div class="modal">
<div class="container imageModal">
<label for="modal-{{ $index }}" class="close"></label>
<img src="{{ . }}" alt="Recipe card">
</div>
</div>
{{ end }}
{{ if or .StockImage .Images }}
</div>
{{ end }}
<div class="col-sm">
<a class="recipeCardTitle" href="{{ .DocxURL }}"><h1>{{ .ID }}</h1></a>
<hr>
{{ .Description }}
</div>
</div>
</div>
{{ end }}
{{ template "footer" . }}
{{ end }}`
)
// TemplateData defines default template data to pass to every template
type TemplateData struct {
// title of the page
PageTitle string
// previous value used for searching
SearchValue string
// list of recipes to display
Recipes []*TemplateRecipe
}
// TemplateRecipe used for all recipes whether it is an aggregate or a singular recipe
type TemplateRecipe struct {
// title of the recipe
ID string
// description of the recipe in HTML
Description template.HTML
// relative URL to the recipe page itself
URL string
// relative URL to the recipe docx file
DocxURL string
// stock image relative path for the recipe
StockImage string
// card scan images relative paths for the recipe
Images []string
}
// NewTemplate creates a new template instance with all recipe-card related templates parsed
func NewTemplate(logger *log.Logger) (tmpl *template.Template, err error) {
tmpl = template.New("recipe-card")
if logger == nil {
logger = log.New()
logger.Out = ioutil.Discard
}
logger.Debugln("Parsing header template")
_, err = tmpl.Parse(templateHeader)
if err != nil {
err = fmt.Errorf("templateHeader: %s", err)
return
}
logger.Debugln("Finished parsing header template")
logger.Debugln("Parsing footer template")
_, err = tmpl.Parse(templateFooter)
if err != nil {
err = fmt.Errorf("templateFooter: %s", err)
return
}
logger.Debugln("Finished parsing footer template")
logger.Debugln("Parsing recipe card template")
_, err = tmpl.Parse(templateRecipeCard)
if err != nil {
err = fmt.Errorf("templateRecipeCard: %s", err)
return
}
logger.Debugln("Finished parsing recipe card template")
logger.Debugln("Parsing recipe cards template")
_, err = tmpl.Parse(templateRecipeCards)
if err != nil {
err = fmt.Errorf("templateRecipeCards: %s", err)
return
}
logger.Debugln("Finished parsing recipe cards template")
logger.Debugln("Parsing search form template")
_, err = tmpl.Parse(templateSearchForm)
if err != nil {
err = fmt.Errorf("templateSearchForm: %s", err)
return
}
logger.Debugln("Finished parsing search form template")
logger.Debugln("Parsing index template")
_, err = tmpl.Parse(templateIndex)
if err != nil {
err = fmt.Errorf("templateIndex: %s", err)
return
}
logger.Debugln("Finished parsing index template")
logger.Debugln("Parsing recipes template")
_, err = tmpl.Parse(templateRecipes)
if err != nil {
err = fmt.Errorf("templateRecipes: %s", err)
return
}
logger.Debugln("Finished parsing recipes template")
logger.Debugln("Parsing recipe template")
_, err = tmpl.Parse(templateRecipe)
if err != nil {
err = fmt.Errorf("templateRecipe: %s", err)
return
}
logger.Debugln("Finished parsing recipe template")
logger.Debugln("Parsing search template")
_, err = tmpl.Parse(templateSearch)
if err != nil {
err = fmt.Errorf("templateSearch: %s", err)
}
logger.Debugln("Finished parsing search template")
return
}