syntax highlighting and blog/post liveviews working; numerous other styling updates

This commit is contained in:
Adam Piontek 2021-04-05 16:51:59 -04:00
parent a6186ba639
commit 2218a678b1
35 changed files with 610 additions and 120 deletions

3
.gitignore vendored
View file

@ -39,5 +39,8 @@ npm-debug.log
# for vscode elixir_ls extension files # for vscode elixir_ls extension files
/.elixir_ls /.elixir_ls
# Ignore the pygments venv directory
/priv/pygments/
# dev # dev
TODO.md TODO.md

View file

@ -1,2 +1,3 @@
alias Home73k.Temp
alias Home73k.Blog alias Home73k.Blog
alias Home73k.Blog.Post alias Home73k.Blog.Post

View file

@ -1,19 +1,26 @@
# Home73k # Home73k
To start your Phoenix server: Personal website with blog.
* Install dependencies with `mix deps.get` ## Blog posts
* Install Node.js dependencies with `npm install` inside the `assets` directory
* Start Phoenix endpoint with `mix phx.server`
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. Posts are markdown files stored under `priv/content` and parsed by [Earmark](https://hexdocs.pm/earmark/Earmark.html). This can be configured in `config.exs` under `config :home73k, :app_global_vars, blog_content: "path/to/content"`
Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). ### Syntax highlighting
## Learn more For the challenge of it, and to keep user's browsers from having to run javascript just to highlight some code, I chose to do server-side syntax highlighting.
* Official website: https://www.phoenixframework.org/ Due to the lexer limitations of elixir-native solutions, the highlighter uses [Pygments](https://pygments.org/) by calling [pygmentize](https://pygments.org/docs/cmdline/) via [System.cmd](https://hexdocs.pm/elixir/System.html#cmd/3)
* Guides: https://hexdocs.pm/phoenix/overview.html
* Docs: https://hexdocs.pm/phoenix However, this requires installing python3 & Pygments. Best way to do this is with a venv (virtual python environment), so you'll also want `python3-venv` installed on a debian system, for example.
* Forum: https://elixirforum.com/c/phoenix-forum
* Source: https://github.com/phoenixframework/phoenix By default, Home73k is configured to look for pygmentize in a venv at `priv/pygments/bin/pygmentize` -- here's a quick howto for how to set that up:
```shell
cd priv
python3 -m venv pygments
source pygments/bin/activate
pip3 install Pygments
```
The location of bin/pygmentize can be configured in `config.exs` under `config :home73k, :app_global_vars, pygmentize_bin: "path/to/bin/pygmentize"`

View file

@ -4,8 +4,8 @@
// //
// Font, line-height, and color for body text, headings, and more. // Font, line-height, and color for body text, headings, and more.
// stylelint-disable value-keyword-case $font-size-base: 1.125rem;
$font-family-sans-serif: "Work Sans", system-ui, -apple-system, "Segoe UI", Roboto, $font-family-sans-serif: "Open Sans", system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important; "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
$font-family-base: $font-family-sans-serif; $font-family-base: $font-family-sans-serif;
@ -14,10 +14,8 @@ $font-family-brand: Righteous, system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important; "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
$font-family-monospace: "Fira Mono", SFMono-Regular, Menlo, Monaco, Consolas, $font-family-monospace: "Fira Code", SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace !important; "Liberation Mono", "Courier New", monospace !important;
$font-family-code: "Fira Code", "Fira Mono", SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace !important;
// Features // Features
$enable-shadows: true; $enable-shadows: true;

View file

@ -1,35 +1,23 @@
/* Fontsource Work Sans */ /* brand : Fontsource Righteous */
@import "../node_modules/@fontsource/work-sans/100.css"; /* thin | normal */
@import "../node_modules/@fontsource/work-sans/100-italic.css"; /* thin | italic */
// @import "../node_modules/@fontsource/work-sans/200.css"; /* thin-light | normal */
// @import "../node_modules/@fontsource/work-sans/200-italic.css"; /* thin-light | italic */
@import "../node_modules/@fontsource/work-sans/300.css"; /* light | normal */
@import "../node_modules/@fontsource/work-sans/300-italic.css"; /* light | italic */
@import "../node_modules/@fontsource/work-sans/400.css"; /* normal | normal */
@import "../node_modules/@fontsource/work-sans/400-italic.css"; /* normal | italic */
// @import "../node_modules/@fontsource/work-sans/500.css"; /* heavier | normal */
// @import "../node_modules/@fontsource/work-sans/500-italic.css"; /* heavier | italic */
@import "../node_modules/@fontsource/work-sans/600.css"; /* heavier? | normal */
@import "../node_modules/@fontsource/work-sans/600-italic.css"; /* heavier | italic */
@import "../node_modules/@fontsource/work-sans/700.css"; /* bold | normal */
@import "../node_modules/@fontsource/work-sans/700-italic.css"; /* bold | italic */
// @import "../node_modules/@fontsource/work-sans/800.css"; /* bolder? | normal */
// @import "../node_modules/@fontsource/work-sans/800-italic.css"; /* bolder? | italic */
// @import "../node_modules/@fontsource/work-sans/900.css"; /* black | normal */
// @import "../node_modules/@fontsource/work-sans/900-italic.css"; /* black | italic */
/* Fontsource Righteous */
@import "../node_modules/@fontsource/righteous/400.css"; /* normal | normal */ @import "../node_modules/@fontsource/righteous/400.css"; /* normal | normal */
/* Fontsource Fira Mono */ /* monospace : Fontsource Fira Code */
@import "../node_modules/@fontsource/fira-mono/400.css"; /* normal | normal */
@import "../node_modules/@fontsource/fira-mono/500.css"; /* heavier normal? */
// @import "../node_modules/@fontsource/fira-mono/700.css"; /* bold | normal */
/* Fontsource Fira Code */
@import "../node_modules/@fontsource/fira-code/300.css"; /* light | normal */ @import "../node_modules/@fontsource/fira-code/300.css"; /* light | normal */
@import "../node_modules/@fontsource/fira-code/400.css"; /* normal | normal */ @import "../node_modules/@fontsource/fira-code/400.css"; /* normal | normal */
// @import "../node_modules/@fontsource/fira-code/500.css"; /* heavier normal? */ @import "../node_modules/@fontsource/fira-code/500.css"; /* heavier normal? */
// @import "../node_modules/@fontsource/fira-code/600.css"; /* heavier normal? */ @import "../node_modules/@fontsource/fira-code/600.css"; /* heavier normal? */
@import "../node_modules/@fontsource/fira-code/700.css"; /* bold | normal */ @import "../node_modules/@fontsource/fira-code/700.css"; /* bold | normal */
/* Fontsource Open Sans */
@import "../node_modules/@fontsource/open-sans/300.css"; /* light | normal */
@import "../node_modules/@fontsource/open-sans/300-italic.css"; /* light | italic */
@import "../node_modules/@fontsource/open-sans/400.css"; /* normal | normal */
@import "../node_modules/@fontsource/open-sans/400-italic.css"; /* normal | italic */
@import "../node_modules/@fontsource/open-sans/600.css"; /* heavier? | normal */
@import "../node_modules/@fontsource/open-sans/600-italic.css"; /* heavier | italic */
@import "../node_modules/@fontsource/open-sans/700.css"; /* bold | normal */
@import "../node_modules/@fontsource/open-sans/700-italic.css"; /* bold | italic */
@import "../node_modules/@fontsource/open-sans/800.css"; /* bolder? | normal */
@import "../node_modules/@fontsource/open-sans/800-italic.css"; /* bolder? | italic */

