Add editable pages
This commit is contained in:
15
app/relations/pages.rb
Normal file
15
app/relations/pages.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Adamantium
|
||||||
|
module Relations
|
||||||
|
class Pages < ROM::Relation[:sql]
|
||||||
|
schema :pages, infer: true
|
||||||
|
|
||||||
|
auto_struct(true)
|
||||||
|
|
||||||
|
def published
|
||||||
|
where(self[:published_at] <= Time.now)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
11
app/repos/page_repo.rb
Normal file
11
app/repos/page_repo.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module Adamantium
|
||||||
|
module Repos
|
||||||
|
class PageRepo < Adamantium::Repo[:pages]
|
||||||
|
def fetch!(slug:)
|
||||||
|
pages
|
||||||
|
.published
|
||||||
|
.where(slug: slug).one!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@@ -2,6 +2,7 @@
|
|||||||
- context.content_for(:highlight_code, false)
|
- 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"
|
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
|
== page_content
|
||||||
|
|
||||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
||||||
|
@@ -2,15 +2,21 @@ module Adamantium
|
|||||||
module Views
|
module Views
|
||||||
module Pages
|
module Pages
|
||||||
class Show < Adamantium::View
|
class Show < Adamantium::View
|
||||||
include Deps[renderer: "renderers.markdown"]
|
include Deps["repos.page_repo", renderer: "renderers.markdown"]
|
||||||
|
|
||||||
expose :page_content do |slug:|
|
expose :page_content do |page|
|
||||||
markdown_content = File.read("app/content/pages/#{slug}.md")
|
renderer.call(content: page.content)
|
||||||
|
|
||||||
renderer.call(content: markdown_content)
|
|
||||||
rescue Errno::ENOENT
|
rescue Errno::ENOENT
|
||||||
renderer.call(content: "## Page not found")
|
renderer.call(content: "## Page not found")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
expose :page_name do |page|
|
||||||
|
page.name
|
||||||
|
end
|
||||||
|
|
||||||
|
private_expose :page do |slug:|
|
||||||
|
page_repo.fetch!(slug: slug)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
15
db/migrate/20231117222529_create_pages.rb
Normal file
15
db/migrate/20231117222529_create_pages.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
ROM::SQL.migration do
|
||||||
|
change do
|
||||||
|
create_table :pages do
|
||||||
|
primary_key :id
|
||||||
|
column :name, :text, null: false
|
||||||
|
column :content, :text, null: false
|
||||||
|
column :slug, :text, null: false
|
||||||
|
column :published_at, :date
|
||||||
|
|
||||||
|
index :slug, unique: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
slices/admin/actions/pages/archive.rb
Normal file
13
slices/admin/actions/pages/archive.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module Admin
|
||||||
|
module Actions
|
||||||
|
module Pages
|
||||||
|
class Archive < Action
|
||||||
|
include Deps["repos.page_repo"]
|
||||||
|
|
||||||
|
def handle(req, res)
|
||||||
|
page_repo.archive(slug: req.params[:slug])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
slices/admin/actions/pages/create.rb
Normal file
15
slices/admin/actions/pages/create.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
module Admin
|
||||||
|
module Actions
|
||||||
|
module Pages
|
||||||
|
class Create < Action
|
||||||
|
include Deps["commands.pages.create"]
|
||||||
|
|
||||||
|
def handle(req, res)
|
||||||
|
create.call(page: req.params[:page])
|
||||||
|
|
||||||
|
res.redirect_to "/admin/pages"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
slices/admin/actions/pages/delete.rb
Normal file
13
slices/admin/actions/pages/delete.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module Admin
|
||||||
|
module Actions
|
||||||
|
module Pages
|
||||||
|
class Delete < Action
|
||||||
|
include Deps["repos.page_repo"]
|
||||||
|
|
||||||
|
def handle(req, res)
|
||||||
|
page_repo.delete(id: req.params[:slug])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
slices/admin/actions/pages/edit.rb
Normal file
13
slices/admin/actions/pages/edit.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module Admin
|
||||||
|
module Actions
|
||||||
|
module Pages
|
||||||
|
class Edit < Action
|
||||||
|
include Deps["views.pages.edit"]
|
||||||
|
|
||||||
|
def handle(req, res)
|
||||||
|
res.render edit, slug: req.params[:slug]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
slices/admin/actions/pages/index.rb
Normal file
13
slices/admin/actions/pages/index.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module Admin
|
||||||
|
module Actions
|
||||||
|
module Pages
|
||||||
|
class Index < Action
|
||||||
|
include Deps["views.pages.index"]
|
||||||
|
|
||||||
|
def handle(req, res)
|
||||||
|
res.render index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
slices/admin/actions/pages/new.rb
Normal file
13
slices/admin/actions/pages/new.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module Admin
|
||||||
|
module Actions
|
||||||
|
module Pages
|
||||||
|
class New < Action
|
||||||
|
include Deps["views.pages.new"]
|
||||||
|
|
||||||
|
def handle(req, res)
|
||||||
|
res.render new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
slices/admin/actions/pages/publish.rb
Normal file
13
slices/admin/actions/pages/publish.rb
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module Admin
|
||||||
|
module Actions
|
||||||
|
module Pages
|
||||||
|
class Publish < Action
|
||||||
|
include Deps["repos.page_repo"]
|
||||||
|
|
||||||
|
def handle(req, res)
|
||||||
|
page_repo.publish(slug: req.params[:slug])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
14
slices/admin/actions/pages/update.rb
Normal file
14
slices/admin/actions/pages/update.rb
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
module Admin
|
||||||
|
module Actions
|
||||||
|
module Pages
|
||||||
|
class Update < Action
|
||||||
|
include Deps["commands.pages.update"]
|
||||||
|
|
||||||
|
def handle(req, res)
|
||||||
|
update.call(page: req.params[:page])
|
||||||
|
res.redirect "/admin/pages/#{req.params[:page][:slug]}/edit"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
slices/admin/commands/pages/create.rb
Normal file
15
slices/admin/commands/pages/create.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
require "down"
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
module Commands
|
||||||
|
module Pages
|
||||||
|
class Create
|
||||||
|
include Deps["repos.page_repo"]
|
||||||
|
|
||||||
|
def call(page:)
|
||||||
|
page_repo.create(page)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
17
slices/admin/commands/pages/update.rb
Normal file
17
slices/admin/commands/pages/update.rb
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
require "down"
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
module Commands
|
||||||
|
module Pages
|
||||||
|
class Update
|
||||||
|
include Deps["repos.page_repo"]
|
||||||
|
|
||||||
|
def call(page:)
|
||||||
|
id = page_repo.find(slug: page[:slug]).id
|
||||||
|
|
||||||
|
page_repo.update(id, page)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@@ -11,6 +11,15 @@ module Admin
|
|||||||
|
|
||||||
get "/", to: Auth.call(action: "index")
|
get "/", to: Auth.call(action: "index")
|
||||||
|
|
||||||
|
get "/pages", to: Auth.call(action: "pages.index")
|
||||||
|
get "/pages/new", to: Auth.call(action: "pages.new")
|
||||||
|
get "/pages/:slug/edit", to: Auth.call(action: "pages.edit")
|
||||||
|
post "/pages/create", to: Auth.call(action: "pages.create")
|
||||||
|
post "/pages/:slug", to: Auth.call(action: "pages.update")
|
||||||
|
post "/pages/:slug/archive", to: Auth.call(action: "pages.archive")
|
||||||
|
post "/pages/:slug/publish", to: Auth.call(action: "pages.publish")
|
||||||
|
delete "/pages/:slug", to: Auth.call(action: "pages.delete")
|
||||||
|
|
||||||
get "/tags", to: Auth.call(action: "tags.index")
|
get "/tags", to: Auth.call(action: "tags.index")
|
||||||
delete "/tags/:id", to: Auth.call(action: "tags.delete")
|
delete "/tags/:id", to: Auth.call(action: "tags.delete")
|
||||||
|
|
||||||
|
31
slices/admin/repos/page_repo.rb
Normal file
31
slices/admin/repos/page_repo.rb
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
require "time_math"
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
module Repos
|
||||||
|
class PageRepo < Adamantium::Repo[:pages]
|
||||||
|
commands :create, update: :by_pk
|
||||||
|
|
||||||
|
def list
|
||||||
|
pages
|
||||||
|
.order(Sequel.lit("published_at desc"))
|
||||||
|
.to_a
|
||||||
|
end
|
||||||
|
|
||||||
|
def find(slug:)
|
||||||
|
pages.where(slug: slug).one!
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(slug:)
|
||||||
|
pages.where(slug: slug).delete
|
||||||
|
end
|
||||||
|
|
||||||
|
def publish(slug:)
|
||||||
|
pages.where(slug: slug).update(published_at: Time.now)
|
||||||
|
end
|
||||||
|
|
||||||
|
def archive(slug:)
|
||||||
|
pages.where(slug: slug).update(published_at: nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@@ -3,6 +3,8 @@ div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:
|
|||||||
|
|
||||||
div class="max-w-prose mx-auto prose dark:prose-invert"
|
div class="max-w-prose mx-auto prose dark:prose-invert"
|
||||||
ul
|
ul
|
||||||
|
li
|
||||||
|
a href="/admin/pages" Pages
|
||||||
li
|
li
|
||||||
a href="/admin/posts" Posts
|
a href="/admin/posts" Posts
|
||||||
li
|
li
|
||||||
|
18
slices/admin/templates/pages/edit.html.slim
Normal file
18
slices/admin/templates/pages/edit.html.slim
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
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"
|
||||||
|
form action="/admin/pages/#{page.slug}" method="POST"
|
||||||
|
div class="mb-4"
|
||||||
|
label for="name" Name
|
||||||
|
input type="text" id="name" name="page[name]" class="text-gray-800 w-full border-blue-200 border-2 rounded p-2" value=page.name
|
||||||
|
div class="mb-4"
|
||||||
|
label for="slug" slug
|
||||||
|
input type="text" id="slug" name="page[slug]" class="text-gray-800 w-full border-blue-200 border-2 rounded p-2" value=page.slug
|
||||||
|
div class="mb-4"
|
||||||
|
label for="body" Body
|
||||||
|
textarea name="page[content]" id="body" class="text-gray-800 w-full border-blue-200 border-2 rounded p-2" x-data="{ resize: () => { $el.style.height = '5px'; $el.style.height = $el.scrollHeight + 'px' } }" x-init="resize()" @input="resize()"
|
||||||
|
== markdown_content
|
||||||
|
div class="mb-4"
|
||||||
|
label for="published_at" Publish date
|
||||||
|
input type="date" id="published_at" name="page[published_at]" class="text-gray-800 w-full border-blue-200 border-2 rounded p-2" value=page.published_at
|
||||||
|
button class="rounded bg-blue-100 hover:bg-blue-200 text-blue-600 px-2 hover:cursor-pointer" type="submit"
|
||||||
|
= "Update"
|
51
slices/admin/templates/pages/index.html.slim
Normal file
51
slices/admin/templates/pages/index.html.slim
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
div class="mb-12 prose dark:prose-invert max-w-prose mx-auto text-gray-800 dark:text-gray-200"
|
||||||
|
h1 Admin // Pages
|
||||||
|
|
||||||
|
div class="max-w-prose mx-auto" x-data="{ activeTab: 0 }"
|
||||||
|
div class="mb-4"
|
||||||
|
a href="/admin/pages/new" class="text-gray-200 cursor-pointer p-2 border-2 mr-2 rounded border-blue-400 bg-blue-400 text-blue-900" New page
|
||||||
|
div class="flex"
|
||||||
|
a href="#" class="text-gray-200 cursor-pointer p-2 border-2 mr-2 rounded border-blue-400" :class="{ 'bg-blue-400 text-blue-900': activeTab === 0 }" @click="activeTab = 0" class="tab-control" Published
|
||||||
|
a href="#" class="text-gray-200 cursor-pointer p-2 border-2 rounded border-blue-400" :class="{ 'bg-blue-400 text-blue-900': activeTab === 1 }" @click="activeTab = 1" class="tab-control" Un published
|
||||||
|
table class="prose dark:prose-invert table-auto prose-a:text-blue-600 prose-a:no-underline"
|
||||||
|
thead
|
||||||
|
th Details
|
||||||
|
th Date
|
||||||
|
th colspan="2" Actions
|
||||||
|
tbody class="{ 'active': activeTab === 0 }" x-show.transition.in.opacity.duration.600="activeTab === 0"
|
||||||
|
- published_pages.each do |page|
|
||||||
|
tr id="post-#{page.slug}"
|
||||||
|
td
|
||||||
|
div
|
||||||
|
= page.name
|
||||||
|
a class="no-underline" href="/#{page.slug}"
|
||||||
|
small class="text-gray-400 dark:text-gray-600" = page.slug
|
||||||
|
td
|
||||||
|
= page.published_at&.strftime("%d %b %Y")
|
||||||
|
td
|
||||||
|
a href="/admin/pages/#{page.slug}/edit" edit
|
||||||
|
td
|
||||||
|
button class="text-red-600" hx-delete="/admin/pages/#{page.slug}" hx-target="#post-#{page.slug}" delete
|
||||||
|
td
|
||||||
|
button hx-post="/admin/pages/#{page.slug}/archive" unpublish
|
||||||
|
tbody class="{ 'active': activeTab === 1 }" x-show.transition.in.opacity.duration.600="activeTab === 1"
|
||||||
|
- unpublished_pages.each do |page|
|
||||||
|
tr id="post-#{page.slug}"
|
||||||
|
td
|
||||||
|
div
|
||||||
|
= page.name
|
||||||
|
a class="no-underline" href="/#{page.slug}"
|
||||||
|
small class="text-gray-400 dark:text-gray-600" = page.slug
|
||||||
|
td
|
||||||
|
= page.published_at&.strftime("%d %b %Y")
|
||||||
|
td
|
||||||
|
a href="/admin/pages/#{page.slug}/edit" edit
|
||||||
|
td
|
||||||
|
button class="text-red-600" hx-delete="/admin/pages/#{page.slug}" hx-target="#post-#{page.slug}" delete
|
||||||
|
td
|
||||||
|
button hx-post="/admin/pages/#{page.slug}/publish" publish
|
||||||
|
|
||||||
|
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
||||||
|
|
||||||
|
|
||||||
|
|
17
slices/admin/templates/pages/new.html.slim
Normal file
17
slices/admin/templates/pages/new.html.slim
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
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"
|
||||||
|
form action="/admin/pages/create" method="POST"
|
||||||
|
div class="mb-4"
|
||||||
|
label for="name" Name
|
||||||
|
input type="text" id="name" name="page[name]" class="text-gray-800 w-full border-blue-200 border-2 rounded p-2"
|
||||||
|
div class="mb-4"
|
||||||
|
label for="slug" slug
|
||||||
|
input type="text" id="slug" name="page[slug]" class="text-gray-800 w-full border-blue-200 border-2 rounded p-2"
|
||||||
|
div class="mb-4"
|
||||||
|
label for="body" Body
|
||||||
|
textarea name="page[content]" id="body" class="text-gray-800 w-full border-blue-200 border-2 rounded p-2" x-data="{ resize: () => { $el.style.height = '5px'; $el.style.height = $el.scrollHeight + 'px' } }" x-init="resize()" @input="resize()"
|
||||||
|
div class="mb-4"
|
||||||
|
label for="published_at" Publish date
|
||||||
|
input type="date" id="published_at" name="page[published_at]" class="text-gray-800 w-full border-blue-200 border-2 rounded p-2"
|
||||||
|
button class="rounded bg-blue-100 hover:bg-blue-200 text-blue-600 px-2 hover:cursor-pointer" type="submit"
|
||||||
|
= "Create"
|
19
slices/admin/views/pages/edit.rb
Normal file
19
slices/admin/views/pages/edit.rb
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
require "reverse_markdown"
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
module Views
|
||||||
|
module Pages
|
||||||
|
class Edit < Admin::View
|
||||||
|
include Deps["repos.page_repo"]
|
||||||
|
|
||||||
|
expose :page do |slug:|
|
||||||
|
page_repo.find(slug: slug)
|
||||||
|
end
|
||||||
|
|
||||||
|
expose :markdown_content do |page|
|
||||||
|
page.content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
21
slices/admin/views/pages/index.rb
Normal file
21
slices/admin/views/pages/index.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
module Admin
|
||||||
|
module Views
|
||||||
|
module Pages
|
||||||
|
class Index < Admin::View
|
||||||
|
include Deps["repos.page_repo"]
|
||||||
|
|
||||||
|
expose :published_pages do |pages|
|
||||||
|
pages[0]
|
||||||
|
end
|
||||||
|
|
||||||
|
expose :unpublished_pages do |pages|
|
||||||
|
pages[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
expose :pages do
|
||||||
|
page_repo.list.partition { |p| p.published_at }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
9
slices/admin/views/pages/new.rb
Normal file
9
slices/admin/views/pages/new.rb
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
module Admin
|
||||||
|
module Views
|
||||||
|
module Pages
|
||||||
|
class New < Admin::View
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Reference in New Issue
Block a user