syntax highlighting and blog/post liveviews working; numerous other styling updates
This commit is contained in:
parent
a6186ba639
commit
2218a678b1
35 changed files with 610 additions and 120 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -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
|
||||
|
|
1
.iex.exs
1
.iex.exs
|
@ -1,2 +1,3 @@
|
|||
alias Home73k.Temp
|
||||
alias Home73k.Blog
|
||||
alias Home73k.Blog.Post
|
||||
|
|
31
README.md
31
README.md
|
@ -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"`
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
269
assets/css/_pygments.css
Normal 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 */
|
|
@ -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 */
|
||||
|
|
|
@ -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
133
assets/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -93,7 +93,7 @@ module.exports = (env, options) => {
|
|||
"../**/live/**/*.ex",
|
||||
"./js/**/*.js",
|
||||
]),
|
||||
safelist: [/phx/, /topbar/],
|
||||
safelist: [/phx/, /topbar/, /linenos/, /pygments/],
|
||||
}),
|
||||
]
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
52
lib/home73k/highlighter.ex
Normal file
52
lib/home73k/highlighter.ex
Normal 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 = [{"&", ?&}, {"<", ?<}, {">", ?>}, {""", ?"}, {"'", ?'}]
|
||||
|
||||
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
19
lib/home73k/temp.ex
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
9
lib/home73k_web/views/date_helpers.ex
Normal file
9
lib/home73k_web/views/date_helpers.ex
Normal 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
|
1
mix.lock
1
mix.lock
|
@ -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"},
|
||||
|
|
|
@ -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"],
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
%{
|
||||
title: "If no one will do it, it needn’t 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"],
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
%{
|
||||
title: "Lana Del Arr's ‘Ultrapirates’ (parody lyrics for songs from Lana Del Rey’s ‘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"],
|
||||
}
|
||||
|
|
|
@ -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"],
|
||||
}
|
||||
|
|
|
@ -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"],
|
||||
}
|
||||
|
|
23
priv/content/2020/12/2020-12-07_shame-american-leadership.md
Normal file
23
priv/content/2020/12/2020-12-07_shame-american-leadership.md
Normal 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 Don’t 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.
|
|
@ -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"]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue