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
/.elixir_ls
# Ignore the pygments venv directory
/priv/pygments/
# dev
TODO.md

View file

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

View file

@ -1,19 +1,26 @@
# Home73k
To start your Phoenix server:
Personal website with blog.
* Install dependencies with `mix deps.get`
* Install Node.js dependencies with `npm install` inside the `assets` directory
* Start Phoenix endpoint with `mix phx.server`
## Blog posts
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/
* Guides: https://hexdocs.pm/phoenix/overview.html
* Docs: https://hexdocs.pm/phoenix
* Forum: https://elixirforum.com/c/phoenix-forum
* Source: https://github.com/phoenixframework/phoenix
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)
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.
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.
// stylelint-disable value-keyword-case
$font-family-sans-serif: "Work Sans", system-ui, -apple-system, "Segoe UI", Roboto,
$font-size-base: 1.125rem;
$font-family-sans-serif: "Open Sans", system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
$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,
"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;
$font-family-code: "Fira Code", "Fira Mono", SFMono-Regular, Menlo, Monaco,
Consolas, "Liberation Mono", "Courier New", monospace !important;
// Features
$enable-shadows: true;

View file

@ -1,35 +1,23 @@
/* Fontsource Work Sans */
@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 */
/* brand : Fontsource Righteous */
@import "../node_modules/@fontsource/righteous/400.css"; /* normal | normal */
/* Fontsource Fira Mono */
@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 */
/* monospace : Fontsource Fira Code */
@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/500.css"; /* heavier normal? */
// @import "../node_modules/@fontsource/fira-code/600.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/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 */
@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 */
html,
body {
@ -24,12 +47,12 @@ body {
height: 100%;
}
a {
color: $primary;
color: $secondary;
&:visited {
color: $info;
}
&:hover {
color: $secondary;
color: $primary;
}
}
.border-gray-900 {
@ -59,15 +82,9 @@ a {
.fw-600 {
font-weight: 600;
}
.font-sans-serif {
font-family: $font-family-sans-serif;
}
.font-brand {
font-family: $font-family-brand;
}
.font-code {
font-family: $font-family-code;
}
.fs-larger {
font-size: larger;
}
@ -124,10 +141,8 @@ a {
margin-top: 2rem;
}
}
.post-lede.lead {
font-size: 1.25rem;
font-weight: 300;
color: $gray-300;
.post li {
line-height: 1.75rem;
}
/* extra */

View file

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

133
assets/package-lock.json generated
View file

@ -7,17 +7,18 @@
"license": "MIT",
"dependencies": {
"@fontsource/fira-code": "^4.x",
"@fontsource/fira-mono": "^4.x",
"@fontsource/open-sans": "^4.2.2",
"@fontsource/righteous": "^4.x",
"@fontsource/work-sans": "^4.2.2",
"@mdi/svg": "^5.x",
"@popperjs/core": "^2.x",
"babel-plugin-prismjs": "^2.0.1",
"bootstrap": "^5.0.0-beta3",
"bootstrap-icons": "^1.x",
"hamburgers": "^1.x",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"phoenix_live_view": "file:../deps/phoenix_live_view",
"prismjs": "^1.23.0",
"topbar": "^1.x"
},
"devDependencies": {
@ -1287,21 +1288,16 @@
"resolved": "https://registry.npmjs.org/@fontsource/fira-code/-/fira-code-4.2.2.tgz",
"integrity": "sha512-Bhg7rQ/CUbedA6B6K6gS2GDEa5JJjQwSqq1KMz4wVMaXXL+igsLrr4VKKmdfExwlB6o7Ie8kScXg4camZmt7TQ=="
},
"node_modules/@fontsource/fira-mono": {
"node_modules/@fontsource/open-sans": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/fira-mono/-/fira-mono-4.2.2.tgz",
"integrity": "sha512-t2WRThg+eLkQNQCtPG2sCCq40lz3xeb7nsL7P8l4+wfSRbdLQXAY5IebMftI2YEZR4MRRhdgrg0p5fi/2yXypA=="
"resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-4.2.2.tgz",
"integrity": "sha512-NbsL1a9asJO6N/5kRxYPCy0kNhKMi9T75kl4QfIGtmpd/5IfB+UIAUxd9AICmCLaH4Osc2TImeTJj94xc9MNKg=="
},
"node_modules/@fontsource/righteous": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/righteous/-/righteous-4.2.2.tgz",
"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": {
"version": "5.9.55",
"resolved": "https://registry.npmjs.org/@mdi/svg/-/svg-5.9.55.tgz",
@ -1876,6 +1872,14 @@
"@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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -2315,6 +2319,17 @@
"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": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@ -3133,6 +3148,12 @@
"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": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -3946,6 +3967,15 @@
"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": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
@ -7588,6 +7618,14 @@
"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": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@ -8099,6 +8137,12 @@
"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": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@ -9164,6 +9208,12 @@
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
"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": {
"version": "2.0.0",
"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",
"integrity": "sha512-Bhg7rQ/CUbedA6B6K6gS2GDEa5JJjQwSqq1KMz4wVMaXXL+igsLrr4VKKmdfExwlB6o7Ie8kScXg4camZmt7TQ=="
},
"@fontsource/fira-mono": {
"@fontsource/open-sans": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/fira-mono/-/fira-mono-4.2.2.tgz",
"integrity": "sha512-t2WRThg+eLkQNQCtPG2sCCq40lz3xeb7nsL7P8l4+wfSRbdLQXAY5IebMftI2YEZR4MRRhdgrg0p5fi/2yXypA=="
"resolved": "https://registry.npmjs.org/@fontsource/open-sans/-/open-sans-4.2.2.tgz",
"integrity": "sha512-NbsL1a9asJO6N/5kRxYPCy0kNhKMi9T75kl4QfIGtmpd/5IfB+UIAUxd9AICmCLaH4Osc2TImeTJj94xc9MNKg=="
},
"@fontsource/righteous": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@fontsource/righteous/-/righteous-4.2.2.tgz",
"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": {
"version": "5.9.55",
"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-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": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -11738,6 +11789,17 @@
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
"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": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@ -12356,6 +12418,12 @@
"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": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@ -12979,6 +13047,15 @@
"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": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
@ -15706,6 +15783,14 @@
"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": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@ -16072,6 +16157,12 @@
"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": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@ -16930,6 +17021,12 @@
"integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=",
"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": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",

View file

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

View file

@ -93,7 +93,7 @@ module.exports = (env, options) => {
"../**/live/**/*.ex",
"./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
# 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
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_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

View file

@ -3,7 +3,7 @@ defmodule Home73k.Blog do
Application.ensure_all_started(:earmark)
posts_paths = "priv/content/**/*.md" |> Path.wildcard()
posts_paths = "#{Home73k.app_blog_content()}/**/*.md" |> Path.wildcard()
posts =
for post_path <- posts_paths do
@ -11,7 +11,7 @@ defmodule Home73k.Blog do
Post.parse!(post_path)
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()

View file

@ -1,4 +1,6 @@
defmodule Home73k.Blog.Post do
alias Home73k.Highlighter
@enforce_keys [: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
# """ parse_body/1
# Convert body markdown to html
# TODO: handle syntax highlighting
# Convert body markdown to html, and highlight code fence blocks
defp parse_body({fm, md}) do
Map.put(fm, :body, Earmark.as_html!(md))
# TODO: Earmark.as_ast(md) |> parse_body(fm)
# def parse_body({:ok, ast, _}, fm)
html = Earmark.as_html!(md) |> Highlighter.highlight_code_blocks()
Map.put(fm, :body, html)
end
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.
# Otherwise return nil with body.
# """
defp extract_lede([lede, body]),
do: {String.trim_trailing(lede) |> Earmark.as_html!(), String.trim_leading(body)}
defp extract_lede([lede, body]) do
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}

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 Home73kWeb.IconHelpers
# Import Date formatter helper
import Home73kWeb.DateHelpers
import Home73kWeb.ErrorHelpers
import Home73kWeb.Gettext
alias Home73kWeb.Router.Helpers, as: Routes

View file

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

View file

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

View file

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

View file

@ -6,13 +6,13 @@
<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" %>">
<%= icon_div @socket, "mdi-calendar-clock", [class: "icon baseline"] %>
<%= @post.date |> format_date() %>
<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 me-2"] %><%= @post.date |> format_date() %>
by <%= icon_div @socket, "mdi-account", [class: "icon baseline me-1"] %>Adam Piontek
</div>
<%= 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"] %>
<%= for {tag, i} <- Enum.with_index(@post.tags) do %>
#<%= tag %><%= i < (length(@post.tags) - 1) && "," || "" %>
@ -20,7 +20,7 @@
</div>
<% end %>
<div class="post-lede lead">
<div class="post-lede">
<%= raw @post.lede %>
</div>

View file

@ -1,6 +1,6 @@
<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>

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">
<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>
</h2>

View file

@ -13,7 +13,7 @@
<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>
</h2>

View file

@ -4,7 +4,7 @@
<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>
</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_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"},
"temp": {:hex, :temp, "0.4.7", "2c78482cc2294020a4bc0c95950b907ff386523367d4e63308a252feffbea9f2", [:mix], [], "hexpm", "6af19e7d6a85a427478be1021574d1ae2a1e1b90882586f06bde76c63cd03e0d"},
"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"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},

View file

@ -2,7 +2,7 @@
%{
title: "Pi-Hole Love",
id: "pihole-love",
date: ~N[2018-03-24 14:00:00],
date: ~D[2018-03-24],
author: "Adam Piontek",
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",
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",
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",
id: "lana-del-arr-ultrapirates-parody-lyrics",
date: ~N[2018-11-09 13:30:00],
date: ~D[2018-11-09],
author: "Adam Piontek",
tags: ["song", "lyrics", "lanadelrey", "music", "parody", "pirates"],
}

View file

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

View file

@ -1,7 +1,7 @@
---
%{
title: "Enable Visual Studio CLI environment in PowerShell",
date: ~N[2020-08-01 02:00:00],
date: ~D[2020-08-01],
author: "Adam Piontek",
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",
date: ~N[2020-12-29 01:00:00],
date: ~D[2020-12-29],
author: "Adam Piontek",
tags: ["food", "recipe", "mealprep", "pandemiceats", "plaguecooking"]
}