269
assets/css/_pygments.css Normal file
View file

@ -0,0 +1,269 @@
pre {
line-height: 125%;
}
td.linenos .normal {
color: #37474f;
background-color: #263238;
padding-left: 5px;
padding-right: 5px;
}
span.linenos {
color: #37474f;
background-color: #263238;
padding-left: 5px;
padding-right: 5px;
}
td.linenos .special {
color: #607a86;
background-color: #263238;
padding-left: 5px;
padding-right: 5px;
}
span.linenos.special {
color: #607a86;
background-color: #263238;
padding-left: 5px;
padding-right: 5px;
}
pre.pygments .hll {
background-color: #2c3b41;
}
pre.pygments {
background: #263238;
color: #eeffff;
}
pre.pygments .c {
color: #546e7a;
font-style: italic;
} /* Comment */
pre.pygments .err {
color: #ff5370;
} /* Error */
pre.pygments .esc {
color: #89ddff;
} /* Escape */
pre.pygments .g {
color: #eeffff;
} /* Generic */
pre.pygments .k {
color: #bb80b3;
} /* Keyword */
pre.pygments .l {
color: #c3e88d;
} /* Literal */
pre.pygments .n {
color: #eeffff;
} /* Name */
pre.pygments .o {
color: #89ddff;
} /* Operator */
pre.pygments .p {
color: #89ddff;
} /* Punctuation */
pre.pygments .ch {
color: #546e7a;
font-style: italic;
} /* Comment.Hashbang */
pre.pygments .cm {
color: #546e7a;
font-style: italic;
} /* Comment.Multiline */
pre.pygments .cp {
color: #546e7a;
font-style: italic;
} /* Comment.Preproc */
pre.pygments .cpf {
color: #546e7a;
font-style: italic;
} /* Comment.PreprocFile */
pre.pygments .c1 {
color: #546e7a;
font-style: italic;
} /* Comment.Single */
pre.pygments .cs {
color: #546e7a;
font-style: italic;
} /* Comment.Special */
pre.pygments .gd {
color: #ff5370;
} /* Generic.Deleted */
pre.pygments .ge {
color: #89ddff;
} /* Generic.Emph */
pre.pygments .gr {
color: #ff5370;
} /* Generic.Error */
pre.pygments .gh {
color: #c3e88d;
} /* Generic.Heading */
pre.pygments .gi {
color: #c3e88d;
} /* Generic.Inserted */
pre.pygments .go {
color: #546e7a;
} /* Generic.Output */
pre.pygments .gp {
color: #ffcb6b;
} /* Generic.Prompt */
pre.pygments .gs {
color: #ff5370;
} /* Generic.Strong */
pre.pygments .gu {
color: #89ddff;
} /* Generic.Subheading */
pre.pygments .gt {
color: #ff5370;
} /* Generic.Traceback */
pre.pygments .kc {
color: #89ddff;
} /* Keyword.Constant */
pre.pygments .kd {
color: #bb80b3;
} /* Keyword.Declaration */
pre.pygments .kn {
color: #89ddff;
font-style: italic;
} /* Keyword.Namespace */
pre.pygments .kp {
color: #89ddff;
} /* Keyword.Pseudo */
pre.pygments .kr {
color: #bb80b3;
} /* Keyword.Reserved */
pre.pygments .kt {
color: #bb80b3;
} /* Keyword.Type */
pre.pygments .ld {
color: #c3e88d;
} /* Literal.Date */
pre.pygments .m {
color: #f78c6c;
} /* Literal.Number */
pre.pygments .s {
color: #c3e88d;
} /* Literal.String */
pre.pygments .na {
color: #bb80b3;
} /* Name.Attribute */
pre.pygments .nb {
color: #82aaff;
} /* Name.Builtin */
pre.pygments .nc {
color: #ffcb6b;
} /* Name.Class */
pre.pygments .no {
color: #eeffff;
} /* Name.Constant */
pre.pygments .nd {
color: #82aaff;
} /* Name.Decorator */
pre.pygments .ni {
color: #89ddff;
} /* Name.Entity */
pre.pygments .ne {
color: #ffcb6b;
} /* Name.Exception */
pre.pygments .nf {
color: #82aaff;
} /* Name.Function */
pre.pygments .nl {
color: #82aaff;
} /* Name.Label */
pre.pygments .nn {
color: #ffcb6b;
} /* Name.Namespace */
pre.pygments .nx {
color: #eeffff;
} /* Name.Other */
pre.pygments .py {
color: #ffcb6b;
} /* Name.Property */
pre.pygments .nt {
color: #ff5370;
} /* Name.Tag */
pre.pygments .nv {
color: #89ddff;
} /* Name.Variable */
pre.pygments .ow {
color: #89ddff;
font-style: italic;
} /* Operator.Word */
pre.pygments .w {
color: #eeffff;
} /* Text.Whitespace */
pre.pygments .mb {
color: #f78c6c;
} /* Literal.Number.Bin */
pre.pygments .mf {
color: #f78c6c;
} /* Literal.Number.Float */
pre.pygments .mh {
color: #f78c6c;
} /* Literal.Number.Hex */
pre.pygments .mi {
color: #f78c6c;
} /* Literal.Number.Integer */
pre.pygments .mo {
color: #f78c6c;
} /* Literal.Number.Oct */
pre.pygments .sa {
color: #bb80b3;
} /* Literal.String.Affix */
pre.pygments .sb {
color: #c3e88d;
} /* Literal.String.Backtick */
pre.pygments .sc {
color: #c3e88d;
} /* Literal.String.Char */
pre.pygments .dl {
color: #eeffff;
} /* Literal.String.Delimiter */
pre.pygments .sd {
color: #546e7a;
font-style: italic;
} /* Literal.String.Doc */
pre.pygments .s2 {
color: #c3e88d;
} /* Literal.String.Double */
pre.pygments .se {
color: #eeffff;
} /* Literal.String.Escape */
pre.pygments .sh {
color: #c3e88d;
} /* Literal.String.Heredoc */
pre.pygments .si {
color: #89ddff;
} /* Literal.String.Interpol */
pre.pygments .sx {
color: #c3e88d;
} /* Literal.String.Other */
pre.pygments .sr {
color: #89ddff;
} /* Literal.String.Regex */
pre.pygments .s1 {
color: #c3e88d;
} /* Literal.String.Single */
pre.pygments .ss {
color: #89ddff;
} /* Literal.String.Symbol */
pre.pygments .bp {
color: #89ddff;
} /* Name.Builtin.Pseudo */
pre.pygments .fm {
color: #82aaff;
} /* Name.Function.Magic */
pre.pygments .vc {
color: #89ddff;
} /* Name.Variable.Class */
pre.pygments .vg {
color: #89ddff;
} /* Name.Variable.Global */
pre.pygments .vi {
color: #89ddff;
} /* Name.Variable.Instance */
pre.pygments .vm {
color: #82aaff;
} /* Name.Variable.Magic */
pre.pygments .il {
color: #f78c6c;
} /* Literal.Number.Integer.Long */

