Add Markdown editor to admin area
This commit is contained in:
13
slices/admin/actions/posts/new.rb
Normal file
13
slices/admin/actions/posts/new.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Admin
|
||||
module Actions
|
||||
module Posts
|
||||
class New < Action
|
||||
include Deps["views.posts.new"]
|
||||
|
||||
def handle(req, res)
|
||||
res.render new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@@ -0,0 +1,3 @@
|
||||
.milkdown .ProseMirror {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
@@ -3,6 +3,36 @@ import "@main/css/app.css";
|
||||
import "@app/builds/tailwind.css";
|
||||
import "../css/app.css";
|
||||
|
||||
import { Crepe } from "@milkdown/crepe";
|
||||
import { listener, listenerCtx } from "@milkdown/kit/plugin/listener";
|
||||
import "@milkdown/crepe/theme/common/style.css";
|
||||
|
||||
// We have some themes for you to choose
|
||||
import "@milkdown/crepe/theme/frame.css";
|
||||
|
||||
async function uploadImage(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file); // Append the file to the FormData object
|
||||
|
||||
try {
|
||||
const response = await fetch("/micropub/media", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const jsonResponse = await response.json();
|
||||
return jsonResponse["url"];
|
||||
} else {
|
||||
alert("File upload failed.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
alert("An error occurred during the upload.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
(function () {
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.magic("clipboard", () => {
|
||||
@@ -32,5 +62,31 @@ import "../css/app.css";
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
let editor = document.getElementById("editor");
|
||||
|
||||
const crepe = new Crepe({
|
||||
root: editor,
|
||||
defaultValue: editor.dataset.postText,
|
||||
featureConfigs: {
|
||||
[Crepe.Feature.ImageBlock]: {
|
||||
onUpload: async (file: File) => {
|
||||
return uploadImage(file);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
crepe.editor.config((ctx) => {
|
||||
const bodyText = document.getElementById("body");
|
||||
bodyText.hidden = true;
|
||||
ctx.get(listenerCtx).markdownUpdated((ctx, markdown, prevMarkdown) => {
|
||||
bodyText.innerHTML = markdown;
|
||||
});
|
||||
});
|
||||
|
||||
crepe.editor.use(listener);
|
||||
|
||||
crepe.create();
|
||||
});
|
||||
})();
|
||||
|
@@ -46,6 +46,8 @@ module Admin
|
||||
post "/bookmarks/:id/mark_unread", to: Auth.call(action: "bookmarks.mark_unread")
|
||||
|
||||
get "/posts", to: Auth.call(action: "posts.index")
|
||||
get "/posts/new", to: Auth.call(action: "posts.new")
|
||||
post "/posts/create", to: Auth.call(action: "posts.create")
|
||||
delete "/posts/:id", to: Auth.call(action: "posts.delete")
|
||||
post "/posts/:id/archive", to: Auth.call(action: "posts.archive")
|
||||
post "/posts/:id/publish", to: Auth.call(action: "posts.publish")
|
||||
|
@@ -5,6 +5,8 @@ div class="max-w-prose mx-auto" x-data="{ activeTab: 0 }"
|
||||
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
|
||||
div class="flex-1"
|
||||
a href="/admin/posts/new" class="text-gray-200 cursor-pointer p-2 border-2 rounded border-blue-400 bg-blue-400 text-blue-900" New Post
|
||||
table class="prose dark:prose-invert table-auto prose-a:text-blue-600 prose-a:no-underline"
|
||||
thead
|
||||
th Details
|
||||
@@ -54,6 +56,3 @@ div class="max-w-prose mx-auto" x-data="{ activeTab: 0 }"
|
||||
button hx-post="/admin/posts/#{post.id}/publish" publish
|
||||
|
||||
div class="max-w-screen-md mx-auto border-t border-solid border-gray-200 dark:border-gray-600"
|
||||
|
||||
|
||||
|
||||
|
26
slices/admin/templates/posts/new.html.slim
Normal file
26
slices/admin/templates/posts/new.html.slim
Normal file
@@ -0,0 +1,26 @@
|
||||
article x-data="{postTitle: 'Post title', postSlug: 'post-title', slugify(event) {
|
||||
var str = event.target.value.replace(/^\s+|\s+$/g, '');
|
||||
str = str.toLowerCase();
|
||||
str = str.replace(/[^a-z0-9 -]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-');
|
||||
this.postSlug = str;
|
||||
}}" 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 hx-post="/micropub"
|
||||
fieldset class="mb-4 flex"
|
||||
label for="name" class="mr-2"
|
||||
input type="text" name="name" id="name" class="text-3xl w-full" x-on:change.debounce="slugify($event)" x-model="postTitle"
|
||||
fieldset class="mb-4 flex"
|
||||
label for="slug" class="mr-2" Slug:
|
||||
input type="text" name="slug" id="slug" class="w-full px-1 border rounded" x-model="postSlug"
|
||||
div id="editor" data-post-text=""
|
||||
textarea id="body" name="content" 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()"
|
||||
|
||||
// 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=true
|
||||
fieldset class="mb-4 flex"
|
||||
label for="tags" class="mr-2" Tags:
|
||||
input type="text" name="category" id="tags" class="w-full px-1 border rounded" value=""
|
||||
button class="rounded bg-blue-100 hover:bg-blue-200 text-blue-600 px-2 hover:cursor-pointer" type="submit"
|
||||
= "Create"
|
@@ -30,8 +30,12 @@ article x-data="$textHighlighter, {isOpen: false, anchorX: 0, anchorY: 0, text:
|
||||
h1= post.name || "💬"
|
||||
form action="/admin/post/#{post.id}/update" method="POST"
|
||||
- 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
|
||||
fieldset class="mb-4 flex"
|
||||
label for="slug" class="mr-2" Slug:
|
||||
input type="text" name="slug" id="slug" class="w-full px-1 border rounded" value="#{post.slug}"
|
||||
div id="editor" data-post-text="#{markdown_body}"
|
||||
textarea id="body" 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
|
||||
|
21
slices/admin/views/posts/new.rb
Normal file
21
slices/admin/views/posts/new.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
module Admin
|
||||
module Views
|
||||
module Posts
|
||||
class New < Admin::View
|
||||
include Deps["repos.post_repo"]
|
||||
|
||||
expose :published_posts do |posts|
|
||||
posts[0]
|
||||
end
|
||||
|
||||
expose :unpublished_posts do |posts|
|
||||
posts[1]
|
||||
end
|
||||
|
||||
expose :posts do
|
||||
post_repo.list.partition { |p| p.published_at }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@@ -38,6 +38,7 @@ module Micropub
|
||||
m.success do |post|
|
||||
post_type = (post.value!.post_type == :bookmark) ? :bookmark : :post
|
||||
res.headers["Location"] = "#{settings.micropub_site_url}/#{post_type}/#{post.value!.slug}"
|
||||
res.headers["HX-Redirect"] = "#{settings.micropub_site_url}/#{post_type}/#{post.value!.slug}"
|
||||
res.status = 201
|
||||
end
|
||||
|
||||
|
Reference in New Issue
Block a user