diff --git a/app/content/pages/more.md b/app/content/pages/more.md deleted file mode 100644 index e69de29..0000000 diff --git a/app/relations/pages.rb b/app/relations/pages.rb new file mode 100644 index 0000000..dee595d --- /dev/null +++ b/app/relations/pages.rb @@ -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 diff --git a/app/repos/page_repo.rb b/app/repos/page_repo.rb new file mode 100644 index 0000000..9266d66 --- /dev/null +++ b/app/repos/page_repo.rb @@ -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 diff --git a/app/templates/pages/show.html.slim b/app/templates/pages/show.html.slim index f57cbcb..1781935 100644 --- a/app/templates/pages/show.html.slim +++ b/app/templates/pages/show.html.slim @@ -2,6 +2,7 @@ - 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" diff --git a/app/views/pages/show.rb b/app/views/pages/show.rb index 97af299..e5b4489 100644 --- a/app/views/pages/show.rb +++ b/app/views/pages/show.rb @@ -2,15 +2,21 @@ module Adamantium module Views module Pages class Show < Adamantium::View - include Deps[renderer: "renderers.markdown"] + include Deps["repos.page_repo", renderer: "renderers.markdown"] - expose :page_content do |slug:| - markdown_content = File.read("app/content/pages/#{slug}.md") - - renderer.call(content: markdown_content) + expose :page_content do |page| + renderer.call(content: page.content) rescue Errno::ENOENT renderer.call(content: "## Page not found") end + + expose :page_name do |page| + page.name + end + + private_expose :page do |slug:| + page_repo.fetch!(slug: slug) + end end end end diff --git a/db/migrate/20231117222529_create_pages.rb b/db/migrate/20231117222529_create_pages.rb new file mode 100644 index 0000000..2df9728 --- /dev/null +++ b/db/migrate/20231117222529_create_pages.rb @@ -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 diff --git a/slices/admin/actions/pages/archive.rb b/slices/admin/actions/pages/archive.rb new file mode 100644 index 0000000..91c330b --- /dev/null +++ b/slices/admin/actions/pages/archive.rb @@ -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 diff --git a/slices/admin/actions/pages/create.rb b/slices/admin/actions/pages/create.rb new file mode 100644 index 0000000..edb5946 --- /dev/null +++ b/slices/admin/actions/pages/create.rb @@ -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 diff --git a/slices/admin/actions/pages/delete.rb b/slices/admin/actions/pages/delete.rb new file mode 100644 index 0000000..bc9c904 --- /dev/null +++ b/slices/admin/actions/pages/delete.rb @@ -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 diff --git a/slices/admin/actions/pages/edit.rb b/slices/admin/actions/pages/edit.rb new file mode 100644 index 0000000..c7c01e3 --- /dev/null +++ b/slices/admin/actions/pages/edit.rb @@ -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 diff --git a/slices/admin/actions/pages/index.rb b/slices/admin/actions/pages/index.rb new file mode 100644 index 0000000..92cda59 --- /dev/null +++ b/slices/admin/actions/pages/index.rb @@ -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 diff --git a/slices/admin/actions/pages/new.rb b/slices/admin/actions/pages/new.rb new file mode 100644 index 0000000..02ec8f2 --- /dev/null +++ b/slices/admin/actions/pages/new.rb @@ -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 diff --git a/slices/admin/actions/pages/publish.rb b/slices/admin/actions/pages/publish.rb new file mode 100644 index 0000000..b9a6462 --- /dev/null +++ b/slices/admin/actions/pages/publish.rb @@ -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 diff --git a/slices/admin/actions/pages/update.rb b/slices/admin/actions/pages/update.rb new file mode 100644 index 0000000..cc9e387 --- /dev/null +++ b/slices/admin/actions/pages/update.rb @@ -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 diff --git a/slices/admin/commands/pages/create.rb b/slices/admin/commands/pages/create.rb new file mode 100644 index 0000000..d1a76cd --- /dev/null +++ b/slices/admin/commands/pages/create.rb @@ -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 diff --git a/slices/admin/commands/pages/update.rb b/slices/admin/commands/pages/update.rb new file mode 100644 index 0000000..52bdc83 --- /dev/null +++ b/slices/admin/commands/pages/update.rb @@ -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 diff --git a/slices/admin/config/routes.rb b/slices/admin/config/routes.rb index 0af80aa..7a253d6 100644 --- a/slices/admin/config/routes.rb +++ b/slices/admin/config/routes.rb @@ -11,6 +11,15 @@ module Admin 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") delete "/tags/:id", to: Auth.call(action: "tags.delete") diff --git a/slices/admin/repos/page_repo.rb b/slices/admin/repos/page_repo.rb new file mode 100644 index 0000000..eea6131 --- /dev/null +++ b/slices/admin/repos/page_repo.rb @@ -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 diff --git a/slices/admin/templates/index.html.slim b/slices/admin/templates/index.html.slim index b57f4d2..5f1c56f 100644 --- a/slices/admin/templates/index.html.slim +++ b/slices/admin/templates/index.html.slim @@ -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" ul + li + a href="/admin/pages" Pages li a href="/admin/posts" Posts li diff --git a/slices/admin/templates/pages/edit.html.slim b/slices/admin/templates/pages/edit.html.slim new file mode 100644 index 0000000..1e60d55 --- /dev/null +++ b/slices/admin/templates/pages/edit.html.slim @@ -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" diff --git a/slices/admin/templates/pages/index.html.slim b/slices/admin/templates/pages/index.html.slim new file mode 100644 index 0000000..8a4ae6a --- /dev/null +++ b/slices/admin/templates/pages/index.html.slim @@ -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" + + + diff --git a/slices/admin/templates/pages/new.html.slim b/slices/admin/templates/pages/new.html.slim new file mode 100644 index 0000000..3e7e3e3 --- /dev/null +++ b/slices/admin/templates/pages/new.html.slim @@ -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" diff --git a/slices/admin/views/pages/edit.rb b/slices/admin/views/pages/edit.rb new file mode 100644 index 0000000..fea3374 --- /dev/null +++ b/slices/admin/views/pages/edit.rb @@ -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 diff --git a/slices/admin/views/pages/index.rb b/slices/admin/views/pages/index.rb new file mode 100644 index 0000000..d8623cf --- /dev/null +++ b/slices/admin/views/pages/index.rb @@ -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 diff --git a/slices/admin/views/pages/new.rb b/slices/admin/views/pages/new.rb new file mode 100644 index 0000000..7e0dfa3 --- /dev/null +++ b/slices/admin/views/pages/new.rb @@ -0,0 +1,9 @@ +module Admin + module Views + module Pages + class New < Admin::View + + end + end + end +end