Create a new link
- - - -Popular Links
-| Link | -Clicks | -
|---|---|
| go/{{.Short}} | {{.NumClicks}} |
diff --git a/golink.go b/golink.go index d2e6da3..720c7cb 100644 --- a/golink.go +++ b/golink.go @@ -47,7 +47,7 @@ var stats struct { //go:embed link-snapshot.json var lastSnapshot []byte -//go:embed *.html static +//go:embed static tmpl/*.html var embeddedFS embed.FS // db stores short links. @@ -89,6 +89,7 @@ func main() { http.HandleFunc("/", serveGo) http.HandleFunc("/.export", serveExport) + http.HandleFunc("/.help", serveHelp) http.Handle("/_/export", http.RedirectHandler("/.export", http.StatusMovedPermanently)) http.Handle("/.static/", http.StripPrefix("/.", http.FileServer(http.FS(embeddedFS)))) @@ -120,23 +121,27 @@ func main() { } } -// homeCreate is the template used by the http://go/ index page where you can +// homeTmpl is the template used by the http://go/ index page where you can // create or edit links. -var homeCreate *template.Template +var homeTmpl *template.Template + +// helpTmpl is the template used by the http://go/.help page +var helpTmpl *template.Template type visitData struct { Short string NumClicks int } -// homeData is the data used by the homeCreate template. +// homeData is the data used by the homeTmpl template. type homeData struct { Short string Clicks []visitData } func init() { - homeCreate = template.Must(template.ParseFS(embeddedFS, "home.html")) + homeTmpl = template.Must(template.ParseFS(embeddedFS, "tmpl/base.html", "tmpl/home.html")) + helpTmpl = template.Must(template.ParseFS(embeddedFS, "tmpl/base.html", "tmpl/help.html")) } // initStats initializes the in-memory stats counter with counts from db. @@ -199,12 +204,16 @@ func serveHome(w http.ResponseWriter, short string) { return clicks[i].Short < clicks[j].Short }) - homeCreate.Execute(w, homeData{ + homeTmpl.Execute(w, homeData{ Short: html.EscapeString(short), Clicks: clicks, }) } +func serveHelp(w http.ResponseWriter, r *http.Request) { + helpTmpl.Execute(w, nil) +} + func serveGo(w http.ResponseWriter, r *http.Request) { if r.RequestURI == "/" { switch r.Method { diff --git a/package.json b/package.json index 00b64fd..654f315 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ }, "devDependencies": { "@tailwindcss/forms": "^0.5.2", + "@tailwindcss/typography": "^0.5.2", "tailwindcss": "^3.0.24" } } diff --git a/static/base.css b/static/base.css index 8150cb9..d7b5a7c 100644 --- a/static/base.css +++ b/static/base.css @@ -671,6 +671,422 @@ select { } } +.prose { + color: #333; + max-width: 65ch; +} + +.prose :where([class~="lead"]):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-lead); + font-size: 1.25em; + line-height: 1.6; + margin-top: 1.2em; + margin-bottom: 1.2em; +} + +.prose :where(a):not(:where([class~="not-prose"] *)) { + color: #3182ce; + text-decoration: underline; + font-weight: 500; +} + +.prose :where(a):not(:where([class~="not-prose"] *)):hover { + color: #2c5282; +} + +.prose :where(strong):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-bold); + font-weight: 600; +} + +.prose :where(ol):not(:where([class~="not-prose"] *)) { + list-style-type: decimal; + padding-left: 1.625em; +} + +.prose :where(ol[type="A"]):not(:where([class~="not-prose"] *)) { + list-style-type: upper-alpha; +} + +.prose :where(ol[type="a"]):not(:where([class~="not-prose"] *)) { + list-style-type: lower-alpha; +} + +.prose :where(ol[type="A" s]):not(:where([class~="not-prose"] *)) { + list-style-type: upper-alpha; +} + +.prose :where(ol[type="a" s]):not(:where([class~="not-prose"] *)) { + list-style-type: lower-alpha; +} + +.prose :where(ol[type="I"]):not(:where([class~="not-prose"] *)) { + list-style-type: upper-roman; +} + +.prose :where(ol[type="i"]):not(:where([class~="not-prose"] *)) { + list-style-type: lower-roman; +} + +.prose :where(ol[type="I" s]):not(:where([class~="not-prose"] *)) { + list-style-type: upper-roman; +} + +.prose :where(ol[type="i" s]):not(:where([class~="not-prose"] *)) { + list-style-type: lower-roman; +} + +.prose :where(ol[type="1"]):not(:where([class~="not-prose"] *)) { + list-style-type: decimal; +} + +.prose :where(ul):not(:where([class~="not-prose"] *)) { + list-style-type: disc; + padding-left: 1.625em; +} + +.prose :where(ol > li):not(:where([class~="not-prose"] *))::marker { + font-weight: 400; + color: var(--tw-prose-counters); +} + +.prose :where(ul > li):not(:where([class~="not-prose"] *))::marker { + color: var(--tw-prose-bullets); +} + +.prose :where(hr):not(:where([class~="not-prose"] *)) { + border-color: var(--tw-prose-hr); + border-top-width: 1px; + margin-top: 3em; + margin-bottom: 3em; +} + +.prose :where(blockquote):not(:where([class~="not-prose"] *)) { + font-weight: 500; + font-style: italic; + color: var(--tw-prose-quotes); + border-left-width: 0.25rem; + border-left-color: var(--tw-prose-quote-borders); + quotes: "\201C""\201D""\2018""\2019"; + margin-top: 1.6em; + margin-bottom: 1.6em; + padding-left: 1em; +} + +.prose :where(blockquote p:first-of-type):not(:where([class~="not-prose"] *))::before { + content: open-quote; +} + +.prose :where(blockquote p:last-of-type):not(:where([class~="not-prose"] *))::after { + content: close-quote; +} + +.prose :where(h1):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 800; + font-size: 2.25em; + margin-top: 0; + margin-bottom: 0.8888889em; + line-height: 1.1111111; +} + +.prose :where(h1 strong):not(:where([class~="not-prose"] *)) { + font-weight: 900; +} + +.prose :where(h2):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 700; + font-size: 1.5em; + margin-top: 2em; + margin-bottom: 1em; + line-height: 1.3333333; +} + +.prose :where(h2 strong):not(:where([class~="not-prose"] *)) { + font-weight: 800; +} + +.prose :where(h3):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + font-size: 1.25em; + margin-top: 1.6em; + margin-bottom: 0.6em; + line-height: 1.6; +} + +.prose :where(h3 strong):not(:where([class~="not-prose"] *)) { + font-weight: 700; +} + +.prose :where(h4):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + margin-top: 1.5em; + margin-bottom: 0.5em; + line-height: 1.5; +} + +.prose :where(h4 strong):not(:where([class~="not-prose"] *)) { + font-weight: 700; +} + +.prose :where(figure > *):not(:where([class~="not-prose"] *)) { + margin-top: 0; + margin-bottom: 0; +} + +.prose :where(figcaption):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-captions); + font-size: 0.875em; + line-height: 1.4285714; + margin-top: 0.8571429em; +} + +.prose :where(code):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-code); + font-weight: 600; + font-size: 0.875em; +} + +.prose :where(code):not(:where([class~="not-prose"] *))::before { + content: ; +} + +.prose :where(code):not(:where([class~="not-prose"] *))::after { + content: ; +} + +.prose :where(a code):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-links); +} + +.prose :where(pre):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-pre-code); + background-color: var(--tw-prose-pre-bg); + overflow-x: auto; + font-weight: 400; + font-size: 0.875em; + line-height: 1.7142857; + margin-top: 1.7142857em; + margin-bottom: 1.7142857em; + border-radius: 0.375rem; + padding-top: 0.8571429em; + padding-right: 1.1428571em; + padding-bottom: 0.8571429em; + padding-left: 1.1428571em; +} + +.prose :where(pre code):not(:where([class~="not-prose"] *)) { + background-color: transparent; + border-width: 0; + border-radius: 0; + padding: 0; + font-weight: inherit; + color: inherit; + font-size: inherit; + font-family: inherit; + line-height: inherit; +} + +.prose :where(pre code):not(:where([class~="not-prose"] *))::before { + content: none; +} + +.prose :where(pre code):not(:where([class~="not-prose"] *))::after { + content: none; +} + +.prose :where(table):not(:where([class~="not-prose"] *)) { + width: 100%; + table-layout: auto; + text-align: left; + margin-top: 2em; + margin-bottom: 2em; + font-size: 0.875em; + line-height: 1.7142857; +} + +.prose :where(thead):not(:where([class~="not-prose"] *)) { + border-bottom-width: 1px; + border-bottom-color: var(--tw-prose-th-borders); +} + +.prose :where(thead th):not(:where([class~="not-prose"] *)) { + color: var(--tw-prose-headings); + font-weight: 600; + vertical-align: bottom; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; +} + +.prose :where(tbody tr):not(:where([class~="not-prose"] *)) { + border-bottom-width: 1px; + border-bottom-color: var(--tw-prose-td-borders); +} + +.prose :where(tbody tr:last-child):not(:where([class~="not-prose"] *)) { + border-bottom-width: 0; +} + +.prose :where(tbody td):not(:where([class~="not-prose"] *)) { + vertical-align: baseline; + padding-top: 0.5714286em; + padding-right: 0.5714286em; + padding-bottom: 0.5714286em; + padding-left: 0.5714286em; +} + +.prose { + --tw-prose-body: #374151; + --tw-prose-headings: #111827; + --tw-prose-lead: #4b5563; + --tw-prose-links: #111827; + --tw-prose-bold: #111827; + --tw-prose-counters: #6b7280; + --tw-prose-bullets: #d1d5db; + --tw-prose-hr: #e5e7eb; + --tw-prose-quotes: #111827; + --tw-prose-quote-borders: #e5e7eb; + --tw-prose-captions: #6b7280; + --tw-prose-code: #111827; + --tw-prose-pre-code: #e5e7eb; + --tw-prose-pre-bg: #1f2937; + --tw-prose-th-borders: #d1d5db; + --tw-prose-td-borders: #e5e7eb; + --tw-prose-invert-body: #d1d5db; + --tw-prose-invert-headings: #fff; + --tw-prose-invert-lead: #9ca3af; + --tw-prose-invert-links: #fff; + --tw-prose-invert-bold: #fff; + --tw-prose-invert-counters: #9ca3af; + --tw-prose-invert-bullets: #4b5563; + --tw-prose-invert-hr: #374151; + --tw-prose-invert-quotes: #f3f4f6; + --tw-prose-invert-quote-borders: #374151; + --tw-prose-invert-captions: #9ca3af; + --tw-prose-invert-code: #fff; + --tw-prose-invert-pre-code: #d1d5db; + --tw-prose-invert-pre-bg: rgb(0 0 0 / 50%); + --tw-prose-invert-th-borders: #4b5563; + --tw-prose-invert-td-borders: #374151; + font-size: 1rem; + line-height: 1.75; +} + +.prose :where(p):not(:where([class~="not-prose"] *)) { + margin-top: 1.25em; + margin-bottom: 1.25em; +} + +.prose :where(img):not(:where([class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(video):not(:where([class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(figure):not(:where([class~="not-prose"] *)) { + margin-top: 2em; + margin-bottom: 2em; +} + +.prose :where(h2 code):not(:where([class~="not-prose"] *)) { + font-size: 0.875em; +} + +.prose :where(h3 code):not(:where([class~="not-prose"] *)) { + font-size: 0.9em; +} + +.prose :where(li):not(:where([class~="not-prose"] *)) { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +.prose :where(ol > li):not(:where([class~="not-prose"] *)) { + padding-left: 0.375em; +} + +.prose :where(ul > li):not(:where([class~="not-prose"] *)) { + padding-left: 0.375em; +} + +.prose > :where(ul > li p):not(:where([class~="not-prose"] *)) { + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.prose > :where(ul > li > *:first-child):not(:where([class~="not-prose"] *)) { + margin-top: 1.25em; +} + +.prose > :where(ul > li > *:last-child):not(:where([class~="not-prose"] *)) { + margin-bottom: 1.25em; +} + +.prose > :where(ol > li > *:first-child):not(:where([class~="not-prose"] *)) { + margin-top: 1.25em; +} + +.prose > :where(ol > li > *:last-child):not(:where([class~="not-prose"] *)) { + margin-bottom: 1.25em; +} + +.prose :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"] *)) { + margin-top: 0.75em; + margin-bottom: 0.75em; +} + +.prose :where(hr + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h2 + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h3 + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(h4 + *):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose :where(thead th:first-child):not(:where([class~="not-prose"] *)) { + padding-left: 0; +} + +.prose :where(thead th:last-child):not(:where([class~="not-prose"] *)) { + padding-right: 0; +} + +.prose :where(tbody td:first-child):not(:where([class~="not-prose"] *)) { + padding-left: 0; +} + +.prose :where(tbody td:last-child):not(:where([class~="not-prose"] *)) { + padding-right: 0; +} + +.prose > :where(:first-child):not(:where([class~="not-prose"] *)) { + margin-top: 0; +} + +.prose > :where(:last-child):not(:where([class~="not-prose"] *)) { + margin-bottom: 0; +} + +.absolute { + position: absolute; +} + .m-2 { margin: 0.5rem; } @@ -680,6 +1096,11 @@ select { margin-right: auto; } +.mx-4 { + margin-left: 1rem; + margin-right: 1rem; +} + .my-2 { margin-top: 0.5rem; margin-bottom: 0.5rem; @@ -693,6 +1114,10 @@ select { margin-top: 1.5rem; } +.mr-2 { + margin-right: 0.5rem; +} + .block { display: block; } @@ -713,10 +1138,18 @@ select { min-height: 100vh; } +.max-w-4xl { + max-width: 56rem; +} + .max-w-full { max-width: 100%; } +.max-w-5xl { + max-width: 64rem; +} + .flex-1 { flex: 1 1 0%; } @@ -759,14 +1192,14 @@ select { border-bottom-width: 1px; } -.border-r-0 { - border-right-width: 0px; -} - .border-t { border-top-width: 1px; } +.border-r-0 { + border-right-width: 0px; +} + .border-gray-200 { --tw-border-opacity: 1; border-color: rgb(230 228 226 / var(--tw-border-opacity)); @@ -823,6 +1256,10 @@ select { padding-bottom: 0.25rem; } +.pt-2 { + padding-top: 0.5rem; +} + .pt-6 { padding-top: 1.5rem; } @@ -831,10 +1268,6 @@ select { padding-right: 2rem; } -.pt-2 { - padding-top: 0.5rem; -} - .text-left { text-align: left; } @@ -862,6 +1295,11 @@ select { line-height: 1rem; } +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + .font-bold { font-weight: 700; } @@ -889,6 +1327,20 @@ select { color: rgb(159 153 149 / var(--tw-text-opacity)); } +.text-blue-200 { + color: rgba(43, 123, 185, 0.15); +} + +.text-blue-500 { + --tw-text-opacity: 1; + color: rgb(74 125 221 / var(--tw-text-opacity)); +} + +.text-blue-600 { + --tw-text-opacity: 1; + color: rgb(77 120 200 / var(--tw-text-opacity)); +} + .placeholder\:text-gray-400::-moz-placeholder { --tw-text-opacity: 1; color: rgb(182 176 173 / var(--tw-text-opacity)); diff --git a/static/images/create-link.png b/static/images/create-link.png new file mode 100644 index 0000000..94ca4d1 Binary files /dev/null and b/static/images/create-link.png differ diff --git a/tailwind.config.js b/tailwind.config.js index ab66f51..8ddfa34 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,5 +1,5 @@ module.exports = { - content: ["./*.html"], + content: ["./tmpl/*.html"], theme: { colors: { blue: { @@ -27,8 +27,23 @@ module.exports = { white: '#fff', current: 'currentColor', }, + extend: { + typography: { + DEFAULT: { + css: { + 'code::before': { + 'content': '', + }, + 'code::after': { + 'content': '', + }, + }, + }, + }, + }, }, plugins: [ require('@tailwindcss/forms'), + require('@tailwindcss/typography'), ], } diff --git a/home.html b/tmpl/base.html similarity index 50% rename from home.html rename to tmpl/base.html index 3e397e8..1414a31 100644 --- a/home.html +++ b/tmpl/base.html @@ -1,4 +1,5 @@ - + +
| Link | -Clicks | -
|---|---|
| go/{{.Short}} | {{.NumClicks}} |