various changes
This commit is contained in:
parent
c3a8469371
commit
adc02f2e5b
@ -7,21 +7,16 @@ import { cookies } from "next/headers";
|
|||||||
|
|
||||||
|
|
||||||
async function tryCreatePost(request: Request) {
|
async function tryCreatePost(request: Request) {
|
||||||
|
|
||||||
// Make sure the DB is ready
|
// Make sure the DB is ready
|
||||||
const sync = await dbSync;
|
const sync = await dbSync;
|
||||||
|
|
||||||
// Prepare data
|
// Prepare data
|
||||||
const requestBody = await request.json();
|
const requestBody = await request.json();
|
||||||
const authCkie = await cookies().get("auth");
|
const authCkie = await cookies().get("auth");
|
||||||
|
|
||||||
// Sanity check auth cookie
|
// Sanity check auth cookie
|
||||||
if ( !authCkie || !authCkie.value) throw new APIError({ status: 500, responseText: "missing auth cookie" });
|
if ( !authCkie || !authCkie.value) throw new APIError({ status: 500, responseText: "missing auth cookie" });
|
||||||
|
|
||||||
// Get JSON from the Cookie
|
// Get JSON from the Cookie
|
||||||
const cookieJSON = authCkie.value;
|
const cookieJSON = authCkie.value;
|
||||||
const authObject = JSON.parse(cookieJSON);
|
const authObject = JSON.parse(cookieJSON);
|
||||||
|
|
||||||
// Fetch User Auth from the database
|
// Fetch User Auth from the database
|
||||||
const auth = await Auth.findOne({
|
const auth = await Auth.findOne({
|
||||||
include: [
|
include: [
|
||||||
@ -42,6 +37,7 @@ async function tryCreatePost(request: Request) {
|
|||||||
if (!requestBody) throw new APIError({ status: 500, responseText: "Empty request body" });
|
if (!requestBody) throw new APIError({ status: 500, responseText: "Empty request body" });
|
||||||
if (!requestBody.title) throw new APIError({ status: 500, responseText: "Missing post title" });
|
if (!requestBody.title) throw new APIError({ status: 500, responseText: "Missing post title" });
|
||||||
if (!requestBody.content) throw new APIError({ status: 500, responseText: "Missing post content" });
|
if (!requestBody.content) throw new APIError({ status: 500, responseText: "Missing post content" });
|
||||||
|
if (!requestBody.description) throw new APIError({ status: 500, responseText: "Missing post description" });
|
||||||
if (!auth.user.id) throw new APIError({ status: 500, responseText: "Missing user id" });
|
if (!auth.user.id) throw new APIError({ status: 500, responseText: "Missing user id" });
|
||||||
if (!auth.user.perms || !auth.user.perms.isAdmin) throw new APIError({ status: 401, responseText: `Unauthorized ${JSON.stringify(auth.user)}` });
|
if (!auth.user.perms || !auth.user.perms.isAdmin) throw new APIError({ status: 401, responseText: `Unauthorized ${JSON.stringify(auth.user)}` });
|
||||||
|
|
||||||
@ -49,6 +45,7 @@ async function tryCreatePost(request: Request) {
|
|||||||
const post = await Post.create(
|
const post = await Post.create(
|
||||||
{
|
{
|
||||||
content: requestBody.content,
|
content: requestBody.content,
|
||||||
|
description: requestBody.description,
|
||||||
user_id: auth.user.id,
|
user_id: auth.user.id,
|
||||||
title: requestBody.title,
|
title: requestBody.title,
|
||||||
},{
|
},{
|
||||||
|
|||||||
8
src/app/attachment/[...slug]/route.ts
Normal file
8
src/app/attachment/[...slug]/route.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { open, openSync, readFileSync } from "fs";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export async function GET(req:NextRequest, { params }: {params:{slug: string}}){
|
||||||
|
const fp = path.resolve('.',`bucket`,...params.slug);
|
||||||
|
return new Response(readFileSync(fp));
|
||||||
|
}
|
||||||
@ -28,127 +28,132 @@ body {
|
|||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--text-50: #f3eff5;
|
--text: #ffffff;
|
||||||
--text-100: #e7dfec;
|
--text-50: #f2f2f2;
|
||||||
--text-200: #cfbfd9;
|
--text-100: #e6e6e6;
|
||||||
--text-300: #b89fc6;
|
--text-200: #cccccc;
|
||||||
--text-400: #a080b3;
|
--text-300: #b3b3b3;
|
||||||
--text-500: #88609f;
|
--text-400: #999999;
|
||||||
--text-600: #6d4d80;
|
--text-500: #808080;
|
||||||
--text-700: #523960;
|
--text-600: #666666;
|
||||||
--text-800: #362640;
|
--text-700: #4d4d4d;
|
||||||
--text-900: #1b1320;
|
--text-800: #333333;
|
||||||
--text-950: #0e0a10;
|
--text-900: #1a1a1a;
|
||||||
|
--text-950: #0d0d0d;
|
||||||
|
|
||||||
--background-50: #f4eef6;
|
--background: #1e546b;
|
||||||
--background-100: #e8ddee;
|
--background-50: #ebf5f9;
|
||||||
--background-200: #d2bbdd;
|
--background-100: #d7ebf4;
|
||||||
--background-300: #bb99cc;
|
--background-200: #afd7e9;
|
||||||
--background-400: #a477bb;
|
--background-300: #88c4dd;
|
||||||
--background-500: #8e55aa;
|
--background-400: #60b0d2;
|
||||||
--background-600: #714488;
|
--background-500: #389cc7;
|
||||||
--background-700: #553366;
|
--background-600: #2d7d9f;
|
||||||
--background-800: #392244;
|
--background-700: #225e77;
|
||||||
--background-900: #1c1122;
|
--background-800: #163e50;
|
||||||
--background-950: #0e0911;
|
--background-900: #0b1f28;
|
||||||
|
--background-950: #061014;
|
||||||
|
|
||||||
--primary-50: #f3edf8;
|
--primary: #008bc7;
|
||||||
--primary-100: #e6daf1;
|
--primary-50: #e5f7ff;
|
||||||
--primary-200: #ceb5e3;
|
--primary-100: #ccf0ff;
|
||||||
--primary-300: #b590d5;
|
--primary-200: #99e0ff;
|
||||||
--primary-400: #9c6bc7;
|
--primary-300: #66d1ff;
|
||||||
--primary-500: #8346b9;
|
--primary-400: #33c2ff;
|
||||||
--primary-600: #693894;
|
--primary-500: #00b2ff;
|
||||||
--primary-700: #4f2a6f;
|
--primary-600: #008fcc;
|
||||||
--primary-800: #351c4a;
|
--primary-700: #006b99;
|
||||||
--primary-900: #1a0e25;
|
--primary-800: #004766;
|
||||||
--primary-950: #0d0712;
|
--primary-900: #002433;
|
||||||
|
--primary-950: #00121a;
|
||||||
|
|
||||||
--secondary-50: #f3ebf9;
|
--secondary: #136d94;
|
||||||
--secondary-100: #e7d7f4;
|
--secondary-50: #e8f6fc;
|
||||||
--secondary-200: #cfb0e8;
|
--secondary-100: #d2edf9;
|
||||||
--secondary-300: #b788dd;
|
--secondary-200: #a5dcf3;
|
||||||
--secondary-400: #9f61d1;
|
--secondary-300: #78caed;
|
||||||
--secondary-500: #8739c6;
|
--secondary-400: #4ab8e8;
|
||||||
--secondary-600: #6c2e9e;
|
--secondary-500: #1da7e2;
|
||||||
--secondary-700: #512277;
|
--secondary-600: #1785b5;
|
||||||
--secondary-800: #36174f;
|
--secondary-700: #126487;
|
||||||
--secondary-900: #1b0b28;
|
--secondary-800: #0c435a;
|
||||||
--secondary-950: #0d0614;
|
--secondary-900: #06212d;
|
||||||
|
--secondary-950: #031117;
|
||||||
|
|
||||||
--accent-50: #f3eafb;
|
--accent: #42aedc;
|
||||||
--accent-100: #e7d5f6;
|
--accent-50: #e9f6fb;
|
||||||
--accent-200: #cfaaee;
|
--accent-100: #d4edf7;
|
||||||
--accent-300: #b880e5;
|
--accent-200: #a9daef;
|
||||||
--accent-400: #a056dc;
|
--accent-300: #7ec8e7;
|
||||||
--accent-500: #882bd4;
|
--accent-400: #53b5df;
|
||||||
--accent-600: #6d23a9;
|
--accent-500: #28a3d7;
|
||||||
--accent-700: #521a7f;
|
--accent-600: #2082ac;
|
||||||
--accent-800: #361155;
|
--accent-700: #186281;
|
||||||
--accent-900: #1b092a;
|
--accent-800: #104156;
|
||||||
--accent-950: #0e0415;
|
--accent-900: #08212b;
|
||||||
|
--accent-950: #041016;
|
||||||
|
|
||||||
}
|
}
|
||||||
.dark {
|
.dark {
|
||||||
--text-50: #0e0910;
|
--text-50: #0d0d0d;
|
||||||
--text-100: #1b1320;
|
--text-100: #1a1a1a;
|
||||||
--text-200: #362541;
|
--text-200: #333333;
|
||||||
--text-300: #513861;
|
--text-300: #4d4d4d;
|
||||||
--text-400: #6c4a82;
|
--text-400: #666666;
|
||||||
--text-500: #885da2;
|
--text-500: #808080;
|
||||||
--text-600: #9f7db5;
|
--text-600: #999999;
|
||||||
--text-700: #b79ec7;
|
--text-700: #b3b3b3;
|
||||||
--text-800: #cfbeda;
|
--text-800: #cccccc;
|
||||||
--text-900: #e7dfec;
|
--text-900: #e6e6e6;
|
||||||
--text-950: #f3eff6;
|
--text-950: #f2f2f2;
|
||||||
|
|
||||||
--background-50: #0d0911;
|
--background-50: #061014;
|
||||||
--background-100: #1b1122;
|
--background-100: #0b1f28;
|
||||||
--background-200: #352244;
|
--background-200: #163e50;
|
||||||
--background-300: #503366;
|
--background-300: #225e77;
|
||||||
--background-400: #6a4488;
|
--background-400: #2d7d9f;
|
||||||
--background-500: #8555aa;
|
--background-500: #389cc7;
|
||||||
--background-600: #9d77bb;
|
--background-600: #60b0d2;
|
||||||
--background-700: #b699cc;
|
--background-700: #88c4dd;
|
||||||
--background-800: #cebbdd;
|
--background-800: #afd7e9;
|
||||||
--background-900: #e7ddee;
|
--background-900: #d7ebf4;
|
||||||
--background-950: #f3eef6;
|
--background-950: #ebf5f9;
|
||||||
|
|
||||||
--primary-50: #0d0712;
|
--primary-50: #00121a;
|
||||||
--primary-100: #1a0e25;
|
--primary-100: #002433;
|
||||||
--primary-200: #351c4a;
|
--primary-200: #004766;
|
||||||
--primary-300: #4f2a6f;
|
--primary-300: #006b99;
|
||||||
--primary-400: #693894;
|
--primary-400: #008fcc;
|
||||||
--primary-500: #8346b9;
|
--primary-500: #00b2ff;
|
||||||
--primary-600: #9c6bc7;
|
--primary-600: #33c2ff;
|
||||||
--primary-700: #b590d5;
|
--primary-700: #66d1ff;
|
||||||
--primary-800: #ceb5e3;
|
--primary-800: #99e0ff;
|
||||||
--primary-900: #e6daf1;
|
--primary-900: #ccf0ff;
|
||||||
--primary-950: #f3edf8;
|
--primary-950: #e5f7ff;
|
||||||
|
|
||||||
--secondary-50: #0d0614;
|
--secondary-50: #031117;
|
||||||
--secondary-100: #1b0b28;
|
--secondary-100: #06212d;
|
||||||
--secondary-200: #36174f;
|
--secondary-200: #0c435a;
|
||||||
--secondary-300: #512277;
|
--secondary-300: #126487;
|
||||||
--secondary-400: #6c2e9e;
|
--secondary-400: #1785b5;
|
||||||
--secondary-500: #8739c6;
|
--secondary-500: #1da7e2;
|
||||||
--secondary-600: #9f61d1;
|
--secondary-600: #4ab8e8;
|
||||||
--secondary-700: #b788dd;
|
--secondary-700: #78caed;
|
||||||
--secondary-800: #cfb0e8;
|
--secondary-800: #a5dcf3;
|
||||||
--secondary-900: #e7d7f4;
|
--secondary-900: #d2edf9;
|
||||||
--secondary-950: #f3ebf9;
|
--secondary-950: #e8f6fc;
|
||||||
|
|
||||||
--accent-50: #0e0415;
|
--accent-50: #041016;
|
||||||
--accent-100: #1b092a;
|
--accent-100: #08212b;
|
||||||
--accent-200: #361155;
|
--accent-200: #104156;
|
||||||
--accent-300: #521a7f;
|
--accent-300: #186281;
|
||||||
--accent-400: #6d23a9;
|
--accent-400: #2082ac;
|
||||||
--accent-500: #882bd4;
|
--accent-500: #28a3d7;
|
||||||
--accent-600: #a056dc;
|
--accent-600: #53b5df;
|
||||||
--accent-700: #b880e5;
|
--accent-700: #7ec8e7;
|
||||||
--accent-800: #cfaaee;
|
--accent-800: #a9daef;
|
||||||
--accent-900: #e7d5f6;
|
--accent-900: #d4edf7;
|
||||||
--accent-950: #f3eafb;
|
--accent-950: #e9f6fb;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,11 +1,10 @@
|
|||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { revalidatePath, revalidateTag } from 'next/cache'
|
import { revalidatePath, revalidateTag } from 'next/cache'
|
||||||
import { Post } from "@/model/Post";
|
import { Bucket, Post, PostBucket, User, dbSync } from "@/model/Models";
|
||||||
import { Attributes, where } from "@sequelize/core";
|
import { Attributes, where } from "@sequelize/core";
|
||||||
import { cookies } from 'next/headers';
|
import { cookies } from 'next/headers';
|
||||||
import { getCookieAuth, userIsAdmin } from '../actions';
|
import { getCookieAuth, userIsAdmin } from '../actions';
|
||||||
import { User } from '@/model/User';
|
|
||||||
import { ActionResult } from '../ActionResult';
|
import { ActionResult } from '../ActionResult';
|
||||||
|
|
||||||
|
|
||||||
@ -13,6 +12,7 @@ import { ActionResult } from '../ActionResult';
|
|||||||
|
|
||||||
|
|
||||||
export async function deletePost(postID: number): Promise<ActionResult<boolean>> {
|
export async function deletePost(postID: number): Promise<ActionResult<boolean>> {
|
||||||
|
await dbSync;
|
||||||
// revalidatePath("/admin/man-post","page")
|
// revalidatePath("/admin/man-post","page")
|
||||||
if(! await userIsAdmin()) return {error:"Unauthorized, not deleting Post", result: false}
|
if(! await userIsAdmin()) return {error:"Unauthorized, not deleting Post", result: false}
|
||||||
const destroy = await Post.destroy({ where: { id: postID } });
|
const destroy = await Post.destroy({ where: { id: postID } });
|
||||||
@ -21,12 +21,14 @@ export async function deletePost(postID: number): Promise<ActionResult<boolean>>
|
|||||||
|
|
||||||
|
|
||||||
export async function getPosts(): Promise<ActionResult<Attributes<Post>[]>> {
|
export async function getPosts(): Promise<ActionResult<Attributes<Post>[]>> {
|
||||||
|
await dbSync;
|
||||||
if(! await userIsAdmin()) return {error:"Unauthorized, not fetching Posts."}
|
if(! await userIsAdmin()) return {error:"Unauthorized, not fetching Posts."}
|
||||||
const posts = await Post.findAll();
|
const posts = await Post.findAll({include: {association: Post.associations.postBuckets, include: Bucket.associations.attachments}},);
|
||||||
return {result:JSON.parse(JSON.stringify(posts))};
|
return {result:JSON.parse(JSON.stringify(posts))};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updatePost(postAttributes: Partial<Attributes<Post>>): Promise<ActionResult<Attributes<Post>[]>> {
|
export async function updatePost(postAttributes: Partial<Attributes<Post>>): Promise<ActionResult<Attributes<Post>[]>> {
|
||||||
|
await dbSync;
|
||||||
if(! await userIsAdmin()) return {error:"Unauthorized, not updating Post."}
|
if(! await userIsAdmin()) return {error:"Unauthorized, not updating Post."}
|
||||||
const post = await Post.update(postAttributes, {where:{id:postAttributes.id}});
|
const post = await Post.update(postAttributes, {where:{id:postAttributes.id}});
|
||||||
return {result:JSON.parse(JSON.stringify(post))};
|
return {result:JSON.parse(JSON.stringify(post))};
|
||||||
|
|||||||
39
src/app/lib/jsxtostring.ts
Normal file
39
src/app/lib/jsxtostring.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React, { ReactElement, ReactNode } from 'react';
|
||||||
|
|
||||||
|
function renderAttributes(props: Record<string, any>): string {
|
||||||
|
return Object.keys(props)
|
||||||
|
.map((key) => {
|
||||||
|
const value = props[key];
|
||||||
|
if (key === 'children' || value === undefined || value === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return `${key}="${value}"`;
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderElement(element: ReactNode): string {
|
||||||
|
if (typeof element === 'string' || typeof element === 'number') {
|
||||||
|
return String(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (React.isValidElement(element)) {
|
||||||
|
const { type, props } = element;
|
||||||
|
const mapped = React.Children.map(props.children, renderElement)
|
||||||
|
const cilds = mapped? mapped.join('') : props.children;
|
||||||
|
const attributes = renderAttributes(props);
|
||||||
|
return `<${type}${attributes ? ' ' + attributes : ''}>${cilds}</${type}>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(element)) {
|
||||||
|
return element.map(renderElement).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function jsxToString(element: JSX.Element): string {
|
||||||
|
console.log(element);
|
||||||
|
return renderElement(element);
|
||||||
|
}
|
||||||
@ -34,7 +34,7 @@ export default async function Test() {
|
|||||||
<main>
|
<main>
|
||||||
{articles.map((article, i) => {
|
{articles.map((article, i) => {
|
||||||
// Return the element. Also pass key
|
// Return the element. Also pass key
|
||||||
return (<ArticlePreview key={article?.id} id={article?.id?.toString()} content={article?.content} title={article?.title}></ArticlePreview>)
|
return (<ArticlePreview key={article?.id} post={article}></ArticlePreview>)
|
||||||
})}
|
})}
|
||||||
</main>
|
</main>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
|
|||||||
@ -1,18 +1,26 @@
|
|||||||
import { ActionResult } from "@/app/lib/actions/ActionResult";
|
import { ActionResult } from "@/app/lib/actions/ActionResult";
|
||||||
import { handleActionResult } from "@/app/lib/actions/clientActionHandler";
|
import { handleActionResult } from "@/app/lib/actions/clientActionHandler";
|
||||||
import { Post } from "@/model/Post";
|
import { Post, Project, Bucket, PostBucket, Attachment } from "@/model/Models";
|
||||||
import { Project } from "@/model/Project";
|
|
||||||
import { Attributes } from "@sequelize/core";
|
import { Attributes } from "@sequelize/core";
|
||||||
|
import { UUID } from "crypto";
|
||||||
import { ChangeEventHandler, MouseEventHandler, useLayoutEffect, useRef, useState } from "react";
|
import { ChangeEventHandler, MouseEventHandler, useLayoutEffect, useRef, useState } from "react";
|
||||||
|
import { Accordion, AccordionBody, AccordionHeader, AccordionItem } from "react-bootstrap";
|
||||||
|
|
||||||
export type PostTableCallbacks = {
|
export type PostTableCallbacks = {
|
||||||
savePost: (p:Partial<Attributes<Post>>)=>any;
|
savePost: (p:Partial<Attributes<Post>>)=>any;
|
||||||
closeEditor: ()=>any;
|
closeEditor: ()=>any;
|
||||||
}
|
}
|
||||||
|
type PostEditorBucket = Partial<Attributes<Bucket>> & {
|
||||||
|
attachments:Partial<Attributes<Attachment>>[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostEditorPost = Partial<Attributes<Post>> & {
|
||||||
|
buckets:PostEditorBucket[]
|
||||||
|
}
|
||||||
|
|
||||||
export type EditorProps = {
|
export type EditorProps = {
|
||||||
open:boolean;
|
open:boolean;
|
||||||
post:Partial<Attributes<Post>>;
|
post:PostEditorPost;
|
||||||
projects?:Attributes<Project>[];
|
projects?:Attributes<Project>[];
|
||||||
callbacks:PostTableCallbacks;
|
callbacks:PostTableCallbacks;
|
||||||
}
|
}
|
||||||
@ -23,7 +31,7 @@ export default function PostEditor(props:EditorProps){
|
|||||||
let [title,setTitle] = useState(props.post?.title)
|
let [title,setTitle] = useState(props.post?.title)
|
||||||
let [projectID,setProjectID] = useState(props.post?.project_id)
|
let [projectID,setProjectID] = useState(props.post?.project_id)
|
||||||
let textbox:any = useRef(undefined);
|
let textbox:any = useRef(undefined);
|
||||||
|
|
||||||
function adjustHeight() {
|
function adjustHeight() {
|
||||||
if(!textbox.current || !textbox.current.style) return
|
if(!textbox.current || !textbox.current.style) return
|
||||||
textbox.current.style.height = "fit-content";
|
textbox.current.style.height = "fit-content";
|
||||||
@ -55,6 +63,31 @@ export default function PostEditor(props:EditorProps){
|
|||||||
<option value={0}>unassigned</option>
|
<option value={0}>unassigned</option>
|
||||||
{props.projects?.map(p=><option value={p.id}>{p.readableIdentifier}</option>)}
|
{props.projects?.map(p=><option value={p.id}>{p.readableIdentifier}</option>)}
|
||||||
</select>
|
</select>
|
||||||
|
<h2>Attachments</h2>
|
||||||
|
<table className="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr><td>Buckets</td></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{
|
||||||
|
(()=>{
|
||||||
|
let bucketMap:Map<UUID,PostEditorBucket> = new Map(props.post.buckets.map((b)=>[b.id as UUID,b]));
|
||||||
|
let bucketList = [...props.post.buckets.map((b)=>b.id)];
|
||||||
|
return bucketList.map((e)=>{
|
||||||
|
return <tr><Accordion><AccordionItem eventKey={bucketList.indexOf(e).toString()}><AccordionHeader>{e}</AccordionHeader><AccordionBody><ul>{bucketMap.get(e as UUID)?.attachments.map((attachment)=><li>{attachment.filename}</li>)}</ul></AccordionBody></AccordionItem></Accordion></tr>
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
<tr>
|
||||||
|
<td colSpan={4}>
|
||||||
|
<table className="table mb-0">
|
||||||
|
...
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
...
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
<button type="button" className="m-2 btn btn-primary">Preview</button>
|
<button type="button" className="m-2 btn btn-primary">Preview</button>
|
||||||
<button type="button" className="m-2 btn btn-success" onClick={onClickSaveButton}>Save</button>
|
<button type="button" className="m-2 btn btn-success" onClick={onClickSaveButton}>Save</button>
|
||||||
<button type="button" className="m-2 btn btn-danger" onClick={onClickCancelButton}>Cancel</button>
|
<button type="button" className="m-2 btn btn-danger" onClick={onClickCancelButton}>Cancel</button>
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
cache: 'no-store'
|
cache: 'no-store'
|
||||||
|
|
||||||
import { tryFetchPosts } from "@/app/api/post/route";
|
import { tryFetchPosts } from "@/app/api/post/route";
|
||||||
import { Post } from "@/model/Post";
|
|
||||||
import { constructAPIUrl } from "@/util/Utils";
|
import { constructAPIUrl } from "@/util/Utils";
|
||||||
import { ReactNode, useEffect } from "react";
|
import { ReactNode, useEffect } from "react";
|
||||||
import TableGen from "../../../client/TableGen";
|
import TableGen from "../../../client/TableGen";
|
||||||
import PostTable from "@/components/client/admin/PostTable";
|
import PostTable from "@/components/client/admin/PostTable";
|
||||||
import { deletePost, getPosts, updatePost } from "@/app/lib/actions/entityManagement/postActions";
|
import { deletePost, getPosts, updatePost } from "@/app/lib/actions/entityManagement/postActions";
|
||||||
import { getProjects } from "@/app/lib/actions/entityManagement/projectActions";
|
import { getProjects } from "@/app/lib/actions/entityManagement/projectActions";
|
||||||
import { Project } from "@/model/Project";
|
import { Bucket, Project, Post, dbSync, Attachment } from "@/model/Models";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children?:ReactNode
|
children?:ReactNode
|
||||||
@ -16,6 +15,8 @@ type Props = {
|
|||||||
|
|
||||||
|
|
||||||
export default async function PostView(props:Props){
|
export default async function PostView(props:Props){
|
||||||
|
await dbSync;
|
||||||
|
|
||||||
const headings = [
|
const headings = [
|
||||||
'#',
|
'#',
|
||||||
'Title',
|
'Title',
|
||||||
@ -32,7 +33,8 @@ export default async function PostView(props:Props){
|
|||||||
getProjects,
|
getProjects,
|
||||||
savePost:updatePost
|
savePost:updatePost
|
||||||
};
|
};
|
||||||
const posts:Post[] = await Post.findAll().then(posts=>posts.map((e)=>JSON.parse(JSON.stringify(e))));
|
|
||||||
|
const posts:Post[] = await Post.findAll({include: {model: Bucket, include: {model: Attachment}}}).then(posts=>posts.map((e)=>JSON.parse(JSON.stringify(e))));
|
||||||
const projects = await Project.findAll().then(projects=>projects.map((e)=>JSON.parse(JSON.stringify(e))));
|
const projects = await Project.findAll().then(projects=>projects.map((e)=>JSON.parse(JSON.stringify(e))));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
.header{
|
.header{
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: #008BC7;
|
/* background-color: #008BC7; */
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import styles from "./header.module.css"
|
import styles from "./header.module.css"
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
return <div className={`${styles.header} pp`}>
|
return <div className={`${styles.header} pp bg-primary`}>
|
||||||
|
|
||||||
<img src="/logo.png" width="80px" height="auto" alt="" />
|
<img src="/logo.png" width="80px" height="auto" alt="" />
|
||||||
<div className={styles.headertitle}>
|
<div className={`${styles.headertitle}`}>
|
||||||
<div style={{flexGrow:1}}>Andreas<br/>Schaafsma</div>
|
<div style={{flexGrow:1}}>Andreas<br/>Schaafsma</div>
|
||||||
<div>>Software Developer</div>
|
<div>>Software Developer</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
.navbar{
|
.navbar{
|
||||||
background-color: #136D94;
|
/* background-color: #136D94; */
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
.navItem{
|
.navItem{
|
||||||
|
|||||||
@ -5,7 +5,7 @@ const navList = navItems.map(value => <a key={value} className={styles.navItem}>
|
|||||||
|
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
return <nav className={`${styles.navbar}`}>
|
return <nav className={`${styles.navbar} bg-secondary`}>
|
||||||
<ul>{navList}</ul>
|
<ul>{navList}</ul>
|
||||||
</nav>
|
</nav>
|
||||||
}
|
}
|
||||||
@ -2,25 +2,50 @@ import Tagbar from "@/components/shared/news/tagbar";
|
|||||||
import styles from "@/components/shared/news/article-preview.module.css"
|
import styles from "@/components/shared/news/article-preview.module.css"
|
||||||
|
|
||||||
import bg from "public/placeholder-square.png"
|
import bg from "public/placeholder-square.png"
|
||||||
import { ReactNode } from "react"
|
import { DOMElement, JSXElementConstructor, ReactNode } from "react"
|
||||||
import { Style } from "util";
|
import { Style } from "util";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import { Router } from "next/router";
|
import { Router } from "next/router";
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { truncateString } from "@/util/Utils";
|
import { truncateString } from "@/util/Utils";
|
||||||
|
import { MDXRemote } from "next-mdx-remote/rsc";
|
||||||
|
import { ExampleComponent } from "./article";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import renderToString from 'react-dom/server'
|
||||||
|
import { jsxToString } from "@/app/lib/jsxtostring";
|
||||||
|
import remarkMdx from "remark-mdx";
|
||||||
|
import { remark } from "remark";
|
||||||
|
import strip from 'remark-mdx-to-plain-text';
|
||||||
|
import { Post } from "@/model/Post";
|
||||||
|
import { Attributes } from "@sequelize/core";
|
||||||
|
|
||||||
|
type ArticlePreviewPost = Attributes<Post>
|
||||||
type Props = {
|
type Props = {
|
||||||
id?:string
|
post:ArticlePreviewPost;
|
||||||
title?:string
|
|
||||||
content?:string
|
|
||||||
date?:string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default async function ArticlePreview(props:Props){
|
||||||
|
const components = { ExampleComponent }
|
||||||
|
const content = ["import ExampleComponent from './article';\n",props.post.content].join('\n');
|
||||||
|
let stripped = ''
|
||||||
|
remark()
|
||||||
|
.use(remarkMdx)
|
||||||
|
.use(strip)
|
||||||
|
.process(content, (err:any, file:any):any => {
|
||||||
|
if (err) throw err
|
||||||
|
// console.log(String(file));
|
||||||
|
stripped=String(file)
|
||||||
|
return String(file)
|
||||||
|
} )
|
||||||
|
console.log(stripped)
|
||||||
|
|
||||||
export default function ArticlePreview(props:Props){
|
// const parsedMDX = await MDXRemote({source:props.content,components:components});
|
||||||
|
// const b = new ReactDOM.contain
|
||||||
|
// const renderedMDX = ReactDOM.render(parsedMDX,)
|
||||||
|
// console.log(eval(parsedMDX as any))
|
||||||
|
// const a = jsxToString(renderedMDX);
|
||||||
|
// console.log(parsedMDX);
|
||||||
// if (!props.content)
|
// if (!props.content)
|
||||||
return (
|
return (
|
||||||
<section className={styles.previewbox}>
|
<section className={styles.previewbox}>
|
||||||
@ -28,8 +53,8 @@ export default function ArticlePreview(props:Props){
|
|||||||
|
|
||||||
<div className={`${styles.summary} flex flex-col justify-between p-0`}>
|
<div className={`${styles.summary} flex flex-col justify-between p-0`}>
|
||||||
<section className="w-[100%]">
|
<section className="w-[100%]">
|
||||||
<span className="inline-block"><h2><Link href={`/article/${props.id}`}>{props.title}</Link></h2></span>
|
<span className="inline-block"><h2><Link href={`/article/${props.post.id}`}>{props.post.title}</Link></h2></span>
|
||||||
<p>{truncateString(props.content,255)}</p>
|
<p>{truncateString(props.post.description? props.post.description : "No Description",255)}</p>
|
||||||
</section>
|
</section>
|
||||||
<Tagbar/>
|
<Tagbar/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import "/public/global.css"
|
|||||||
import "@/app/index.css"
|
import "@/app/index.css"
|
||||||
import styles from "./article.module.css"
|
import styles from "./article.module.css"
|
||||||
|
|
||||||
import { serialize } from 'next-mdx-remote/serialize'
|
|
||||||
import { MDXComponents, MDXContent } from "mdx/types";
|
import { MDXComponents, MDXContent } from "mdx/types";
|
||||||
import { MDXRemote } from 'next-mdx-remote/rsc'
|
import { MDXRemote } from 'next-mdx-remote/rsc'
|
||||||
|
|
||||||
@ -20,14 +19,15 @@ export default async function Article(params: { id: string|undefined, title: str
|
|||||||
const components = { ExampleComponent }
|
const components = { ExampleComponent }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article id={`post-${params.id}`}>
|
<article id={`post-${params.id}`} className="bg-background-800 w-[80%] m-auto">
|
||||||
<h1 className=".article-title pl-5 pr-5">{params.title}</h1>
|
<div id="image-container" className={`${styles.imagecontainer} flex flex-col`}>
|
||||||
<div className={`${styles.imagecontainer} m-5`}/>
|
<h1 id="article-title" className=".article-title p-5 self-start">{params.title}</h1>
|
||||||
<div className="pl-5 pr-5"><Tagbar/></div>
|
<div id="spacer" className="flex flex-grow"></div>
|
||||||
<div className=".article-content p-5">
|
<div id="tagbar" className="p-5 self-start"><Tagbar/></div>
|
||||||
<MDXRemote
|
|
||||||
source={params.content?params.content:""} components={components}
|
</div>
|
||||||
/>
|
<div className=".article-content p-5 whitespace-pre">
|
||||||
|
<MDXRemote source={params.content?params.content:""} components={components}/>
|
||||||
</div>
|
</div>
|
||||||
<section className=".article-date">{params.date}</section> <br/>
|
<section className=".article-date">{params.date}</section> <br/>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
color: #C0F0FF;
|
color: #C0F0FF;
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
background-color: #1E536A;
|
/* background-color: #1E536A; */
|
||||||
padding:8px;
|
padding:8px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
@ -6,7 +6,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function PageContainer(props:Props) {
|
export default function PageContainer(props:Props) {
|
||||||
return <div className={`${styles.pagecontainer}`}>
|
return <div className={`${styles.pagecontainer} bg-background`}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
@ -20,7 +20,7 @@ const sequelize = new Sequelize({
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const dbSync = (async ()=> await sequelize.sync())().then(()=>{
|
const dbSync = (async ()=> await sequelize.sync({alter:true}))().then(()=>{
|
||||||
addUserScopes();
|
addUserScopes();
|
||||||
addUserPermsScopes();
|
addUserPermsScopes();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -20,9 +20,11 @@ export class Post extends Model<InferAttributes<Post>, InferCreationAttributes<P
|
|||||||
declare id: CreationOptional<number>;
|
declare id: CreationOptional<number>;
|
||||||
|
|
||||||
@Attribute(DataTypes.STRING)
|
@Attribute(DataTypes.STRING)
|
||||||
declare title:string
|
declare title:string;
|
||||||
@Attribute(DataTypes.STRING)
|
@Attribute(DataTypes.STRING)
|
||||||
declare content:string
|
declare content:string;
|
||||||
|
@Attribute(DataTypes.STRING)
|
||||||
|
declare description?:CreationOptional<string>;
|
||||||
|
|
||||||
// Date thingies
|
// Date thingies
|
||||||
@CreatedAt
|
@CreatedAt
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user