View file

@ -16,6 +16,29 @@
/* Navbar custom styling */ /* Navbar custom styling */
@import "nav-bar-help"; @import "nav-bar-help";
/* Pygments syntax highlighting styles */
@import "pygments";
/* code highlighting */
pre.pygments {
line-height: 125%;
background-color: #272822 !important;
overflow: wrap !important;
padding: 0.75rem;
border-radius: .25em;
code {
font-family: $font-family-code;
color: #eeffff;
}
}
code.inline {
background-color: #272822;
color: #eeffff;
padding: .2em .5em;
border-radius: .25em;
display: inline;
}
/* main */ /* main */
html, html,
body { body {
@ -24,12 +47,12 @@ body {
height: 100%; height: 100%;
} }
a { a {
color: $primary; color: $secondary;
&:visited { &:visited {
color: $info; color: $info;
} }
&:hover { &:hover {
color: $secondary; color: $primary;
} }
} }
.border-gray-900 { .border-gray-900 {
@ -59,15 +82,9 @@ a {
.fw-600 { .fw-600 {
font-weight: 600; font-weight: 600;
} }
.font-sans-serif {
font-family: $font-family-sans-serif;
}
.font-brand { .font-brand {
font-family: $font-family-brand; font-family: $font-family-brand;
} }
.font-code {
font-family: $font-family-code;
}
.fs-larger { .fs-larger {
font-size: larger; font-size: larger;
} }
@ -124,10 +141,8 @@ a {
margin-top: 2rem; margin-top: 2rem;
} }
} }
.post-lede.lead { .post li {
font-size: 1.25rem; line-height: 1.75rem;
font-weight: 300;
color: $gray-300;
} }
/* extra */ /* extra */

View file

@ -43,6 +43,9 @@ import { Socket } from "phoenix";
import topbar from "topbar"; import topbar from "topbar";
import { LiveSocket } from "phoenix_live_view"; import { LiveSocket } from "phoenix_live_view";
// // Prismjs import
// import Prism from "prismjs"
// // Bootstrap v5 js imports // // Bootstrap v5 js imports
// import "bootstrap/js/dist/alert"; // import "bootstrap/js/dist/alert";
import "bootstrap/js/dist/collapse"; import "bootstrap/js/dist/collapse";

133
assets/package-lock.json generated
View file

@ -7,17 +7,18 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fontsource/fira-code": "^4.x", "@fontsource/fira-code": "^4.x",
"@fontsource/fira-mono": "^4.x", "@fontsource/open-sans": "^4.2.2",
"@fontsource/righteous": "^4.x", "@fontsource/righteous": "^4.x",
"@fontsource/work-sans": "^4.2.2",
"@mdi/svg": "^5.x", "@mdi/svg": "^5.x",
"@popperjs/core": "^2.x", "@popperjs/core": "^2.x",
"babel-plugin-prismjs": "^2.0.1",
"bootstrap": "^5.0.0-beta3", "bootstrap": "^5.0.0-beta3",
"bootstrap-icons": "^1.x", "bootstrap-icons": "^1.x",
"hamburgers": "^1.x", "hamburgers": "^1.x",
"phoenix": "file:../deps/phoenix", "phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html", "phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view", "phoenix_live_view": "file:../deps/phoenix_live_view",
"prismjs": "^1.23.0",
"topbar": "^1.x" "topbar": "^1.x"
}, },
"devDependencies": { "devDependencies": {
@ -1287,21 +1288,16 @@
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.2.2.tgz", "resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.2.2.tgz",
"integrity": "sha512-Bhg7rQ/CUbedA6B6K6gS2GDEa5JJjQwSqq1KMz4wVMaXXL+igsLrr4VKKmdfExwlB6o7Ie8kScXg4camZmt7TQ==" "integrity": "sha512-Bhg7rQ/CUbedA6B6K6gS2GDEa5JJjQwSqq1KMz4wVMaXXL+igsLrr4VKKmdfExwlB6o7Ie8kScXg4camZmt7TQ=="
}, },
"node_modules/@fontsource/fira-mono": { "node_modules/@fontsource/open-sans": {
"version": "4.2.2", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/fira-mono/-/fira-mono-4.2.2.tgz", "resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-4.2.2.tgz",
"integrity": "sha512-t2WRThg+eLkQNQCtPG2sCCq40lz3xeb7nsL7P8l4+wfSRbdLQXAY5IebMftI2YEZR4MRRhdgrg0p5fi/2yXypA==" "integrity": "sha512-NbsL1a9asJO6N/5kRxYPCy0kNhKMi9T75kl4QfIGtmpd/5IfB+UIAUxd9AICmCLaH4Osc2TImeTJj94xc9MNKg=="
}, },
"node_modules/@fontsource/righteous": { "node_modules/@fontsource/righteous": {
"version": "4.2.2", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/righteous/-/righteous-4.2.2.tgz", "resolved": "https://registry.npmjs.org/@fontsource/righteous/-/righteous-4.2.2.tgz",
"integrity": "sha512-mUjFblfCV6eWZj+lkrXFZsER8pq/3LOCoT3ezKPcerYH7StXQ8Gflcs0uMqacZP7CVLyzVUkPvSgLMQJTQvypg==" "integrity": "sha512-mUjFblfCV6eWZj+lkrXFZsER8pq/3LOCoT3ezKPcerYH7StXQ8Gflcs0uMqacZP7CVLyzVUkPvSgLMQJTQvypg=="
}, },
"node_modules/@fontsource/work-sans": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/work-sans/-/work-sans-4.2.2.tgz",
"integrity": "sha512-fFm8a1TbE+qDnRNsf4R6Z/yZP2f3mbj54zSoWUMIWVkgTHM0RfpcOTqoIp7Aj5jon6na0Oynlq5yXvMA5p3pKA=="
},
"node_modules/@mdi/svg": { "node_modules/@mdi/svg": {
"version": "5.9.55", "version": "5.9.55",
"resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-5.9.55.tgz", "resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-5.9.55.tgz",
@ -1876,6 +1872,14 @@
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"
} }
}, },
"node_modules/babel-plugin-prismjs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/babel-plugin-prismjs/-/babel-plugin-prismjs-2.0.1.tgz",
"integrity": "sha512-GqQGa3xX3Z2ft97oDbGvEFoxD8nKqb3ZVszrOc5H7icnEUA56BIjVYm86hfZZA82uuHLwTIfCXbEKzKG1BzKzg==",
"peerDependencies": {
"prismjs": "^1.18.0"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -2315,6 +2319,17 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/clipboard": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz",
"integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==",
"optional": true,
"dependencies": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"node_modules/cliui": { "node_modules/cliui": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@ -3133,6 +3148,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
"optional": true
},
"node_modules/dir-glob": { "node_modules/dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -3946,6 +3967,15 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
"optional": true,
"dependencies": {
"delegate": "^3.1.2"
}
},
"node_modules/graceful-fs": { "node_modules/graceful-fs": {
"version": "4.2.6", "version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
@ -7588,6 +7618,14 @@
"posthtml-render": "^1.0.6" "posthtml-render": "^1.0.6"
} }
}, },
"node_modules/prismjs": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz",
"integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==",
"optionalDependencies": {
"clipboard": "^2.0.0"
}
},
"node_modules/promise-inflight": { "node_modules/promise-inflight": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@ -8099,6 +8137,12 @@
"url": "https://opencollective.com/webpack" "url": "https://opencollective.com/webpack"
} }
}, },
"node_modules/select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
"optional": true
},
"node_modules/semver": { "node_modules/semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@ -9164,6 +9208,12 @@
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
"dev": true "dev": true
}, },
"node_modules/tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
"optional": true
},
"node_modules/to-fast-properties": { "node_modules/to-fast-properties": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@ -10914,21 +10964,16 @@
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.2.2.tgz", "resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.2.2.tgz",
"integrity": "sha512-Bhg7rQ/CUbedA6B6K6gS2GDEa5JJjQwSqq1KMz4wVMaXXL+igsLrr4VKKmdfExwlB6o7Ie8kScXg4camZmt7TQ==" "integrity": "sha512-Bhg7rQ/CUbedA6B6K6gS2GDEa5JJjQwSqq1KMz4wVMaXXL+igsLrr4VKKmdfExwlB6o7Ie8kScXg4camZmt7TQ=="
}, },
"@fontsource/fira-mono": { "@fontsource/open-sans": {
"version": "4.2.2", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/fira-mono/-/fira-mono-4.2.2.tgz", "resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-4.2.2.tgz",
"integrity": "sha512-t2WRThg+eLkQNQCtPG2sCCq40lz3xeb7nsL7P8l4+wfSRbdLQXAY5IebMftI2YEZR4MRRhdgrg0p5fi/2yXypA==" "integrity": "sha512-NbsL1a9asJO6N/5kRxYPCy0kNhKMi9T75kl4QfIGtmpd/5IfB+UIAUxd9AICmCLaH4Osc2TImeTJj94xc9MNKg=="
}, },
"@fontsource/righteous": { "@fontsource/righteous": {
"version": "4.2.2", "version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/righteous/-/righteous-4.2.2.tgz", "resolved": "https://registry.npmjs.org/@fontsource/righteous/-/righteous-4.2.2.tgz",
"integrity": "sha512-mUjFblfCV6eWZj+lkrXFZsER8pq/3LOCoT3ezKPcerYH7StXQ8Gflcs0uMqacZP7CVLyzVUkPvSgLMQJTQvypg==" "integrity": "sha512-mUjFblfCV6eWZj+lkrXFZsER8pq/3LOCoT3ezKPcerYH7StXQ8Gflcs0uMqacZP7CVLyzVUkPvSgLMQJTQvypg=="
}, },
"@fontsource/work-sans": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/work-sans/-/work-sans-4.2.2.tgz",
"integrity": "sha512-fFm8a1TbE+qDnRNsf4R6Z/yZP2f3mbj54zSoWUMIWVkgTHM0RfpcOTqoIp7Aj5jon6na0Oynlq5yXvMA5p3pKA=="
},
"@mdi/svg": { "@mdi/svg": {
"version": "5.9.55", "version": "5.9.55",
"resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-5.9.55.tgz", "resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-5.9.55.tgz",
@ -11394,6 +11439,12 @@
"@babel/helper-define-polyfill-provider": "^0.1.5" "@babel/helper-define-polyfill-provider": "^0.1.5"
} }
}, },
"babel-plugin-prismjs": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/babel-plugin-prismjs/-/babel-plugin-prismjs-2.0.1.tgz",
"integrity": "sha512-GqQGa3xX3Z2ft97oDbGvEFoxD8nKqb3ZVszrOc5H7icnEUA56BIjVYm86hfZZA82uuHLwTIfCXbEKzKG1BzKzg==",
"requires": {}
},
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -11738,6 +11789,17 @@
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
"dev": true "dev": true
}, },
"clipboard": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.8.tgz",
"integrity": "sha512-Y6WO0unAIQp5bLmk1zdThRhgJt/x3ks6f30s3oE3H1mgIEU33XyQjEf8gsf6DxC7NPX8Y1SsNWjUjL/ywLnnbQ==",
"optional": true,
"requires": {
"good-listener": "^1.2.2",
"select": "^1.1.2",
"tiny-emitter": "^2.0.0"
}
},
"cliui": { "cliui": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@ -12356,6 +12418,12 @@
"is-descriptor": "^1.0.0" "is-descriptor": "^1.0.0"
} }
}, },
"delegate": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz",
"integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
"optional": true
},
"dir-glob": { "dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -12979,6 +13047,15 @@
"slash": "^3.0.0" "slash": "^3.0.0"
} }
}, },
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
"integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=",
"optional": true,
"requires": {
"delegate": "^3.1.2"
}
},
"graceful-fs": { "graceful-fs": {
"version": "4.2.6", "version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
@ -15706,6 +15783,14 @@
"posthtml-render": "^1.0.6" "posthtml-render": "^1.0.6"
} }
}, },
"prismjs": {
"version": "1.23.0",
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.23.0.tgz",
"integrity": "sha512-c29LVsqOaLbBHuIbsTxaKENh1N2EQBOHaWv7gkHN4dgRbxSREqDnDbtFJYdpPauS4YCplMSNCABQ6Eeor69bAA==",
"requires": {
"clipboard": "^2.0.0"
}
},
"promise-inflight": { "promise-inflight": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@ -16072,6 +16157,12 @@
"ajv-keywords": "^3.5.2" "ajv-keywords": "^3.5.2"
} }
}, },
"select": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
"integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=",
"optional": true
},
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@ -16930,6 +17021,12 @@
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
"dev": true "dev": true
}, },
"tiny-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
"integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
"optional": true
},
"to-fast-properties": { "to-fast-properties": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",

View file

@ -8,17 +8,18 @@
}, },
"dependencies": { "dependencies": {
"@fontsource/fira-code": "^4.x", "@fontsource/fira-code": "^4.x",
"@fontsource/fira-mono": "^4.x", "@fontsource/open-sans": "^4.2.2",
"@fontsource/righteous": "^4.x", "@fontsource/righteous": "^4.x",
"@fontsource/work-sans": "^4.2.2",
"@mdi/svg": "^5.x", "@mdi/svg": "^5.x",
"@popperjs/core": "^2.x", "@popperjs/core": "^2.x",
"babel-plugin-prismjs": "^2.0.1",
"bootstrap": "^5.0.0-beta3", "bootstrap": "^5.0.0-beta3",
"bootstrap-icons": "^1.x", "bootstrap-icons": "^1.x",
"hamburgers": "^1.x", "hamburgers": "^1.x",
"phoenix": "file:../deps/phoenix", "phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html", "phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view", "phoenix_live_view": "file:../deps/phoenix_live_view",
"prismjs": "^1.23.0",
"topbar": "^1.x" "topbar": "^1.x"
}, },
"devDependencies": { "devDependencies": {

View file

@ -93,7 +93,7 @@ module.exports = (env, options) => {
"../**/live/**/*.ex", "../**/live/**/*.ex",
"./js/**/*.js", "./js/**/*.js",
]), ]),
safelist: [/phx/, /topbar/], safelist: [/phx/, /topbar/, /linenos/, /pygments/],
}), }),
] ]
), ),

