Add highlights

This commit is contained in:
2024-04-05 21:35:39 +11:00
parent d02b74621a
commit 38d5108979
22 changed files with 188 additions and 25 deletions

View File

@@ -1,4 +1,5 @@
web: bundle exec hanami server
tailwind: bundle exec rake tailwind:watch
tailwind_main: bundle exec rake tailwind:watch
tailwind_admin: bundle exec rake tailwind:watch_admin
assets: bundle exec hanami assets watch
redis: redis-server

View File

@@ -41,7 +41,7 @@ namespace :blog do
next if tweet["tweet"]["full_text"].start_with? "@"
tweet["tweet"]["full_text"] = tweet["tweet"]["full_text"].gsub(/(#{URI::DEFAULT_PARSER.make_regexp})/, "<a href='#{$1}'>#{$1}</a>")
repo.create({slug: tweet["tweet"]["id"], content: tweet["tweet"]["full_text"], published_at: tweet["tweet"]["created_at"], category: [], post_type: "post", syndication_sources: {twitter: "https://twitter.com/nitza/status/#{tweet["tweet"]["id"]}"}})
repo.create({slug: tweet["tweet"]["id"], content: tweet["tweet"]["full_text"], published_at: tweet["tweet"]["created_at"], category: ["tweet"], post_type: "post", syndication_sources: {twitter: "https://twitter.com/nitza/status/#{tweet["tweet"]["id"]}"}})
end
end
@@ -100,10 +100,15 @@ end
namespace :tailwind do
task :watch do
system("npx tailwindcss -i ./slices/main/assets/css/app.css -o ./public/assets/main/app.css --watch")
system("npx tailwindcss -i ./slices/main/assets/css/app.css -o ./public/assets/_main/app.css --watch")
end
task :watch_admin do
system("npx tailwindcss -i ./slices/admin/assets/css/app.css -o ./public/assets/_admin/app.css --watch")
end
task :build do
system("npx tailwindcss -i ./slices/main/assets/css/app.css -o ./slices/main/assets/builds/app.css --minify")
system("npx tailwindcss -i ./slices/main/assets/css/app.css -o ./slices/_main/assets/builds/app.css --minify")
system("npx tailwindcss -i ./slices/admin/assets/css/app.css -o ./slices/_admin/assets/builds/app.css --minify")
end
end

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
module Adamantium
module Relations
class Highlights < ROM::Relation[:sql]
schema :highlights, infer: true do
associations do
belongs_to :post
end
end
auto_struct(true)
end
end
end

View File

@@ -11,8 +11,8 @@ module Adamantium
has_many :post_trips
has_many :trips, through: :post_trips
has_many :highlights
has_many :reactions
has_many :webmentions
end
end

View File

@@ -31,9 +31,9 @@ html x-data="{darkMode: $persist(false)}" :class="{'dark' : darkMode === true}"
script data-goatcounter="https://stats.dnitza.com/count" async="" src="//stats.dnitza.com/count.js"
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"
script src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.13.8/dist/cdn.min.js"
= javascript_tag "app"
script src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js" defer=""
script src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.8/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=""

View File

@@ -9,8 +9,9 @@ module Adamantium
config.actions.content_security_policy[:script_src] += " https://api.mapbox.com/mapbox-gl-js/v2.9.1/mapbox-gl.js"
config.actions.content_security_policy[:media_src] += " https://dnitza.com"
config.actions.content_security_policy[:script_src] += " https://unpkg.com/htmx.org@1.9.2/dist/htmx.min.js "
config.actions.content_security_policy[:script_src] += " https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js"
config.actions.content_security_policy[:script_src] += " https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.13.3/dist/cdn.min.js"
config.actions.content_security_policy[:script_src] += " https://cdn.jsdelivr.net/npm/alpinejs@3.13.8/dist/cdn.min.js"
config.actions.content_security_policy[:script_src] += " https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.13.8/dist/cdn.min.js"
config.actions.content_security_policy[:script_src] += " https://cdn.jsdelivr.net/npm/@alpinejs/anchor@3.13.8/dist/cdn.min.js"
config.actions.content_security_policy[:script_src] += " https://unpkg.com/@highlightjs/cdn-assets@11.8.0/highlight.min.js"
config.actions.content_security_policy[:connect_src] += " https://stats.dnitza.com/count https://*.mapbox.com"

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
ROM::SQL.migration do
change do
create_table :highlights do
primary_key :id
foreign_key :post_id, :posts, null: false
column :text, :text, null: false
index :post_id
end
end
end

View File

@@ -0,0 +1,17 @@
# frozen_string_literal: true
module Admin
module Actions
module Posts
class CreateHighlight < Admin::Action
include Deps["commands.highlight.create"]
def handle(req, res)
create.call(post_id: req.params[:id], text: req.params[:text])
res.redirect_to "/admin/posts/#{req.params[:id]}"
end
end
end
end
end

View File

@@ -0,0 +1,17 @@
# frozen_string_literal: true
module Admin
module Actions
module Posts
class DeleteHighlight < Admin::Action
include Deps["repos.highlight_repo"]
def handle(req, res)
highlight_repo.delete(req.params[:highlight_id])
res.redirect_to "/admin/posts/#{req.params[:post_id]}"
end
end
end
end
end

View File

@@ -1,5 +1,4 @@
body {
background-color: #fff;
color: #000;
font-family: sans-serif;
}
@tailwind base;
@tailwind components;
@tailwind utilities;
@tailwind typography;

View File

@@ -6,5 +6,26 @@ import "../css/app.css";
Alpine.magic('clipboard', () => {
return subject => navigator.clipboard.writeText(subject)
})
Alpine.magic('textHighlighter', (el, {Alpine}) => {
return {
isOpen: false,
anchorX: "0px",
anchorY: "0px",
selection: null,
text: "",
highlightText() {
this.isOpen = false
// document.
this.selection = document.getSelection()
this.text = this.selection.toString()
const anchor = this.selection.focusNode.parentElement.getBoundingClientRect()
this.isOpen = el.contains(this.selection.focusNode) && this.selection.focusOffset != this.selection.anchorOffset;
this.anchorX = `${anchor.left - 170}px`
this.anchorY = `${anchor.top + window.scrollY}px`
}
}
})
})
})();

