Server push is back. But only for Chrome and Firefox.
Added the DisableServerPush and EnableCDNPush config.json settings.
This commit is contained in:
parent
167bb230b4
commit
af9a56a9a9
|
@ -10,10 +10,10 @@ import (
|
||||||
"github.com/Azareal/Gosora/common/phrases"
|
"github.com/Azareal/Gosora/common/phrases"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HResource struct {
|
/*type HResource struct {
|
||||||
Name string
|
Name string
|
||||||
Hash string
|
Hash string
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// TODO: Allow resources in spots other than /static/ and possibly even external domains (e.g. CDNs)
|
// TODO: Allow resources in spots other than /static/ and possibly even external domains (e.g. CDNs)
|
||||||
// TODO: Preload Trumboyg on Cosora on the forum list
|
// TODO: Preload Trumboyg on Cosora on the forum list
|
||||||
|
@ -21,11 +21,11 @@ type Header struct {
|
||||||
Title string
|
Title string
|
||||||
//Title []byte // Experimenting with []byte for increased efficiency, let's avoid converting too many things to []byte, as it involves a lot of extra boilerplate
|
//Title []byte // Experimenting with []byte for increased efficiency, let's avoid converting too many things to []byte, as it involves a lot of extra boilerplate
|
||||||
NoticeList []string
|
NoticeList []string
|
||||||
Scripts []HResource
|
Scripts []string
|
||||||
PreScriptsAsync []HResource
|
PreScriptsAsync []string
|
||||||
ScriptsAsync []HResource
|
ScriptsAsync []string
|
||||||
//Preload []string
|
//Preload []string
|
||||||
Stylesheets []HResource
|
Stylesheets []string
|
||||||
Widgets PageWidgets
|
Widgets PageWidgets
|
||||||
Site *site
|
Site *site
|
||||||
Settings SettingMap
|
Settings SettingMap
|
||||||
|
@ -51,40 +51,48 @@ type Header struct {
|
||||||
|
|
||||||
func (header *Header) AddScript(name string) {
|
func (header *Header) AddScript(name string) {
|
||||||
fname := "/static/" + name
|
fname := "/static/" + name
|
||||||
var hash string
|
var oname string
|
||||||
if fname[0] == '/' && fname[1] != '/' {
|
if fname[0] == '/' && fname[1] != '/' {
|
||||||
file, ok := StaticFiles.Get(fname)
|
file, ok := StaticFiles.Get(fname)
|
||||||
if ok {
|
if ok {
|
||||||
hash = file.Sha256
|
oname = name + "?h=" + file.Sha256
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//log.Print("name:", name)
|
if oname == "" {
|
||||||
//log.Print("hash:", hash)
|
oname = name
|
||||||
header.Scripts = append(header.Scripts, HResource{name, hash})
|
}
|
||||||
|
//log.Print("oname:", oname)
|
||||||
|
header.Scripts = append(header.Scripts, oname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (header *Header) AddPreScriptAsync(name string) {
|
func (header *Header) AddPreScriptAsync(name string) {
|
||||||
fname := "/static/" + name
|
fname := "/static/" + name
|
||||||
var hash string
|
var oname string
|
||||||
if fname[0] == '/' && fname[1] != '/' {
|
if fname[0] == '/' && fname[1] != '/' {
|
||||||
file, ok := StaticFiles.Get(fname)
|
file, ok := StaticFiles.Get(fname)
|
||||||
if ok {
|
if ok {
|
||||||
hash = file.Sha256
|
oname = name + "?h=" + file.Sha256
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header.PreScriptsAsync = append(header.PreScriptsAsync, HResource{name, hash})
|
if oname == "" {
|
||||||
|
oname = name
|
||||||
|
}
|
||||||
|
header.PreScriptsAsync = append(header.PreScriptsAsync, oname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (header *Header) AddScriptAsync(name string) {
|
func (header *Header) AddScriptAsync(name string) {
|
||||||
fname := "/static/" + name
|
fname := "/static/" + name
|
||||||
var hash string
|
var oname string
|
||||||
if fname[0] == '/' && fname[1] != '/' {
|
if fname[0] == '/' && fname[1] != '/' {
|
||||||
file, ok := StaticFiles.Get(fname)
|
file, ok := StaticFiles.Get(fname)
|
||||||
if ok {
|
if ok {
|
||||||
hash = file.Sha256
|
oname = name + "?h=" + file.Sha256
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header.ScriptsAsync = append(header.ScriptsAsync, HResource{name, hash})
|
if oname == "" {
|
||||||
|
oname = name
|
||||||
|
}
|
||||||
|
header.ScriptsAsync = append(header.ScriptsAsync, oname)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*func (header *Header) Preload(name string) {
|
/*func (header *Header) Preload(name string) {
|
||||||
|
@ -93,14 +101,17 @@ func (header *Header) AddScriptAsync(name string) {
|
||||||
|
|
||||||
func (header *Header) AddSheet(name string) {
|
func (header *Header) AddSheet(name string) {
|
||||||
fname := "/static/" + name
|
fname := "/static/" + name
|
||||||
var hash string
|
var oname string
|
||||||
if fname[0] == '/' && fname[1] != '/' {
|
if fname[0] == '/' && fname[1] != '/' {
|
||||||
file, ok := StaticFiles.Get(fname)
|
file, ok := StaticFiles.Get(fname)
|
||||||
if ok {
|
if ok {
|
||||||
hash = file.Sha256
|
oname = name + "?h=" + file.Sha256
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
header.Stylesheets = append(header.Stylesheets, HResource{name, hash})
|
if oname == "" {
|
||||||
|
oname = name
|
||||||
|
}
|
||||||
|
header.Stylesheets = append(header.Stylesheets, oname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (header *Header) AddNotice(name string) {
|
func (header *Header) AddNotice(name string) {
|
||||||
|
|
|
@ -90,6 +90,8 @@ type config struct {
|
||||||
DisableLiveTopicList bool
|
DisableLiveTopicList bool
|
||||||
DisableJSAntispam bool
|
DisableJSAntispam bool
|
||||||
//LooseCSP bool
|
//LooseCSP bool
|
||||||
|
DisableServerPush bool
|
||||||
|
EnableCDNPush bool
|
||||||
|
|
||||||
Noavatar string // ? - Move this into the settings table?
|
Noavatar string // ? - Move this into the settings table?
|
||||||
ItemsPerPage int // ? - Move this into the settings table?
|
ItemsPerPage int // ? - Move this into the settings table?
|
||||||
|
|
|
@ -89,14 +89,14 @@ var Template_account_handle = genIntTmpl("account")
|
||||||
|
|
||||||
func tmplInitUsers() (User, User, User) {
|
func tmplInitUsers() (User, User, User) {
|
||||||
avatar, microAvatar := BuildAvatar(62, "")
|
avatar, microAvatar := BuildAvatar(62, "")
|
||||||
user := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", avatar, microAvatar, "", "", "", "", 0, 0, 0, "0.0.0.0.0", 0}
|
user := User{62, BuildProfileURL("fake-user", 62), "Fake User", "compiler@localhost", 0, false, false, false, false, false, false, GuestPerms, make(map[string]bool), "", false, "", avatar, microAvatar, "", "", "", "", 0, 0, 0, "0.0.0.0.0", "", 0}
|
||||||
|
|
||||||
// TODO: Do a more accurate level calculation for this?
|
// TODO: Do a more accurate level calculation for this?
|
||||||
avatar, microAvatar = BuildAvatar(1, "")
|
avatar, microAvatar = BuildAvatar(1, "")
|
||||||
user2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 58, 1000, 0, "127.0.0.1", 0}
|
user2 := User{1, BuildProfileURL("admin-alice", 1), "Admin Alice", "alice@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 58, 1000, 0, "127.0.0.1", "", 0}
|
||||||
|
|
||||||
avatar, microAvatar = BuildAvatar(2, "")
|
avatar, microAvatar = BuildAvatar(2, "")
|
||||||
user3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 42, 900, 0, "::1", 0}
|
user3 := User{2, BuildProfileURL("admin-fred", 62), "Admin Fred", "fred@localhost", 1, true, true, true, true, false, false, AllPerms, make(map[string]bool), "", true, "", avatar, microAvatar, "", "", "", "", 42, 900, 0, "::1", "", 0}
|
||||||
return user, user2, user3
|
return user, user2, user3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,10 +108,10 @@ func tmplInitHeaders(user User, user2 User, user3 User) (*Header, *Header, *Head
|
||||||
Theme: Themes[DefaultThemeBox.Load().(string)],
|
Theme: Themes[DefaultThemeBox.Load().(string)],
|
||||||
CurrentUser: user,
|
CurrentUser: user,
|
||||||
NoticeList: []string{"test"},
|
NoticeList: []string{"test"},
|
||||||
Stylesheets: []HResource{HResource{"panel.css", "d"}},
|
Stylesheets: []string{"panel.css"},
|
||||||
Scripts: []HResource{HResource{"whatever.js", "d"}},
|
Scripts: []string{"whatever.js"},
|
||||||
PreScriptsAsync: []HResource{HResource{"whatever.js", "d"}},
|
PreScriptsAsync: []string{"whatever.js"},
|
||||||
ScriptsAsync: []HResource{HResource{"whatever.js", "d"}},
|
ScriptsAsync: []string{"whatever.js"},
|
||||||
Widgets: PageWidgets{
|
Widgets: PageWidgets{
|
||||||
LeftSidebar: template.HTML("lalala"),
|
LeftSidebar: template.HTML("lalala"),
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,6 +53,7 @@ type User struct {
|
||||||
Score int
|
Score int
|
||||||
Liked int
|
Liked int
|
||||||
LastIP string // ! This part of the UserCache data might fall out of date
|
LastIP string // ! This part of the UserCache data might fall out of date
|
||||||
|
LastAgent string // ! Temporary hack, don't use
|
||||||
TempGroup int
|
TempGroup int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,6 +76,10 @@ DisableLiveTopicList - This switch allows you to disable the live topic list.
|
||||||
|
|
||||||
DisableJSAntispam - This switch lets you disable the JS anti-spam feature. It may be useful if you primarily get users who for one reason or another have decided to disable JavaScript.
|
DisableJSAntispam - This switch lets you disable the JS anti-spam feature. It may be useful if you primarily get users who for one reason or another have decided to disable JavaScript.
|
||||||
|
|
||||||
|
DisableServerPush - This switch lets you disable the HTTP/2 server push feature.
|
||||||
|
|
||||||
|
EnableCDNPush - This switch lets you enable the HTTP/2 CDN Server Push feature. This operates by sending a Link header on every request and may also work with reverse-proxies like Nginx for doing HTTP/2 server pushes.
|
||||||
|
|
||||||
NoAvatar - The default avatar to use for users when they don't have their own. The default for this may change in the near future to better utilise HTTP/2. Example: https://api.adorable.io/avatars/{width}/{id}.png
|
NoAvatar - The default avatar to use for users when they don't have their own. The default for this may change in the near future to better utilise HTTP/2. Example: https://api.adorable.io/avatars/{width}/{id}.png
|
||||||
|
|
||||||
ItemsPerPage - The number of posts, topics, etc. you want on each page.
|
ItemsPerPage - The number of posts, topics, etc. you want on each page.
|
||||||
|
|
|
@ -824,6 +824,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
// TODO: Add a setting to disable this?
|
// TODO: Add a setting to disable this?
|
||||||
// TODO: Use a more efficient detector instead of smashing every possible combination in
|
// TODO: Use a more efficient detector instead of smashing every possible combination in
|
||||||
ua := strings.TrimSpace(strings.Replace(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36","",-1)) // Noise, no one's going to be running this and it would require some sort of agent ranking system to determine which identifier should be prioritised over another
|
ua := strings.TrimSpace(strings.Replace(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36","",-1)) // Noise, no one's going to be running this and it would require some sort of agent ranking system to determine which identifier should be prioritised over another
|
||||||
|
var agent string
|
||||||
if ua == "" {
|
if ua == "" {
|
||||||
counters.AgentViewCounter.Bump(26)
|
counters.AgentViewCounter.Bump(26)
|
||||||
if common.Dev.DebugMode {
|
if common.Dev.DebugMode {
|
||||||
|
@ -878,7 +879,6 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over this in reverse as the real UA tends to be on the right side
|
// Iterate over this in reverse as the real UA tends to be on the right side
|
||||||
var agent string
|
|
||||||
for i := len(items) - 1; i >= 0; i-- {
|
for i := len(items) - 1; i >= 0; i-- {
|
||||||
fAgent, ok := markToAgent[items[i]]
|
fAgent, ok := markToAgent[items[i]]
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -974,6 +974,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
user.LastAgent = agent
|
||||||
if common.Dev.SuperDebug {
|
if common.Dev.SuperDebug {
|
||||||
r.requestLogger.Print(
|
r.requestLogger.Print(
|
||||||
"after PreRoute\n" +
|
"after PreRoute\n" +
|
||||||
|
|
|
@ -603,6 +603,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
// TODO: Add a setting to disable this?
|
// TODO: Add a setting to disable this?
|
||||||
// TODO: Use a more efficient detector instead of smashing every possible combination in
|
// TODO: Use a more efficient detector instead of smashing every possible combination in
|
||||||
ua := strings.TrimSpace(strings.Replace(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36","",-1)) // Noise, no one's going to be running this and it would require some sort of agent ranking system to determine which identifier should be prioritised over another
|
ua := strings.TrimSpace(strings.Replace(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36","",-1)) // Noise, no one's going to be running this and it would require some sort of agent ranking system to determine which identifier should be prioritised over another
|
||||||
|
var agent string
|
||||||
if ua == "" {
|
if ua == "" {
|
||||||
counters.AgentViewCounter.Bump({{.AllAgentMap.blank}})
|
counters.AgentViewCounter.Bump({{.AllAgentMap.blank}})
|
||||||
if common.Dev.DebugMode {
|
if common.Dev.DebugMode {
|
||||||
|
@ -657,7 +658,6 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over this in reverse as the real UA tends to be on the right side
|
// Iterate over this in reverse as the real UA tends to be on the right side
|
||||||
var agent string
|
|
||||||
for i := len(items) - 1; i >= 0; i-- {
|
for i := len(items) - 1; i >= 0; i-- {
|
||||||
fAgent, ok := markToAgent[items[i]]
|
fAgent, ok := markToAgent[items[i]]
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -753,6 +753,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
user.LastAgent = agent
|
||||||
if common.Dev.SuperDebug {
|
if common.Dev.SuperDebug {
|
||||||
r.requestLogger.Print(
|
r.requestLogger.Print(
|
||||||
"after PreRoute\n" +
|
"after PreRoute\n" +
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
//"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -20,6 +21,60 @@ func ParseSEOURL(urlBit string) (slug string, id int, err error) {
|
||||||
return halves[0], tid, err
|
return halves[0], tid, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func doPush(w http.ResponseWriter, header *common.Header) {
|
||||||
|
//fmt.Println("in doPush")
|
||||||
|
if common.Config.EnableCDNPush {
|
||||||
|
// TODO: Faster string building...
|
||||||
|
var sbuf string
|
||||||
|
var push = func(in []string) {
|
||||||
|
for _, path := range in {
|
||||||
|
sbuf += "</static/" + path + ">; rel=preload; as=script,"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
push(header.Scripts)
|
||||||
|
//push(header.PreScriptsAsync)
|
||||||
|
push(header.ScriptsAsync)
|
||||||
|
|
||||||
|
if len(header.Stylesheets) > 0 {
|
||||||
|
for _, path := range header.Stylesheets {
|
||||||
|
sbuf += "</static/" + path + ">; rel=preload; as=style,"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Push avatars?
|
||||||
|
|
||||||
|
if len(sbuf) > 0 {
|
||||||
|
sbuf = sbuf[:len(sbuf)-1]
|
||||||
|
w.Header().Set("Link", sbuf)
|
||||||
|
}
|
||||||
|
} else if !common.Config.DisableServerPush {
|
||||||
|
//fmt.Println("push enabled")
|
||||||
|
gzw, ok := w.(common.GzipResponseWriter)
|
||||||
|
if ok {
|
||||||
|
w = gzw.ResponseWriter
|
||||||
|
}
|
||||||
|
pusher, ok := w.(http.Pusher)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//fmt.Println("has pusher")
|
||||||
|
|
||||||
|
var push = func(in []string) {
|
||||||
|
for _, path := range in {
|
||||||
|
//fmt.Println("pushing /static/" + path)
|
||||||
|
err := pusher.Push("/static/"+path, nil)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
push(header.Scripts)
|
||||||
|
//push(header.PreScriptsAsync)
|
||||||
|
push(header.ScriptsAsync)
|
||||||
|
push(header.Stylesheets)
|
||||||
|
// TODO: Push avatars?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, header *common.Header, pi interface{}) common.RouteError {
|
func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, header *common.Header, pi interface{}) common.RouteError {
|
||||||
if header.CurrentUser.Loggedin {
|
if header.CurrentUser.Loggedin {
|
||||||
header.MetaDesc = ""
|
header.MetaDesc = ""
|
||||||
|
@ -32,6 +87,14 @@ func renderTemplate(tmplName string, w http.ResponseWriter, r *http.Request, hea
|
||||||
w.Header().Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; frame-src 'self' www.youtube-nocookie.com;upgrade-insecure-requests")
|
w.Header().Set("Content-Security-Policy", "default-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-eval' 'unsafe-inline'; img-src * data: 'unsafe-eval' 'unsafe-inline'; connect-src * 'unsafe-eval' 'unsafe-inline'; frame-src 'self' www.youtube-nocookie.com;upgrade-insecure-requests")
|
||||||
}
|
}
|
||||||
header.AddScript("global.js")
|
header.AddScript("global.js")
|
||||||
|
|
||||||
|
// Server pushes can backfire on certain browsers, so we want to make sure it's only triggered for ones where it'll help
|
||||||
|
lastAgent := header.CurrentUser.LastAgent
|
||||||
|
//fmt.Println("lastAgent:", lastAgent)
|
||||||
|
if lastAgent == "chrome" || lastAgent == "firefox" {
|
||||||
|
doPush(w, header)
|
||||||
|
}
|
||||||
|
|
||||||
if header.CurrentUser.IsAdmin {
|
if header.CurrentUser.IsAdmin {
|
||||||
header.Elapsed1 = time.Since(header.StartedAt).String()
|
header.Elapsed1 = time.Since(header.StartedAt).String()
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,16 @@
|
||||||
<head>
|
<head>
|
||||||
<title>{{.Title}} | {{.Header.Site.Name}}</title>
|
<title>{{.Title}} | {{.Header.Site.Name}}</title>
|
||||||
{{range .Header.Stylesheets}}
|
{{range .Header.Stylesheets}}
|
||||||
<link href="/static/{{.Name}}{{if .Hash}}?h={{.Hash}}{{end}}" rel="stylesheet" type="text/css">{{end}}
|
<link href="/static/{{.}}" rel="stylesheet" type="text/css">{{end}}
|
||||||
{{range .Header.PreScriptsAsync}}
|
{{range .Header.PreScriptsAsync}}
|
||||||
<script async type="text/javascript" src="/static/{{.Name}}{{if .Hash}}?h={{.Hash}}{{end}}"></script>{{end}}
|
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
|
||||||
<meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" />
|
<meta property="x-loggedin" content="{{.CurrentUser.Loggedin}}" />
|
||||||
<script type="text/javascript" src="/static/init.js"></script>
|
<script type="text/javascript" src="/static/init.js"></script>
|
||||||
{{range .Header.ScriptsAsync}}
|
{{range .Header.ScriptsAsync}}
|
||||||
<script async type="text/javascript" src="/static/{{.Name}}{{if .Hash}}?h={{.Hash}}{{end}}"></script>{{end}}
|
<script async type="text/javascript" src="/static/{{.}}"></script>{{end}}
|
||||||
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script>
|
<script type="text/javascript" src="/static/jquery-3.1.1.min.js"></script>
|
||||||
{{range .Header.Scripts}}
|
{{range .Header.Scripts}}
|
||||||
<script type="text/javascript" src="/static/{{.Name}}{{if .Hash}}?h={{.Hash}}{{end}}"></script>{{end}}
|
<script type="text/javascript" src="/static/{{.}}"></script>{{end}}
|
||||||
<meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" />
|
<meta name="viewport" content="width=device-width,initial-scale = 1.0, maximum-scale=1.0,user-scalable=no" />
|
||||||
{{if .Header.MetaDesc}}<meta name="description" content="{{.Header.MetaDesc}}" />{{end}}
|
{{if .Header.MetaDesc}}<meta name="description" content="{{.Header.MetaDesc}}" />{{end}}
|
||||||
{{/** TODO: Have page / forum / topic level tags and descriptions below as-well **/}}
|
{{/** TODO: Have page / forum / topic level tags and descriptions below as-well **/}}
|
||||||
|
|
Loading…
Reference in New Issue