View file

@ -11,7 +11,10 @@ use Mix.Config
config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
# Custom application global variables # Custom application global variables
config :home73k, :app_global_vars, time_zone: "America/New_York" config :home73k, :app_global_vars,
time_zone: "America/New_York",
blog_content: "priv/content",
pygmentize_bin: "priv/pygments/bin/pygmentize"
# Configures the endpoint # Configures the endpoint
config :home73k, Home73kWeb.Endpoint, config :home73k, Home73kWeb.Endpoint,

View file

@ -8,7 +8,9 @@ defmodule Home73k do
""" """
@app_vars Application.compile_env(:home73k, :app_global_vars, time_zone: "America/New_York") @app_vars Application.compile_env(:home73k, :app_global_vars, time_zone: "America/New_York")
@app_time_zone @app_vars[:time_zone]
def app_time_zone, do: @app_time_zone def app_vars, do: @app_vars
def app_time_zone, do: @app_vars[:time_zone]
def app_blog_content, do: @app_vars[:blog_content]
def app_pygmentize_bin, do: @app_vars[:pygmentize_bin]
end end

View file

@ -3,7 +3,7 @@ defmodule Home73k.Blog do
Application.ensure_all_started(:earmark) Application.ensure_all_started(:earmark)
posts_paths = "priv/content/**/*.md" |> Path.wildcard() posts_paths = "#{Home73k.app_blog_content()}/**/*.md" |> Path.wildcard()
posts = posts =
for post_path <- posts_paths do for post_path <- posts_paths do
@ -11,7 +11,7 @@ defmodule Home73k.Blog do
Post.parse!(post_path) Post.parse!(post_path)
end end
@posts Enum.sort_by(posts, & &1.date, {:desc, NaiveDateTime}) @posts Enum.sort_by(posts, & &1.date, {:desc, Date})
@tags posts |> Stream.flat_map(& &1.tags) |> Stream.uniq() |> Enum.sort() @tags posts |> Stream.flat_map(& &1.tags) |> Stream.uniq() |> Enum.sort()

