Updates
This commit is contained in:
parent
d1ae498a2f
commit
57f1ecf5c3
3
.gitignore
vendored
3
.gitignore
vendored
@ -38,4 +38,5 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
dsql-schema-to-typedef.json
|
dsql-schema-to-typedef.json
|
||||||
|
.data
|
||||||
@ -24,6 +24,7 @@ export type TinyMCEEditorProps<KeyType extends string> = {
|
|||||||
showLabel?: boolean;
|
showLabel?: boolean;
|
||||||
useParentCSS?: boolean;
|
useParentCSS?: boolean;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
refreshDependencyArray?: any[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,7 +33,7 @@ export type TinyMCEEditorProps<KeyType extends string> = {
|
|||||||
*/
|
*/
|
||||||
export default function TinyMCEEditor<KeyType extends string>({
|
export default function TinyMCEEditor<KeyType extends string>({
|
||||||
options,
|
options,
|
||||||
editorRef,
|
editorRef: passedEditorRef,
|
||||||
setEditor: passedSetEditor,
|
setEditor: passedSetEditor,
|
||||||
wrapperProps,
|
wrapperProps,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
@ -43,10 +44,12 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
showLabel,
|
showLabel,
|
||||||
useParentCSS,
|
useParentCSS,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
refreshDependencyArray,
|
||||||
}: TinyMCEEditorProps<KeyType>) {
|
}: TinyMCEEditorProps<KeyType>) {
|
||||||
const { tinyMCE } = useTinyMCE();
|
const { tinyMCE } = useTinyMCE();
|
||||||
|
|
||||||
const editorComponentRef = React.useRef<HTMLDivElement>(null);
|
const editorComponentRef = React.useRef<HTMLDivElement>(null);
|
||||||
|
const editorRef = passedEditorRef || React.useRef<Editor>(null);
|
||||||
|
|
||||||
const EDITOR_VALUE_CHANGE_TIMEOUT = 500;
|
const EDITOR_VALUE_CHANGE_TIMEOUT = 500;
|
||||||
|
|
||||||
@ -97,7 +100,10 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
"body { font-family:Helvetica,Arial,sans-serif; font-size:14px; background-color: transparent }",
|
"body { font-family:Helvetica,Arial,sans-serif; font-size:14px; background-color: transparent }",
|
||||||
init_instance_callback: (editor) => {
|
init_instance_callback: (editor) => {
|
||||||
setEditor(editor as any);
|
setEditor(editor as any);
|
||||||
if (editorRef) editorRef.current = editor as any;
|
if (editorRef) {
|
||||||
|
editorRef.current = editor;
|
||||||
|
passedSetEditor?.(editor);
|
||||||
|
}
|
||||||
if (defaultValue) editor.setContent(defaultValue);
|
if (defaultValue) editor.setContent(defaultValue);
|
||||||
setReady(true);
|
setReady(true);
|
||||||
|
|
||||||
@ -140,7 +146,15 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
: undefined;
|
: undefined;
|
||||||
instance?.remove();
|
instance?.remove();
|
||||||
};
|
};
|
||||||
}, [tinyMCE, themeReady, refresh]);
|
}, [tinyMCE, themeReady, refresh, ...(refreshDependencyArray || [])]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const instance = editorRef.current;
|
||||||
|
|
||||||
|
if (instance) {
|
||||||
|
instance.setContent(defaultValue || "");
|
||||||
|
}
|
||||||
|
}, [defaultValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -148,7 +162,7 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
className={twMerge(
|
className={twMerge(
|
||||||
"relative w-full [&_.tox-tinymce]:!border-none",
|
"relative w-full [&_.tox-tinymce]:!border-none",
|
||||||
"bg-background-light dark:bg-background-dark",
|
"bg-background-light dark:bg-background-dark",
|
||||||
wrapperWrapperProps?.className
|
wrapperWrapperProps?.className,
|
||||||
)}
|
)}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
console.log(`Input Detected`);
|
console.log(`Input Detected`);
|
||||||
@ -159,7 +173,7 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
className={twMerge(
|
className={twMerge(
|
||||||
"absolute z-10 -top-[7px] left-[10px] px-2 text-xs",
|
"absolute z-10 -top-[7px] left-[10px] px-2 text-xs",
|
||||||
"bg-background-light dark:bg-background-dark text-gray-500",
|
"bg-background-light dark:bg-background-dark text-gray-500",
|
||||||
"dark:text-white/80 rounded"
|
"dark:text-white/80 rounded",
|
||||||
)}
|
)}
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
>
|
>
|
||||||
@ -170,7 +184,7 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
{...borderProps}
|
{...borderProps}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"dark:border-white/30 p-0 pt-2",
|
"dark:border-white/30 p-0 pt-2",
|
||||||
borderProps?.className
|
borderProps?.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -183,7 +197,7 @@ export default function TinyMCEEditor<KeyType extends string>({
|
|||||||
}}
|
}}
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"bg-slate-200 dark:bg-slate-700 rounded-sm w-full",
|
"bg-slate-200 dark:bg-slate-700 rounded-sm w-full",
|
||||||
"twui-rte-wrapper"
|
"twui-rte-wrapper",
|
||||||
)}
|
)}
|
||||||
id={id}
|
id={id}
|
||||||
></div>
|
></div>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import React from "react";
|
|||||||
type Params = {
|
type Params = {
|
||||||
initialLoading?: boolean;
|
initialLoading?: boolean;
|
||||||
initialReady?: boolean;
|
initialReady?: boolean;
|
||||||
|
initialOpen?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UseStatusStatusType = {
|
export type UseStatusStatusType = {
|
||||||
@ -13,10 +14,11 @@ export type UseStatusStatusType = {
|
|||||||
export default function useStatus(params?: Params) {
|
export default function useStatus(params?: Params) {
|
||||||
const [refresh, setRefresh] = React.useState(0);
|
const [refresh, setRefresh] = React.useState(0);
|
||||||
const [loading, setLoading] = React.useState(
|
const [loading, setLoading] = React.useState(
|
||||||
params?.initialLoading || false
|
params?.initialLoading || false,
|
||||||
);
|
);
|
||||||
const [status, setStatus] = React.useState<UseStatusStatusType>({});
|
const [status, setStatus] = React.useState<UseStatusStatusType>({});
|
||||||
const [ready, setReady] = React.useState(params?.initialReady || false);
|
const [ready, setReady] = React.useState(params?.initialReady || false);
|
||||||
|
const [open, setOpen] = React.useState(params?.initialOpen || false);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
refresh,
|
refresh,
|
||||||
@ -27,5 +29,7 @@ export default function useStatus(params?: Params) {
|
|||||||
setStatus,
|
setStatus,
|
||||||
ready,
|
ready,
|
||||||
setReady,
|
setReady,
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import {
|
import React, {
|
||||||
AnchorHTMLAttributes,
|
AnchorHTMLAttributes,
|
||||||
ButtonHTMLAttributes,
|
ButtonHTMLAttributes,
|
||||||
ComponentProps,
|
ComponentProps,
|
||||||
@ -35,8 +35,8 @@ export type TWUIButtonProps = DetailedHTMLProps<
|
|||||||
AnchorHTMLAttributes<HTMLAnchorElement>,
|
AnchorHTMLAttributes<HTMLAnchorElement>,
|
||||||
HTMLAnchorElement
|
HTMLAnchorElement
|
||||||
>;
|
>;
|
||||||
beforeIcon?: TWUILucideIconName | JSX.Element;
|
beforeIcon?: TWUILucideIconName | React.JSX.Element;
|
||||||
afterIcon?: TWUILucideIconName | JSX.Element;
|
afterIcon?: TWUILucideIconName | React.JSX.Element;
|
||||||
buttonContentProps?: DetailedHTMLProps<
|
buttonContentProps?: DetailedHTMLProps<
|
||||||
HTMLAttributes<HTMLDivElement>,
|
HTMLAttributes<HTMLDivElement>,
|
||||||
HTMLDivElement
|
HTMLDivElement
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { ComponentProps } from "react";
|
import React, { ComponentProps, useRef } from "react";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import MarkdownEditorPreviewComponent from "./MarkdownEditorPreviewComponent";
|
import MarkdownEditorPreviewComponent from "./MarkdownEditorPreviewComponent";
|
||||||
import MarkdownEditorComponent from "./MarkdownEditorComponent";
|
import MarkdownEditorComponent from "./MarkdownEditorComponent";
|
||||||
@ -6,6 +6,7 @@ import MarkdownEditorSelectorButtons from "./MarkdownEditorSelectorButtons";
|
|||||||
import Row from "../../layout/Row";
|
import Row from "../../layout/Row";
|
||||||
import Stack from "../../layout/Stack";
|
import Stack from "../../layout/Stack";
|
||||||
import AceEditor from "../../editors/AceEditor";
|
import AceEditor from "../../editors/AceEditor";
|
||||||
|
import useStatus from "../../hooks/useStatus";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value?: string;
|
value?: string;
|
||||||
@ -29,17 +30,30 @@ export default function MarkdownEditor({
|
|||||||
const [value, setValue] = React.useState<any>(existingValue || ``);
|
const [value, setValue] = React.useState<any>(existingValue || ``);
|
||||||
|
|
||||||
const [sideBySide, setSideBySide] = React.useState(
|
const [sideBySide, setSideBySide] = React.useState(
|
||||||
defaultSideBySide || false
|
defaultSideBySide || false,
|
||||||
);
|
);
|
||||||
const [preview, setPreview] = React.useState(false);
|
const [preview, setPreview] = React.useState(false);
|
||||||
|
const { refresh, setRefresh } = useStatus();
|
||||||
|
const updatingFromExtValueRef = useRef(false);
|
||||||
|
|
||||||
const maxHeight = existingMaxHeight || "600px";
|
const maxHeight = existingMaxHeight || "600px";
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
if (updatingFromExtValueRef.current) return;
|
||||||
setExistingValue?.(value);
|
setExistingValue?.(value);
|
||||||
changeHandler?.(value);
|
changeHandler?.(value);
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!existingValue) return;
|
||||||
|
updatingFromExtValueRef.current = true;
|
||||||
|
setValue(existingValue);
|
||||||
|
setTimeout(() => {
|
||||||
|
updatingFromExtValueRef.current = false;
|
||||||
|
setRefresh((prev) => prev + 1);
|
||||||
|
}, 500);
|
||||||
|
}, [existingValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className="w-full items-stretch">
|
<Stack className="w-full items-stretch">
|
||||||
{!noToggleButtons && (
|
{!noToggleButtons && (
|
||||||
@ -52,13 +66,14 @@ export default function MarkdownEditor({
|
|||||||
<Row
|
<Row
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
`w-full grid xl:grid-cols-2 gap-6 max-h-[${maxHeight}]`,
|
`w-full grid xl:grid-cols-2 gap-6 max-h-[${maxHeight}]`,
|
||||||
"overflow-auto"
|
"overflow-auto",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MarkdownEditorComponent
|
<MarkdownEditorComponent
|
||||||
setValue={setValue}
|
setValue={setValue}
|
||||||
value={value}
|
value={value}
|
||||||
maxHeight={maxHeight}
|
maxHeight={maxHeight}
|
||||||
|
refreshDepArr={[refresh]}
|
||||||
{...editorProps}
|
{...editorProps}
|
||||||
/>
|
/>
|
||||||
<MarkdownEditorPreviewComponent
|
<MarkdownEditorPreviewComponent
|
||||||
@ -80,6 +95,7 @@ export default function MarkdownEditor({
|
|||||||
setValue={setValue}
|
setValue={setValue}
|
||||||
value={value}
|
value={value}
|
||||||
maxHeight={maxHeight}
|
maxHeight={maxHeight}
|
||||||
|
refreshDepArr={[refresh]}
|
||||||
{...editorProps}
|
{...editorProps}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -25,11 +25,7 @@ export const work = {
|
|||||||
description: "Mortgage Broker in Utah",
|
description: "Mortgage Broker in Utah",
|
||||||
href: "https://summitlending.com",
|
href: "https://summitlending.com",
|
||||||
image: "/images/work/devops/server-management.png",
|
image: "/images/work/devops/server-management.png",
|
||||||
metrics: [
|
metrics: ["500+ leads/month", "99.99% uptime"],
|
||||||
"500+ leads/month",
|
|
||||||
"~10 deploys/week",
|
|
||||||
"99.99% uptime",
|
|
||||||
],
|
|
||||||
technologies: [
|
technologies: [
|
||||||
"Next JS",
|
"Next JS",
|
||||||
"Tailwind CSS",
|
"Tailwind CSS",
|
||||||
@ -142,6 +138,13 @@ export const work = {
|
|||||||
"Azure",
|
"Azure",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Bun SQLite",
|
||||||
|
description:
|
||||||
|
"A schema-driven SQLite manager for Bun, featuring automatic schema synchronization, type-safe CRUD operations, vector embedding support, and TypeScript type definition generation.",
|
||||||
|
href: "https://git.tben.me/Moduletrace/bun-sqlite",
|
||||||
|
technologies: ["SQLite", "Bun", "Shell", "Typescript"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Turbo Sync",
|
title: "Turbo Sync",
|
||||||
description:
|
description:
|
||||||
|
|||||||
159
scripts/substack/save-all-notes.ts
Normal file
159
scripts/substack/save-all-notes.ts
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
const articles_objects = [];
|
||||||
|
|
||||||
|
const substack_href = "https://substack.com/@benjamintoby";
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
async function sleep(wait) {
|
||||||
|
return new Promise((res) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
res(true);
|
||||||
|
}, wait);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function grabContentHeight() {
|
||||||
|
return document.querySelector(
|
||||||
|
"div[style='max-width: 568px;']",
|
||||||
|
// @ts-ignore
|
||||||
|
)?.offsetHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scrollToEnd() {
|
||||||
|
let last_content_height = grabContentHeight();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
window.scrollTo({
|
||||||
|
top: document.body.scrollHeight,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
|
||||||
|
await sleep(5000);
|
||||||
|
|
||||||
|
const current_content_height = grabContentHeight();
|
||||||
|
|
||||||
|
if (current_content_height > last_content_height) {
|
||||||
|
last_content_height = current_content_height;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await scrollToEnd();
|
||||||
|
|
||||||
|
const articles = Array.from(
|
||||||
|
document.querySelectorAll("div[role='article']"),
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Handling ${articles.length} Articles ...`);
|
||||||
|
|
||||||
|
for (let i = 0; i < articles.length; i++) {
|
||||||
|
let present_articles = Array.from(
|
||||||
|
document.querySelectorAll("div[role='article']"),
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Found ${present_articles.length} Present Articles!`);
|
||||||
|
|
||||||
|
while (i > present_articles.length - 1) {
|
||||||
|
window.scrollTo({
|
||||||
|
top: document.body.scrollHeight,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Searching for Article #${i} ...`);
|
||||||
|
|
||||||
|
await sleep(5000);
|
||||||
|
|
||||||
|
present_articles = Array.from(
|
||||||
|
document.querySelectorAll("div[role='article']"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const article = present_articles[i];
|
||||||
|
|
||||||
|
console.log(`Handling Article #${i} ...`);
|
||||||
|
|
||||||
|
const content_div = article.querySelector(`.FeedProseMirror`);
|
||||||
|
const date_link = Array.from(article.querySelectorAll("a")).find((a) =>
|
||||||
|
Boolean(a.getAttribute("title")),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!content_div) continue;
|
||||||
|
|
||||||
|
const content_div_first_paragraph = content_div.querySelector(`p`);
|
||||||
|
|
||||||
|
if (!content_div_first_paragraph) continue;
|
||||||
|
|
||||||
|
let window_url = window.location.href;
|
||||||
|
const initial_text_content = content_div.textContent;
|
||||||
|
|
||||||
|
const article_object = {
|
||||||
|
title: initial_text_content,
|
||||||
|
content: initial_text_content,
|
||||||
|
html: content_div.innerHTML,
|
||||||
|
images: [],
|
||||||
|
date: date_link?.getAttribute("title"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const article_images = Array.from(
|
||||||
|
article.querySelectorAll("picture img"),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (article_images?.[0]) {
|
||||||
|
for (let img = 0; img < article_images.length; img++) {
|
||||||
|
if (img > 0) {
|
||||||
|
const article_image = article_images[img];
|
||||||
|
// @ts-ignore
|
||||||
|
const article_image_srcset = article_image.srcset;
|
||||||
|
const largest_image = article_image_srcset
|
||||||
|
.split(` `)
|
||||||
|
.at(-2);
|
||||||
|
// @ts-ignore
|
||||||
|
article_object.images.push(largest_image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const more_content = Array.from(article.querySelectorAll("a"))
|
||||||
|
.find((el) => el.textContent.includes("See more"))
|
||||||
|
?.click();
|
||||||
|
|
||||||
|
await sleep(2000);
|
||||||
|
|
||||||
|
let new_window_url = window.location.href;
|
||||||
|
|
||||||
|
if (new_window_url === window_url) {
|
||||||
|
const new_article_content =
|
||||||
|
article.querySelector(`.FeedProseMirror`);
|
||||||
|
|
||||||
|
if (new_article_content) {
|
||||||
|
article_object.content = new_article_content.textContent;
|
||||||
|
article_object.html = new_article_content.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
articles_objects.push(article_object);
|
||||||
|
} else {
|
||||||
|
const text_sample = content_div_first_paragraph.textContent;
|
||||||
|
const target_content_div = Array.from(
|
||||||
|
document.querySelectorAll(".ProseMirror.FeedProseMirror"),
|
||||||
|
).find((el) => el.textContent.includes(text_sample));
|
||||||
|
|
||||||
|
if (target_content_div) {
|
||||||
|
article_object.content = target_content_div.textContent;
|
||||||
|
article_object.html = target_content_div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
articles_objects.push(article_object);
|
||||||
|
|
||||||
|
window.history.back();
|
||||||
|
await sleep(2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
console.log(articles_objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
Loading…
Reference in New Issue
Block a user