View File

@@ -0,0 +1,16 @@
module Admin
module Commands
module Highlight
class Create
include Dry::Monads[:result]
include Deps["repos.highlight_repo"]
def call(post_id:, text:)
highlight_repo.create(post_id:, text:)
Success()
end
end
end
end
end

View File

@@ -54,6 +54,8 @@ module Admin
post "/posts/:id/de_syndicate/:target", to: Auth.call(action: "posts.de_syndicate")
post "/posts/:id/syndicate/:target", to: Auth.call(action: "posts.syndicate")
post "/post/:id/update", to: Auth.call(action: "posts.update")
post "/post/:id/highlight", to: Auth.call(action: "posts.create_highlight")
delete "/post/:post_id/highlight/:highlight_id", to: Auth.call(action: "posts.delete_highlight")
get "/media", to: Auth.call(action: "photos.index")
delete "/media/public/media/:year/:path", to: Auth.call(action: "photos.delete")

View File

@@ -0,0 +1,12 @@
module Admin
module Repos
class HighlightRepo < Adamantium::Repo[:highlights]
commands :create, delete: :by_pk
def list_all
highlights
.to_a
end
end
end
end

View File

@@ -71,7 +71,7 @@ module Admin
def find(id:)
posts
.combine(:tags)
.combine(:tags, :highlights)
.where(id: id).one!
end

View File

@@ -19,7 +19,7 @@ div class="max-w-prose mx-auto" x-data="{ activeTab: 0 }"
tr id="bookmark-#{bookmark.id}"
td
div
a href="/bookmark/#{bookmark.slug}"
a href="/admin/posts/#{bookmark.id}"
= bookmark.name
a class="no-underline" href=bookmark.url
small class="text-gray-400 dark:text-gray-600" = bookmark.url

View File

@@ -14,7 +14,8 @@ html
= javascript_tag "app"
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@3.12.0/dist/cdn.min.js" defer="true"
script src="https://cdn.jsdelivr.net/npm/@alpinejs/anchor@3.13.8/dist/cdn.min.js" defer="true"
script src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.8/dist/cdn.min.js" defer="true"
- if Hanami.app.settings.micropub_pub_key
link rel="pgpkey" href="/key"

View File