View file

@ -1,4 +1,6 @@
defmodule Home73k.Blog.Post do defmodule Home73k.Blog.Post do
alias Home73k.Highlighter
@enforce_keys [:title, :id, :date, :author, :tags, :lede, :body, :corpus] @enforce_keys [:title, :id, :date, :author, :tags, :lede, :body, :corpus]
defstruct [:title, :id, :date, :author, :tags, :lede, :body, :corpus] defstruct [:title, :id, :date, :author, :tags, :lede, :body, :corpus]
@ -59,12 +61,10 @@ defmodule Home73k.Blog.Post do
defp parse_lede(_), do: nil defp parse_lede(_), do: nil
# """ parse_body/1 # """ parse_body/1
# Convert body markdown to html # Convert body markdown to html, and highlight code fence blocks
# TODO: handle syntax highlighting
defp parse_body({fm, md}) do defp parse_body({fm, md}) do
Map.put(fm, :body, Earmark.as_html!(md)) html = Earmark.as_html!(md) |> Highlighter.highlight_code_blocks()
# TODO: Earmark.as_ast(md) |> parse_body(fm) Map.put(fm, :body, html)
# def parse_body({:ok, ast, _}, fm)
end end
defp parse_body(_), do: nil defp parse_body(_), do: nil
@ -128,8 +128,10 @@ defmodule Home73k.Blog.Post do
# Handle split of post body. If lede found, return as html with body. # Handle split of post body. If lede found, return as html with body.
# Otherwise return nil with body. # Otherwise return nil with body.
# """ # """
defp extract_lede([lede, body]), defp extract_lede([lede, body]) do
do: {String.trim_trailing(lede) |> Earmark.as_html!(), String.trim_leading(body)} lede_html = String.trim_trailing(lede) |> Earmark.as_html!() |> Highlighter.highlight_code_blocks()
{lede_html, String.trim_leading(body)}
end
defp extract_lede([body]), do: {nil, body} defp extract_lede([body]), do: {nil, body}

