fucking shit working properly now. prod works.
TODO; Fix asset loading :3
This commit is contained in:
142
package-lock.json
generated
142
package-lock.json
generated
@@ -25,6 +25,7 @@
|
||||
"emoji-picker-react": "^4.12.2",
|
||||
"gray-matter": "^4.0.3",
|
||||
"highlight.js": "^11.11.1",
|
||||
"isomorphic-dompurify": "^2.25.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"marked": "^12.0.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
@@ -1212,19 +1213,6 @@
|
||||
"parse5": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsdom/node_modules/parse5": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/json5": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
|
||||
@@ -5183,6 +5171,92 @@
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/isomorphic-dompurify": {
|
||||
"version": "2.25.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-dompurify/-/isomorphic-dompurify-2.25.0.tgz",
|
||||
"integrity": "sha512-bcpJzu9DOjN21qaCVpcoCwUX1ytpvA6EFqCK5RNtPg5+F0Jz9PX50jl6jbEicBNeO87eDDfC7XtPs4zjDClZJg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dompurify": "^3.2.6",
|
||||
"jsdom": "^26.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/isomorphic-dompurify/node_modules/agent-base": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
|
||||
"integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/isomorphic-dompurify/node_modules/https-proxy-agent": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.2",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/isomorphic-dompurify/node_modules/jsdom": {
|
||||
"version": "26.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",
|
||||
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssstyle": "^4.2.1",
|
||||
"data-urls": "^5.0.0",
|
||||
"decimal.js": "^10.5.0",
|
||||
"html-encoding-sniffer": "^4.0.0",
|
||||
"http-proxy-agent": "^7.0.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"nwsapi": "^2.2.16",
|
||||
"parse5": "^7.2.1",
|
||||
"rrweb-cssom": "^0.8.0",
|
||||
"saxes": "^6.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^5.1.1",
|
||||
"w3c-xmlserializer": "^5.0.0",
|
||||
"webidl-conversions": "^7.0.0",
|
||||
"whatwg-encoding": "^3.1.1",
|
||||
"whatwg-mimetype": "^4.0.0",
|
||||
"whatwg-url": "^14.1.1",
|
||||
"ws": "^8.18.0",
|
||||
"xml-name-validator": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"canvas": "^3.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"canvas": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/isomorphic-dompurify/node_modules/tough-cookie": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
|
||||
"integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"tldts": "^6.1.32"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/iterator.prototype": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
|
||||
@@ -5327,18 +5401,6 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/parse5": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom/node_modules/rrweb-cssom": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
|
||||
@@ -6314,6 +6376,18 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
@@ -8246,6 +8320,24 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "6.1.86",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
|
||||
"integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tldts-core": "^6.1.86"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts-core": {
|
||||
"version": "6.1.86",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
|
||||
"integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"emoji-picker-react": "^4.12.2",
|
||||
"gray-matter": "^4.0.3",
|
||||
"highlight.js": "^11.11.1",
|
||||
"isomorphic-dompurify": "^2.25.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"marked": "^12.0.0",
|
||||
"monaco-editor": "^0.52.2",
|
||||
|
||||
12
src/app/admin/MonacoEditor.tsx
Normal file
12
src/app/admin/MonacoEditor.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import Editor from "@monaco-editor/react";
|
||||
|
||||
export default function MonacoEditorWrapper(props: any) {
|
||||
return (
|
||||
<Editor
|
||||
height="600px"
|
||||
defaultLanguage="markdown"
|
||||
defaultValue={props.defaultValue || ""}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
'use client';
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/*********************************************
|
||||
* This is the main admin page for the blog.
|
||||
*
|
||||
* Written Jun 19 2025
|
||||
* Rewritten fucking 15 times cause of the
|
||||
* fucking
|
||||
* typescript linter.
|
||||
*
|
||||
* If any Issues about "Window" (For Monaco) pop up. Its not my fucking fault
|
||||
**********************************************/
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
@@ -12,17 +18,21 @@ import Link from 'next/link';
|
||||
import { marked } from 'marked';
|
||||
import hljs from 'highlight.js';
|
||||
import matter from 'gray-matter';
|
||||
import dynamic from 'next/dynamic';
|
||||
import dynamicImport from 'next/dynamic';
|
||||
import { Theme } from 'emoji-picker-react';
|
||||
import '../highlight-github.css';
|
||||
import MonacoEditor from '@monaco-editor/react';
|
||||
import { initVimMode, VimMode } from 'monaco-vim';
|
||||
const MonacoEditor = dynamicImport(() => import('./MonacoEditor'), { ssr: false });
|
||||
// Import monaco-vim only on client side
|
||||
let initVimMode: any = null;
|
||||
let VimMode: any = null;
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
const monacoVim = require('monaco-vim');
|
||||
initVimMode = monacoVim.initVimMode;
|
||||
VimMode = monacoVim.VimMode;
|
||||
}
|
||||
import '@fontsource/jetbrains-mono';
|
||||
|
||||
// @ts-ignore-next-line
|
||||
// eslint-disable-next-line
|
||||
// If you want, you can move this to a global.d.ts file
|
||||
// declare module 'monaco-vim';
|
||||
|
||||
interface Post {
|
||||
slug: string;
|
||||
@@ -55,7 +65,7 @@ interface Post {
|
||||
|
||||
type Node = Post | Folder;
|
||||
|
||||
const EmojiPicker = dynamic(() => import('emoji-picker-react'), { ssr: false });
|
||||
const EmojiPicker = dynamicImport(() => import('emoji-picker-react'), { ssr: false });
|
||||
|
||||
// Patch marked renderer to always add 'hljs' class to code blocks
|
||||
const renderer = new marked.Renderer();
|
||||
@@ -89,12 +99,7 @@ export default function AdminPage() {
|
||||
});
|
||||
const [showManageContent, setShowManageContent] = useState(false);
|
||||
const [managePath, setManagePath] = useState<string[]>([]);
|
||||
const [pinned, setPinned] = useState<string[]>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return JSON.parse(localStorage.getItem('pinnedPosts') || '[]');
|
||||
}
|
||||
return [];
|
||||
});
|
||||
const [pinned, setPinned] = useState<string[]>([]);
|
||||
const [pinFeedback, setPinFeedback] = useState<string | null>(null);
|
||||
const [showChangePassword, setShowChangePassword] = useState(false);
|
||||
const [changePwOld, setChangePwOld] = useState('');
|
||||
@@ -104,12 +109,7 @@ export default function AdminPage() {
|
||||
const [previewHtml, setPreviewHtml] = useState('');
|
||||
const [editingPost, setEditingPost] = useState<{ slug: string, path: string } | null>(null);
|
||||
const [isDocker, setIsDocker] = useState<boolean>(false);
|
||||
const [rememberExportChoice, setRememberExportChoice] = useState<boolean>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('rememberExportChoice') === 'true';
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const [rememberExportChoice, setRememberExportChoice] = useState<boolean>(false);
|
||||
const [lastExportChoice, setLastExportChoice] = useState<string | null>(null);
|
||||
const [emojiPickerOpen, setEmojiPickerOpen] = useState<string | null>(null);
|
||||
const [emojiPickerAnchor, setEmojiPickerAnchor] = useState<HTMLElement | null>(null);
|
||||
@@ -536,15 +536,7 @@ export default function AdminPage() {
|
||||
};
|
||||
|
||||
function handleExportTarball() {
|
||||
// Check if we should use the remembered choice
|
||||
if (rememberExportChoice && lastExportChoice) {
|
||||
if (lastExportChoice === 'docker') {
|
||||
exportFromEndpoint('/api/admin/export');
|
||||
} else if (lastExportChoice === 'local') {
|
||||
exportFromEndpoint('/api/admin/exportlocal');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
// Create popup modal
|
||||
const modal = document.createElement('div');
|
||||
@@ -637,6 +629,7 @@ export default function AdminPage() {
|
||||
}
|
||||
|
||||
function exportFromEndpoint(endpoint: string) {
|
||||
if (typeof window === 'undefined') return;
|
||||
fetch(endpoint)
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw new Error('Export failed');
|
||||
@@ -660,6 +653,15 @@ export default function AdminPage() {
|
||||
setLastExportChoice(null);
|
||||
};
|
||||
|
||||
// Hydrate pinned, rememberExportChoice, lastExportChoice from localStorage on client only
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
setPinned(JSON.parse(localStorage.getItem('pinnedPosts') || '[]'));
|
||||
setRememberExportChoice(localStorage.getItem('rememberExportChoice') === 'true');
|
||||
setLastExportChoice(localStorage.getItem('lastExportChoice'));
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Simple and reliable emoji update handler
|
||||
const handleSetFolderEmoji = async (folderPath: string, emoji: string) => {
|
||||
try {
|
||||
@@ -772,7 +774,7 @@ export default function AdminPage() {
|
||||
|
||||
// Attach/detach Vim mode when vimMode changes
|
||||
useEffect(() => {
|
||||
if (vimMode && monacoRef.current) {
|
||||
if (vimMode && monacoRef.current && initVimMode) {
|
||||
// @ts-ignore
|
||||
vimInstanceRef.current = initVimMode(monacoRef.current, vimStatusRef.current);
|
||||
} else if (vimInstanceRef.current) {
|
||||
@@ -917,7 +919,7 @@ export default function AdminPage() {
|
||||
<path d="M75.8578 99.1263C73.4721 100.274 70.6219 99.7885 68.75 97.9166C71.0564 100.223 75 98.5895 75 95.3278V4.67213C75 1.41039 71.0564 -0.223106 68.75 2.08329C70.6219 0.211402 73.4721 -0.273666 75.8578 0.873633L96.4587 10.7807C98.6234 11.8217 100 14.0112 100 16.4132V83.5871C100 85.9891 98.6234 88.1786 96.4586 89.2196L75.8578 99.1263Z" fill="#1F9CF0"/>
|
||||
</g>
|
||||
<g style={{ mixBlendMode: 'overlay' }} opacity="0.25">
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M70.8511 99.3171C72.4261 99.9306 74.2221 99.8913 75.8117 99.1264L96.4 89.2197C98.5634 88.1787 99.9392 85.9892 99.9392 83.5871V16.4133C99.9392 14.0112 98.5635 11.8217 96.4001 10.7807L75.8117 0.873695C73.7255 -0.13019 71.2838 0.115699 69.4527 1.44688C69.1912 1.63705 68.942 1.84937 68.7082 2.08335L29.2943 38.0414L12.1264 25.0096C10.5283 23.7964 8.29285 23.8959 6.80855 25.246L1.30225 30.2548C-0.513334 31.9064 -0.515415 34.7627 1.29775 36.4169L16.1863 50L1.29775 63.5832C-0.515415 65.2374 -0.513334 68.0937 1.30225 69.7452L6.80855 74.754C8.29285 76.1042 10.5283 76.2036 12.1264 74.9905L29.2943 61.9586L68.7082 97.9167C69.3317 98.5405 70.0638 99.0104 70.8511 99.3171ZM74.9544 27.2989L45.0483 50L74.9544 72.7012V27.2989Z" fill="url(#paint0_linear)"/>
|
||||
<path fillRule="evenodd" clipRule="evenodd" d="M70.8511 99.3171C72.4261 99.9306 74.2221 99.8913 75.8117 99.1264L96.4 89.2197C98.5634 88.1787 99.9392 85.9892 99.9392 83.5871V16.4133C99.9392 14.0112 98.5635 11.8217 96.4001 10.7807L75.8117 0.873695C73.7255 -0.13019 71.2838 0.115699 69.4527 1.44688C69.1912 1.63705 68.942 1.84937 68.7082 2.08335L29.2943 38.0414L12.1264 25.0096C10.5283 23.7964 8.29285 23.8959 6.80855 25.246L1.30225 30.2548C-0.513334 31.9064 -0.515415 34.7627 1.29775 36.4169L16.1863 50L1.29775 63.5832C-0.515415 65.2374 -0.513334 68.0937 1.29775 69.7452L6.80855 74.754C8.29285 76.1042 10.5283 76.2036 12.1264 74.9905L29.2943 61.9586L68.7082 97.9167C69.3317 98.5405 70.0638 99.0104 70.8511 99.3171ZM74.9544 27.2989L45.0483 50L74.9544 72.7012V27.2989Z" fill="url(#paint0_linear)"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
@@ -1182,7 +1184,7 @@ export default function AdminPage() {
|
||||
height="100%"
|
||||
defaultLanguage="markdown"
|
||||
value={newPost.content}
|
||||
onChange={(value) => setNewPost({ ...newPost, content: value || '' })}
|
||||
onChange={(value?: string) => setNewPost({ ...newPost, content: value || '' })}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
wordWrap: 'on',
|
||||
@@ -1193,7 +1195,7 @@ export default function AdminPage() {
|
||||
automaticLayout: true,
|
||||
fontFamily: 'JetBrains Mono, monospace',
|
||||
}}
|
||||
onMount={(editor) => {
|
||||
onMount={(editor: any) => {
|
||||
monacoRef.current = editor;
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -5,8 +5,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import matter from 'gray-matter';
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import createDOMPurify from 'isomorphic-dompurify';
|
||||
import hljs from 'highlight.js';
|
||||
import { getPostsDirectory } from '@/lib/postsDirectory';
|
||||
|
||||
@@ -106,10 +105,8 @@ async function getPostByPath(filePath: string, relPath: string, pinnedData: { pi
|
||||
|
||||
let processedContent = '';
|
||||
try {
|
||||
const rawHtml = marked.parse(content);
|
||||
const window = new JSDOM('').window;
|
||||
const purify = DOMPurify(window);
|
||||
processedContent = purify.sanitize(rawHtml as string, {
|
||||
const rawHtml = marked.parse(content) as string;
|
||||
processedContent = createDOMPurify.sanitize(rawHtml, {
|
||||
ALLOWED_TAGS: [
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
||||
'p', 'a', 'ul', 'ol', 'li', 'blockquote',
|
||||
@@ -123,7 +120,7 @@ async function getPostByPath(filePath: string, relPath: string, pinnedData: { pi
|
||||
'src', 'alt', 'title', 'width', 'height',
|
||||
'frameborder', 'allowfullscreen'
|
||||
],
|
||||
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i
|
||||
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+\.\-]+(?:[^a-z+\.\-:]|$))/i
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(`Error processing markdown for ${relPath}:`, err);
|
||||
|
||||
Reference in New Issue
Block a user