cecks
This commit is contained in:
10
package-lock.json
generated
10
package-lock.json
generated
@@ -20,6 +20,7 @@
|
||||
"dompurify": "^3.0.9",
|
||||
"electron": "^29.1.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"highlight.js": "^11.11.1",
|
||||
"jsdom": "^24.0.0",
|
||||
"marked": "^12.0.0",
|
||||
"next": "14.1.0",
|
||||
@@ -3897,6 +3898,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight.js": {
|
||||
"version": "11.11.1",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
|
||||
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||
|
||||
15
package.json
15
package.json
@@ -11,14 +11,19 @@
|
||||
"electron-dev": "concurrently \"npm run dev\" \"npm run electron\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/react": "^18.2.61",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"bcrypt": "^5.0.2",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"chokidar": "^3.6.0",
|
||||
"date-fns": "^3.6.0",
|
||||
"dompurify": "^3.0.9",
|
||||
"electron": "^29.1.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
"highlight.js": "^11.11.1",
|
||||
"jsdom": "^24.0.0",
|
||||
"marked": "^12.0.0",
|
||||
"next": "14.1.0",
|
||||
@@ -26,19 +31,15 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3",
|
||||
"date-fns": "^3.6.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bcrypt": "^5.0.2"
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/jsdom": "^21.1.6",
|
||||
"@types/marked": "^5.0.2",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "14.1.0",
|
||||
"@types/bcryptjs": "^2.4.6"
|
||||
"eslint-config-next": "14.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,14 @@ print("Hello, world!")
|
||||
|
||||
```
|
||||
|
||||
### Code Blog (test)
|
||||
|
||||
```Python
|
||||
def hello():
|
||||
return true;
|
||||
```
|
||||
|
||||
|
||||
### Code Block (indented)
|
||||
```Python
|
||||
def indented_example():
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import Link from 'next/link';
|
||||
import { marked } from 'marked';
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
interface Post {
|
||||
slug: string;
|
||||
@@ -67,6 +69,7 @@ export default function AdminPage() {
|
||||
const [changePwNew, setChangePwNew] = useState('');
|
||||
const [changePwConfirm, setChangePwConfirm] = useState('');
|
||||
const [changePwFeedback, setChangePwFeedback] = useState<string | null>(null);
|
||||
const [previewHtml, setPreviewHtml] = useState('');
|
||||
const router = useRouter();
|
||||
const usernameRef = useRef<HTMLInputElement>(null);
|
||||
const passwordRef = useRef<HTMLInputElement>(null);
|
||||
@@ -85,6 +88,20 @@ export default function AdminPage() {
|
||||
useEffect(() => {
|
||||
localStorage.setItem('pinnedPosts', JSON.stringify(pinned));
|
||||
}, [pinned]);
|
||||
useEffect(() => {
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
highlight: function(code: string, lang: string) {
|
||||
if (lang && hljs.getLanguage(lang)) {
|
||||
return hljs.highlight(code, { language: lang }).value;
|
||||
} else {
|
||||
return hljs.highlightAuto(code).value;
|
||||
}
|
||||
}
|
||||
} as any);
|
||||
setPreviewHtml(marked.parse(newPost.content || '') as string);
|
||||
}, [newPost.content]);
|
||||
|
||||
const loadContent = async () => {
|
||||
try {
|
||||
@@ -659,13 +676,21 @@ export default function AdminPage() {
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">Content (Markdown)</label>
|
||||
<textarea
|
||||
value={newPost.content}
|
||||
onChange={(e) => setNewPost({ ...newPost, content: e.target.value })}
|
||||
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 font-mono"
|
||||
rows={10}
|
||||
required
|
||||
/>
|
||||
<div className="flex flex-col md:flex-row gap-4 mt-1">
|
||||
{/* Markdown Editor */}
|
||||
<textarea
|
||||
value={newPost.content}
|
||||
onChange={(e) => setNewPost({ ...newPost, content: e.target.value })}
|
||||
className="w-full md:w-1/2 rounded-md border border-gray-300 px-3 py-2 font-mono min-h-[200px] md:min-h-[300px] resize-y"
|
||||
rows={10}
|
||||
required
|
||||
/>
|
||||
{/* Live Markdown Preview */}
|
||||
<div className="w-full md:w-1/2 p-4 border rounded bg-gray-50 overflow-auto min-h-[200px] md:min-h-[300px]">
|
||||
<div className="text-xs text-gray-500 mb-2">Live Preview</div>
|
||||
<div className="prose prose-sm max-w-none" dangerouslySetInnerHTML={{ __html: previewHtml }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
@@ -7,9 +7,26 @@ import matter from 'gray-matter';
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
const postsDirectory = path.join(process.cwd(), 'posts');
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.code = (code, infostring, escaped) => {
|
||||
const lang = (infostring || '').match(/\S*/)?.[0];
|
||||
const highlighted = lang && hljs.getLanguage(lang)
|
||||
? hljs.highlight(code, { language: lang }).value
|
||||
: hljs.highlightAuto(code).value;
|
||||
const langClass = lang ? `language-${lang}` : '';
|
||||
return `<pre><code class="hljs ${langClass}">${highlighted}</code></pre>`;
|
||||
};
|
||||
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
renderer,
|
||||
});
|
||||
|
||||
// Function to get file creation date
|
||||
function getFileCreationDate(filePath: string): Date {
|
||||
const stats = fs.statSync(filePath);
|
||||
@@ -25,12 +42,6 @@ async function getPostBySlug(slug: string) {
|
||||
|
||||
let processedContent = '';
|
||||
try {
|
||||
// Configure marked options
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true
|
||||
});
|
||||
|
||||
// Convert markdown to HTML
|
||||
const rawHtml = marked.parse(content);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import matter from 'gray-matter';
|
||||
import { marked } from 'marked';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
const postsDirectory = path.join(process.cwd(), 'posts');
|
||||
|
||||
@@ -26,6 +27,22 @@ function getFileCreationDate(filePath: string): Date {
|
||||
return stats.birthtime ?? stats.mtime;
|
||||
}
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.code = (code, infostring, escaped) => {
|
||||
const lang = (infostring || '').match(/\S*/)?.[0];
|
||||
const highlighted = lang && hljs.getLanguage(lang)
|
||||
? hljs.highlight(code, { language: lang }).value
|
||||
: hljs.highlightAuto(code).value;
|
||||
const langClass = lang ? `language-${lang}` : '';
|
||||
return `<pre><code class="hljs ${langClass}">${highlighted}</code></pre>`;
|
||||
};
|
||||
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
renderer,
|
||||
});
|
||||
|
||||
async function getPostByPath(filePath: string, relPath: string) {
|
||||
const fileContents = fs.readFileSync(filePath, 'utf8');
|
||||
const { data, content } = matter(fileContents);
|
||||
@@ -33,10 +50,6 @@ async function getPostByPath(filePath: string, relPath: string) {
|
||||
|
||||
let processedContent = '';
|
||||
try {
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true
|
||||
});
|
||||
const rawHtml = marked.parse(content);
|
||||
const window = new JSDOM('').window;
|
||||
const purify = DOMPurify(window);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import './highlight-github.css';
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -24,4 +25,20 @@ body {
|
||||
|
||||
.prose a:hover {
|
||||
color: #1d4ed8;
|
||||
}
|
||||
|
||||
/* Ensure highlight.js styles override Tailwind prose for code blocks */
|
||||
.prose pre code.hljs {
|
||||
background: inherit !important;
|
||||
color: inherit !important;
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
/* Remove Tailwind's border radius if you want the highlight.js look */
|
||||
border-radius: 0;
|
||||
}
|
||||
.prose pre {
|
||||
background: #292d3e !important;
|
||||
color: #fff !important;
|
||||
border-radius: 0.5em;
|
||||
overflow-x: auto;
|
||||
}
|
||||
12
src/app/highlight-github.css
Normal file
12
src/app/highlight-github.css
Normal file
@@ -0,0 +1,12 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||
Theme: GitHub
|
||||
Description: Light theme as seen on github.com
|
||||
Author: github.com
|
||||
Maintainer: @Hirse
|
||||
Updated: 2021-05-15
|
||||
|
||||
Outdated base version: https://github.com/primer/github-syntax-light
|
||||
Current colors taken from GitHub's CSS
|
||||
*/.hljs{color:#24292e;background:#fff}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#d73a49}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#6f42c1}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#005cc5}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#032f62}.hljs-built_in,.hljs-symbol{color:#e36209}.hljs-code,.hljs-comment,.hljs-formula{color:#6a737d}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#22863a}.hljs-subst{color:#24292e}.hljs-section{color:#005cc5;font-weight:700}.hljs-bullet{color:#735c0f}.hljs-emphasis{color:#24292e;font-style:italic}.hljs-strong{color:#24292e;font-weight:700}.hljs-addition{color:#22863a;background-color:#f0fff4}.hljs-deletion{color:#b31d28;background-color:#ffeef0}.hljs-string, .hljs-meta .hljs-string {
|
||||
color: #b2d485 !important;
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import DOMPurify from 'dompurify';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import chokidar from 'chokidar';
|
||||
import type { FSWatcher } from 'chokidar';
|
||||
import hljs from 'highlight.js';
|
||||
|
||||
export interface Post {
|
||||
slug: string;
|
||||
@@ -26,6 +27,22 @@ function getFileCreationDate(filePath: string): Date {
|
||||
return stats.birthtime;
|
||||
}
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
renderer.code = (code, infostring, escaped) => {
|
||||
const lang = (infostring || '').match(/\S*/)?.[0];
|
||||
const highlighted = lang && hljs.getLanguage(lang)
|
||||
? hljs.highlight(code, { language: lang }).value
|
||||
: hljs.highlightAuto(code).value;
|
||||
const langClass = lang ? `language-${lang}` : '';
|
||||
return `<pre><code class="hljs ${langClass}">${highlighted}</code></pre>`;
|
||||
};
|
||||
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
renderer,
|
||||
});
|
||||
|
||||
export async function getPostBySlug(slug: string): Promise<Post> {
|
||||
const realSlug = slug.replace(/\.md$/, '');
|
||||
const fullPath = path.join(postsDirectory, `${realSlug}.md`);
|
||||
@@ -35,10 +52,6 @@ export async function getPostBySlug(slug: string): Promise<Post> {
|
||||
|
||||
let processedContent = '';
|
||||
try {
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
breaks: true
|
||||
});
|
||||
const rawHtml = marked.parse(content);
|
||||
const window = new JSDOM('').window;
|
||||
const purify = DOMPurify(window);
|
||||
|
||||
Reference in New Issue
Block a user