View file

@ -0,0 +1,52 @@
defmodule Home73k.Highlighter do
@moduledoc """
Performs code highlighting.
"""
alias Home73k.Temp
@pygments_cmd Home73k.app_pygmentize_bin() |> Path.expand()
@doc """
Highlights all code block in an already generated HTML document.
"""
def highlight_code_blocks(html) do
~r/<pre><code(?:\s+class="(\w*)")?>([^<]*)<\/code><\/pre>/
|> Regex.replace(html, &highlight_code_block(&1, &2, &3))
end
defp highlight_code_block(_full_block, lang, code) do
# unescape the code
unescaped_code = unescape_html(code) |> IO.iodata_to_binary()
# write code to temp file
tmp_file = Temp.file()
File.write!(tmp_file, unescaped_code)
# pygmentize the code via temp file
pyg_args = ["-l", lang, "-f", "html", "-O", "cssclass=pygments", tmp_file]
{highlighted, _} = System.cmd(@pygments_cmd, pyg_args)
# correct pygment wrapping markup
highlighted
|> String.replace("<span></span>", "")
|> String.replace("<div class=\"pygments\"><pre>", "<pre class=\"pygments\"><code class=\"language-#{lang}\">")
|> String.replace("</pre></div>", "</code></pre>")
end
entities = [{"&amp;", ?&}, {"&lt;", ?<}, {"&gt;", ?>}, {"&quot;", ?"}, {"&#39;", ?'}]
for {encoded, decoded} <- entities do
defp unescape_html(unquote(encoded) <> rest) do
[unquote(decoded) | unescape_html(rest)]
end
end
defp unescape_html(<<c, rest::binary>>) do
[c | unescape_html(rest)]
end
defp unescape_html(<<>>) do
[]
end
end

19
lib/home73k/temp.ex Normal file
View file

@ -0,0 +1,19 @@
defmodule Home73k.Temp do
@moduledoc """
Simple module to generate temporary files
"""
def file do
System.tmp_dir!()
|> Path.join(random_filename())
|> touch_file()
end
defp random_filename do
:crypto.strong_rand_bytes(32) |> Base.url_encode64 |> binary_part(0, 32)
end
defp touch_file(fdname) do
File.touch!(fdname)
fdname
end
end

View file

@ -93,6 +93,9 @@ defmodule Home73kWeb do
# Import SVG Icon helper # Import SVG Icon helper
import Home73kWeb.IconHelpers import Home73kWeb.IconHelpers
# Import Date formatter helper
import Home73kWeb.DateHelpers
import Home73kWeb.ErrorHelpers import Home73kWeb.ErrorHelpers
import Home73kWeb.Gettext import Home73kWeb.Gettext
alias Home73kWeb.Router.Helpers, as: Routes alias Home73kWeb.Router.Helpers, as: Routes

View file

@ -48,7 +48,4 @@ defmodule Home73kWeb.BlogLive do
# do: {app, vsn} # do: {app, vsn}
# end # end
defp format_date(date) do
Calendar.strftime(date, "%B %-d, %Y")
end
end end

View file

@ -1,22 +1,22 @@
<main class="container d-flex justify-content-center"> <main class="container d-flex justify-content-center">
<div class="col-12 col-md-10 col-lg-8 col-xl-7 col-xxl-6 pb-2 mb-4 mt-3"> <div class="col-12 col-md-10 col-lg-9 col-xl-8 col-xxl-7 pb-2 mb-4 mt-3">
<%= for post <- @posts do %> <%= for post <- @posts do %>
<div class="post border-bottom border-gray pb-4 mb-3"> <div class="post border-bottom border-gray pb-4 mb-3">
<h2 class="post-title fs-2 fw-normal mb-2"> <h2 class="post-title fs-2 fw-600 mb-2">
<%= live_redirect "#{post.title}", to: Routes.post_path(@socket, :show, post) %> <%= live_redirect "#{post.title}", to: Routes.post_path(@socket, :show, post) %>
</h2> </h2>
<div class="post-date font-monospace text-gray-500 <%= if length(post.tags) == 0, do: "mb-3" %>"> <div class="post-date font-monospace text-gray-400 <%= if length(post.tags) == 0, do: "mb-3" %>">
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline"] %> <%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline me-2"] %><%= post.date |> format_date() %>
<%= post.date |> format_date() %> by <%= icon_div @socket, "mdi-account", [class: "icon baseline me-1"] %>Adam Piontek
</div> </div>
<%= if length(post.tags) > 0 do %> <%= if length(post.tags) > 0 do %>
<div class="post-tags fs-6 mb-3"> <div class="post-tags fs-smaller mb-3">
<%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %> <%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %>
<%= for {tag, i} <- Enum.with_index(post.tags) do %> <%= for {tag, i} <- Enum.with_index(post.tags) do %>
#<%= tag %><%= i < (length(post.tags) - 1) && "," || "" %> #<%= tag %><%= i < (length(post.tags) - 1) && "," || "" %>
@ -24,7 +24,7 @@
</div> </div>
<% end %> <% end %>
<div class="post-lede lead"> <div class="post-lede">
<%= raw post.lede %> <%= raw post.lede %>
</div> </div>

View file

@ -55,7 +55,4 @@ defmodule Home73kWeb.PostLive do
# do: {app, vsn} # do: {app, vsn}
# end # end
defp format_date(date) do
Calendar.strftime(date, "%B %-d, %Y")
end
end end

View file

@ -6,13 +6,13 @@
<h2 class="post-title fs-2 fw-normal mb-2"><%= raw @post.title %></h2> <h2 class="post-title fs-2 fw-normal mb-2"><%= raw @post.title %></h2>
<div class="post-date font-monospace text-gray-500 <%= if length(@post.tags) == 0, do: "mb-3" %>"> <div class="post-date font-monospace text-gray-400 <%= if length(@post.tags) == 0, do: "mb-3" %>">
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline"] %> <%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline me-2"] %><%= @post.date |> format_date() %>
<%= @post.date |> format_date() %> by <%= icon_div @socket, "mdi-account", [class: "icon baseline me-1"] %>Adam Piontek
</div> </div>
<%= if length(@post.tags) > 0 do %> <%= if length(@post.tags) > 0 do %>
<div class="post-tags fs-6 mb-3"> <div class="post-tags fs-smaller mb-3">
<%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %> <%= icon_div @socket, "mdi-tag-multiple", [class: "icon baseline"] %>
<%= for {tag, i} <- Enum.with_index(@post.tags) do %> <%= for {tag, i} <- Enum.with_index(@post.tags) do %>
#<%= tag %><%= i < (length(@post.tags) - 1) && "," || "" %> #<%= tag %><%= i < (length(@post.tags) - 1) && "," || "" %>
@ -20,7 +20,7 @@
</div> </div>
<% end %> <% end %>
<div class="post-lede lead"> <div class="post-lede">
<%= raw @post.lede %> <%= raw @post.lede %>
</div> </div>

