-
Notifications
You must be signed in to change notification settings - Fork 6
Self-hosted editor #636
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Self-hosted editor #636
Conversation
📝 WalkthroughWalkthroughThis pull request introduces a comprehensive internal publishing workflow for the self-hosted app. It adds new publish-related components, hooks, and utilities while updating configuration and routing to support an internal Changes
Sequence DiagramsequenceDiagram
participant User as User
participant UI as PublishEditor UI
participant State as usePublishState<br/>(localStorage)
participant Editor as usePublishEditor<br/>(TipTap)
participant Mutation as usePublishPost<br/>(React Query)
participant Hive as Hive Blockchain
User->>UI: Enter title, content, tags
UI->>State: setTitleState, setContentState, setTagsState
State->>State: Validate & persist to localStorage
User->>Editor: Edit content in TipTap
Editor->>State: Convert HTML to Markdown on change
User->>UI: Click Publish button
UI->>Mutation: Call usePublishPost with title, content, tags
Mutation->>Mutation: Validate inputs & generate permlink
Mutation->>Hive: Broadcast comment operation (post)
Hive-->>Mutation: Success with permlink
Mutation->>State: clearAll (clear draft from localStorage)
Mutation->>User: Navigate to /blog?filter=posts
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/self-hosted/src/features/auth/components/create-post-button.tsx (1)
12-29:⚠️ Potential issue | 🟠 MajorUse
hrefprop for external URLs instead ofto; whencreatePostUrlis external,towon't work correctly.TanStack Link supports external URLs, but requires the
hrefprop (notto). SincecreatePostUrldefaults to"https://ecency.com/submit"and is configurable, it may be external. The code should detect external URLs and usehrefinstead ofto:const createPostUrl = InstanceConfigManager.getConfigValue( ({ configuration }) => configuration.general.createPostUrl || "https://ecency.com/submit", ); const isExternal = /^https?:\/\//i.test(createPostUrl); // Only show for blog owner when auth is enabled if (!isAuthEnabled || !isBlogOwner) { return null; } return ( <Link {...(isExternal ? { href: createPostUrl } : { to: createPostUrl })} {...(isExternal && { target: "_blank", rel: "noopener noreferrer" })} className="fixed bottom-6 right-30 z-50 px-4 py-2 flex items-center text-sm !no-underline rounded-full border border-gray-400 dark:border-gray-600 !font-serif" > <UilPen className="w-4 h-4" /> <span className="hidden sm:block">{t("create_post")}</span> </Link> );
🤖 Fix all issues with AI agents
In `@apps/self-hosted/package.json`:
- Around line 39-54: Remove the unnecessary devDependency "@types/marked"
(marked ships its own types) by deleting it from devDependencies, and resolve
the speakingurl/type mismatch by either downgrading the "speakingurl" dependency
to a version that matches "@types/speakingurl@13.0.6" (e.g., speakingurl@13.x)
or updating/removing "@types/speakingurl" so types align with
"speakingurl@14.0.1"; update package.json accordingly and run a TypeScript build
to verify the change (look for entries "@types/marked" and "@types/speakingurl"
in devDependencies and the "speakingurl" entry in dependencies to make the
change).
In `@apps/self-hosted/src/features/publish/components/publish-tags-selector.tsx`:
- Around line 46-131: validateTag currently rejects tags with two hyphens
because tag.split("-").length > 2 is wrong, and handlePaste bypasses validateTag
allowing invalid tags; fix by changing the hyphen check in validateTag to count
hyphens (e.g. (tag.match(/-/g) || []).length > 2) so up to two hyphens are
allowed, and update handlePaste to validate each candidate before adding (call
validateTag for each sanitized part or reuse addTag) so pasted tags go through
the same validation and warning logic as manual input (references: validateTag,
handlePaste, addTag, sanitizeTagInput).
In `@apps/self-hosted/src/features/publish/hooks/use-publish-post.ts`:
- Around line 36-42: The first call to createPermlink(title) is dead code
because it's immediately overwritten by createPermlink(title, true); remove the
redundant call and initialize permlink only once (keep the existing
createPermlink(title, true) if you always want a random suffix), or if you need
conditional suffixing, replace with a single conditional call that uses
createPermlink(title) when no suffix is needed and createPermlink(title, true)
when a suffix is required; update the variable declaration around permlink
accordingly.
- Around line 48-49: The payload currently sets parent_author and
parent_permlink to empty strings; for top-level Hive posts parent_permlink must
be the first tag (community/category). In the hook (use-publish-post.ts) where
the post object is built (referencing parent_author and parent_permlink), set
parent_permlink to tags[0] (or tags?.[0]) when parent_author is empty/undefined
and tags exist; ensure you handle the case of no tags by falling back to an
empty string and normalize the tag (e.g., lowercase/trim) before assigning.
In `@apps/self-hosted/src/features/publish/utils/markdown.ts`:
- Around line 81-89: The image rule's replacement inserts alt and src directly
into markdown (in the .addRule("image" replacement function using variables alt
and src), which allows markdown-breaking chars; escape special characters in alt
(at least ']' and backslashes) by backslash-escaping them and ensure src is
safely wrapped or escaped (e.g., wrap the URL in angle brackets <...> or
percent-encode problematic chars like ')' and spaces) before returning
`` so markdown structure cannot be broken or injected into.
In `@apps/self-hosted/src/features/publish/utils/permlink.ts`:
- Around line 24-34: Truncate the base derived from slug before appending the
random suffix so the suffix from permlinkRnd() is preserved: compute
parts/permBase from slug (using parts, perm), if random generate rnd =
permlinkRnd().toLowerCase(), compute allowedBaseLen = 255 - (1 + rnd.length),
truncate permBase to allowedBaseLen (or keep full base if shorter), then set
perm = `${permBase}-${rnd}`; if not random simply ensure perm is no longer than
255 by truncating permBase to 255. Use the variables slug, perm, permBase,
random and function permlinkRnd() to locate and modify the logic.
🧹 Nitpick comments (8)
apps/self-hosted/package.json (1)
21-30: Consider standardizing TipTap package versions for consistency (optional improvement).While
@tiptappackages in the 2.9.x / 2.11.x / 2.12.x range are peer-compatible and won't cause conflicts, pinning all@tiptap/* packages to the same version is a best practice to prevent potential real-world mismatches during updates and to improve maintainability.apps/self-hosted/src/features/publish/hooks/use-publish-state.ts (1)
12-13: Consider exportingMAX_TITLE_LENGTHto avoid duplication.This constant is also defined in
publish-editor.tsx(line 11). Exporting it from this file would ensure consistency and avoid drift if the value changes.♻️ Proposed refactor to export constants
-const MAX_TITLE_LENGTH = 255; -const MAX_TAG_LENGTH = 24; +export const MAX_TITLE_LENGTH = 255; +export const MAX_TAG_LENGTH = 24;Then in
publish-editor.tsx:import { usePublishState, MAX_TITLE_LENGTH } from "../hooks/use-publish-state";apps/self-hosted/src/features/publish/hooks/use-publish-post.ts (1)
54-55: Redundant check:tags.length > 0already validated.Lines 32-34 throw an error if
tags.lengthis 0, so this ternary is always true.♻️ Simplify
json_metadata: JSON.stringify({ - tags: tags.length > 0 ? tags : [], + tags, app: "ecency-selfhost/1.0",apps/self-hosted/src/features/publish/hooks/use-publish-editor.ts (2)
67-72: Missing ESLint exhaustive-deps warning:setEditorContentandpublishState.contentnot in dependency array.While the comment indicates "Only on mount" intent, this pattern can cause subtle bugs if
editoris recreated. Consider using a ref to track if initial load has occurred, or add a disable comment with justification.♻️ Alternative using ref to track initialization
+import { useCallback, useEffect, useRef } from "react"; export function usePublishEditor() { const publishState = usePublishState(); + const initializedRef = useRef(false); // ... editor setup ... // Load content from state when editor is ready useEffect(() => { - if (editor && publishState.content) { + if (editor && publishState.content && !initializedRef.current) { + initializedRef.current = true; setEditorContent(publishState.content); } - }, [editor]); // Only on mount + }, [editor, publishState.content, setEditorContent]);
38-43: Consider debouncing content state updates.
onUpdatefires on every keystroke, triggering HTML-to-Markdown conversion and localStorage writes. For large documents, this could impact performance. Consider debouncing the state update.apps/self-hosted/src/features/publish/components/publish-action-bar.tsx (1)
44-51: Missingtoprop onLinkcomponent.The
Linkcomponent hassearchbuttoprop comes after. While this works, havingtobeforesearchimproves readability and follows typical usage patterns.♻️ Reorder props for clarity
<Link + to="/blog" search={{ filter: "posts" }} className="text-sm flex items-center gap-2 whitespace-nowrap" - to="/blog" >apps/self-hosted/src/routes/publish.tsx (2)
15-30: MoveusePublishEditor()below the guard check to avoid initializing the editor for unauthorized users.The
usePublishEditor()hook (line 18) initializes a TipTap editor with multiple extensions before the authorization guard (lines 28-30). For unauthorized users, this allocates resources that are immediately discarded upon redirect.♻️ Proposed fix
function RouteComponent() { const isBlogOwner = useIsBlogOwner(); const isAuthEnabled = useIsAuthEnabled(); - const { editor } = usePublishEditor(); const navigate = useNavigate(); // Redirect if auth is disabled or user is not blog owner useEffect(() => { if (!isAuthEnabled || !isBlogOwner) { navigate({ to: "/blog", search: { filter: "posts" } }); } }, [isAuthEnabled, isBlogOwner, navigate]); if (!isAuthEnabled || !isBlogOwner) { return null; } + return <AuthorizedPublishContent />; +} + +function AuthorizedPublishContent() { + const { editor } = usePublishEditor(); + return ( <div className="min-h-screen bg-theme-primary">
11-13: Consider using TanStack Router'sbeforeLoadfor route-level authorization.The current useEffect-based redirect works but the component still mounts and hooks execute before the redirect. TanStack Router's
beforeLoadcan prevent unauthorized access at the route level, before the component renders.♻️ Example using beforeLoad
export const Route = createFileRoute("/publish")({ beforeLoad: ({ context }) => { // Access auth state from router context const { isAuthEnabled, isBlogOwner } = context; if (!isAuthEnabled || !isBlogOwner) { throw redirect({ to: "/blog", search: { filter: "posts" } }); } }, component: RouteComponent });This requires setting up the auth state in your router context, but provides cleaner separation of authorization from rendering logic.
Also applies to: 21-26
apps/self-hosted/src/features/publish/components/publish-tags-selector.tsx
Show resolved
Hide resolved
| // Generate permlink from title | ||
| let permlink = createPermlink(title); | ||
|
|
||
| // If permlink already exists or is too short, add random suffix | ||
| // In a real implementation, you might want to check if permlink exists | ||
| // For now, we'll add random suffix to ensure uniqueness | ||
| permlink = createPermlink(title, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dead code: first createPermlink call is immediately overwritten.
Line 37 creates a permlink that is immediately replaced on line 42. The first call serves no purpose.
🧹 Proposed fix to remove dead code
- // Generate permlink from title
- let permlink = createPermlink(title);
-
- // If permlink already exists or is too short, add random suffix
- // In a real implementation, you might want to check if permlink exists
- // For now, we'll add random suffix to ensure uniqueness
- permlink = createPermlink(title, true);
+ // Generate permlink from title with random suffix for uniqueness
+ const permlink = createPermlink(title, true);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Generate permlink from title | |
| let permlink = createPermlink(title); | |
| // If permlink already exists or is too short, add random suffix | |
| // In a real implementation, you might want to check if permlink exists | |
| // For now, we'll add random suffix to ensure uniqueness | |
| permlink = createPermlink(title, true); | |
| // Generate permlink from title with random suffix for uniqueness | |
| const permlink = createPermlink(title, true); |
🤖 Prompt for AI Agents
In `@apps/self-hosted/src/features/publish/hooks/use-publish-post.ts` around lines
36 - 42, The first call to createPermlink(title) is dead code because it's
immediately overwritten by createPermlink(title, true); remove the redundant
call and initialize permlink only once (keep the existing createPermlink(title,
true) if you always want a random suffix), or if you need conditional suffixing,
replace with a single conditional call that uses createPermlink(title) when no
suffix is needed and createPermlink(title, true) when a suffix is required;
update the variable declaration around permlink accordingly.
| parent_author: "", | ||
| parent_permlink: "", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parent_permlink should be the first tag for top-level Hive posts.
For top-level posts on Hive, parent_permlink should be set to the first tag (community/category), not an empty string. An empty parent_permlink may cause the post to not be indexed or displayed correctly.
🐛 Proposed fix
const postOp: Operation = [
"comment",
{
parent_author: "",
- parent_permlink: "",
+ parent_permlink: tags[0],
author: user.username,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| parent_author: "", | |
| parent_permlink: "", | |
| const postOp: Operation = [ | |
| "comment", | |
| { | |
| parent_author: "", | |
| parent_permlink: tags[0], | |
| author: user.username, |
🤖 Prompt for AI Agents
In `@apps/self-hosted/src/features/publish/hooks/use-publish-post.ts` around lines
48 - 49, The payload currently sets parent_author and parent_permlink to empty
strings; for top-level Hive posts parent_permlink must be the first tag
(community/category). In the hook (use-publish-post.ts) where the post object is
built (referencing parent_author and parent_permlink), set parent_permlink to
tags[0] (or tags?.[0]) when parent_author is empty/undefined and tags exist;
ensure you handle the case of no tags by falling back to an empty string and
normalize the tag (e.g., lowercase/trim) before assigning.
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.