@@ -24,12 +24,18 @@ div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:
button hx-post="/admin/posts/#{post.id}/syndicate/day_one" Send to Day One
// TODO: Add preview, fix sending to DayOne
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"
a href="/post/#{post.slug}"
h1= post.name || "💬"
article x-data="$textHighlighter, {isOpen: false, anchorX: 0, anchorY: 0, text: ''}" 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"
- if post.post_type != "bookmark"
a href="/post/#{post.slug}"
h1= post.name || "💬"
form action="/admin/post/#{post.id}/update" method="POST"
textarea name="body" class="text-gray-800 w-full border-blue-200 border-2 rounded p-2 mb-4" x-data="{ resize: () => { $el.style.height = '5px'; $el.style.height = $el.scrollHeight + 'px' } }" x-init="resize()" @input="resize()"
== markdown_body
- if post.post_type != "bookmark"
textarea name="body" class="text-gray-800 w-full border-blue-200 border-2 rounded p-2 mb-4" x-data="{ resize: () => { $el.style.height = '5px'; $el.style.height = $el.scrollHeight + 'px' } }" x-init="resize()" @input="resize()"
== markdown_body
- if post.post_type == "bookmark"
div x-ref="bookmarkText" @mouseup.capture="highlightText()"
== post.cached_content
fieldset class="mb-4 flex"
label for="commentable" class="mr-2" Commentable?
input class="mt-2" type="checkbox" value="true" id="commentable" name="commentable" switch="switch" checked=post.commentable
@@ -38,3 +44,21 @@ article class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 d
input type="text" name="tags" id="tags" class="w-full px-1 border rounded" value="#{post.tags.map(&:label).join(", ")}"
button class="rounded bg-blue-100 hover:bg-blue-200 text-blue-600 px-2 hover:cursor-pointer" type="submit"
= "Update"
- if post.highlights.count > 0
table class="prose dark:prose-invert table-auto"
- post.highlights.each_with_index do |highlight, idx|
tr class="#{idx.even? ? 'bg-amber-50' : ''}"
td class="p-2"
= highlight.text
td class="p-2"
form method="POST" action="/admin/post/#{post.id}/highlight/#{highlight.id}"
input type="hidden" name="_method" value="delete"
button
= "Delete"
div @click.outside="isOpen = false" class="p-2 bg-indigo-900 hover:bg-indigo-800 rounded text-white shadow-md shadow-indigo-500" x-show="isOpen" x-anchor.no-style="$refs.bookmarkText" :style="{ position: 'absolute', hidden: isOpen, top: isOpen ? anchorY : '0px', left: isOpen ? anchorX : '0px' }"
form method="POST" class="p-0 m-0" action="/admin/post/#{post.id}/highlight"
input type="hidden" name="post_id" value="#{post.id}"
input class="text-gray-600" type="hidden" x-model="text" name="text"
button = "Save highlight"

View File

@@ -184,7 +184,7 @@ module Main
def fetch!(slug)
posts
.published
.combine(:tags, :trips, :webmentions, :reactions)
.combine(:tags, :trips, :webmentions, :reactions, :highlights)
.node(:webmentions) { |webmention|
webmention.published.where(type: "reply")
}

View File

@@ -13,6 +13,13 @@ div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:
== bookmark.content
- if bookmark.highlights.count > 0
h2 Highlights
- bookmark.highlights.each do |highlight|
div class="rounded p-2 bg-amber-100 dark:bg-amber-800 mb-4"
= render "shared/quote", color: "fill-amber-200 dark:fill-amber-700"
= highlight.text
- unless bookmark.cached_content.nil?
button class="hover:text-gray-400" @click="open = ! open" Toggle cached version

View File

@@ -31,9 +31,9 @@ html x-data="{darkMode: $persist(false)}" :class="{'dark' : darkMode === true}"
script data-goatcounter="https://stats.dnitza.com/count" async="" src="//stats.dnitza.com/count.js"
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"
script src="https://cdn.jsdelivr.net/npm/@alpinejs/persist@3.13.8/dist/cdn.min.js"
= javascript_tag "app"
script src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.3/dist/cdn.min.js" defer=""
script src="https://cdn.jsdelivr.net/npm/alpinejs@3.13.8/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=""

View File

@@ -0,0 +1,12 @@
- color_class = defined?(color) ? color : "fill-gray-900"
xml version="1.0" encoding="iso-8859-1"
svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 57 57" xml:space="preserve" class="w-8"
g
circle class="#{color_class}" cx="18.5" cy="31.5" r="5.5"
path class="#{color_class}" d="M18.5,38c-3.584,0-6.5-2.916-6.5-6.5s2.916-6.5,6.5-6.5s6.5,2.916,6.5,6.5S22.084,38,18.5,38z M18.5,27c-2.481,0-4.5,2.019-4.5,4.5s2.019,4.5,4.5,4.5s4.5-2.019,4.5-4.5S20.981,27,18.5,27z"
g
circle class="#{color_class}" cx="35.5" cy="31.5" r="5.5"
path class="#{color_class}" d="M35.5,38c-3.584,0-6.5-2.916-6.5-6.5s2.916-6.5,6.5-6.5s6.5,2.916,6.5,6.5S39.084,38,35.5,38z M35.5,27c-2.481,0-4.5,2.019-4.5,4.5s2.019,4.5,4.5,4.5s4.5-2.019,4.5-4.5S37.981,27,35.5,27z"
path class="#{color_class}" d="M13,32c-0.553,0-1-0.447-1-1c0-7.72,6.28-14,14-14c0.553,0,1,0.447,1,1s-0.447,1-1,1 c-6.617,0-12,5.383-12,12C14,31.553,13.553,32,13,32z"
path class="#{color_class}" d="M30,32c-0.553,0-1-0.447-1-1c0-7.72,6.28-14,14-14c0.553,0,1,0.447,1,1s-0.447,1-1,1 c-6.617,0-12,5.383-12,12C31,31.553,30.553,32,30,32z"