View file

@ -1,6 +1,6 @@
<main class="container d-flex justify-content-center"> <main class="container d-flex justify-content-center">
<div class="col-12 col-md-10 col-lg-8 col-xl-7 col-xxl-6 border-bottom border-gray pb-3 mb-5 mt-3"> <div class="col-12 col-md-10 col-lg-9 col-xl-8 col-xxl-7 border-bottom border-gray pb-3 mb-5 mt-3">
<h2 class="fs-2 fw-600 mb-0">About</h2> <h2 class="fs-2 fw-600 mb-0">About</h2>

View file

@ -13,7 +13,7 @@
<div class="col-12 col-sm-10 col-md-7 col-lg-6 col-xl-5 col-xxl-4 justify-content-start ms-lg-3"> <div class="col-12 col-sm-10 col-md-7 col-lg-6 col-xl-5 col-xxl-4 justify-content-start ms-lg-3">
<h2 class="font-monospace fs-2 fw-500 mb-0"> <h2 class="fs-2 fw-600 mb-0">
<%= icon_div @conn, "mdi-account-hard-hat", [class: "icon baseline me-2"] %><span>Working on it!</span> <%= icon_div @conn, "mdi-account-hard-hat", [class: "icon baseline me-2"] %><span>Working on it!</span>
</h2> </h2>

View file

@ -13,7 +13,7 @@
<div class="col-auto justify-content-start ms-lg-3"> <div class="col-auto justify-content-start ms-lg-3">
<h2 class="font-monospace fs-2 fw-500 mb-0"> <h2 class="fs-2 fw-600 mb-0">
<%= icon_div @conn, "mdi-account", [class: "icon baseline me-2"] %><span>Adam Piontek</span> <%= icon_div @conn, "mdi-account", [class: "icon baseline me-2"] %><span>Adam Piontek</span>
</h2> </h2>

View file

@ -4,7 +4,7 @@
<div class="col-auto justify-content-start"> <div class="col-auto justify-content-start">
<h2 class="font-monospace fs-2 fw-500 mb-0"> <h2 class="fs-2 fw-600 mb-0">
<%= icon_div @conn, "mdi-account", [class: "icon baseline me-2"] %><span>Adam Piontek</span> <%= icon_div @conn, "mdi-account", [class: "icon baseline me-2"] %><span>Adam Piontek</span>
</h2> </h2>

