Refactor app in to its own slice
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import "../../../../app/assets/builds/app.css";
|
||||
import "../../../../slices/main/assets/builds/app.css";
|
||||
import "../css/app.css";
|
||||
|
||||
import TinyMDE from "tiny-markdown-editor";
|
||||
|
8
slices/main/action.rb
Normal file
8
slices/main/action.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
# auto_register: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Main
|
||||
class Action < Adamantium::Action
|
||||
|
||||
end
|
||||
end
|
0
slices/main/actions/.keep
Normal file
0
slices/main/actions/.keep
Normal file
15
slices/main/actions/blogroll/index.rb
Normal file
15
slices/main/actions/blogroll/index.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
require "time_math"
|
||||
|
||||
module Main
|
||||
module Actions
|
||||
module Blogroll
|
||||
class Index < Action
|
||||
include Deps["views.blogroll.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
15
slices/main/actions/blogroll/list.rb
Normal file
15
slices/main/actions/blogroll/list.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
require "time_math"
|
||||
|
||||
module Main
|
||||
module Actions
|
||||
module Blogroll
|
||||
class List < Action
|
||||
include Deps["views.blogroll.list"]
|
||||
|
||||
def handle(req, res)
|
||||
res.body = cache(key: "blogroll", content_proc: -> { list.call.to_str }, expiry: TimeMath.min.advance(Time.now, +60))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
slices/main/actions/blogroll/opml.rb
Normal file
16
slices/main/actions/blogroll/opml.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
require "time_math"
|
||||
|
||||
module Main
|
||||
module Actions
|
||||
module Blogroll
|
||||
class Opml < Action
|
||||
include Deps["views.blogroll.opml"]
|
||||
|
||||
def handle(req, res)
|
||||
res.content_type = "text/xml; charset=utf-8"
|
||||
res.render opml, format: :xml
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/bookmarks/index.rb
Normal file
13
slices/main/actions/bookmarks/index.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Bookmarks
|
||||
class Index < Action
|
||||
include Deps["views.bookmarks.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render index, query: req.params[:q]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/bookmarks/metadata.rb
Normal file
13
slices/main/actions/bookmarks/metadata.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Bookmarks
|
||||
class Metadata < Action
|
||||
include Deps["views.bookmarks.metadata"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render metadata, id: req.params[:id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
12
slices/main/actions/bookmarks/show.rb
Normal file
12
slices/main/actions/bookmarks/show.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Bookmarks
|
||||
class Show < Action
|
||||
include Deps["views.bookmarks.show"]
|
||||
def handle(req, res)
|
||||
res.render show, slug: req.params[:slug]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/books/index.rb
Normal file
13
slices/main/actions/books/index.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Books
|
||||
class Index < Action
|
||||
include Deps["views.books.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
14
slices/main/actions/feeds/rss.rb
Normal file
14
slices/main/actions/feeds/rss.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Feeds
|
||||
class Rss < Action
|
||||
include Deps["views.feeds.rss"]
|
||||
|
||||
def handle(req, res)
|
||||
res.content_type = "text/xml; charset=utf-8"
|
||||
res.render rss, format: :xml
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
14
slices/main/actions/feeds/statuses_rss.rb
Normal file
14
slices/main/actions/feeds/statuses_rss.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Feeds
|
||||
class StatusesRss < Action
|
||||
include Deps["views.feeds.statuses_rss"]
|
||||
|
||||
def handle(req, res)
|
||||
res.content_type = "text/xml; charset=utf-8"
|
||||
res.render statuses_rss, format: :xml
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/key/show.rb
Normal file
13
slices/main/actions/key/show.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Key
|
||||
class Show < Action
|
||||
include Deps["settings"]
|
||||
def handle(req, res)
|
||||
res.content_type = "text/plain"
|
||||
res.body = settings.micropub_pub_key
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/more/index.rb
Normal file
13
slices/main/actions/more/index.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module More
|
||||
class Index < Action
|
||||
include Deps["views.more.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/movies/index.rb
Normal file
13
slices/main/actions/movies/index.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Movies
|
||||
class Index < Action
|
||||
include Deps["views.movies.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
slices/main/actions/pages/show.rb
Normal file
16
slices/main/actions/pages/show.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Pages
|
||||
class Show < Action
|
||||
include Deps["views.pages.show"]
|
||||
|
||||
def handle(req, res)
|
||||
slug = req.params[:slug]
|
||||
|
||||
res.status = File.exist?("slices/main/content/pages/#{slug}.md") ? 200 : 404
|
||||
res.render show, slug: slug
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/photos/index.rb
Normal file
13
slices/main/actions/photos/index.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Photos
|
||||
class Index < Action
|
||||
include Deps["views.photos.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/places/index.rb
Normal file
13
slices/main/actions/places/index.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Places
|
||||
class Index < Action
|
||||
include Deps["views.places.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/places/map_page.rb
Normal file
13
slices/main/actions/places/map_page.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Places
|
||||
class MapPage < Action
|
||||
include Deps["views.places.map_page"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render map_page
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/podcasts/index.rb
Normal file
13
slices/main/actions/podcasts/index.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Podcasts
|
||||
class Index < Action
|
||||
include Deps["views.podcasts.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
15
slices/main/actions/posts/archive.rb
Normal file
15
slices/main/actions/posts/archive.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Posts
|
||||
class Archive < Action
|
||||
include Deps["views.posts.archive"]
|
||||
|
||||
def handle(req, res)
|
||||
year = req.params[:year]
|
||||
|
||||
res.render archive, year: year
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
12
slices/main/actions/posts/index.rb
Normal file
12
slices/main/actions/posts/index.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Posts
|
||||
class Index < Action
|
||||
include Deps["views.posts.index"]
|
||||
def handle(req, res)
|
||||
res.render index, query: req.params[:q]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
15
slices/main/actions/posts/show.rb
Normal file
15
slices/main/actions/posts/show.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Posts
|
||||
class Show < Action
|
||||
include Deps["views.posts.show"]
|
||||
|
||||
def handle(req, res)
|
||||
slug = req.params[:slug]
|
||||
|
||||
res.render show, slug: slug
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
25
slices/main/actions/posts/top_tracks.rb
Normal file
25
slices/main/actions/posts/top_tracks.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Posts
|
||||
class TopTracks < Action
|
||||
include Deps["views.posts.top_tracks", query: "queries.posts.top_tracks"]
|
||||
|
||||
def handle(req, res)
|
||||
res.content_type = "Application/JSON"
|
||||
res.status = 200
|
||||
tracks = query.call(slug: req.params[:slug])
|
||||
|
||||
track = if tracks.is_a? Array
|
||||
tracks.first
|
||||
else
|
||||
tracks
|
||||
end
|
||||
|
||||
if track
|
||||
res.render top_tracks, track: track
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
15
slices/main/actions/recently_played/index.rb
Normal file
15
slices/main/actions/recently_played/index.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
require "time_math"
|
||||
|
||||
module Main
|
||||
module Actions
|
||||
module RecentlyPlayed
|
||||
class Index < Action
|
||||
include Deps["views.recently_played.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.body = cache(key: "recently_played", content_proc: -> { index.call.to_str })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
12
slices/main/actions/site/home.rb
Normal file
12
slices/main/actions/site/home.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Site
|
||||
class Home < Action
|
||||
include Deps["views.site.home"]
|
||||
def handle(req, res)
|
||||
res.render home
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
12
slices/main/actions/statuses/index.rb
Normal file
12
slices/main/actions/statuses/index.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Statuses
|
||||
class Index < Action
|
||||
include Deps["views.statuses.index"]
|
||||
def handle(req, res)
|
||||
res.render index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/tags/index.rb
Normal file
13
slices/main/actions/tags/index.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Tags
|
||||
class Index < Action
|
||||
include Deps["views.tags.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
15
slices/main/actions/tags/show.rb
Normal file
15
slices/main/actions/tags/show.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Tags
|
||||
class Show < Action
|
||||
include Deps["views.tags.show"]
|
||||
|
||||
def handle(req, res)
|
||||
slug = req.params[:slug]
|
||||
|
||||
res.render show, slug: slug
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
slices/main/actions/timemachine/show.rb
Normal file
19
slices/main/actions/timemachine/show.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Timemachine
|
||||
class Show < Action
|
||||
include Deps["views.timemachine.show"]
|
||||
|
||||
def handle(req, res)
|
||||
year, month, day = [
|
||||
req.params[:year],
|
||||
req.params[:month],
|
||||
req.params[:day]
|
||||
]
|
||||
|
||||
res.render show, year: year, month: month, day: day
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
12
slices/main/actions/trips/index.rb
Normal file
12
slices/main/actions/trips/index.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Main
|
||||
module Actions
|
||||
module Trips
|
||||
class Index < Adamantium::Action
|
||||
def handle(req, res)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
15
slices/main/actions/trips/show.rb
Normal file
15
slices/main/actions/trips/show.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Main
|
||||
module Actions
|
||||
module Trips
|
||||
class Show < Adamantium::Action
|
||||
include Deps["views.trips.show"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render show, id: req.params[:id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
33
slices/main/actions/workouts/create.rb
Normal file
33
slices/main/actions/workouts/create.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Workouts
|
||||
class Create < Action
|
||||
include Deps["geo.gpx_parser", "commands.workouts.create"]
|
||||
|
||||
def handle(req, res)
|
||||
tempfile = Tempfile.new(%w[path .gpx])
|
||||
|
||||
if !req.params.to_h.dig(:file, :tempfile).nil?
|
||||
tempfile.write req.params[:file][:tempfile].read
|
||||
else
|
||||
tempfile.write req.params[:file]
|
||||
end
|
||||
|
||||
tempfile.rewind
|
||||
|
||||
gpxfile = gpx_parser.call(path: tempfile.path)
|
||||
|
||||
if gpxfile.success?
|
||||
create.call(**gpxfile.value!)
|
||||
res.status = 201
|
||||
else
|
||||
res.status = 500
|
||||
end
|
||||
ensure
|
||||
tempfile.close
|
||||
tempfile.unlink
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
slices/main/actions/workouts/index.rb
Normal file
13
slices/main/actions/workouts/index.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Main
|
||||
module Actions
|
||||
module Workouts
|
||||
class Index < Action
|
||||
include Deps["views.workouts.index"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render index
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
15773
slices/main/assets/builds/app.css
Normal file
15773
slices/main/assets/builds/app.css
Normal file
File diff suppressed because it is too large
Load Diff
100
slices/main/assets/css/app.css
Normal file
100
slices/main/assets/css/app.css
Normal file
@@ -0,0 +1,100 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@tailwind typography;
|
||||
|
||||
@font-face {
|
||||
font-family: "Montagu";
|
||||
src: url("../fonts/MontaguSlab-VariableFont_opsz,wght.ttf") format("truetype");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "JetBrainsMono";
|
||||
src: url("../fonts/JetBrainsMono-VariableFont_wght.ttf") format("truetype");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Figtree";
|
||||
src: url("../fonts/Figtree-VariableFont_wght.ttf") format("truetype");
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: "Figtree", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6, h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||
font-family: "Montagu", Times New Roman, serif;
|
||||
}
|
||||
|
||||
.prose h1 a {
|
||||
border-bottom: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.prose h1 a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.gist tr {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.gist span, pre, pre span {
|
||||
font-family: "JetBrainsMono", Monaco, monospace;
|
||||
}
|
||||
|
||||
.map-marker {
|
||||
border: 3px solid blue;
|
||||
border-radius: 8px;
|
||||
background: RGBa(0, 0, 255, 0.1);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.prose pre code::before {
|
||||
padding-left: unset
|
||||
}
|
||||
|
||||
.prose pre code:after {
|
||||
padding-right: unset
|
||||
}
|
||||
|
||||
.prose code {
|
||||
font-weight: 400;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.prose code:before {
|
||||
content: "";
|
||||
padding: 0 0 0 0.25rem;
|
||||
}
|
||||
|
||||
.prose pre {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.prose code:after {
|
||||
content: "";
|
||||
padding: 0 0.25rem 0 0;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
position: relative;
|
||||
padding-bottom: 56.25%; /* 16:9 */
|
||||
height: 0;
|
||||
}
|
||||
.video-container iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.squircle {
|
||||
clip-path: url(#squircleClip);
|
||||
}
|
BIN
slices/main/assets/fonts/Figtree-Italic-VariableFont_wght.ttf
Normal file
BIN
slices/main/assets/fonts/Figtree-Italic-VariableFont_wght.ttf
Normal file
Binary file not shown.
BIN
slices/main/assets/fonts/Figtree-VariableFont_wght.ttf
Normal file
BIN
slices/main/assets/fonts/Figtree-VariableFont_wght.ttf
Normal file
Binary file not shown.
BIN
slices/main/assets/fonts/JetBrainsMono-VariableFont_wght.ttf
Normal file
BIN
slices/main/assets/fonts/JetBrainsMono-VariableFont_wght.ttf
Normal file
Binary file not shown.
BIN
slices/main/assets/fonts/Karla-VariableFont_wght.ttf
Normal file
BIN
slices/main/assets/fonts/Karla-VariableFont_wght.ttf
Normal file
Binary file not shown.
BIN
slices/main/assets/fonts/MontaguSlab-VariableFont_opsz,wght.ttf
Normal file
BIN
slices/main/assets/fonts/MontaguSlab-VariableFont_opsz,wght.ttf
Normal file
Binary file not shown.
BIN
slices/main/assets/fonts/Rubik-VariableFont_wght.ttf
Normal file
BIN
slices/main/assets/fonts/Rubik-VariableFont_wght.ttf
Normal file
Binary file not shown.
76
slices/main/assets/js/app.js
Normal file
76
slices/main/assets/js/app.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { md_gallery } from "./gallery.js";
|
||||
|
||||
(function() {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||||
localStorage.setItem("_x_darkMode", event.matches ? true : false)
|
||||
});
|
||||
|
||||
if (window.hljs !== undefined) {
|
||||
window.hljs.highlightAll();
|
||||
}
|
||||
|
||||
const times = document.querySelectorAll('time');
|
||||
times.forEach((time) => {
|
||||
const oldDtime = Date.parse(time.dateTime);
|
||||
time.innerHTML = new Date(oldDtime).toLocaleDateString(navigator.language, { weekday:"long", year:"numeric", month:"short", day:"numeric"});
|
||||
|
||||
md_gallery({
|
||||
"class_name": "grid gap-4 grid-cols-2 prose-img:m-0"
|
||||
});
|
||||
|
||||
const mediaForm = document.getElementById("media_form");
|
||||
if (mediaForm !== null) {
|
||||
htmx.on('#media_form', 'htmx:xhr:progress', function (evt) {
|
||||
htmx.find('#progress').setAttribute('value', evt.detail.loaded / evt.detail.total * 100)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const videos = document.querySelectorAll('video');
|
||||
videos.forEach((video) => {
|
||||
video.addEventListener("click", () => {
|
||||
const isPaused = video.paused;
|
||||
video[isPaused ? "play" : "pause"]();
|
||||
video.classList.toggle("u-none", !isPaused);
|
||||
});
|
||||
});
|
||||
|
||||
const mapContainer = document.getElementById("map");
|
||||
const goBack = document.getElementById("go-back");
|
||||
if (mapContainer !== null) {
|
||||
if (goBack !== null) {
|
||||
document.getElementById("go-back").addEventListener("click", () => {
|
||||
history.back();
|
||||
});
|
||||
}
|
||||
|
||||
mapboxgl.accessToken = 'pk.eyJ1IjoiZG5pdHphIiwiYSI6ImNsZWIyY3ZzaTE0cjUzdm4xdnZ6czRlYjUifQ.FRETOXYRID6T2IoB7qqRLg';
|
||||
var map = new mapboxgl.Map({
|
||||
container: 'map',
|
||||
style: 'mapbox://styles/mapbox/streets-v11',
|
||||
maxZoom: 8
|
||||
});
|
||||
|
||||
const markers = JSON.parse(mapContainer.dataset["markers"]);
|
||||
|
||||
const bounds = new mapboxgl.LngLatBounds(markers[0], markers[0]);
|
||||
|
||||
for (var i = 0; i < markers.length; i++) {
|
||||
bounds.extend(markers[i]);
|
||||
}
|
||||
|
||||
map.fitBounds(bounds, { padding: 60 });
|
||||
|
||||
for (var i = 0; i < markers.length; i++) {
|
||||
const marker = markers[i];
|
||||
const el = document.createElement("div");
|
||||
el.className = "map-marker";
|
||||
|
||||
new mapboxgl.Marker(el)
|
||||
.setLngLat(marker)
|
||||
.addTo(map);
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
103
slices/main/assets/js/gallery.js
Normal file
103
slices/main/assets/js/gallery.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Markdown Gallery
|
||||
-- v1.0 2016
|
||||
-- Created by Lee Penney
|
||||
-- Released under GPLv3
|
||||
*/
|
||||
|
||||
export function md_gallery(config) {
|
||||
var config = config || {},
|
||||
list_type = config.list_type || 'ul',
|
||||
class_name = config.class_name || 'gallery',
|
||||
tag_type = config.tag_type || 'div';
|
||||
|
||||
function find_lists(list_type) {
|
||||
var lists = document.getElementsByTagName(list_type), matching_lists = [];
|
||||
for (var i = 0; i < lists.length; i++) {
|
||||
var list_elements = lists[i].children;
|
||||
var total_matches = 0;
|
||||
for (var c = 0; c < list_elements.length; c++) {
|
||||
if (!list_elements[c].textContent.length && (list_elements[c].firstChild.tagName == 'A' || list_elements[c].firstChild.tagName == 'IMG') && (!list_elements[c].firstChild.firstChild || (list_elements[c].firstChild.firstChild && list_elements[c].firstChild.firstChild.tagName == 'IMG') )) {
|
||||
total_matches++;
|
||||
}
|
||||
}
|
||||
if (total_matches == list_elements.length) {
|
||||
matching_lists[matching_lists.length] = lists[i];
|
||||
}
|
||||
}
|
||||
return matching_lists;
|
||||
}
|
||||
|
||||
function prepend_tag(img_lists, list_tag, prepend_tag, class_name) {
|
||||
for (var i = 0; i < img_lists.length; i++) {
|
||||
// add_figure_tags(img_lists[i]);
|
||||
add_anchor(img_lists[i]);
|
||||
wrap_tag(img_lists[i], prepend_tag, class_name, null, true);
|
||||
strip_tag(img_lists[i], 'li');
|
||||
strip_tag(img_lists[i].parentNode, list_tag);
|
||||
}
|
||||
}
|
||||
|
||||
function append_caption(el) {
|
||||
if ((el.tagName == 'A' && el.firstChild.tagName == 'IMG' && el.firstChild.hasAttribute('alt') && el.firstChild.getAttribute('alt').length > 0) || (el.tagName == 'IMG' && el.hasAttribute('alt') && el.getAttribute('alt').length > 0)) {
|
||||
var caption = document.createElement('figcaption');
|
||||
try {
|
||||
caption.textContent = el.firstChild.getAttribute('alt')
|
||||
el.appendChild(caption);
|
||||
} catch (e) {
|
||||
caption.textContent = el.getAttribute('alt');
|
||||
el.parentNode.appendChild(caption);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function strip_tag(el, tag_type) {
|
||||
var start_tag_regex = new RegExp('<'+tag_type+'>', 'gi');
|
||||
var end_tag_regex = new RegExp('<\/'+tag_type+'>', 'gi');
|
||||
el.innerHTML = el.innerHTML.replace(start_tag_regex,'').replace(end_tag_regex,'');
|
||||
}
|
||||
|
||||
function add_figure_tags(img_list) {
|
||||
var list_elements = img_list.children;
|
||||
for (var i = 0; i < list_elements.length; i++) {
|
||||
append_caption(list_elements[i].firstChild);
|
||||
wrap_tag(list_elements[i], 'figure');
|
||||
}
|
||||
}
|
||||
|
||||
function add_anchor(img_list) {
|
||||
var list_elements = img_list.children;
|
||||
for (var i = 0; i < list_elements.length; i++) {
|
||||
let img = list_elements[i].getElementsByTagName('img')[0];
|
||||
let src = img.getAttribute("src");
|
||||
let alt = img.getAttribute("alt");
|
||||
wrap_tag(list_elements[i],
|
||||
'a',
|
||||
'hover:cursor-pointer',
|
||||
"$dispatch('img-modal', { imgModalSrc: '" + src + "', imgModalDesc: '" + alt + "' })",
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function wrap_tag(el, tag_type, class_name, click, root) {
|
||||
var wrap = document.createElement(tag_type);
|
||||
if (root) {
|
||||
wrap.setAttribute('x-data', "{}");
|
||||
}
|
||||
if (class_name) {
|
||||
wrap.setAttribute('class', class_name);
|
||||
}
|
||||
if (click) {
|
||||
wrap.setAttribute('x-on:click.prevent', click);
|
||||
wrap.setAttribute('href', '#');
|
||||
}
|
||||
el.parentNode.replaceChild(wrap, el);
|
||||
wrap.appendChild(el);
|
||||
}
|
||||
|
||||
var found_img_lists = find_lists(list_type);
|
||||
if (found_img_lists.length) {
|
||||
prepend_tag(found_img_lists, list_type, tag_type, class_name);
|
||||
}
|
||||
}
|
0
slices/main/commands/.keep
Normal file
0
slices/main/commands/.keep
Normal file
55
slices/main/config/routes.rb
Normal file
55
slices/main/config/routes.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
module Main
|
||||
class Routes < Hanami::Routes
|
||||
root to: "site.home"
|
||||
get "/post/top_tracks/:slug", to: "posts.top_tracks"
|
||||
get "/post/:slug", to: "posts.show"
|
||||
get "/posts", to: "posts.index"
|
||||
# get "/posts/archive", to: "posts.archive"
|
||||
get "/posts/archive/:year", to: "posts.archive"
|
||||
|
||||
get "/bookmarks", to: "bookmarks.index"
|
||||
get "/bookmarks/metadata/:id", to: "bookmarks.metadata"
|
||||
get "/bookmark/:slug", to: "bookmarks.show"
|
||||
|
||||
get "/photos", to: "photos.index"
|
||||
get "/places", to: "places.index"
|
||||
get "/places/map", to: "places.map_page"
|
||||
get "/statuses", to: "statuses.index"
|
||||
|
||||
get "/tags", to: "tags.index"
|
||||
get "/tagged/:slug", to: "tags.show"
|
||||
|
||||
get "/key", to: "key.show" if Hanami.app.settings.micropub_pub_key
|
||||
|
||||
get "/feeds/rss", to: "feeds.rss"
|
||||
get "/feeds/statuses_rss", to: "feeds.statuses_rss"
|
||||
|
||||
get "/more", to: "more.index"
|
||||
|
||||
get "/hikes", to: "workouts.index"
|
||||
post "/workouts", to: "workouts.create"
|
||||
|
||||
get "/movies", to: "movies.index"
|
||||
|
||||
get "/blogroll", to: "blogroll.index"
|
||||
get "/blogroll/list", to: "blogroll.list"
|
||||
get "/blogroll/opml", to: "blogroll.opml"
|
||||
|
||||
get "/recently_played", to: "recently_played.index"
|
||||
|
||||
get "/:slug", to: "pages.show"
|
||||
|
||||
get "/trips", to: "trips.index"
|
||||
get "/trips/:id", to: "trips.show"
|
||||
|
||||
get "/podcasts", to: "podcasts.index"
|
||||
|
||||
get "/bookshelf", to: "books.index"
|
||||
|
||||
get "/timemachine/:year/:month/:day", to: "timemachine.show"
|
||||
|
||||
redirect "deploying-a-hanami-app-to-fly-io", to: "/post/deploying-a-hanami-20-app-to-flyio"
|
||||
redirect "deploying-a-hanami-app-to-fly-io/", to: "/post/deploying-a-hanami-20-app-to-flyio"
|
||||
end
|
||||
end
|
4
slices/main/content/home.md
Normal file
4
slices/main/content/home.md
Normal file
@@ -0,0 +1,4 @@
|
||||
Hi! 👋 I'm Daniel, a software engineer living in Canberra, Australia.
|
||||
|
||||
Welcome to my personal site! This is where I post the things I have [written](/posts), the [photos](/photos) I have taken, the [bookmarks](/bookmarks) I have saved, the [places](/places) I have been, and a whole bunch [more](/more).
|
||||
|
56
slices/main/decorators/bookmarks/decorator.rb
Normal file
56
slices/main/decorators/bookmarks/decorator.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
# frozen_string_literal: false
|
||||
|
||||
# auto_register: false
|
||||
|
||||
module Main
|
||||
module Decorators
|
||||
module Bookmarks
|
||||
class Decorator < SimpleDelegator
|
||||
def display_published_at
|
||||
published_at.strftime("%e %B, %Y")
|
||||
end
|
||||
|
||||
def display_title
|
||||
"🔖: #{name}"
|
||||
end
|
||||
|
||||
def feed_content
|
||||
content || ""
|
||||
end
|
||||
|
||||
def permalink
|
||||
"#{Hanami.app.settings.micropub_site_url}/bookmark/#{slug}"
|
||||
end
|
||||
|
||||
def machine_published_at
|
||||
published_at.rfc2822
|
||||
end
|
||||
|
||||
def syndicated?
|
||||
!syndication_sources.empty?
|
||||
end
|
||||
|
||||
def template_type
|
||||
:bookmark
|
||||
end
|
||||
|
||||
def syndicated_to
|
||||
syndication_sources.map do |source, url|
|
||||
{
|
||||
location: source,
|
||||
url: url
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def youtube_embed
|
||||
pattern = /watch[?]v=(\w*)/i
|
||||
code = url.scan(pattern).flatten.first
|
||||
if code
|
||||
"<div class='video-container'><iframe width='100%' src='https://www.youtube.com/embed/#{code}' title='YouTube video player' frameborder='0' allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share' allowfullscreen></iframe></div>"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
53
slices/main/decorators/books/decorator.rb
Normal file
53
slices/main/decorators/books/decorator.rb
Normal file
@@ -0,0 +1,53 @@
|
||||
# frozen_string_literal: false
|
||||
|
||||
# auto_register: false
|
||||
|
||||
module Main
|
||||
module Decorators
|
||||
module Books
|
||||
class Decorator < SimpleDelegator
|
||||
def display_published_at
|
||||
published_at.strftime("%e %B, %Y")
|
||||
end
|
||||
|
||||
def machine_published_at
|
||||
published_at.rfc2822
|
||||
end
|
||||
|
||||
def syndicated?
|
||||
!syndication_sources.empty?
|
||||
end
|
||||
|
||||
def template_type
|
||||
:book
|
||||
end
|
||||
|
||||
def authors
|
||||
book_author.split(";").join(" ")
|
||||
end
|
||||
|
||||
def status_colour
|
||||
case book_status
|
||||
when "read" || "finished"
|
||||
"text-green-100 bg-green-500"
|
||||
when "to-read"
|
||||
"text-blue-100 bg-blue-500"
|
||||
when "reading"
|
||||
"text-orange-100 bg-orange-500"
|
||||
end
|
||||
end
|
||||
|
||||
def status_label
|
||||
case book_status
|
||||
when "read" || "finished"
|
||||
"Read"
|
||||
when "to-read"
|
||||
"To read"
|
||||
when "reading"
|
||||
"Reading"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
21
slices/main/decorators/movies/decorator.rb
Normal file
21
slices/main/decorators/movies/decorator.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: false
|
||||
|
||||
# auto_register: false
|
||||
|
||||
module Main
|
||||
module Decorators
|
||||
module Movies
|
||||
class Decorator < SimpleDelegator
|
||||
include Deps["clients.omdb"]
|
||||
|
||||
def poster
|
||||
omdb_record&.poster
|
||||
end
|
||||
|
||||
def omdb_record
|
||||
@omdb_record ||= omdb.call(imdb_id: imdb_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
178
slices/main/decorators/posts/decorator.rb
Normal file
178
slices/main/decorators/posts/decorator.rb
Normal file
@@ -0,0 +1,178 @@
|
||||
# frozen_string_literal: false
|
||||
|
||||
# auto_register: false
|
||||
|
||||
require "rexml/parsers/pullparser"
|
||||
require "sanitize"
|
||||
require "nokogiri"
|
||||
|
||||
module Main
|
||||
module Decorators
|
||||
module Posts
|
||||
class Decorator < SimpleDelegator
|
||||
def syndicated?
|
||||
!syndication_sources.empty?
|
||||
end
|
||||
|
||||
def syndicated_to
|
||||
syndication_sources.map do |source, url|
|
||||
{
|
||||
location: source,
|
||||
url: url
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def photos?
|
||||
__getobj__.photos.count { |p| !p["value"].end_with?("mp4") } > 0
|
||||
end
|
||||
|
||||
def photos
|
||||
__getobj__.photos.select { |p| !p["value"].end_with?("mp4") }
|
||||
end
|
||||
|
||||
def videos?
|
||||
__getobj__.photos.count { |p| p["value"].end_with?("mp4") } > 0
|
||||
end
|
||||
|
||||
def videos
|
||||
__getobj__.photos.select { |p| p["value"].end_with?("mp4") }
|
||||
end
|
||||
|
||||
def key_image
|
||||
if photos?
|
||||
return photos.first["url"]
|
||||
end
|
||||
|
||||
doc = Nokogiri::HTML(content)
|
||||
images = doc.at("//img")
|
||||
images.first[1] if images
|
||||
end
|
||||
|
||||
def prefix_emoji
|
||||
if name
|
||||
""
|
||||
elsif photos? && content == ""
|
||||
"📷"
|
||||
else
|
||||
"💬"
|
||||
end
|
||||
end
|
||||
|
||||
def display_title
|
||||
title = name
|
||||
"#{prefix_emoji} #{title}"
|
||||
end
|
||||
|
||||
def display_published_at
|
||||
published_at.strftime("%e %B, %Y")
|
||||
end
|
||||
|
||||
def machine_published_at
|
||||
published_at.rfc2822
|
||||
end
|
||||
|
||||
def feed_content
|
||||
photos? ? "<div>#{photos.map { |p| "<img src='#{p["value"]}'/>" }.join("")} #{content}</div>" : content
|
||||
end
|
||||
|
||||
def raw_content
|
||||
Sanitize.fragment(content)
|
||||
end
|
||||
|
||||
def excerpt
|
||||
name ? truncate_html(content, 240, true) : content
|
||||
end
|
||||
|
||||
def permalink
|
||||
"#{Hanami.app.settings.micropub_site_url}/post/#{slug}"
|
||||
end
|
||||
|
||||
def lat
|
||||
geo[0]
|
||||
end
|
||||
|
||||
def lon
|
||||
geo[1]
|
||||
end
|
||||
|
||||
def small_map
|
||||
"https://api.mapbox.com/styles/v1/dnitza/cleb2o734000k01pbifls5620/static/pin-s+555555(#{lon},#{lat})/#{lon},#{lat},14,0/200x100@2x?access_token=pk.eyJ1IjoiZG5pdHphIiwiYSI6ImNsZWIzOHFmazBkODIzdm9kZHgxdDF4ajQifQ.mSneE-1SKeju8AOz5gp4BQ"
|
||||
end
|
||||
|
||||
def large_map
|
||||
"https://api.mapbox.com/styles/v1/dnitza/cleb2o734000k01pbifls5620/static/pin-s+555555(#{lon},#{lat})/#{lon},#{lat},14,0/620x310@2x?access_token=pk.eyJ1IjoiZG5pdHphIiwiYSI6ImNsZWIzOHFmazBkODIzdm9kZHgxdDF4ajQifQ.mSneE-1SKeju8AOz5gp4BQ"
|
||||
end
|
||||
|
||||
def template_type
|
||||
:post
|
||||
end
|
||||
|
||||
def posted_in
|
||||
if name.nil?
|
||||
:statuses
|
||||
elsif post_type.to_sym == :book
|
||||
:bookshelf
|
||||
elsif location.nil?
|
||||
:posts
|
||||
else
|
||||
:places
|
||||
end
|
||||
end
|
||||
|
||||
def trips
|
||||
__getobj__.trips
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# e.g. geo:-37.75188,144.90417;u=35
|
||||
def geo
|
||||
loc = location.split(":")[1]
|
||||
p = loc.split(";")[0]
|
||||
|
||||
p.split(",")
|
||||
end
|
||||
|
||||
def truncate_html(content, len = 30, at_end = nil)
|
||||
return content if content.to_s.length <= len
|
||||
|
||||
p = REXML::Parsers::PullParser.new(content)
|
||||
tags = []
|
||||
new_len = len
|
||||
results = ""
|
||||
while p.has_next? && new_len > 0
|
||||
p_e = p.pull
|
||||
case p_e.event_type
|
||||
when :start_element
|
||||
tags.push p_e[0]
|
||||
results << "<#{tags.last}#{attrs_to_s(p_e[1])}>"
|
||||
when :end_element
|
||||
results << "</#{tags.pop}>"
|
||||
when :text
|
||||
results << p_e[0][0..new_len]
|
||||
new_len -= p_e[0].length
|
||||
else
|
||||
results << "<!-- #{p_e.inspect} -->"
|
||||
end
|
||||
end
|
||||
if at_end
|
||||
results << "..."
|
||||
end
|
||||
tags.reverse_each do |tag|
|
||||
results << "</#{tag}>"
|
||||
end
|
||||
results
|
||||
end
|
||||
|
||||
def attrs_to_s(attrs)
|
||||
if attrs.empty?
|
||||
""
|
||||
else
|
||||
" " + attrs.to_a.map { |attr| %(#{attr[0]}="#{attr[1]}") }.join(" ")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
0
slices/main/entities/.keep
Normal file
0
slices/main/entities/.keep
Normal file
30
slices/main/queries/blogroll/index.rb
Normal file
30
slices/main/queries/blogroll/index.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
require "httparty"
|
||||
|
||||
module Main
|
||||
module Queries
|
||||
module Blogroll
|
||||
class Index
|
||||
include Deps["settings"]
|
||||
|
||||
def call
|
||||
resp = HTTParty.get("https://#{settings.rss_url}/api/greader.php/reader/api/0/subscription/list?output=json", {
|
||||
headers: {
|
||||
"Authorization" => "GoogleLogin auth=#{auth_token}"
|
||||
}
|
||||
})
|
||||
|
||||
resp.body
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def auth_token
|
||||
auth_url = "https://#{settings.rss_url}/api/greader.php/accounts/ClientLogin?Email=#{settings.rss_username}&Passwd=#{settings.rss_password}"
|
||||
resp = HTTParty.get(auth_url)
|
||||
auth = resp.match(/SID=(.*)/)
|
||||
auth[1].strip
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
43
slices/main/queries/posts/recently_played.rb
Normal file
43
slices/main/queries/posts/recently_played.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
require "httparty"
|
||||
require "jwt"
|
||||
|
||||
module Main
|
||||
module Queries
|
||||
module Posts
|
||||
class RecentlyPlayed
|
||||
include Deps["settings"]
|
||||
|
||||
def call
|
||||
resp = HTTParty.get("https://api.music.apple.com/v1/me/recent/played", {
|
||||
headers: {
|
||||
"Authorization" => "Bearer #{jwt}",
|
||||
"Music-User-Token" => settings.apple_music_user_token
|
||||
}
|
||||
})
|
||||
|
||||
resp.body
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def jwt
|
||||
authentication_payload = {
|
||||
iss: settings.apple_music_team,
|
||||
iat: Time.now.to_i, # Issue date
|
||||
exp: Time.now.to_i + 3600 # Expiry of this token.
|
||||
}
|
||||
# The file we got from Apple
|
||||
apple_music_secret = File.read(File.join(Hanami.app.root, "config", "AuthKey_#{settings.apple_music_key}.p8"))
|
||||
private_key = OpenSSL::PKey::EC.new(apple_music_secret)
|
||||
|
||||
JWT.encode(
|
||||
authentication_payload,
|
||||
private_key,
|
||||
"ES256",
|
||||
kid: settings.apple_music_key
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
41
slices/main/queries/posts/top_tracks.rb
Normal file
41
slices/main/queries/posts/top_tracks.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
require "lastfm"
|
||||
require "time_math"
|
||||
|
||||
module Main
|
||||
module Queries
|
||||
module Posts
|
||||
class TopTracks
|
||||
include Deps["settings", "repos.post_repo", "repos.top_track_repo"]
|
||||
|
||||
def call(slug:)
|
||||
ENV["TZ"] = "Australia/Melbourne"
|
||||
post = post_repo.fetch!(slug)
|
||||
start_date = TimeMath.week.floor(post.published_at).to_i
|
||||
end_date = TimeMath.week.ceil(post.published_at).to_i
|
||||
|
||||
top_tracks = top_track_repo.for_post(id: post.id)
|
||||
|
||||
return top_tracks unless top_tracks.empty?
|
||||
|
||||
lastfm = Lastfm.new(settings.lastfm_api_key, settings.lastfm_secret)
|
||||
|
||||
tracks = lastfm.user.get_weekly_track_chart(user: "dNitza", from: start_date, to: end_date)
|
||||
|
||||
track = if tracks.is_a? Array
|
||||
tracks.first
|
||||
else
|
||||
tracks
|
||||
end
|
||||
|
||||
if track
|
||||
mb_id = (track["mbid"] == {}) ? "unknown" : track["mbid"]
|
||||
top_track_repo.upsert(post_id: post.id, name: track["name"], artist: track.dig("artist", "content"), url: track["url"], mb_id: mb_id)
|
||||
end
|
||||
ENV["TZ"] = nil
|
||||
|
||||
top_track_repo.for_post(id: post.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
31
slices/main/repos/movie_repo.rb
Normal file
31
slices/main/repos/movie_repo.rb
Normal file
@@ -0,0 +1,31 @@
|
||||
module Main
|
||||
module Repos
|
||||
class MovieRepo < Adamantium::Repo[:movies]
|
||||
def listing
|
||||
movies.order(Sequel.lit("year desc")).to_a
|
||||
end
|
||||
|
||||
def by_title_and_year(title:, year:)
|
||||
movies.where(title: title, year: year).one
|
||||
end
|
||||
|
||||
def from_the_archives(start_date:, end_date:)
|
||||
# SELECT * FROM posts
|
||||
# WHERE EXTRACT(month FROM "published_at") >= 2
|
||||
# WHERE EXTRACT(month FROM "published_at") <= 2+
|
||||
# AND EXTRACT(day FROM "published_at") > 20
|
||||
# AND EXTRACT(day FROM "published_at") < 27
|
||||
# AND post_type = 'post';
|
||||
|
||||
movies
|
||||
.where { Sequel.extract(:year, :watched_at) >= start_date.year }
|
||||
.where { Sequel.extract(:year, :watched_at) <= start_date.year }
|
||||
.where { Sequel.extract(:month, :watched_at) >= start_date.month }
|
||||
.where { Sequel.extract(:month, :watched_at) <= end_date.month }
|
||||
.where { Sequel.extract(:day, :watched_at) >= start_date.day }
|
||||
.where { Sequel.extract(:day, :watched_at) <= end_date.day }
|
||||
.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
19
slices/main/repos/page_repo.rb
Normal file
19
slices/main/repos/page_repo.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
module Main
|
||||
module Repos
|
||||
class PageRepo < Adamantium::Repo[:pages]
|
||||
def fetch!(slug:)
|
||||
pages
|
||||
.published
|
||||
.where(slug: slug).one!
|
||||
end
|
||||
|
||||
def for_main_nav
|
||||
pages
|
||||
.select(:name, :slug, :light_colour, :dark_colour, :published_at)
|
||||
.published
|
||||
.where(main_menu: true)
|
||||
.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
9
slices/main/repos/podcast_repo.rb
Normal file
9
slices/main/repos/podcast_repo.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
module Main
|
||||
module Repos
|
||||
class PodcastRepo < Adamantium::Repo[:podcasts]
|
||||
def listing
|
||||
podcasts.order(:name).to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
26
slices/main/repos/podcast_scrobble_repo.rb
Normal file
26
slices/main/repos/podcast_scrobble_repo.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
module Main
|
||||
module Repos
|
||||
class PodcastScrobbleRepo < Adamantium::Repo[:podcast_scrobbles]
|
||||
commands :create
|
||||
|
||||
def exists?(id:)
|
||||
!!podcast_scrobbles
|
||||
.where(overcast_id: id)
|
||||
.one
|
||||
end
|
||||
|
||||
def listing
|
||||
podcast_scrobbles
|
||||
.order(Sequel.desc(:listened_at))
|
||||
.limit(5)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def for_timemachine(date:)
|
||||
podcast_scrobbles
|
||||
.where(listened_at: TimeMath.day.floor(date)...TimeMath.day.advance(date, +1))
|
||||
.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
223
slices/main/repos/post_repo.rb
Normal file
223
slices/main/repos/post_repo.rb
Normal file
@@ -0,0 +1,223 @@
|
||||
module Main
|
||||
module Repos
|
||||
class PostRepo < Adamantium::Repo[:posts]
|
||||
Sequel.extension :pg_json
|
||||
Sequel.extension :pg_json_ops
|
||||
|
||||
def by_year(year:)
|
||||
posts
|
||||
.where(post_type: "post", location: nil)
|
||||
.exclude(name: nil)
|
||||
.published
|
||||
.where { Sequel.&(Sequel.extract(:year, :published_at) =~ year) }
|
||||
.combine(:tags)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def week_posts(limit: nil)
|
||||
posts
|
||||
.where(post_type: "post")
|
||||
.weekly
|
||||
.published
|
||||
.combine(:tags)
|
||||
.order(Sequel.desc(:published_at))
|
||||
.limit(limit)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def post_listing(limit: nil)
|
||||
posts
|
||||
.where(post_type: "post", location: nil)
|
||||
.exclude(name: nil)
|
||||
.published
|
||||
.combine(:tags)
|
||||
.order(Sequel.desc(:published_at))
|
||||
.limit(limit)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def home_post_listing(limit: nil)
|
||||
posts
|
||||
.where(post_type: "post", location: nil)
|
||||
.exclude(name: nil)
|
||||
.non_weekly
|
||||
.published
|
||||
.combine(:tags)
|
||||
.order(Sequel.desc(:published_at))
|
||||
.limit(limit)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def photo_listing(limit: nil)
|
||||
posts
|
||||
.where(post_type: ["post", "checkin"])
|
||||
.where(Sequel[:photos].pg_json.array_length > 0)
|
||||
.published
|
||||
.combine(:tags)
|
||||
.order(Sequel.desc(:published_at))
|
||||
.limit(limit)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def places_listing(limit: nil)
|
||||
posts
|
||||
.where(post_type: ["checkin", "post"])
|
||||
.exclude(location: nil)
|
||||
.published
|
||||
.combine(:tags)
|
||||
.order(Sequel.desc(:published_at))
|
||||
.limit(limit)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def books_listing(limit: nil)
|
||||
posts
|
||||
.where(post_type: "book")
|
||||
.published
|
||||
.order(Sequel.asc(:name))
|
||||
.limit(limit)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def bookmark_listing(query: nil)
|
||||
base = posts
|
||||
.where(post_type: "bookmark")
|
||||
.published
|
||||
.combine(:tags)
|
||||
.order(Sequel.desc(:published_at))
|
||||
|
||||
query ? base.where(Sequel.ilike(:name, "%#{query}%")).to_a : base.to_a
|
||||
end
|
||||
|
||||
def statuses_listing(limit: nil)
|
||||
posts
|
||||
.where(post_type: "post", name: nil)
|
||||
.exclude(Sequel.pg_jsonb_op(:syndication_sources).has_key?("instagram"))
|
||||
.published
|
||||
.combine(:tags, :webmentions)
|
||||
.node(:webmentions) { |webmention|
|
||||
webmention.published.where(type: "reply")
|
||||
}
|
||||
.order(Sequel.desc(:published_at))
|
||||
.limit(limit)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def latest_status
|
||||
posts
|
||||
.where(name: nil)
|
||||
.published
|
||||
.order(Sequel.desc(:published_at))
|
||||
.limit(1)
|
||||
.one
|
||||
end
|
||||
|
||||
def last_location
|
||||
posts
|
||||
.where(post_type: "checkin")
|
||||
.published
|
||||
.order(Sequel.desc(:published_at))
|
||||
.limit(1)
|
||||
.one
|
||||
end
|
||||
|
||||
def from_the_archives(start_date:, end_date:)
|
||||
# SELECT * FROM posts
|
||||
# WHERE EXTRACT(month FROM "published_at") >= 2
|
||||
# WHERE EXTRACT(month FROM "published_at") <= 2+
|
||||
# AND EXTRACT(day FROM "published_at") > 20
|
||||
# AND EXTRACT(day FROM "published_at") < 27
|
||||
# AND post_type = 'post';
|
||||
|
||||
posts
|
||||
.where(post_type: "post")
|
||||
.published
|
||||
.where { Sequel.extract(:year, :published_at) < start_date.year }
|
||||
.where { Sequel.extract(:month, :published_at) >= start_date.month }
|
||||
.where { Sequel.extract(:month, :published_at) <= end_date.month }
|
||||
.where { Sequel.extract(:day, :published_at) >= start_date.day }
|
||||
.where { Sequel.extract(:day, :published_at) <= end_date.day }
|
||||
.to_a
|
||||
end
|
||||
|
||||
def posts_for_timemachine(date:)
|
||||
posts
|
||||
.where(post_type: "post")
|
||||
.where(published_at: TimeMath.day.floor(date)...TimeMath.day.advance(date, +1))
|
||||
.to_a
|
||||
end
|
||||
|
||||
def bookmarks_for_timemachine(date:)
|
||||
posts
|
||||
.where(post_type: "bookmark")
|
||||
.where(published_at: TimeMath.day.floor(date)...TimeMath.day.advance(date, +1))
|
||||
.to_a
|
||||
end
|
||||
|
||||
def all_posts
|
||||
posts
|
||||
.where(post_type: ["post", "bookmark"])
|
||||
.published
|
||||
.order(Sequel.desc(:published_at))
|
||||
.to_a
|
||||
end
|
||||
|
||||
def for_rss
|
||||
posts
|
||||
.where(post_type: ["post", "bookmark"], location: nil)
|
||||
.exclude(name: nil)
|
||||
.published
|
||||
.combine(:tags)
|
||||
.order(Sequel.desc(:published_at))
|
||||
.to_a
|
||||
end
|
||||
|
||||
def statuses_for_rss
|
||||
posts
|
||||
.where(post_type: "post", name: nil, location: nil)
|
||||
.published
|
||||
.combine(:tags)
|
||||
.order(Sequel.desc(:published_at))
|
||||
.to_a
|
||||
end
|
||||
|
||||
def fetch!(slug)
|
||||
posts
|
||||
.published
|
||||
.combine(:tags, :trips, :webmentions)
|
||||
.node(:webmentions) { |webmention|
|
||||
webmention.published.where(type: "reply")
|
||||
}
|
||||
.where(slug: slug)
|
||||
.one!
|
||||
end
|
||||
|
||||
def find!(id)
|
||||
posts
|
||||
.by_pk(id)
|
||||
.one!
|
||||
end
|
||||
|
||||
def post_years
|
||||
posts
|
||||
.where(post_type: "post", location: nil)
|
||||
.exclude(name: nil)
|
||||
.published
|
||||
.dataset
|
||||
.group_and_count(Sequel.extract(:year, :published_at).as(:year))
|
||||
.order(:year)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def search(term:)
|
||||
posts
|
||||
.where(post_type: "post", location: nil)
|
||||
.published
|
||||
.search(term: term)
|
||||
.combine(:tags)
|
||||
.order(Sequel.desc(:published_at))
|
||||
.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
26
slices/main/repos/post_tag_repo.rb
Normal file
26
slices/main/repos/post_tag_repo.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
module Main
|
||||
module Repos
|
||||
class PostTagRepo < Adamantium::Repo[:post_tags]
|
||||
def posts_tagged(tag:)
|
||||
tag_id = post_tags
|
||||
.tags
|
||||
.where(slug: tag)
|
||||
.one!
|
||||
.id
|
||||
|
||||
post_ids = post_tags
|
||||
.where(tag_id: tag_id)
|
||||
.to_a
|
||||
.map(&:post_id)
|
||||
|
||||
post_tags
|
||||
.posts
|
||||
.where(id: post_ids)
|
||||
.published
|
||||
.combine(:tags)
|
||||
.order(Sequel.desc(:published_at))
|
||||
.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
16
slices/main/repos/tag_repo.rb
Normal file
16
slices/main/repos/tag_repo.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
module Main
|
||||
module Repos
|
||||
class TagRepo < Adamantium::Repo[:tags]
|
||||
def fetch!(slug)
|
||||
tags.where(slug: slug).one!
|
||||
end
|
||||
|
||||
def list
|
||||
tags
|
||||
.order(Sequel.function(:lower, :label))
|
||||
.combine(:posts)
|
||||
.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
slices/main/repos/top_track_repo.rb
Normal file
17
slices/main/repos/top_track_repo.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
module Main
|
||||
module Repos
|
||||
class TopTrackRepo < Adamantium::Repo[:top_tracks]
|
||||
def for_post(id:)
|
||||
top_tracks
|
||||
.where(post_id: id)
|
||||
.to_a
|
||||
end
|
||||
|
||||
def upsert(post_id:, name:, artist:, url:, mb_id:)
|
||||
top_tracks
|
||||
.upsert({name: name, artist: artist, url: url, mb_id: mb_id, post_id: post_id},
|
||||
{target: :post_id, update: {name: name, artist: artist, url: url, mb_id: mb_id, post_id: post_id}})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
20
slices/main/repos/trip_repo.rb
Normal file
20
slices/main/repos/trip_repo.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
module Main
|
||||
module Repos
|
||||
class TripRepo < Adamantium::Repo[:trips]
|
||||
def fetch!(id)
|
||||
trips
|
||||
.where(id: id)
|
||||
.combine(posts: :tags)
|
||||
.one!
|
||||
end
|
||||
|
||||
def list
|
||||
trips
|
||||
.published
|
||||
.order(:start_date)
|
||||
.reverse
|
||||
.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
9
slices/main/repos/workout_repo.rb
Normal file
9
slices/main/repos/workout_repo.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
module Main
|
||||
module Repos
|
||||
class WorkoutRepo < Adamantium::Repo[:workouts]
|
||||
def list
|
||||
workouts.order(:published_at).to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
23
slices/main/templates/apps/patternmate/show.html.slim
Normal file
23
slices/main/templates/apps/patternmate/show.html.slim
Normal file
@@ -0,0 +1,23 @@
|
||||
- context.content_for(:title, "")
|
||||
- context.content_for(:highlight_code, false)
|
||||
|
||||
article class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200 prose-em:font-bold prose-em:not-italic prose-em:bg-blue-600 prose-em:px-1 prose-em:rounded prose-a:text-blue-600 prose-a:dark:text-indigo-300 prose-a:p-0.5 prose-a:rounded-sm prose-a:no-underline hover:prose-a:underline prose-em:text-blue-100"
|
||||
|
||||
h1 Hello world
|
||||
/ # PatternMate
|
||||
/
|
||||
/ ## Features
|
||||
/
|
||||
/ ### Accessibility
|
||||
/
|
||||
/ ### Paid features
|
||||
/
|
||||
/ ## Who is this app for?
|
||||
/
|
||||
/ ## Why did I build it?
|
||||
/
|
||||
/ ## Pricing
|
||||
/
|
||||
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
11
slices/main/templates/blogroll/index.html.slim
Normal file
11
slices/main/templates/blogroll/index.html.slim
Normal file
@@ -0,0 +1,11 @@
|
||||
- context.content_for(:title, "Blogroll | ")
|
||||
div class="mb-4 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 Blogroll
|
||||
|
||||
div class="h-feed mb-12 max-w-prose mx-auto prose dark:prose-invert"
|
||||
p Here's the list of all the sites & blogs I enjoy reading, taken directly from my RSS aggregator!
|
||||
p
|
||||
a href="/blogroll/opml" Get the OPML
|
||||
div class="grow" hx-get="/blogroll/list" hx-trigger="load"
|
||||
|
||||
|
13
slices/main/templates/blogroll/list.html.slim
Normal file
13
slices/main/templates/blogroll/list.html.slim
Normal file
@@ -0,0 +1,13 @@
|
||||
- blogroll.each do |blog|
|
||||
div class="flex mb-4"
|
||||
img loading="lazy" class="w-8 h-8 rounded mx-0 my-0 mr-4" src=blog[:icon]
|
||||
div
|
||||
div
|
||||
= "#{blog[:title]} — ("
|
||||
a href=blog[:url] RSS
|
||||
= " | "
|
||||
a href=blog[:html_url] Website
|
||||
= ")"
|
||||
div
|
||||
small Categories:
|
||||
small = " #{blog[:categories].join(",")}"
|
16
slices/main/templates/blogroll/opml.xml.builder
Normal file
16
slices/main/templates/blogroll/opml.xml.builder
Normal file
@@ -0,0 +1,16 @@
|
||||
# xml.instruct! "xml-stylesheet", {href: "/assets/style.xslt", type: "text/xsl"}
|
||||
xml.opml("xmlns:frss" => "https://freshrss.org/opml", version: "2.0") do
|
||||
xml.head do |head|
|
||||
head.title "Daniel Nitsikopoulos's RSS subscriptions"
|
||||
head.link "https://dnitza.com/blogroll/opml"
|
||||
head.lastBuildDate Time.now.rfc2822
|
||||
head.pubDate Time.now.rfc2822
|
||||
head.ttl 1800
|
||||
end
|
||||
|
||||
xml.body do
|
||||
blogroll.each do |blog|
|
||||
xml.outline(text: blog[:title], type: "rss", xmlUrl: blog[:url], htmlUrl: blog[:html_url])
|
||||
end
|
||||
end
|
||||
end
|
22
slices/main/templates/bookmarks/index.html.slim
Normal file
22
slices/main/templates/bookmarks/index.html.slim
Normal file
@@ -0,0 +1,22 @@
|
||||
- context.content_for(:title, "Bookmarks | ")
|
||||
|
||||
div class="flex justify-between prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
div
|
||||
h1 Bookmarks
|
||||
|
||||
div class="h-feed mb-12 max-w-prose mx-auto"
|
||||
form action="/bookmarks" method="GET"
|
||||
div class="flex"
|
||||
div class="flex-auto basis-auto mr-4"
|
||||
input id="search" type="text" class="p-1 rounded w-full bg-indigo-50 dark:bg-indigo-400" name="q" value=q
|
||||
div class="mr-4"
|
||||
input type="submit" class="bg-indigo-300 p-1 rounded text-indigo-900" value="Search"
|
||||
-if q
|
||||
div class=""
|
||||
a href="/bookmarks" class="text-gray-400 dark:text-gray-100 pt-1" Clear search
|
||||
|
||||
div class="mb-12 max-w-prose mx-auto"
|
||||
- bookmarks.each do |bookmark|
|
||||
== render "shared/bookmark", bookmark: bookmark
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
7
slices/main/templates/bookmarks/metadata.html.slim
Normal file
7
slices/main/templates/bookmarks/metadata.html.slim
Normal file
@@ -0,0 +1,7 @@
|
||||
- if image
|
||||
div class="col-span-1"
|
||||
img class="rounded h-16" src=image
|
||||
|
||||
div class="#{image ? 'col-span-4' : 'col-span-5'}"
|
||||
h6= title
|
||||
small= description
|
45
slices/main/templates/bookmarks/show.html.slim
Normal file
45
slices/main/templates/bookmarks/show.html.slim
Normal file
@@ -0,0 +1,45 @@
|
||||
- context.content_for(:title, "Bookmark | ")
|
||||
|
||||
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 = bookmark.name
|
||||
|
||||
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200" x-data="{ open: false }"
|
||||
a class="text-blue-600 dark:text-amber-500 no-underline hover:underline" href=bookmark.url
|
||||
p class="text-xl text-ellipsis overflow-hidden"
|
||||
= bookmark.url
|
||||
|
||||
div class="mb-6"
|
||||
== bookmark.youtube_embed
|
||||
|
||||
== bookmark.content
|
||||
|
||||
- unless bookmark.cached_content.nil?
|
||||
button class="hover:text-gray-400" @click="open = ! open" Toggle cached version
|
||||
|
||||
span x-show="open"
|
||||
div class="mt-4 rounded bg-blue-50 dark:bg-blue-900 px-4 py-2"
|
||||
== bookmark.cached_content
|
||||
|
||||
div class="prose max-w-prose mx-auto text-gray-800 dark:text-gray-200 grid grid-cols-5 gap-2" hx-get="/bookmarks/metadata/#{bookmark.id}" hx-trigger="load"
|
||||
|
||||
div class="mb-8 max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
||||
|
||||
|
||||
div class="max-w-prose mx-auto text-gray-600 dark:text-gray-200 flex"
|
||||
div class=""
|
||||
= "Published "
|
||||
time class="dt-published" datetime=bookmark.machine_published_at
|
||||
= bookmark.display_published_at
|
||||
p
|
||||
span in
|
||||
a class="hover:underline" href="/bookmarks" bookmarks
|
||||
|
||||
span class="text-right flex-1"
|
||||
== render "shared/tags", tags: bookmark.tags
|
||||
|
||||
div class="mb-2 max-w-prose mx-auto text-gray-600 dark:text-gray-200 flex"
|
||||
- if bookmark.syndicated?
|
||||
span Also on:
|
||||
- bookmark.syndicated_to.each do |loc|
|
||||
a href=loc[:url]
|
||||
== "shared/#{render loc[:location]}"
|
44
slices/main/templates/books/index.html.slim
Normal file
44
slices/main/templates/books/index.html.slim
Normal file
@@ -0,0 +1,44 @@
|
||||
- context.content_for(:title, "Books | ")
|
||||
|
||||
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 📚 Bookshelf
|
||||
|
||||
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto"
|
||||
table class="text-gray-800 dark:text-white table-auto"
|
||||
thead
|
||||
tr
|
||||
td
|
||||
td class="p-2" Title
|
||||
td class="p-2" Author(s)
|
||||
- reading.each do |book|
|
||||
tr
|
||||
td
|
||||
div class="rounded-md uppercase text-xs #{book.status_colour} px-2 w-full text-center"
|
||||
= book.status_label
|
||||
td
|
||||
a href="/post/#{book.slug}"
|
||||
= book.name
|
||||
td
|
||||
= book.authors
|
||||
- to_read.each do |book|
|
||||
tr
|
||||
td
|
||||
div class="rounded-md uppercase text-xs #{book.status_colour} px-2 w-full text-center"
|
||||
= book.status_label
|
||||
td
|
||||
a href="/post/#{book.slug}"
|
||||
= book.name
|
||||
td
|
||||
= book.authors
|
||||
- read.each do |book|
|
||||
tr
|
||||
td
|
||||
div class="rounded-md uppercase text-xs #{book.status_colour} px-2 w-full text-center"
|
||||
= book.status_label
|
||||
td
|
||||
a href="/post/#{book.slug}"
|
||||
= book.name
|
||||
td
|
||||
= book.authors
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
2
slices/main/templates/error.html.slim
Normal file
2
slices/main/templates/error.html.slim
Normal file
@@ -0,0 +1,2 @@
|
||||
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 There was an error!
|
35
slices/main/templates/feeds/rss.xml.builder
Normal file
35
slices/main/templates/feeds/rss.xml.builder
Normal file
@@ -0,0 +1,35 @@
|
||||
xml.instruct! "xml-stylesheet", {href: "/assets/style.xslt", type: "text/xsl"}
|
||||
|
||||
xml.channel do |channel|
|
||||
channel.title "Daniel Nitsikopoulos"
|
||||
channel.description "The personal blog of Daniel Nitsikopoulos, software engineer from Canberra, ACT"
|
||||
channel.link "https://dnitza.com"
|
||||
channel.name "dnitza.com"
|
||||
channel.lastBuildDate Time.now.rfc2822
|
||||
channel.pubDate Time.now.rfc2822
|
||||
channel.ttl 1800
|
||||
|
||||
channel.alternate_feed do |item|
|
||||
item.link "/feeds/rss"
|
||||
item.title "Main feed (this feed)"
|
||||
item.description "Containing longer text posts and bookmarks"
|
||||
end
|
||||
|
||||
channel.alternate_feed do |item|
|
||||
item.link "/feeds/statuses_rss"
|
||||
item.title "Statuses / Microblog"
|
||||
item.description "Only shorter posts and photo posts, usually also appearing on Mastodon"
|
||||
end
|
||||
|
||||
posts.each do |post|
|
||||
channel.item do |item|
|
||||
item.title post.display_title
|
||||
item.description do |desc|
|
||||
desc.cdata! post.feed_content
|
||||
end
|
||||
item.link(post.permalink)
|
||||
item.guid(post.slug, isPermaLink: true)
|
||||
item.pubDate post.machine_published_at
|
||||
end
|
||||
end
|
||||
end
|
35
slices/main/templates/feeds/statuses_rss.xml.builder
Normal file
35
slices/main/templates/feeds/statuses_rss.xml.builder
Normal file
@@ -0,0 +1,35 @@
|
||||
xml.instruct! "xml-stylesheet", {href: "/assets/style.xslt", type: "text/xsl"}
|
||||
|
||||
xml.channel do |channel|
|
||||
channel.title "Daniel Nitsikopoulos"
|
||||
channel.description "The personal blog of Daniel Nitsikopoulos, software engineer from Canberra, ACT"
|
||||
channel.link "https://dnitza.com"
|
||||
channel.name "dnitza.com"
|
||||
channel.lastBuildDate Time.now.rfc2822
|
||||
channel.pubDate Time.now.rfc2822
|
||||
channel.ttl 1800
|
||||
|
||||
channel.alternate_feed do |item|
|
||||
item.link "/feeds/rss"
|
||||
item.title "Main feed"
|
||||
item.description "Containing longer text posts and bookmarks"
|
||||
end
|
||||
|
||||
channel.alternate_feed do |item|
|
||||
item.link "/feeds/statuses_rss"
|
||||
item.title "Statuses / Microblog (this feed)"
|
||||
item.description "Only shorter posts and photo posts, usually also appearing on Mastodon"
|
||||
end
|
||||
|
||||
posts.each do |post|
|
||||
channel.item do |item|
|
||||
item.title post.raw_content
|
||||
item.description do |desc|
|
||||
desc.cdata! post.feed_content
|
||||
end
|
||||
item.link(post.permalink)
|
||||
item.guid(post.slug, isPermaLink: true)
|
||||
item.pubDate post.machine_published_at
|
||||
end
|
||||
end
|
||||
end
|
78
slices/main/templates/layouts/app.html.slim
Normal file
78
slices/main/templates/layouts/app.html.slim
Normal file
@@ -0,0 +1,78 @@
|
||||
doctype html
|
||||
html x-data="{darkMode: $persist(false)}" :class="{'dark' : darkMode === true}"
|
||||
head
|
||||
meta charest="utf-8"
|
||||
|
||||
meta name="viewport" content="width=device-width, initial-scale=1.0"
|
||||
|
||||
meta name="theme-color" content="#2563eb" media="(prefers-color-scheme: light)"
|
||||
meta name="theme-color" content="#1e1b4b" media="(prefers-color-scheme: dark)"
|
||||
|
||||
title #{context.content_for(:title)} #{Hanami.app.settings.site_name}
|
||||
|
||||
link rel="authorization_endpoint" href=Hanami.app.settings.micropub_authorization_endpoint
|
||||
link rel="token_endpoint" href=Hanami.app.settings.micropub_token_endpoint
|
||||
link rel="micropub" href="#{URI.join(Hanami.app.settings.micropub_site_url, "micropub")}"
|
||||
|
||||
link rel="webmention" href=Hanami.app.settings.webmention_url
|
||||
link rel="pingback" href=Hanami.app.settings.pingback_url
|
||||
link rel="feed" type="text/html" href="#{Hanami.app.settings.micropub_site_url}/statuses"
|
||||
link rel="feed alternate" type="application/rss+xml" href="/feeds/rss"
|
||||
link rel="feed alternate" type="application/rss+xml" href="/feeds/statuses_rss"
|
||||
link rel="alternate" type="text/mf2+html" href="#{Hanami.app.settings.micropub_site_url}/statuses"
|
||||
|
||||
link rel="me" href=Hanami.app.settings.mastodon_url
|
||||
link rel="me" href=Hanami.app.settings.github_url
|
||||
link rel="me" href=Hanami.app.settings.fed_bridge_url
|
||||
|
||||
= stylesheet_tag "app"
|
||||
link rel="icon" type="image/x-icon" href="/assets/favicon.ico"
|
||||
|
||||
script data-domain="dnitza.com" src="https://stats.dnitza.com/js/script.js" defer=""
|
||||
|
||||
script src="https://unpkg.com/htmx.org@1.9.2/dist/htmx.min.js" integrity="sha384-L6OqL9pRWyyFU3+/bjdSri+iIphTN/bvYyM37tICVyOJkWZLpP2vGn6VUEXgzg6h" crossorigin="anonymous"
|
||||
script src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.13.3/dist/cdn.min.js"
|
||||
= javascript_tag "app"
|
||||
script src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js" defer=""
|
||||
|
||||
link rel="stylesheet" href="https://unpkg.com/@highlightjs/cdn-assets@11.8.0/styles/github-dark.min.css"
|
||||
script src="https://unpkg.com/@highlightjs/cdn-assets@11.8.0/highlight.min.js" defer=""
|
||||
|
||||
meta property="og:title" content="#{context.content_for(:title)}"
|
||||
meta property="og:type" content="website"
|
||||
meta property="og:url" content="#{context.content_for(:url)}"
|
||||
- if context.content_for(:image)
|
||||
meta property="og:image" content="#{context.content_for(:image)}"
|
||||
|
||||
- if Hanami.app.settings.micropub_pub_key
|
||||
link rel="pgpkey" href="/key"
|
||||
body class="transition-colors bg-white dark:bg-indigo-950 selection:bg-blue-100 selection:text-blue-900 dark:selection:bg-amber-600 dark:selection:text-amber-400" x-data="{ imgModal : false, imgModalSrc : '', imgModalDesc : '' }" x-on:keydown.escape="imgModal=false"
|
||||
main class="pb-8 px-4 pt-4 md:pt-8"
|
||||
header class="mb-12 max-w-screen-md mx-auto items-center md:items-justify"
|
||||
div class="flex mb-8 md:mb-12 text-lg md:text-xl text-gray-400 dark:text-gray-600 grid grid-cols-1 md:grid-cols-2"
|
||||
div class="flex-none mx-auto md:flex-auto md:mx-0"
|
||||
div class="h-card flex items-center"
|
||||
img class="u-photo w-6 h6 md:w-10 md:h-10 mr-1" alt="Memoji profile picture" src="/assets/memoji.png"
|
||||
a href="/" rel="me" class="u-url u-uid"
|
||||
h1 class="p-name text-sm md:text-sm text-gray-600 dark:text-gray-400" = Hanami.app.settings.site_name
|
||||
nav class="space-x-1 text-sm md:text-sm mx-auto md:flex-auto uppercase md:block mt-4 md:mt-3"
|
||||
- pages.each do |page|
|
||||
a class="transition-colors p-1 rounded text-gray-400 hover:bg-#{page.light_colour}-100 hover:text-#{page.light_colour}-400 dark:hover:bg-#{page.dark_colour}-800 #{context.current_path.start_with?("/#{page.slug}") ? "text-#{page.light_colour}-400 bg-#{page.light_colour}-50 dark:bg-#{page.dark_colour}-900 dark:text-#{page.dark_colour}-400" : 'text-gray-400'}" href="/#{page.slug}" #{page.name}
|
||||
span class="text-gray-400 dark:text-gray-600"
|
||||
= "/"
|
||||
a class="transition-colors p-1 rounded hover:bg-emerald-100 hover:text-emerald-400 dark:hover:bg-emerald-800 #{context.current_path.start_with?('/post') ? 'text-emerald-400 bg-emerald-50 dark:bg-emerald-900 dark:text-emerald-400' : 'text-gray-400'}" href="/posts" Writing
|
||||
span class="text-gray-400 dark:text-gray-600"
|
||||
= "/"
|
||||
a class="transition-colors p-1 rounded text-gray-400 hover:bg-orange-100 hover:text-orange-400 dark:hover:bg-orange-800" href="#{Hanami.app.settings.micropub_site_url}/feeds/rss" RSS
|
||||
span class="text-gray-400 dark:text-gray-600"
|
||||
= "/"
|
||||
button x-data="" @click="darkMode = ! darkMode"
|
||||
span class="opacity-80 hover:opacity-100 hidden dark:block" ☀️
|
||||
span class="opacity-80 hover:opacity-100 block dark:hidden" 🌝
|
||||
== yield
|
||||
div class="px-4 max-w-screen-md mx-auto pb-10"
|
||||
p class="float-left text-gray-200 dark:text-indigo-900" © 2010 - 2024 Daniel Nitsikopoulos. All rights reserved.
|
||||
p class="float-right text-gray-200 dark:text-gray-600"
|
||||
a href="https://xn--sr8hvo.ws/%F0%9F%8D%93%E2%9E%97%F0%9F%8E%B0/previous" ←
|
||||
a href="https://xn--sr8hvo.ws" 🕸💍
|
||||
a href="https://xn--sr8hvo.ws/%F0%9F%8D%93%E2%9E%97%F0%9F%8E%B0/next" →
|
2
slices/main/templates/layouts/app.xml.builder
Normal file
2
slices/main/templates/layouts/app.xml.builder
Normal file
@@ -0,0 +1,2 @@
|
||||
xml.instruct!
|
||||
xml << yield
|
34
slices/main/templates/layouts/map.html.slim
Normal file
34
slices/main/templates/layouts/map.html.slim
Normal file
@@ -0,0 +1,34 @@
|
||||
html
|
||||
head
|
||||
meta charest="utf-8"
|
||||
|
||||
meta name="viewport" content="width=device-width, initial-scale=1.0"
|
||||
|
||||
meta name="theme-color" content="rgb(37, 99, 235)"
|
||||
|
||||
title #{context.content_for(:title)} Daniel Nitsikopoulos
|
||||
|
||||
link rel="authorization_endpoint" href=Hanami.app.settings.micropub_authorization_endpoint
|
||||
link rel="token_endpoint" href=Hanami.app.settings.micropub_token_endpoint
|
||||
link rel="micropub" href="#{URI.join(Hanami.app.settings.micropub_site_url, "micropub")}"
|
||||
|
||||
link rel="webmention" href=Hanami.app.settings.webmention_url
|
||||
link rel="pingback" href=Hanami.app.settings.pingback_url
|
||||
link rel="feed" type="text/html" href="/posts"
|
||||
link rel="feed alternate" type="application/rss+xml" href="/feeds/rss"
|
||||
link rel="feed alternate" type="application/rss+xml" href="/feeds/statuses_rss"
|
||||
|
||||
link rel="me" href=Hanami.app.settings.mastodon_url
|
||||
link rel="me" href=Hanami.app.settings.github_url
|
||||
|
||||
= stylesheet_tag "app"
|
||||
link rel="icon" type="image/x-icon" href="/assets/favicon.ico"
|
||||
|
||||
script src='https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.js'
|
||||
link href='https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.css' rel='stylesheet'
|
||||
|
||||
script data-domain="dnitza.com" src="https://stats.dnitza.com/js/script.js" defer="true"
|
||||
= javascript_tag "app"
|
||||
|
||||
body class="bg-white dark:bg-black selection:bg-blue-100 selection:text-blue-900 dark:selection:bg-blue-600 dark:selection:text-blue-100"
|
||||
== yield
|
26
slices/main/templates/more/index.html.slim
Normal file
26
slices/main/templates/more/index.html.slim
Normal file
@@ -0,0 +1,26 @@
|
||||
div class="mb-12 max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 class="text-4xl font-extrabold" More
|
||||
|
||||
h2 class="text-xl" Explore posts
|
||||
|
||||
div class="grid grid-cols-2 gap-2 mb-6"
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/tags" 🔖 By tag
|
||||
/ a class="block p-1 border border-blue-200 bg-blue-300 text-blue-900 hover:bg-blue-200 text-center rounded-lg" href="/years" 🗓️ By year
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/posts" 🪧 All posts
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/tagged/weekly" 🔄 Week posts
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/timemachine/#{Time.now.strftime("%Y/%m/%d")}" ⏳ Time machine
|
||||
|
||||
h2 class="text-xl" Explore everything else
|
||||
|
||||
div class="grid grid-cols-2 gap-2 mb-6"
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/blogroll" 🪵 Blogroll
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/bookshelf" 📚️ Bookshelf
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/collections" 📦 Collections
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/colophon" 🧱 Colophon
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/hikes" 🥾 Hikes
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/movies" 🍿 Movies
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/podcasts" 🎙️ Podcasts
|
||||
a class="block p-1 border border-blue-200 bg-blue-200 text-blue-900 hover:bg-blue-300 text-center rounded-lg" href="/trips" 🛫 Trips
|
||||
|
||||
/ a class="block p-1 border border-blue-200 bg-blue-300 text-blue-900 hover:bg-blue-200 text-center rounded-lg" href="/art" 🎨 Art things
|
||||
|
26
slices/main/templates/movies/index.html.slim
Normal file
26
slices/main/templates/movies/index.html.slim
Normal file
@@ -0,0 +1,26 @@
|
||||
- context.content_for(:title, "Movies | ")
|
||||
|
||||
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 🍿 Movies
|
||||
|
||||
div class="mb-12 max-w-prose mx-auto"
|
||||
table class="prose dark:prose-invert table-auto"
|
||||
thead
|
||||
tr
|
||||
td Title
|
||||
td Year
|
||||
td Rating
|
||||
- movies.each do |movie|
|
||||
tr
|
||||
td
|
||||
a href="#{movie.url}"
|
||||
= movie.title
|
||||
td
|
||||
= movie.year
|
||||
td class="min-w-32"
|
||||
- if movie.rating > 0
|
||||
== "#{'🌕' * movie.rating.floor}#{'🌗' * (movie.rating % movie.rating.floor).ceil}"
|
||||
- else
|
||||
== " "
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
2
slices/main/templates/not_found.html.slim
Normal file
2
slices/main/templates/not_found.html.slim
Normal file
@@ -0,0 +1,2 @@
|
||||
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 Not Found!
|
8
slices/main/templates/pages/show.html.slim
Normal file
8
slices/main/templates/pages/show.html.slim
Normal file
@@ -0,0 +1,8 @@
|
||||
- context.content_for(:title, "")
|
||||
- context.content_for(:highlight_code, false)
|
||||
|
||||
article class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200 prose-em:font-bold prose-em:not-italic prose-em:bg-blue-600 prose-em:px-1 prose-em:rounded prose-a:text-blue-600 prose-a:dark:text-indigo-300 prose-a:p-0.5 prose-a:rounded-sm prose-a:no-underline hover:prose-a:underline prose-em:text-blue-100"
|
||||
h1= page_name
|
||||
== page_content
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
10
slices/main/templates/photos/index.html.slim
Normal file
10
slices/main/templates/photos/index.html.slim
Normal file
@@ -0,0 +1,10 @@
|
||||
- context.content_for(:title, "Photos | ")
|
||||
|
||||
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 Photos
|
||||
|
||||
div class="grid grid-cols-3 gap-4 mb-4 max-w-prose mx-auto"
|
||||
- photos.each do |post|
|
||||
== render "shared/photo_post", post: post, extended: true
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
17
slices/main/templates/places/index.html.slim
Normal file
17
slices/main/templates/places/index.html.slim
Normal file
@@ -0,0 +1,17 @@
|
||||
- context.content_for(:title, "Places | ")
|
||||
|
||||
div class="mb-4 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 Places
|
||||
|
||||
div class="mb-12 max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
nav class="space-x-1 text-sm md:text-sm uppercase md:block"
|
||||
span
|
||||
| 🗺️
|
||||
a href="/places/map" View map
|
||||
|
||||
|
||||
div class="mb-12 max-w-prose mx-auto"
|
||||
- places.each do |post|
|
||||
== render "shared/post", post: post
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
3
slices/main/templates/places/map_page.html.slim
Normal file
3
slices/main/templates/places/map_page.html.slim
Normal file
@@ -0,0 +1,3 @@
|
||||
div class="p-2 bg-white absolute bg-opacity-75 z-40 w-screen"
|
||||
button id="go-back" ← Back
|
||||
div id='map' style='width: 100%; height: 100%;' data-markers="#{JSON.generate(places.map(&:value))}"
|
29
slices/main/templates/podcasts/index.html.slim
Normal file
29
slices/main/templates/podcasts/index.html.slim
Normal file
@@ -0,0 +1,29 @@
|
||||
- context.content_for(:title, "Podcasts | ")
|
||||
|
||||
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 🎙️ Podcasts
|
||||
|
||||
div class="mb-12 max-w-prose mx-auto"
|
||||
- if listens && listens.count > 0
|
||||
div class="p-4 bg-pink-200 dark:bg-pink-900 rounded"
|
||||
h4 class="p-0 m-0 text-pink-900 dark:text-pink-200" Recent listens
|
||||
- listens.each do |listen|
|
||||
div
|
||||
a class="text-pink-800 dark:text-pink-100 no-underline hover:decoration-wavy hover:underline" href=listen.url
|
||||
span = listen.title
|
||||
em= " — #{listen.podcast_name}"
|
||||
|
||||
table class="prose dark:prose-invert table-auto"
|
||||
thead
|
||||
tr
|
||||
td
|
||||
td Name
|
||||
- podcasts.each do |podcast|
|
||||
tr
|
||||
td
|
||||
img class="w-14 m-0 p-0 rounded" src="#{Hanami.app.settings.micropub_site_url}/media/podcast_art/#{podcast.overcast_id}.jpg"
|
||||
td class="p-0 align-middle"
|
||||
a class="block" href="#{podcast.url}"
|
||||
= podcast.name
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
16
slices/main/templates/posts/archive.html.slim
Normal file
16
slices/main/templates/posts/archive.html.slim
Normal file
@@ -0,0 +1,16 @@
|
||||
div class="mb-4 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 Archive: #{year}
|
||||
|
||||
div class="mb-12 max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
nav class="space-x-1 text-sm md:text-sm uppercase md:block"
|
||||
span Archive:
|
||||
- post_years.each do |y|
|
||||
a href="/posts/archive/#{y}" class="text-sm hover:text-gray-400 #{year.to_s == y.to_s ? 'underline decoration-wavy' : ''}"= y
|
||||
- if y != post_years.last
|
||||
span ·
|
||||
div class="h-feed mb-12 max-w-prose mx-auto"
|
||||
- posts.each do |post|
|
||||
== render "shared/post", post: post
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
||||
|
27
slices/main/templates/posts/index.html.slim
Normal file
27
slices/main/templates/posts/index.html.slim
Normal file
@@ -0,0 +1,27 @@
|
||||
- context.content_for(:title, "Writing | ")
|
||||
|
||||
div class="mb-4 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 Writing
|
||||
|
||||
div class="mb-4 max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
nav class="space-x-1 text-sm md:text-sm uppercase md:block"
|
||||
span Archive:
|
||||
- post_years.each do |year|
|
||||
a href="/posts/archive/#{year}" class="text-sm hover:text-gray-400"= year
|
||||
- if year != post_years.last
|
||||
span ·
|
||||
div class="h-feed mb-12 max-w-prose mx-auto"
|
||||
form action="/posts" method="GET"
|
||||
div class="flex"
|
||||
div class="flex-auto basis-auto mr-4"
|
||||
input id="search" type="text" class="p-1 rounded w-full bg-indigo-50 dark:bg-indigo-400" name="q" value=query
|
||||
div class="mr-4"
|
||||
input type="submit" class="bg-indigo-300 p-1 rounded text-indigo-900" value="Search"
|
||||
-if query
|
||||
div class=""
|
||||
a href="/posts" class="text-gray-400 dark:text-gray-100 pt-1" Clear search
|
||||
div class="h-feed mb-12 max-w-prose mx-auto"
|
||||
- posts.each do |post|
|
||||
== render "shared/post", post: post
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
137
slices/main/templates/posts/show.html.slim
Normal file
137
slices/main/templates/posts/show.html.slim
Normal file
@@ -0,0 +1,137 @@
|
||||
- context.content_for(:title, "#{post.display_title} | ")
|
||||
- context.content_for(:url, post.permalink)
|
||||
- context.content_for(:image, post.key_image)
|
||||
|
||||
article class="h-entry"
|
||||
template @img-modal.window="imgModal = true; imgModalSrc = $event.detail.imgModalSrc; imgModalDesc = $event.detail.imgModalDesc;" x-if="imgModal"
|
||||
div @mousedown.outside="imgModalSrc = ''" class="p-2 fixed w-full h-100 inset-0 z-50 overflow-hidden flex justify-center items-center bg-black bg-opacity-75"
|
||||
div @mousedown.outside="imgModal = ''" class="flex flex-col max-w-3xl max-h-full overflow-auto"
|
||||
div class="z-50"
|
||||
button @click="imgModal = ''" class="float-right pt-2 pr-2 outline-none focus:outline-none"
|
||||
svg class="fill-current text-white" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"
|
||||
path d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z">
|
||||
div class="p-2 text-center w-full"
|
||||
img class="rounded object-contain h-1/2-screen shadow-solid shadow-pink-100 dark:shadow-pink-200 mb-4 mx-auto" :src="imgModalSrc" :alt="imgModalSrc"
|
||||
p x-text="imgModalDesc" class="text-center text-white"
|
||||
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||
h1 class="p-name mb-2"
|
||||
a class="u-url" href=post.permalink
|
||||
= post.display_title
|
||||
nav class="space-x-1 text-sm md:text-sm md:block dark:text-gray-600"
|
||||
- if post.location || post.photos? || post.videos?
|
||||
span See more:
|
||||
- if post.location
|
||||
a class="dark:text-gray-400" href="/places" places
|
||||
- if post.photos? || post.videos?
|
||||
a class="dark:text-gray-400" href="/photos" photos
|
||||
article class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200 prose-a:text-blue-600 prose-a:no-underline hover:prose-a:underline prose-img:rounded prose-video:rounded"
|
||||
div class="e-content prose-code:bg-pink-100 prose-code:text-pink-900"
|
||||
== post.content
|
||||
|
||||
- if post.photos?
|
||||
- post.photos.each_with_index do |photo, idx|
|
||||
figure id="photo-#{idx}"
|
||||
img class="u-photo shadow-solid shadow-pink-100 dark:shadow-pink-200 mb-4" src=photo["value"] alt=photo["alt"]
|
||||
figcaption
|
||||
= photo["alt"]
|
||||
- if post.videos?
|
||||
- post.videos.each_with_index do |video, index|
|
||||
figure id="video-#{index}"
|
||||
video loop=false muted=true controls=true
|
||||
source type="video/mp4" src="#{video["value"]}"
|
||||
figcaption= video["alt"]
|
||||
a href="#" data-replay="video-#{index}" Replay
|
||||
|
||||
|
||||
- if post.location
|
||||
img class="shadow-solid shadow-pink-100 dark:shadow-pink-200 rounded mb-4" src=post.large_map
|
||||
-if post.webmentions && post.webmentions.count > 0
|
||||
div class="mt-12"
|
||||
h3 #{post.webmentions.count} Comment#{post.webmentions.count != 1 ? "s" : ""}
|
||||
- post.webmentions.each do |mention|
|
||||
div class="prose-p:m-1 mb-6 p-8 bg-orange-100 dark:bg-indigo-900 squircle"
|
||||
div class="flex h-8"
|
||||
img class="w-8 rounded-full m-0 mr-2" src=mention.author_photo
|
||||
a class="block text-orange-700 dark:text-violet-300 no-underline hover:underline" href=mention.author_url
|
||||
= mention.author_name
|
||||
div class="prose dark:prose-invert dark:text-indigo-250 prose-a:text-orange-700 dark:prose-a:text-violet-300 prose-a:no-underline hover:prose-a:underline"
|
||||
== mention.content_html
|
||||
div class="text-sm"
|
||||
a class="no-underline hover:underline text-orange-900 dark:text-violet-400" href=mention.source_url
|
||||
= mention.published_at.strftime("%e %B, %Y")
|
||||
div class="mb-12"
|
||||
- if trip
|
||||
div class="max-w-prose mx-auto text-gray-600 dark:text-gray-200 flex gap-4"
|
||||
a href="/trips/#{trip.id}" class="block grow bg-orange-100 hover:bg-orange-200 dark:bg-orange-600 hover:dark:bg-orange-900 rounded px-4 py-2 mb-2"
|
||||
span class="pr-8"✈️
|
||||
= "Part of the trip: "
|
||||
strong #{trip.name}
|
||||
- if post.tags.map(&:label).include? "weekly"
|
||||
div class="max-w-prose mx-auto text-gray-600 dark:text-gray-200 flex gap-4"
|
||||
div class="grow" hx-get="/post/top_tracks/#{post.slug}" hx-trigger="load"
|
||||
|
||||
- if past_movies.count > 0
|
||||
div class="max-w-prose mx-auto text-gray-600 dark:text-gray-200 mb-4"
|
||||
h3 class="text-xl" Movies watched for the first time this week
|
||||
div class="flex gap-4 pb-4 mt-4"
|
||||
- past_movies.map do |movie|
|
||||
a href=movie.url
|
||||
figure class="w-24"
|
||||
img class="rounded hover:opacity-80" src=movie.poster
|
||||
/ figcaption= movie.title
|
||||
hr
|
||||
- if text_posts.count > 0 || photo_posts.count > 0
|
||||
div class="max-w-prose mx-auto text-gray-600 dark:text-gray-200 mb-4"
|
||||
h3 class="text-xl mb-0" This week, years ago
|
||||
- if text_posts.count > 0
|
||||
div class="max-w-prose mx-auto text-gray-600 dark:text-gray-200 mb-4"
|
||||
div class="block grow bg-blue-100 dark:bg-blue-600 rounded px-4 py-2 mb-12"
|
||||
ul class="mt-0"
|
||||
- text_posts.each do |past_post|
|
||||
li class="m-0"
|
||||
a class="hover:underline" href=past_post.permalink
|
||||
= "#{past_post.display_title} (#{past_post.published_at.year})"
|
||||
|
||||
div class="max-w-prose mx-auto text-gray-600 dark:text-gray-200 flex"
|
||||
div class="grid grid-cols-3 gap-4 mb-4 max-w-prose mx-auto"
|
||||
- photo_posts.group_by{ |p| p.published_at.year }.each do |year, posts|
|
||||
-posts.each_with_index do |post, index|
|
||||
div
|
||||
-if index == 0
|
||||
p class="mb-1 px-1" = year
|
||||
- else
|
||||
p class="mb-1 px-1"
|
||||
== render "shared/photo_post", post: post, extended: false
|
||||
div class="mb-4 max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
||||
|
||||
div class="max-w-prose mx-auto text-gray-600 dark:text-gray-200 flex"
|
||||
div class=""
|
||||
= "Published "
|
||||
time class="dt-published" datetime=post.machine_published_at
|
||||
= post.display_published_at
|
||||
p
|
||||
a class="p-author h-card" href=Hanami.app.settings.micropub_site_url
|
||||
= "by #{Hanami.app.settings.site_name}"
|
||||
p
|
||||
span in
|
||||
- if post.posted_in == :posts
|
||||
a class="hover:underline text-blue-400 dark:text-indigo-300" href="/posts" posts
|
||||
- if post.posted_in == :places
|
||||
a class="hover:underline text-blue-400 dark:text-indigo-300" href="/places" places
|
||||
- if post.posted_in == :statuses
|
||||
a class="hover:underline text-blue-400 dark:text-indigo-300" href="/statuses" statuses
|
||||
- if post.posted_in == :bookshelf
|
||||
a class="hover:underline text-blue-400 dark:text-indigo-300" href="/bookshelf" bookshelf
|
||||
span class="text-right flex-1 leading-6"
|
||||
== render "shared/tags", tags: post.tags
|
||||
div class="mb-2 max-w-prose mx-auto text-gray-600 dark:text-gray-200 flex"
|
||||
- if post.syndicated?
|
||||
span Also on:
|
||||
- post.syndicated_to.each do |loc|
|
||||
- next if loc[:location] == ""
|
||||
a rel="syndication" class="u-syndication" href=loc[:url]
|
||||
== render "shared/#{loc[:location]}"
|
||||
|
||||
svg width="10" height="10" viewBox="0 0 10 10"
|
||||
clipPath id="squircleClip" clipPathUnits="objectBoundingBox"
|
||||
path fill="red" stroke="none" d="M 0,0.5 C 0,0 0,0 0.5,0 S 1,0 1,0.5 1,1 0.5,1 0,1 0,0.5"
|
11
slices/main/templates/posts/top_tracks.html.slim
Normal file
11
slices/main/templates/posts/top_tracks.html.slim
Normal file
@@ -0,0 +1,11 @@
|
||||
div class="mx-auto"
|
||||
a href=url class="block flex bg-pink-100 hover:bg-pink-200 dark:bg-pink-600 hover:dark:bg-pink-900 rounded px-4 py-2 mb-12"
|
||||
div class="mr-4 my-auto"
|
||||
div class="w-34 h-34 my-auto text-[2.041rem] block dark:hidden" 👩🏼🎤
|
||||
div class="w-34 h-34 my-auto text-[2.041rem] hidden dark:block" 👨🏽🎤
|
||||
div
|
||||
p class="text-sm" Top track
|
||||
p class="hover:underline"
|
||||
p class="font-semibold"= name
|
||||
p class=""= artist
|
||||
|
10
slices/main/templates/recently_played/index.html.slim
Normal file
10
slices/main/templates/recently_played/index.html.slim
Normal file
@@ -0,0 +1,10 @@
|
||||
div class="grid grid-cols-4 gap-2"
|
||||
- recently_played_music.each do |album|
|
||||
a href="#{album[:href]}"
|
||||
div
|
||||
img class="m-0 rounded transition-transform ease-out hover:scale-105" src="#{album[:image]}"
|
||||
span class="inline-block text-sm"
|
||||
= album[:name]
|
||||
br
|
||||
span class="inline-block text-sm font-bold"
|
||||
= album[:artist]
|
2
slices/main/templates/shared/_blue_sky.html.slim
Normal file
2
slices/main/templates/shared/_blue_sky.html.slim
Normal file
@@ -0,0 +1,2 @@
|
||||
- w_class = defined?(width) ? width : "w-6"
|
||||
<svg class="fill-blue-100 hover:fill-blue-400 #{w_class}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M433 179.11c0-97.2-63.71-125.7-63.71-125.7-62.52-28.7-228.56-28.4-290.48 0 0 0-63.72 28.5-63.72 125.7 0 115.7-6.6 259.4 105.63 289.1 40.51 10.7 75.32 13 103.33 11.4 50.81-2.8 79.32-18.1 79.32-18.1l-1.7-36.9s-36.31 11.4-77.12 10.1c-40.41-1.4-83-4.4-89.63-54a102.54 102.54 0 0 1-.9-13.9c85.63 20.9 158.65 9.1 178.75 6.7 56.12-6.7 105-41.3 111.23-72.9 9.8-49.8 9-121.5 9-121.5zm-75.12 125.2h-46.63v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.33V197c0-58.5-64-56.6-64-6.9v114.2H90.19c0-122.1-5.2-147.9 18.41-175 25.9-28.9 79.82-30.8 103.83 6.1l11.6 19.5 11.6-19.5c24.11-37.1 78.12-34.8 103.83-6.1 23.71 27.3 18.4 53 18.4 175z"/></svg>
|
20
slices/main/templates/shared/_bookmark.html.slim
Normal file
20
slices/main/templates/shared/_bookmark.html.slim
Normal file
@@ -0,0 +1,20 @@
|
||||
div class="mb-4"
|
||||
div class="mb-4"
|
||||
h3 class="text-xl text-blue-600 dark:text-slate-200 mb-2"
|
||||
a class="u-url hover:text-blue-900 hover:dark:text-slate-600" href="/bookmark/#{bookmark.slug}"
|
||||
= "#{bookmark.name} "
|
||||
|
||||
small class="text-gray-400 hover:text-gray-600"
|
||||
a href=bookmark.url
|
||||
= bookmark.url
|
||||
== render("link_arrow")
|
||||
p class="e-content leading-relaxed md:text-lg text-gray-800 dark:text-gray-200"
|
||||
= bookmark.content
|
||||
|
||||
== bookmark.youtube_embed
|
||||
|
||||
p class="text-sm text-blue-400 mb-4"
|
||||
a href="/bookmark/#{bookmark.slug}"
|
||||
time class="dt-published" datetime=bookmark.machine_published_at
|
||||
= bookmark.display_published_at
|
||||
hr
|
28
slices/main/templates/shared/_compact_post.html.slim
Normal file
28
slices/main/templates/shared/_compact_post.html.slim
Normal file
@@ -0,0 +1,28 @@
|
||||
div class="mb-5 h-entry relative"
|
||||
- if !first
|
||||
div class="-top-4 -left-1 absolute rounded-full border-2 bg-orange-100 border-orange-200 p-1 w-1 h-2 inline-block dark:border-amber-400 dark:bg-amber-400"
|
||||
- if first
|
||||
div class="inline-block mb-2 dark:text-indigo-400"
|
||||
span class="mr-2"
|
||||
= "🛬"
|
||||
= trip.start_date
|
||||
|
||||
div class="ml-[5] #{ last ? '' : 'mb-[20]'} pl-6 border-solid border-l-2 border-orange-200 dark:border-amber-400"
|
||||
h3 class="text-xl font-semibold text-blue-600 dark:text-indigo-300"
|
||||
a class="border-b-2 border-transparent hover:border-blue-600 hover:border-b-2" href="/post/#{post.slug}"
|
||||
= post.name
|
||||
div class="e-content prose-p:mb-0 prose-img:my-2 prose-a:text-blue-600 prose-a:no-underline hover:prose-a:underline p-name text-base prose prose-ul:list-none prose-ul:pl-0 prose-li:pl-0 text-gray-800 dark:text-gray-200 prose-a:dark:text-gray-100"
|
||||
== post.excerpt
|
||||
div class="grid gap-4 grid-flow-row grid-cols-4 grid-rows-1"
|
||||
-post.photos.each do |photo|
|
||||
img class="w-44 h-44 object-cover rounded" src=photo["value"]
|
||||
|
||||
p class="text-sm text-blue-400 dark:text-indigo-50"
|
||||
a class="u-url" href="#{post.permalink}"
|
||||
time class="dt-published" datetime=post.machine_published_at
|
||||
= post.display_published_at
|
||||
- if last
|
||||
div class="inline-block mb-6 dark:text-indigo-400"
|
||||
span class="mr-2"
|
||||
= "🛫"
|
||||
= trip.end_date
|
1
slices/main/templates/shared/_instagram.html.slim
Normal file
1
slices/main/templates/shared/_instagram.html.slim
Normal file
@@ -0,0 +1 @@
|
||||
<svg class="fill-purple-100 hover:fill-purple-400 w-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.3.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"/></svg>
|
After Width: | Height: | Size: 1.2 KiB |
4
slices/main/templates/shared/_link_arrow.html.slim
Normal file
4
slices/main/templates/shared/_link_arrow.html.slim
Normal file
@@ -0,0 +1,4 @@
|
||||
svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="inline w-2 h2 md:w-3 md:h-3 mt-0.5 ml-1.5"
|
||||
g
|
||||
rect height="12.4434" opacity="0" width="12.4238" x="0" y="0"
|
||||
path d="M14.4238 10.8008L14.4141 0.976562C14.4141 0.419922 14.0527 0.0292969 13.4668 0.0292969L3.64258 0.0292969C3.0957 0.0292969 2.72461 0.449219 2.72461 0.917969C2.72461 1.38672 3.14453 1.78711 3.60352 1.78711L7.00195 1.78711L11.7676 1.63086L9.95117 3.22266L0.273438 12.9199C0.0976562 13.0957 0 13.3203 0 13.5352C0 14.0039 0.419922 14.4434 0.908203 14.4434C1.13281 14.4434 1.34766 14.3652 1.52344 14.1797L11.2207 4.49219L12.832 2.66602L12.6562 7.22656L12.6562 10.8398C12.6562 11.2988 13.0566 11.7285 13.5352 11.7285C14.0039 11.7285 14.4238 11.3281 14.4238 10.8008Z"
|
2
slices/main/templates/shared/_mastodon.html.slim
Normal file
2
slices/main/templates/shared/_mastodon.html.slim
Normal file
@@ -0,0 +1,2 @@
|
||||
- w_class = defined?(width) ? width : "w-6"
|
||||
<svg class="fill-blue-100 hover:fill-blue-400 #{w_class}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M433 179.11c0-97.2-63.71-125.7-63.71-125.7-62.52-28.7-228.56-28.4-290.48 0 0 0-63.72 28.5-63.72 125.7 0 115.7-6.6 259.4 105.63 289.1 40.51 10.7 75.32 13 103.33 11.4 50.81-2.8 79.32-18.1 79.32-18.1l-1.7-36.9s-36.31 11.4-77.12 10.1c-40.41-1.4-83-4.4-89.63-54a102.54 102.54 0 0 1-.9-13.9c85.63 20.9 158.65 9.1 178.75 6.7 56.12-6.7 105-41.3 111.23-72.9 9.8-49.8 9-121.5 9-121.5zm-75.12 125.2h-46.63v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.33V197c0-58.5-64-56.6-64-6.9v114.2H90.19c0-122.1-5.2-147.9 18.41-175 25.9-28.9 79.82-30.8 103.83 6.1l11.6 19.5 11.6-19.5c24.11-37.1 78.12-34.8 103.83-6.1 23.71 27.3 18.4 53 18.4 175z"/></svg>
|
9
slices/main/templates/shared/_photo_post.html.slim
Normal file
9
slices/main/templates/shared/_photo_post.html.slim
Normal file
@@ -0,0 +1,9 @@
|
||||
- if extended
|
||||
- post.photos.each_with_index do |photo, idx|
|
||||
div class="rounded max-w-xs"
|
||||
a href="#{post.permalink}#photo-#{idx}"
|
||||
img class="rounded object-cover transition-transform ease-out hover:scale-105 h-48 w-48" src="#{photo["value"]}" alt="#{photo["alt"]}"
|
||||
- else
|
||||
div class="rounded max-w-xs"
|
||||
a href="#{post.permalink}"
|
||||
img class="rounded object-cover transition-transform ease-out hover:scale-105 h-48 w-48" src="#{post.photos[0]["value"]}" alt="#{post.photos[0]["alt"]}"
|
3
slices/main/templates/shared/_pinboard.html.slim
Normal file
3
slices/main/templates/shared/_pinboard.html.slim
Normal file
@@ -0,0 +1,3 @@
|
||||
- w_class = defined?(width) ? width : "w-6"
|
||||
<svg class="fill-blue-100 hover:fill-blue-400 #{w_class} -rotate-45" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--! Font Awesome Pro 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M32 32C32 14.3 46.3 0 64 0H320c17.7 0 32 14.3 32 32s-14.3 32-32 32H290.5l11.4 148.2c36.7 19.9 65.7 53.2 79.5 94.7l1 3c3.3 9.8 1.6 20.5-4.4 28.8s-15.7 13.3-26 13.3H32c-10.3 0-19.9-4.9-26-13.3s-7.7-19.1-4.4-28.8l1-3c13.8-41.5 42.8-74.8 79.5-94.7L93.5 64H64C46.3 64 32 49.7 32 32zM160 384h64v96c0 17.7-14.3 32-32 32s-32-14.3-32-32V384z"/></svg>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user