forked from ProjectSegfault/website
use ghost api for blogposts
This commit is contained in:
parent
817cd937a6
commit
31df1859cb
@ -3,6 +3,7 @@ DB_HOST=localhost
|
|||||||
DB_PORT=5432
|
DB_PORT=5432
|
||||||
DB_USERNAME=postgres
|
DB_USERNAME=postgres
|
||||||
DB_PASSWORD=your-db-password
|
DB_PASSWORD=your-db-password
|
||||||
|
GHOST_API_KEY=your-ghost-api-key
|
||||||
AUTH_CLIENT_ID=your-authentik-client-id
|
AUTH_CLIENT_ID=your-authentik-client-id
|
||||||
AUTH_CLIENT_SECRET=your-authentik-client-secret
|
AUTH_CLIENT_SECRET=your-authentik-client-secret
|
||||||
AUTH_ISSUER=https://authentik-domain/application/o/app-name/
|
AUTH_ISSUER=https://authentik-domain/application/o/app-name/
|
||||||
|
@ -34,42 +34,6 @@ sequelize.define("Announcements", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
sequelize.define("Posts", {
|
|
||||||
title: {
|
|
||||||
type: DataTypes.STRING,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
type: DataTypes.ARRAY(DataTypes.STRING),
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
author: {
|
|
||||||
type: DataTypes.STRING,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
created: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
updated: {
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
allowNull: true,
|
|
||||||
defaultValue: null
|
|
||||||
},
|
|
||||||
words: {
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
allowNull: false
|
|
||||||
},
|
|
||||||
readingTime: {
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
allowNull: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sequelize.authenticate();
|
await sequelize.authenticate();
|
||||||
await sequelize.sync();
|
await sequelize.sync();
|
||||||
|
9
src/lib/ghost.ts
Normal file
9
src/lib/ghost.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { env } from "$env/dynamic/private";
|
||||||
|
|
||||||
|
const fetchApi = async (action: string, additional?: string) => {
|
||||||
|
const data = await fetch("https://blog.projectsegfau.lt/ghost/api/content/" + action + "/?key=" + env.GHOST_API_KEY + "&include=authors,tags&limit=all&formats=html,plaintext" + (additional ? additional : ""));
|
||||||
|
|
||||||
|
return data.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default fetchApi;
|
@ -2,5 +2,4 @@
|
|||||||
|
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<a href="/admin/announcements">Announcements</a>
|
<a href="/admin/announcements">Announcements</a>
|
||||||
<a href="/admin/blog">Blog</a>
|
|
||||||
</div>
|
</div>
|
@ -1,143 +0,0 @@
|
|||||||
import type { Actions, PageServerLoad } from "./$types";
|
|
||||||
import db from "$lib/db";
|
|
||||||
import Joi from "joi";
|
|
||||||
import { fail } from "@sveltejs/kit";
|
|
||||||
|
|
||||||
export const load = ( async () => {
|
|
||||||
const Posts = db.model("Posts");
|
|
||||||
|
|
||||||
return {
|
|
||||||
postTitles: await Posts.findAll({ attributes: ["title"] }).then((docs) => {
|
|
||||||
const titles = docs.map((doc) => doc.get("title"));
|
|
||||||
return titles;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}) satisfies PageServerLoad;
|
|
||||||
|
|
||||||
export const actions: Actions = {
|
|
||||||
add: async ({ request }) => {
|
|
||||||
const formData = await request.formData();
|
|
||||||
|
|
||||||
const AddPostTypeSchema = Joi.object({
|
|
||||||
title: Joi.string().required(),
|
|
||||||
content: Joi.string().required(),
|
|
||||||
tags: Joi.string().optional().allow(""),
|
|
||||||
author: Joi.string().required()
|
|
||||||
});
|
|
||||||
|
|
||||||
if (AddPostTypeSchema.validate(Object.fromEntries(formData.entries())).error) {
|
|
||||||
return fail(400, { addError: true, addMessage: String(AddPostTypeSchema.validate(Object.fromEntries(formData.entries())).error) });
|
|
||||||
} else {
|
|
||||||
const Posts = db.model("Posts");
|
|
||||||
|
|
||||||
//@ts-ignore
|
|
||||||
const words = formData.get("content")!.trim().split(/\s+/).length;
|
|
||||||
|
|
||||||
//@ts-ignore
|
|
||||||
const tags = formData.get("tags") ? formData.get("tags").split(" ") : [];
|
|
||||||
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
title: formData.get("title"),
|
|
||||||
content: formData.get("content"),
|
|
||||||
tags: tags,
|
|
||||||
author: formData.get("author"),
|
|
||||||
created: now,
|
|
||||||
words: words,
|
|
||||||
readingTime: Math.ceil(words / 225)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (await Posts.findOne({ where: { title: data.title } })) {
|
|
||||||
return fail(409, { addError: true, addMessage: "A post with that title already exists." });
|
|
||||||
} else {
|
|
||||||
await Posts.create(data);
|
|
||||||
|
|
||||||
return { addSuccess: true, addMessage: "Your post has been posted." };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
delete: async ({ request }) => {
|
|
||||||
const Posts = db.model("Posts");
|
|
||||||
|
|
||||||
const formData = await request.formData();
|
|
||||||
|
|
||||||
const deleteFromDb = await Posts.destroy({ where: { title: formData.get("title") } });
|
|
||||||
|
|
||||||
if (!deleteFromDb) {
|
|
||||||
return fail(404, { deleteError: true, deleteMessage: "A post with that title does not exist." });
|
|
||||||
} else {
|
|
||||||
return { deleteSuccess: true, deleteMessage: "Your post has been deleted." };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
edit: async ({ request }) => {
|
|
||||||
const EditPostTypeSchema = Joi.object({
|
|
||||||
title: Joi.string().required(),
|
|
||||||
newTitle: Joi.string().optional().allow(""),
|
|
||||||
content: Joi.string().optional().allow(""),
|
|
||||||
tags: Joi.string().optional().allow(""),
|
|
||||||
area: Joi.string().required().allow("title", "content", "tags")
|
|
||||||
});
|
|
||||||
|
|
||||||
const formData = await request.formData();
|
|
||||||
|
|
||||||
if (EditPostTypeSchema.validate(Object.fromEntries(formData.entries())).error) {
|
|
||||||
return fail(400, { editError: true, editMessage: String(EditPostTypeSchema.validate(Object.fromEntries(formData.entries())).error) });
|
|
||||||
} else {
|
|
||||||
if (formData.get("area") === "title") {
|
|
||||||
const Posts = db.model("Posts");
|
|
||||||
|
|
||||||
const updateOnDb = await Posts.update(
|
|
||||||
{ title: formData.get("newTitle") },
|
|
||||||
{ where: { title: formData.get("title") } }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateOnDb[0] === 0) {
|
|
||||||
return fail(404, { editError: true, editMessage: "A post with that title does not exist." });
|
|
||||||
} else {
|
|
||||||
return { editSuccess: true, editMessage: "Your post has been edited." };
|
|
||||||
}
|
|
||||||
} else if (formData.get("area") === "content") {
|
|
||||||
const Posts = db.model("Posts");
|
|
||||||
|
|
||||||
//@ts-ignore
|
|
||||||
const words = formData.get("content")!.trim().split(/\s+/).length;
|
|
||||||
|
|
||||||
const now = Math.floor(Date.now() / 1000);
|
|
||||||
|
|
||||||
const updateonDb = await Posts.update(
|
|
||||||
{
|
|
||||||
content: formData.get("content"),
|
|
||||||
words: words,
|
|
||||||
readingTime: Math.ceil(words / 225),
|
|
||||||
updated: now
|
|
||||||
},
|
|
||||||
{ where: { title: formData.get("title") } }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateonDb[0] === 0) {
|
|
||||||
return fail(404, { editError: true, editMessage: "A post with that title does not exist." });
|
|
||||||
} else {
|
|
||||||
return { editSuccess: true, editMessage: "Your post has been edited." };
|
|
||||||
}
|
|
||||||
} else if (formData.get("area") === "tags") {
|
|
||||||
const Posts = db.model("Posts");
|
|
||||||
|
|
||||||
//@ts-ignore
|
|
||||||
const tags = formData.get("tags") ? formData.get("tags").split(" ") : [];
|
|
||||||
|
|
||||||
const updateOnDb = await Posts.update(
|
|
||||||
{ tags: tags
|
|
||||||
},
|
|
||||||
{ where: { title: formData.get("title") } }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (updateOnDb[0] === 0) {
|
|
||||||
return fail(404, { editError: true, editMessage: "A post with that title does not exist." });
|
|
||||||
} else {
|
|
||||||
return { editSuccess: true, editMessage: "Your post has been edited." };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { ActionData, PageData } from '.$/types';
|
|
||||||
|
|
||||||
export let data: PageData;
|
|
||||||
|
|
||||||
export let form: ActionData;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>Add post</h1>
|
|
||||||
|
|
||||||
<form action="?/add" method="POST" class="col">
|
|
||||||
<input type="text" name="title" placeholder="Title" required />
|
|
||||||
<textarea
|
|
||||||
name="content"
|
|
||||||
placeholder="Content"
|
|
||||||
rows="4"
|
|
||||||
cols="25"
|
|
||||||
required
|
|
||||||
></textarea>
|
|
||||||
<input type="text" name="tags" placeholder="Tags" />
|
|
||||||
<input type="text" name="author" placeholder="Author" required />
|
|
||||||
{#if form?.addSuccess}
|
|
||||||
{form.addMessage}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if form?.addError}
|
|
||||||
{form.addMessage}
|
|
||||||
{/if}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h1>Delete post</h1>
|
|
||||||
|
|
||||||
<form action="?/delete" method="POST" class="col">
|
|
||||||
<select name="title" required>
|
|
||||||
{#each data.postTitles as title}
|
|
||||||
<option value="{title}">{title}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
{#if form?.deleteSuccess}
|
|
||||||
{form.deleteMessage}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if form?.deleteError}
|
|
||||||
{form.deleteMessage}
|
|
||||||
{/if}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h1>Edit post</h1>
|
|
||||||
|
|
||||||
<form action="?/edit" method="POST" class="col">
|
|
||||||
<select name="title" required>
|
|
||||||
<option disabled>Post title</option>
|
|
||||||
{#each data.postTitles as title}
|
|
||||||
<option value="{title}">{title}</option>
|
|
||||||
{/each}
|
|
||||||
</select>
|
|
||||||
<select name="area">
|
|
||||||
<option disabled>Area to change</option>
|
|
||||||
<option value="title">Title</option>
|
|
||||||
<option value="content">Content</option>
|
|
||||||
<option value="tags">Tags</option>
|
|
||||||
</select>
|
|
||||||
<input type="text" name="newTitle" placeholder="New title" />
|
|
||||||
<textarea
|
|
||||||
name="content"
|
|
||||||
placeholder="New content"
|
|
||||||
rows="4"
|
|
||||||
cols="25"
|
|
||||||
></textarea>
|
|
||||||
<input type="text" name="tags" placeholder="New tags" />
|
|
||||||
{#if form?.editSuccess}
|
|
||||||
{form.editMessage}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if form?.editError}
|
|
||||||
{form.editMessage}
|
|
||||||
{/if}
|
|
||||||
<button type="submit">Submit</button>
|
|
||||||
</form>
|
|
@ -1,20 +1,10 @@
|
|||||||
import type { PageServerLoad } from "./$types";
|
import type { PageServerLoad } from "./$types";
|
||||||
import db from "$lib/db";
|
import fetchApi from "$lib/ghost";
|
||||||
|
|
||||||
export const load: PageServerLoad = async () => {
|
export const load = (async () => {
|
||||||
const Posts = db.model("Posts");
|
const data = await fetchApi("posts");
|
||||||
|
|
||||||
const posts = await Posts.findAll().then((docs) => {
|
|
||||||
return docs.map((doc) => doc.get());
|
|
||||||
});
|
|
||||||
|
|
||||||
if (posts.length === 0 || posts[0] === undefined) {
|
|
||||||
return {
|
return {
|
||||||
posts: []
|
posts: data.posts
|
||||||
}
|
};
|
||||||
} else {
|
}) satisfies PageServerLoad;
|
||||||
return {
|
|
||||||
posts: posts.sort((a, b) => b["created"] - a["created"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -43,19 +43,21 @@
|
|||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
<div class="i-fa6-solid:tags" />
|
<div class="i-fa6-solid:tags" />
|
||||||
{#each post.tags as tag}
|
{#each post.tags as tag}
|
||||||
<a href="/blog/tags/{tag}" class="no-underline">{tag}</a>
|
<a href="/blog/tags/{tag.slug}" class="no-underline">{tag.name}</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<a href="/blog/authors/{post.author}" class="flex items-center gap-2 no-underline"><div class="i-fa6-solid:user" />{post.author}</a>
|
{#each post.authors as author}
|
||||||
|
<a href="/blog/authors/{author.slug}" class="flex items-center gap-2 no-underline"><div class="i-fa6-solid:user" />{author.name}</a>
|
||||||
|
{/each}
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:calendar" /> {dayjs
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:calendar" /> {dayjs
|
||||||
.unix(post.created)
|
(post.published_at)
|
||||||
.format("ddd, DD MMM YYYY HH:mm")}</span>
|
.format("ddd, DD MMM YYYY HH:mm")}</span>
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:pencil" /> {post.words} words</span>
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:pencil" /> {post.plaintext.trim().split(/\s+/).length} words</span>
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:book-open-reader" /> {post.readingTime} minute read</span>
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:book-open-reader" /> {post.reading_time} minute read</span>
|
||||||
</div>
|
</div>
|
||||||
<span>{post.content.split(" ").slice(0, 20).join(" ") + "..."}</span>
|
<span>{post.plaintext.split(" ").slice(0, 20).join(" ") + "..."}</span>
|
||||||
<a href="/blog/{post.title}">Read more...</a>
|
<a href="/blog/{post.slug}">Read more...</a>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
@ -1,27 +1,10 @@
|
|||||||
import type { PageServerLoad } from "./$types";
|
import type { PageServerLoad } from "./$types";
|
||||||
import db from "$lib/db";
|
import fetchApi from "$lib/ghost";
|
||||||
import { compile } from "mdsvex";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params }) => {
|
export const load = (async ({ params }) => {
|
||||||
const Posts = db.model("Posts");
|
const data = await fetchApi("posts/slug/" + params.title);
|
||||||
|
|
||||||
const data = await Posts.findAll({
|
|
||||||
where: {
|
|
||||||
title: params.title
|
|
||||||
}
|
|
||||||
}).then((docs) => {
|
|
||||||
return docs.map((doc) => doc.get());
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.length === 0 || data[0] === undefined) {
|
|
||||||
return {
|
return {
|
||||||
post: {},
|
post: data.posts[0]
|
||||||
content: {}
|
};
|
||||||
}
|
}) satisfies PageServerLoad;
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
post: data[0],
|
|
||||||
content: compile(data[0].content).then((res) => res?.code)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@ -11,16 +11,18 @@
|
|||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
<div class="i-fa6-solid:tags" />
|
<div class="i-fa6-solid:tags" />
|
||||||
{#each data.post.tags as tag}
|
{#each data.post.tags as tag}
|
||||||
<a href="/blog/tags/{tag}" class="no-underline">{tag}</a>
|
<a href="/blog/tags/{tag.slug}" class="no-underline">{tag.name}</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<a href="/blog/authors/{data.post.author}" class="flex items-center gap-2 no-underline"><div class="i-fa6-solid:user" />{data.post.author}</a>
|
{#each data.post.authors as author}
|
||||||
|
<a href="/blog/authors/{author.slug}" class="flex items-center gap-2 no-underline"><div class="i-fa6-solid:user" />{author.name}</a>
|
||||||
|
{/each}
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:calendar" /> {dayjs
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:calendar" /> {dayjs
|
||||||
.unix(data.post.created)
|
(data.post.published_at)
|
||||||
.format("ddd, DD MMM YYYY HH:mm")}</span>
|
.format("ddd, DD MMM YYYY HH:mm")}</span>
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:pencil" /> {data.post.words} words</span>
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:pencil" /> {data.post.plaintext.trim().split(/\s+/).length} words</span>
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:book-open-reader" /> {data.post.readingTime} minute read</span>
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:book-open-reader" /> {data.post.reading_time} minute read</span>
|
||||||
</div>
|
</div>
|
||||||
{@html data.content}
|
{@html data.post.html}
|
||||||
</div>
|
</div>
|
@ -1,23 +1,11 @@
|
|||||||
import type { PageServerLoad } from "./$types";
|
import type { PageServerLoad } from "./$types";
|
||||||
import db from "$lib/db";
|
import fetchApi from "$lib/ghost";
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params }) => {
|
export const load: PageServerLoad = async () => {
|
||||||
const Posts = db.model("Posts");
|
const data = await fetchApi("authors");
|
||||||
|
|
||||||
const data = await Posts.findAll({
|
|
||||||
attributes: ["author"]
|
|
||||||
})
|
|
||||||
|
|
||||||
if (data.length === 0 || data[0] === undefined) {
|
|
||||||
return {
|
|
||||||
authors: []
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const authors = data.map((post) => post["author"]);
|
|
||||||
const uniqueAuthors = [...new Set(authors)];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
authors: uniqueAuthors
|
authors: data.authors
|
||||||
}
|
};
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
|
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
{#each data.authors as author}
|
{#each data.authors as author}
|
||||||
<a href="/blog/authors/{author}" class="bg-secondary w-fit p-2 rounded-2 no-underline">{author}</a>
|
<a href="/blog/authors/{author.slug}" class="bg-secondary w-fit p-2 rounded-2 no-underline">{author.name}</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
@ -1,26 +1,12 @@
|
|||||||
import type { PageServerLoad } from "./$types";
|
import type { PageServerLoad } from "./$types";
|
||||||
import db from "$lib/db";
|
import fetchApi from "$lib/ghost";
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params }) => {
|
export const load: PageServerLoad = async ({ params }) => {
|
||||||
const Posts = db.model("Posts");
|
const data = await fetchApi("posts", "&filter=author:" + params.author);
|
||||||
|
|
||||||
const data = await Posts.findAll({
|
|
||||||
where: {
|
|
||||||
author: params.author
|
|
||||||
}
|
|
||||||
}).then((docs) => {
|
|
||||||
return docs.map((doc) => doc.get());
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.length === 0 || data[0] === undefined) {
|
|
||||||
return {
|
return {
|
||||||
posts: [],
|
posts: data.posts,
|
||||||
authorName: params.author
|
authorName: params.author
|
||||||
}
|
};
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
posts: data,
|
|
||||||
authorName: params.author
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,19 +15,21 @@
|
|||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
<div class="i-fa6-solid:tags" />
|
<div class="i-fa6-solid:tags" />
|
||||||
{#each post.tags as tag}
|
{#each post.tags as tag}
|
||||||
<a href="/blog/tags/{tag}" class="no-underline">{tag}</a>
|
<a href="/blog/tags/{tag.slug}" class="no-underline">{tag.name}</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<a href="/blog/authors/{post.author}" class="flex items-center gap-2 no-underline"><div class="i-fa6-solid:user" />{post.author}</a>
|
{#each post.authors as author}
|
||||||
|
<a href="/blog/authors/{author.slug}" class="flex items-center gap-2 no-underline"><div class="i-fa6-solid:user" />{author.name}</a>
|
||||||
|
{/each}
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:calendar" /> {dayjs
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:calendar" /> {dayjs
|
||||||
.unix(post.created)
|
(post.published_at)
|
||||||
.format("ddd, DD MMM YYYY HH:mm")}</span>
|
.format("ddd, DD MMM YYYY HH:mm")}</span>
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:pencil" /> {post.words} words</span>
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:pencil" /> {post.plaintext.trim().split(/\s+/).length} words</span>
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:book-open-reader" /> {post.readingTime} minute read</span>
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:book-open-reader" /> {post.reading_time} minute read</span>
|
||||||
</div>
|
</div>
|
||||||
<span>{post.content.split(" ").slice(0, 20).join(" ") + "..."}</span>
|
<span>{post.plaintext.split(" ").slice(0, 20).join(" ") + "..."}</span>
|
||||||
<a href="/blog/{post.title}">Read more...</a>
|
<a href="/blog/{post.slug}">Read more...</a>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
@ -1,20 +1,10 @@
|
|||||||
import type { PageServerLoad } from "./$types";
|
import type { PageServerLoad } from "./$types";
|
||||||
import db from "$lib/db";
|
import fetchApi from "$lib/ghost";
|
||||||
|
|
||||||
export const load: PageServerLoad = async () => {
|
export const load: PageServerLoad = async () => {
|
||||||
const Posts = db.model("Posts");
|
const data = await fetchApi("tags");
|
||||||
|
|
||||||
const data = await Posts.findAll({ attributes: ["tags"] })
|
|
||||||
|
|
||||||
if (data.length === 0 || data[0] === undefined) {
|
|
||||||
return {
|
return {
|
||||||
tags: []
|
tags: data.tags
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const tags = data.map((post) => post["tags"]).flat();
|
|
||||||
const uniqueTags = [...new Set(tags)];
|
|
||||||
return {
|
|
||||||
tags: uniqueTags
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -7,6 +7,6 @@
|
|||||||
|
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
{#each data.tags as tag}
|
{#each data.tags as tag}
|
||||||
<a href="/blog/tags/{tag}" class="bg-secondary w-fit p-2 rounded-2 no-underline">{tag}</a>
|
<a href="/blog/tags/{tag.slug}" class="bg-secondary w-fit p-2 rounded-2 no-underline">{tag.name}</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
@ -1,29 +1,11 @@
|
|||||||
import type { PageServerLoad } from "./$types";
|
import type { PageServerLoad } from "./$types";
|
||||||
import db from "$lib/db";
|
import fetchApi from "$lib/ghost";
|
||||||
import { Op } from "sequelize";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params }) => {
|
export const load: PageServerLoad = async ({ params }) => {
|
||||||
const Posts = db.model("Posts");
|
const data = await fetchApi("posts", "&filter=tags:" + params.tag);
|
||||||
|
|
||||||
const data = await Posts.findAll({
|
|
||||||
where: {
|
|
||||||
tags: {
|
|
||||||
[Op.contains]: [params.tag]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).then((docs) => {
|
|
||||||
return docs.map((doc) => doc.get());
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.length === 0 || data[0] === undefined) {
|
|
||||||
return {
|
return {
|
||||||
posts: [],
|
posts: data.posts,
|
||||||
tagName: params.tag
|
tagName: params.tag
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
posts: data,
|
|
||||||
tagName: params.tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
@ -15,19 +15,21 @@
|
|||||||
<div class="flex flex-row items-center gap-2">
|
<div class="flex flex-row items-center gap-2">
|
||||||
<div class="i-fa6-solid:tags" />
|
<div class="i-fa6-solid:tags" />
|
||||||
{#each post.tags as tag}
|
{#each post.tags as tag}
|
||||||
<a href="/blog/tags/{tag}" class="no-underline">{tag}</a>
|
<a href="/blog/tags/{tag.slug}" class="no-underline">{tag.name}</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<a href="/blog/authors/{post.author}" class="flex items-center gap-2 no-underline"><div class="i-fa6-solid:user" />{post.author}</a>
|
{#each post.authors as author}
|
||||||
|
<a href="/blog/authors/{author.slug}" class="flex items-center gap-2 no-underline"><div class="i-fa6-solid:user" />{author.name}</a>
|
||||||
|
{/each}
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:calendar" /> {dayjs
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:calendar" /> {dayjs
|
||||||
.unix(post.created)
|
(post.published_at)
|
||||||
.format("ddd, DD MMM YYYY HH:mm")}</span>
|
.format("ddd, DD MMM YYYY HH:mm")}</span>
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:pencil" /> {post.words} words</span>
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:pencil" /> {post.plaintext.trim().split(/\s+/).length} words</span>
|
||||||
<span class="flex items-center gap-2"><div class="i-fa6-solid:book-open-reader" /> {post.readingTime} minute read</span>
|
<span class="flex items-center gap-2"><div class="i-fa6-solid:book-open-reader" /> {post.reading_time} minute read</span>
|
||||||
</div>
|
</div>
|
||||||
<span>{post.content.split(" ").slice(0, 20).join(" ") + "..."}</span>
|
<span>{post.plaintext.split(" ").slice(0, 20).join(" ") + "..."}</span>
|
||||||
<a href="/blog/{post.title}">Read more...</a>
|
<a href="/blog/{post.slug}">Read more...</a>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
Loading…
Reference in New Issue
Block a user