View file

@ -0,0 +1,9 @@
defmodule Home73kWeb.DateHelpers do
@moduledoc """
Formatters for dates
"""
def format_date(date) do
Calendar.strftime(date, "%B %-d, %Y")
end
end

View file

@ -44,6 +44,7 @@
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.0", "da9d49ee7e6bb1c259d36ce6539cd45ae14d81247a2b0c90edf55e2b50507f7b", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5cfe67ad464b243835512aa44321cee91faed6ea868d7fb761d7016e02915c3d"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.0", "da9d49ee7e6bb1c259d36ce6539cd45ae14d81247a2b0c90edf55e2b50507f7b", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5cfe67ad464b243835512aa44321cee91faed6ea868d7fb761d7016e02915c3d"},
"telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"}, "telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"},
"temp": {:hex, :temp, "0.4.7", "2c78482cc2294020a4bc0c95950b907ff386523367d4e63308a252feffbea9f2", [:mix], [], "hexpm", "6af19e7d6a85a427478be1021574d1ae2a1e1b90882586f06bde76c63cd03e0d"},
"toml": {:hex, :toml, "0.5.2", "e471388a8726d1ce51a6b32f864b8228a1eb8edc907a0edf2bb50eab9321b526", [:mix], [], "hexpm", "f1e3dabef71fb510d015fad18c0e05e7c57281001141504c6b69d94e99750a07"}, "toml": {:hex, :toml, "0.5.2", "e471388a8726d1ce51a6b32f864b8228a1eb8edc907a0edf2bb50eab9321b526", [:mix], [], "hexpm", "f1e3dabef71fb510d015fad18c0e05e7c57281001141504c6b69d94e99750a07"},
"tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"}, "tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},

View file

@ -2,7 +2,7 @@
%{ %{
title: "Pi-Hole Love", title: "Pi-Hole Love",
id: "pihole-love", id: "pihole-love",
date: ~N[2018-03-24 14:00:00], date: ~D[2018-03-24],
author: "Adam Piontek", author: "Adam Piontek",
tags: ["home", "privacy", "tech", "raspberrypi"], tags: ["home", "privacy", "tech", "raspberrypi"],
} }

View file

@ -2,7 +2,7 @@
%{ %{
title: "If no one will do it, it neednt be done — on BS jobs and toddler bosses", title: "If no one will do it, it neednt be done — on BS jobs and toddler bosses",
id: "if-noone-will-do-it-neednt-be-done-bs-jobs-toddler-bosses", id: "if-noone-will-do-it-neednt-be-done-bs-jobs-toddler-bosses",
date: ~N[2018-05-17 15:10:00], date: ~D[2018-05-17],
author: "Adam Piontek", author: "Adam Piontek",
tags: ["economics", "society", "work"], tags: ["economics", "society", "work"],
} }

View file

@ -2,7 +2,7 @@
%{ %{
title: "Lana Del Arr's Ultrapirates (parody lyrics for songs from Lana Del Reys Ultraviolence", title: "Lana Del Arr's Ultrapirates (parody lyrics for songs from Lana Del Reys Ultraviolence",
id: "lana-del-arr-ultrapirates-parody-lyrics", id: "lana-del-arr-ultrapirates-parody-lyrics",
date: ~N[2018-11-09 13:30:00], date: ~D[2018-11-09],
author: "Adam Piontek", author: "Adam Piontek",
tags: ["song", "lyrics", "lanadelrey", "music", "parody", "pirates"], tags: ["song", "lyrics", "lanadelrey", "music", "parody", "pirates"],
} }

View file

@ -2,7 +2,7 @@
%{ %{
title: "Creating a gif from a video", title: "Creating a gif from a video",
id: "creating-gif-from-video", id: "creating-gif-from-video",
date: ~N[2020-05-23 04:41:00], date: ~D[2020-05-23],
author: "Adam Piontek", author: "Adam Piontek",
tags: ["cli", "ffmpeg ", "gif", "multimedia", "video", "notes"], tags: ["cli", "ffmpeg ", "gif", "multimedia", "video", "notes"],
} }

View file

@ -1,7 +1,7 @@
--- ---
%{ %{
title: "Enable Visual Studio CLI environment in PowerShell", title: "Enable Visual Studio CLI environment in PowerShell",
date: ~N[2020-08-01 02:00:00], date: ~D[2020-08-01],
author: "Adam Piontek", author: "Adam Piontek",
tags: ["coding", "tech", "elixir", "windows", "powershell", "scripting"], tags: ["coding", "tech", "elixir", "windows", "powershell", "scripting"],
} }

View file

@ -0,0 +1,23 @@
---
%{
title: "Shame on American leadership",
id: "shame-on-american-leadership",
date: ~D[2020-12-07],
author: "Adam Piontek",
tags: ["politics", "society", "policy", "pandemic"]
}
---
If you haven't read this yet, you should. But you're not going to feel good:
"[Headlines Dont Capture the Horror We Saw](https://www.theatlantic.com/ideas/archive/2020/12/new-york-doctors-know-how-bad-pandemic-can-get/617302/)" (subtitle: "I chronicled what COVID-19 did to a hospital. America must not let down its guard.")
<!--more-->
I'd blockquote something but it should really be read in full.
My partner shared this experience as a nurse at a big NY hospital.
It's abominable that this was allowed to get so bad, and allowed to continue across the country, and is being allowed to get so bad again.
Shame on our leadership. Shame on America.

View file

@ -1,7 +1,7 @@
--- ---
%{ %{
title: "Mom's Meatloaf", title: "Mom's Meatloaf",
date: ~N[2020-12-29 01:00:00], date: ~D[2020-12-29],
author: "Adam Piontek", author: "Adam Piontek",
tags: ["food", "recipe", "mealprep", "pandemiceats", "plaguecooking"] tags: ["food", "recipe", "mealprep", "pandemiceats", "plaguecooking"]
} }