diff --git a/slices/admin/decorators/bookmarks/decorator.rb b/slices/admin/decorators/bookmarks/decorator.rb
new file mode 100644
index 0000000..d5f6e67
--- /dev/null
+++ b/slices/admin/decorators/bookmarks/decorator.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: false
+
+# auto_register: false
+
+module Admin
+ 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
+ "
"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/slices/admin/decorators/books/decorator.rb b/slices/admin/decorators/books/decorator.rb
new file mode 100644
index 0000000..e4c2402
--- /dev/null
+++ b/slices/admin/decorators/books/decorator.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: false
+
+# auto_register: false
+
+module Admin
+ 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
diff --git a/slices/admin/decorators/movies/decorator.rb b/slices/admin/decorators/movies/decorator.rb
new file mode 100644
index 0000000..70710be
--- /dev/null
+++ b/slices/admin/decorators/movies/decorator.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: false
+
+# auto_register: false
+
+module Admin
+ 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
diff --git a/slices/admin/decorators/posts/decorator.rb b/slices/admin/decorators/posts/decorator.rb
new file mode 100644
index 0000000..d16b928
--- /dev/null
+++ b/slices/admin/decorators/posts/decorator.rb
@@ -0,0 +1,211 @@
+# frozen_string_literal: false
+
+# auto_register: false
+
+require "rexml/parsers/pullparser"
+require "sanitize"
+require "nokogiri"
+require "unicode/emoji"
+
+module Admin
+ 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
+
+ inline_images.first[1] if inline_images
+ end
+
+ def inline_images
+ doc = Nokogiri::HTML(content)
+ doc.at("//img")
+ end
+
+ def inline_image_sources
+ inline_images
+ &.select {|attr, _value| attr == "src"}
+ &.map {|img| img[1] } || []
+ end
+
+ def photo_sources
+ photos.map{|photo| photo["value"]}
+ end
+
+ def prefix_emoji
+ if name
+ nil
+ elsif photos? && content == ""
+ "📷"
+ elsif __getobj__.emoji
+ __getobj__.emoji
+ else
+ @prefix_emoji ||= if (match = content.match(Unicode::Emoji::REGEX))
+ match
+ else
+ "💬"
+ end
+ 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? ? "#{photos.map { |p| "

" }.join("")} #{content}
" : content
+ end
+
+ def raw_content
+ res = Sanitize.fragment(content)
+ res.gsub(prefix_emoji[0], "") if prefix_emoji
+ 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
+
+ def to_h
+ clean_content = CGI::unescapeHTML(content.gsub(/<\/?[^>]*>/, "")).strip
+ clean_content = clean_content.gsub(prefix_emoji[0], "") if prefix_emoji
+ {
+ id: slug,
+ emoji: prefix_emoji,
+ content: clean_content,
+ images: (inline_image_sources + photo_sources).compact,
+ published_at: display_published_at
+ }
+ 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 << ""
+ 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
diff --git a/slices/admin/decorators/statuses/decorator.rb b/slices/admin/decorators/statuses/decorator.rb
new file mode 100644
index 0000000..d8b477e
--- /dev/null
+++ b/slices/admin/decorators/statuses/decorator.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: false
+
+# auto_register: false
+
+require "rexml/parsers/pullparser"
+require "sanitize"
+require "nokogiri"
+
+module Admin
+ module Decorators
+ module Statuses
+ class Decorator < Main::Decorators::Posts::Decorator
+ def raw_content
+ res = Sanitize.fragment(content,
+ elements: ["img", "p"],
+ attributes: {"img" => ["alt", "src", "title"]})
+
+ res.gsub(prefix_emoji[0], "") if prefix_emoji
+ end
+ end
+ end
+ end
+end
diff --git a/slices/admin/templates/merge_tags/new.html.slim b/slices/admin/templates/merge_tags/new.html.slim
index cd58755..b7f1004 100644
--- a/slices/admin/templates/merge_tags/new.html.slim
+++ b/slices/admin/templates/merge_tags/new.html.slim
@@ -2,14 +2,14 @@ div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:
h1 Admin // Merge Tags
div class="max-w-prose mx-auto prose dark:prose-invert"
- h3 Pick a tag to merge in to #{tag.label}
+ h3 Pick a tag to merge in to "#{post_tag.label}"
form action="/admin/tags/merge" method="POST"
- input type="hidden" name="target_id" value=tag.id
+ input type="hidden" name="target_id" value=post_tag.id
div class="mb-4"
select class="text-gray-800" name="source_id"
- - tags.each do |source_tag|
+ - post_tags.each do |source_tag|
option value=source_tag.id
= source_tag.label
div class="mb-4"
diff --git a/slices/admin/views/merge_tags/new.rb b/slices/admin/views/merge_tags/new.rb
index 3ea40ac..02ad93a 100644
--- a/slices/admin/views/merge_tags/new.rb
+++ b/slices/admin/views/merge_tags/new.rb
@@ -4,11 +4,11 @@ module Admin
class New < Admin::View
include Deps["repos.tag_repo"]
- expose :tag do |id:|
+ expose :post_tag do |id:|
tag_repo.fetch(id)
end
- expose :tags do |id:|
+ expose :post_tags do |id:|
tag_repo.list.reject { |t| t.id.to_s == id.to_s }
end
end
diff --git a/slices/admin/views/trips/show.rb b/slices/admin/views/trips/show.rb
index 060fe74..a151b5f 100644
--- a/slices/admin/views/trips/show.rb
+++ b/slices/admin/views/trips/show.rb
@@ -16,7 +16,7 @@ module Admin
expose :posts do |trip|
post_repo.created_between(trip.start_date, trip.end_date).map do |post|
- Adamantium::Decorators::Posts::Decorator.new(post)
+ Admin::Decorators::Posts::Decorator.new(post)
end
end
end