Compare commits
2 commits
b91b03adec
...
d1ae051cae
Author | SHA1 | Date | |
---|---|---|---|
d1ae051cae | |||
e96c7d89db |
5 changed files with 1276 additions and 1 deletions
|
@ -35,6 +35,7 @@
|
|||
"packageManager": "pnpm@10.7.1+sha512.2d92c86b7928dc8284f53494fb4201f983da65f0fb4f0d40baafa5cf628fa31dae3e5968f12466f17df7e97310e30f343a648baea1b9b350685dafafffdf5808",
|
||||
"devDependencies": {
|
||||
"@azure/static-web-apps-cli": "^2.0.6",
|
||||
"@milkdown/crepe": "^7.15.5",
|
||||
"@types/node": "^22.15.3",
|
||||
"@waline/client": "^3.6.0"
|
||||
}
|
||||
|
|
1093
pnpm-lock.yaml
generated
1093
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -9,7 +9,7 @@ export const posts = ({image}) => z.object({
|
|||
date: z.coerce.date(),
|
||||
updated: z.coerce.date().optional(),
|
||||
categories: z.union([z.array(z.string()), z.string()]).transform(val => Array.isArray(val) ? val : [val]).default(['uncategorized']),
|
||||
tags: z.array(z.string()).optional(),
|
||||
tags: z.union([z.array(z.string()), z.string()]).transform(val => Array.isArray(val) ? val : [val]).optional(),
|
||||
cover: image().optional(),
|
||||
author: z.union([z.array(reference('authors')), reference('authors')]).optional(),
|
||||
});
|
98
src/pages/editor.astro
Normal file
98
src/pages/editor.astro
Normal file
|
@ -0,0 +1,98 @@
|
|||
---
|
||||
import "@milkdown/crepe/theme/common/style.css";
|
||||
import "/src/styles/editor.css";
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import {getCollection} from "astro:content";
|
||||
const allPosts = await getCollection('posts', ({ data }) => {
|
||||
return import.meta.env.PROD ? data.draft !== true : true;
|
||||
});
|
||||
const uniqueCategories = [...new Set(allPosts.map((post: any) => post.data.categories ? post.data.categories : []).flat())];
|
||||
---
|
||||
<Layout title="Create a new post">
|
||||
{/* in memory of https://github.com/KeJunMao/jekyll-theme-mdui */}
|
||||
<h1 class="title">Create a new post</h1>
|
||||
<div class="content">
|
||||
<form class="form">
|
||||
<label for="title-input">Title</label>
|
||||
<input type="text" placeholder="What is your post title?" id="title-input" required />
|
||||
|
||||
<label for="slug-input">Slug</label>
|
||||
<input type="text" placeholder="The parmallink will be /blog/:slug" id="slug-input" required />
|
||||
|
||||
<label for="description-input">Description</label>
|
||||
<textarea placeholder="What is your description?" id="description-input" />
|
||||
|
||||
<label for="category-input">Category</label>
|
||||
<input list="categories" id="category-input" name="category-input" placeholder="Select or type a category" />
|
||||
<datalist id="categories">
|
||||
{uniqueCategories.map((category) => (
|
||||
<option value={category} />
|
||||
))}
|
||||
</datalist>
|
||||
|
||||
<label for="tags-input">Tags (comma separated)</label>
|
||||
<input type="text" placeholder="e.g. tag1, tag2, tag3" id="tags-input" />
|
||||
|
||||
<div class="content-field">
|
||||
<div id="editor"></div>
|
||||
<p><small>powered by <a href="https://milkdown.dev">milkdown</a></small></p>
|
||||
</div>
|
||||
<button type="button" id="download-btn">Download Markdown</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
import { Crepe } from "@milkdown/crepe";
|
||||
|
||||
const crepe = new Crepe({
|
||||
root: "#editor",
|
||||
defaultValue: "",
|
||||
features: {
|
||||
[Crepe.Feature.Toolbar]: true,
|
||||
[Crepe.Feature.Latex]: true,
|
||||
},
|
||||
featureConfigs: {
|
||||
[Crepe.Feature.Placeholder]: {
|
||||
text: 'Start writing...',
|
||||
mode: 'block',
|
||||
},
|
||||
},
|
||||
});
|
||||
// Get markdown content
|
||||
let markdown = ""
|
||||
|
||||
crepe.create();
|
||||
|
||||
crepe.on((listener) => {
|
||||
listener.markdownUpdated((ctx, md) => {
|
||||
console.log("Content updated:", markdown);
|
||||
});
|
||||
})
|
||||
|
||||
// Download functionality
|
||||
document.getElementById('download-btn').addEventListener('click',() => {
|
||||
const title = document.getElementById('title-input').value || 'untitled';
|
||||
const description = document.getElementById('description-input').value
|
||||
const slug = document.getElementById('slug-input').value
|
||||
const content = crepe.getMarkdown();
|
||||
const category = document.getElementById('category-input').value || 'uncategorized';
|
||||
const tags = [document.getElementById('tags-input').value.split(',').map((tag: string)=> tag.trim()).filter((tag: string) => tag)];
|
||||
const date = new Date();
|
||||
|
||||
// Create markdown content with title
|
||||
const markdownContent = `---\ntitle: ${title}\ndescription: ${description}\ncategories: ${category}\ntags: ${tags}\nslug: ${slug}\ndate: ${date}\ndraft: true\n---\n\n${content}`;
|
||||
|
||||
// Create blob and download
|
||||
const blob = new Blob([markdownContent], { type: 'text/markdown' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `${slug.toLowerCase()}.md`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
</script>
|
||||
|
||||
</Layout>
|
83
src/styles/editor.css
Normal file
83
src/styles/editor.css
Normal file
|
@ -0,0 +1,83 @@
|
|||
form label {
|
||||
display: block;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
form input {
|
||||
flex: 1;
|
||||
padding: 0.5rem;
|
||||
width: 100%;
|
||||
border: unset;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
font-size: .8rem;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
form textarea {
|
||||
flex: 1;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
font-size: .8rem;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
form input:focus,textarea:focus {
|
||||
border-radius: 0;
|
||||
outline: unset;
|
||||
border: 2px solid var(--accent-color);
|
||||
}
|
||||
|
||||
form div.content-field {
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
.milkdown {
|
||||
--crepe-color-background: var(--bg-color);
|
||||
--crepe-color-on-background: var(--text-color);
|
||||
--crepe-color-surface: var(--text-color);
|
||||
--crepe-color-surface-low: var(--secondary-color);
|
||||
--crepe-color-on-surface: var(--secondary-text-color);
|
||||
--crepe-color-on-surface-variant: var(--secondary-text-color);
|
||||
--crepe-color-outline: var(--border-color);
|
||||
--crepe-color-primary: var(--accent-color);
|
||||
--crepe-color-secondary: var(--secondary-color);
|
||||
--crepe-color-on-secondary: var(--secondary-text-color);
|
||||
--crepe-color-inverse: var(--secondary-text-color);
|
||||
--crepe-color-on-inverse: var(--bg-color);
|
||||
--crepe-color-inline-code: var(--terminal-green);
|
||||
--crepe-color-error: var(--terminal-red);
|
||||
--crepe-color-hover: var(--secondary-color);
|
||||
--crepe-color-selected: var(--secondary-color);
|
||||
--crepe-color-inline-area: var(--text-color);
|
||||
|
||||
|
||||
--crepe-font-title: Rubik, Cambria, 'Times New Roman', Times, serif;
|
||||
--crepe-font-default: Inter, Arial, Helvetica, sans-serif;
|
||||
--crepe-font-code:
|
||||
'JetBrains Mono', Menlo, Monaco, 'Courier New', Courier, monospace;
|
||||
|
||||
--crepe-shadow-1:
|
||||
0px 1px 2px 0px rgba(255, 255, 255, 0.3),
|
||||
0px 1px 3px 1px rgba(255, 255, 255, 0.15);
|
||||
--crepe-shadow-2:
|
||||
0px 1px 2px 0px rgba(255, 255, 255, 0.3),
|
||||
0px 2px 6px 2px rgba(255, 255, 255, 0.15);
|
||||
|
||||
border: .1rem solid var(--border-color);
|
||||
}
|
||||
|
||||
form button#download-btn {
|
||||
background-color: var(--accent-color);
|
||||
color: var(--bg-color);
|
||||
border: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
margin-top: 1rem;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue