Added whenisit and default opengraph items
This commit is contained in:
parent
bd178e0168
commit
20c05637d9
|
@ -44,6 +44,10 @@ func TestSiteMap(t *testing.T) {
|
||||||
AssertRegexMatch(t, BuildSiteMap(), RegexSiteMap, nil)
|
AssertRegexMatch(t, BuildSiteMap(), RegexSiteMap, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWhenIsIt(t *testing.T) {
|
||||||
|
AssertRegexMatch(t, BuildWhenIsIt(), RegexWhenIsIt, nil)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAtomFeed(t *testing.T) {
|
func TestAtomFeed(t *testing.T) {
|
||||||
AssertRegexMatch(t, BuildAtomFeed(), RegexAtomFeed, nil)
|
AssertRegexMatch(t, BuildAtomFeed(), RegexAtomFeed, nil)
|
||||||
AssertRegexMatch(t, BuildAtomFeedForProjects(), RegexAtomFeed, map[string]string{"feedtype": "projects"})
|
AssertRegexMatch(t, BuildAtomFeedForProjects(), RegexAtomFeed, map[string]string{"feedtype": "projects"})
|
||||||
|
|
|
@ -49,6 +49,13 @@ func BuildSiteMap() string {
|
||||||
return Url("/sitemap", nil)
|
return Url("/sitemap", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var RegexWhenIsIt = regexp.MustCompile("^/whenisit$")
|
||||||
|
|
||||||
|
func BuildWhenIsIt() string {
|
||||||
|
defer CatchPanic()
|
||||||
|
return Url("/whenisit", nil)
|
||||||
|
}
|
||||||
|
|
||||||
// QUESTION(ben): Can we change these routes?
|
// QUESTION(ben): Can we change these routes?
|
||||||
|
|
||||||
var RegexLoginAction = regexp.MustCompile("^/login$")
|
var RegexLoginAction = regexp.MustCompile("^/login$")
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
{{ template "base.html" . }}
|
||||||
|
|
||||||
|
{{ define "extrahead" }}
|
||||||
|
<style type="text/css">
|
||||||
|
.sidebar { display:none; }
|
||||||
|
|
||||||
|
.display {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display .event_title {
|
||||||
|
font-size: 80px;
|
||||||
|
line-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display .date {
|
||||||
|
margin-top: 40px;
|
||||||
|
font-size: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display .precounter {
|
||||||
|
margin: 40px 0 0 0;
|
||||||
|
font-size: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display .postcounter {
|
||||||
|
margin: 0 0 40px 0;
|
||||||
|
font-size: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display .counter {
|
||||||
|
margin: 20px 0;
|
||||||
|
font-size: 50px;
|
||||||
|
line-height: 50px;
|
||||||
|
font-family: consolas, inconsolata, monospace;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display > a {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "content" }}
|
||||||
|
<div class="display">
|
||||||
|
<p class="event_title"></p>
|
||||||
|
<a class="external event_url" target="_blank" rel="nofollow"></a>
|
||||||
|
<p class="date"></p>
|
||||||
|
<p class="precounter"></p>
|
||||||
|
<p class="counter"></p>
|
||||||
|
<p class="postcounter">ago</p>
|
||||||
|
<input id="notify_checkbox" type="checkbox"><label for="notify_checkbox">Notify me when it starts (Keep this tab open to receive notification)</label><br />
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var timestamp = {{ .Timestamp }};
|
||||||
|
var title = "{{ .Name }}";
|
||||||
|
var url = "{{ .Url }}";
|
||||||
|
var happened = false;
|
||||||
|
var notificationState = "unknown";
|
||||||
|
timestamp *= 1000;
|
||||||
|
var date = new Date(timestamp);
|
||||||
|
happened = timestamp < new Date().getTime();
|
||||||
|
document.querySelector(".display .event_title").textContent = title;
|
||||||
|
var eventUrl = document.querySelector(".display .event_url");
|
||||||
|
if (url && url.length > 0) {
|
||||||
|
eventUrl.textContent = url;
|
||||||
|
if (!(url.startsWith("http://") || url.startsWith("https://"))) {
|
||||||
|
url = "//" + url;
|
||||||
|
}
|
||||||
|
eventUrl.setAttribute("href", url);
|
||||||
|
} else {
|
||||||
|
eventUrl.style.display = "none";
|
||||||
|
}
|
||||||
|
document.querySelector(".display .date").textContent = date.toDateString() + ", " + date.toLocaleTimeString();
|
||||||
|
if (document.querySelector("#notify_checkbox").checked) {
|
||||||
|
if (Notification.permission == "granted") {
|
||||||
|
notificationState = "on";
|
||||||
|
} else {
|
||||||
|
document.querySelector("#notify_checkbox").checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.querySelector("#notify_checkbox").addEventListener("change", updateNotifications);
|
||||||
|
updateTimer();
|
||||||
|
|
||||||
|
function updateTimer() {
|
||||||
|
function pad(t) {
|
||||||
|
var str = t.toString();
|
||||||
|
if (str.length == 1) {
|
||||||
|
str = " " + str;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
var now = new Date().getTime();
|
||||||
|
var delta = Math.floor((timestamp - now) / 1000);
|
||||||
|
var past = delta < 0;
|
||||||
|
if (!happened && past) {
|
||||||
|
happened = true;
|
||||||
|
if (notificationState == "on") {
|
||||||
|
new Notification(title + " is starting!!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delta = Math.abs(delta);
|
||||||
|
var days = Math.floor(delta / 60 / 60 / 24);
|
||||||
|
var hours = Math.floor(delta / 60 / 60) % 24;
|
||||||
|
var minutes = Math.floor(delta / 60) % 60;
|
||||||
|
var seconds = delta % 60;
|
||||||
|
document.querySelector(".display .precounter").textContent = (past ? "Started" : "Starts in");
|
||||||
|
document.querySelector(".display .counter").textContent = (days > 0 ? days + "d " : "") + (days > 0 || hours > 0 ? pad(hours) + "h " : "") + pad(minutes) + "m " + pad(seconds) + "s";
|
||||||
|
document.querySelector(".display .postcounter").style.visibility = (past ? "visible" : "hidden");
|
||||||
|
setTimeout(updateTimer, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNotifications() {
|
||||||
|
var checked = document.querySelector("#notify_checkbox").checked;
|
||||||
|
if (checked) {
|
||||||
|
if (Notification.permission == "granted") {
|
||||||
|
notificationState = "on";
|
||||||
|
} else {
|
||||||
|
Notification.requestPermission().then(function(permission) {
|
||||||
|
if (permission == "granted") {
|
||||||
|
document.querySelector("#notify_checkbox").checked = true;
|
||||||
|
notificationState = "on";
|
||||||
|
} else {
|
||||||
|
document.querySelector("#notify_checkbox").checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
notificationState = "off";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
|
@ -0,0 +1,65 @@
|
||||||
|
{{ template "base.html" . }}
|
||||||
|
|
||||||
|
{{ define "extrahead" }}
|
||||||
|
<style type="text/css">
|
||||||
|
.sidebar { display:none; }
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor label {
|
||||||
|
display: inline-block;
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor input {
|
||||||
|
font-size: 20px;
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor a {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "content" }}
|
||||||
|
<div class="editor">
|
||||||
|
<h1>Make a timer</h1>
|
||||||
|
<label for="date_input">Date:</label> <input id="date_input" type="date"><br />
|
||||||
|
<label for="time_input">Time:</label> <input id="time_input" type="time"></br />
|
||||||
|
<label for="title_input">Title:</label> <input id="title_input" type="text"><br />
|
||||||
|
<label for="url_input">URL:</label> <input id="url_input" type="text"><br />
|
||||||
|
<a target="_blank" href="javascript:;"></a>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
document.querySelector("#title_input").addEventListener("input", updateLink);
|
||||||
|
document.querySelector("#url_input").addEventListener("input", updateLink);
|
||||||
|
document.querySelector("#date_input").addEventListener("input", updateLink);
|
||||||
|
document.querySelector("#time_input").addEventListener("input", updateLink);
|
||||||
|
|
||||||
|
function updateLink() {
|
||||||
|
var title = document.querySelector("#title_input").value.trim();
|
||||||
|
var urlInput = document.querySelector("#url_input").value.trim();
|
||||||
|
var date = document.querySelector("#date_input").value.trim();
|
||||||
|
var time = document.querySelector("#time_input").value.trim();
|
||||||
|
var linkParams = "";
|
||||||
|
var link = document.querySelector(".editor a");
|
||||||
|
if (date.length > 0 && time.length > 0) {
|
||||||
|
var d = new Date(date + " " + time);
|
||||||
|
linkParams = "t=" + d.getTime()/1000 + (title.length > 0 ? "&n=" + encodeURIComponent(title) : "") + (urlInput.length > 0 ? "&u=" + encodeURIComponent(urlInput) : "");
|
||||||
|
var url = location.href.replace(location.search, "").replace("?", "") + "?" + linkParams;
|
||||||
|
link.setAttribute("href", url);
|
||||||
|
link.textContent = url;
|
||||||
|
} else {
|
||||||
|
link.setAttribute("href", "");
|
||||||
|
link.textContent = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
|
@ -160,6 +160,7 @@ func NewWebsiteRoutes(longRequestContext context.Context, conn *pgxpool.Pool, pe
|
||||||
staticPages.GET(hmnurl.RegexContactPage, ContactPage)
|
staticPages.GET(hmnurl.RegexContactPage, ContactPage)
|
||||||
staticPages.GET(hmnurl.RegexMonthlyUpdatePolicy, MonthlyUpdatePolicy)
|
staticPages.GET(hmnurl.RegexMonthlyUpdatePolicy, MonthlyUpdatePolicy)
|
||||||
staticPages.GET(hmnurl.RegexProjectSubmissionGuidelines, ProjectSubmissionGuidelines)
|
staticPages.GET(hmnurl.RegexProjectSubmissionGuidelines, ProjectSubmissionGuidelines)
|
||||||
|
staticPages.GET(hmnurl.RegexWhenIsIt, WhenIsIt)
|
||||||
|
|
||||||
// TODO(asaf): Have separate middleware for HMN-only routes and any-project routes
|
// TODO(asaf): Have separate middleware for HMN-only routes and any-project routes
|
||||||
// NOTE(asaf): HMN-only routes:
|
// NOTE(asaf): HMN-only routes:
|
||||||
|
@ -269,6 +270,8 @@ func getBaseData(c *RequestContext) templates.BaseData {
|
||||||
Session: templateSession,
|
Session: templateSession,
|
||||||
Notices: notices,
|
Notices: notices,
|
||||||
|
|
||||||
|
OpenGraphItems: buildDefaultOpenGraphItems(c.CurrentProject),
|
||||||
|
|
||||||
IsProjectPage: !c.CurrentProject.IsHMN(),
|
IsProjectPage: !c.CurrentProject.IsHMN(),
|
||||||
Header: templates.Header{
|
Header: templates.Header{
|
||||||
AdminUrl: hmnurl.BuildHomepage(), // TODO(asaf)
|
AdminUrl: hmnurl.BuildHomepage(), // TODO(asaf)
|
||||||
|
@ -301,6 +304,15 @@ func getBaseData(c *RequestContext) templates.BaseData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildDefaultOpenGraphItems(project *models.Project) []templates.OpenGraphItem {
|
||||||
|
return []templates.OpenGraphItem{
|
||||||
|
{Property: "og:site_name", Value: "Handmade.Network"},
|
||||||
|
{Property: "og:type", Value: "website"},
|
||||||
|
{Property: "og:image", Value: hmnurl.BuildUserFile(project.LogoLight)},
|
||||||
|
{Property: "og:image:secure_url", Value: hmnurl.BuildUserFile(project.LogoLight)},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func FetchProjectBySlug(ctx context.Context, conn *pgxpool.Pool, slug string) (*models.Project, error) {
|
func FetchProjectBySlug(ctx context.Context, conn *pgxpool.Pool, slug string) (*models.Project, error) {
|
||||||
if len(slug) > 0 && slug != models.HMNProjectSlug {
|
if len(slug) > 0 && slug != models.HMNProjectSlug {
|
||||||
subdomainProjectRow, err := db.QueryOne(ctx, conn, models.Project{}, "SELECT $columns FROM handmade_project WHERE slug = $1", slug)
|
subdomainProjectRow, err := db.QueryOne(ctx, conn, models.Project{}, "SELECT $columns FROM handmade_project WHERE slug = $1", slug)
|
||||||
|
|
|
@ -104,7 +104,7 @@ func Snippet(c *RequestContext) ResponseData {
|
||||||
}
|
}
|
||||||
|
|
||||||
baseData := getBaseData(c)
|
baseData := getBaseData(c)
|
||||||
baseData.OpenGraphItems = opengraph
|
baseData.OpenGraphItems = opengraph // NOTE(asaf): We're overriding the defaults on purpose.
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
err = res.WriteTemplate("snippet.html", SnippetData{
|
err = res.WriteTemplate("snippet.html", SnippetData{
|
||||||
BaseData: baseData,
|
BaseData: baseData,
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package website
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WhenIsItData struct {
|
||||||
|
templates.BaseData
|
||||||
|
Timestamp int
|
||||||
|
Name string
|
||||||
|
Url string
|
||||||
|
}
|
||||||
|
|
||||||
|
func WhenIsIt(c *RequestContext) ResponseData {
|
||||||
|
timestampStr := c.Req.URL.Query().Get("t")
|
||||||
|
timestamp := 0
|
||||||
|
hasTimestamp := false
|
||||||
|
|
||||||
|
if timestampStr != "" {
|
||||||
|
var err error
|
||||||
|
timestamp, err = strconv.Atoi(timestampStr)
|
||||||
|
hasTimestamp = (err == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseData := getBaseData(c)
|
||||||
|
baseData.Title = "When is it?"
|
||||||
|
|
||||||
|
baseData.OpenGraphItems = append(baseData.OpenGraphItems, templates.OpenGraphItem{
|
||||||
|
Property: "og:title",
|
||||||
|
Value: baseData.Title,
|
||||||
|
})
|
||||||
|
baseData.OpenGraphItems = append(baseData.OpenGraphItems, templates.OpenGraphItem{
|
||||||
|
Property: "og:url",
|
||||||
|
Value: c.FullUrl(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if hasTimestamp {
|
||||||
|
name := c.Req.URL.Query().Get("n")
|
||||||
|
url := c.Req.URL.Query().Get("u")
|
||||||
|
|
||||||
|
if name != "" {
|
||||||
|
baseData.OpenGraphItems = append(baseData.OpenGraphItems, templates.OpenGraphItem{
|
||||||
|
Property: "og:description",
|
||||||
|
Value: fmt.Sprintf("Find out when %s starts.", name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var res ResponseData
|
||||||
|
res.MustWriteTemplate("whenisit.html", WhenIsItData{
|
||||||
|
BaseData: baseData,
|
||||||
|
Timestamp: timestamp,
|
||||||
|
Name: name,
|
||||||
|
Url: url,
|
||||||
|
}, c.Perf)
|
||||||
|
return res
|
||||||
|
} else {
|
||||||
|
baseData.OpenGraphItems = append(baseData.OpenGraphItems, templates.OpenGraphItem{
|
||||||
|
Property: "og:description",
|
||||||
|
Value: "A countdown timer",
|
||||||
|
})
|
||||||
|
var res ResponseData
|
||||||
|
res.MustWriteTemplate("whenisit_setup.html", baseData, c.Perf)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue