refactor(admin): migrate admin app to React#1052
Open
Innei wants to merge 43 commits into
Open
Conversation
|
This pull request exceeds GitHub's diff limits and cannot be scanned. GitHub Limits:
Recommendations:
|
- Added ejs@5.0.2 and react-resizable-panels@4.11.1 to the project. - Introduced code-inspector-plugin@1.5.1 and its related packages. - Updated dotenv to version 16.6.1 and chalk to version 4.1.1. - Added new Vue compiler packages: @vue/compiler-core@3.5.34, @vue/compiler-dom@3.5.34, and @vue/shared@3.5.34. - Updated various dependencies to their latest versions for improved performance and security. Signed-off-by: Innei <tukon479@gmail.com>
…kage path Signed-off-by: Innei <tukon479@gmail.com>
Move all submodules (api, constants, hooks, i18n, models, rich-editor, socket, ui, utils, views) and top-level files (providers, query-client, routes, shell, theme) from src/app/ up to src/. Rewrite ~/app/X imports to ~/X, fix relative ./app/ refs in App.tsx, and update tsconfig include.
| import { API_URL } from '~/constants/env' | ||
| import { SESSION_WITH_LOGIN } from '~/constants/keys' | ||
|
|
||
| const requestUuid = createRequestUuid() |
- add reusable Drawer component with motion-driven slide/fade animations and directional shadow - align drawer header height to page header (h-16) - migrate ContentSettingsDrawer and PageSettingsDrawer to shared Drawer - extract draft-status-tag, header-back-button, and tag UI primitives
Introduce ui/datetime-picker.tsx with Base UI Popover trigger, decade/month/day views, time grid, and datetime-local string IO.
Unify post/note/page rendering into a single canvas. Replace the form-style header (separate title, slug, subtitle, AI, format-toggle fields) with: - Top meta strip (opacity-60 default, full opacity on hover) showing draft/save/published status as a colored dot + relative-time string, with ghost icon buttons for format swap and AI generate. - Title input rendered as plain typography (text-3xl, no border/background). - Slug surfaced as a hover-revealed mono pill below the title; clicking it opens a Base UI popover with the slug input (Enter preventDefault to avoid form submission), optional copy-URL action. - Subtitle slot per kind: page shows always-visible subtitle input, post exposes a hover-revealed summary input, note hides the slot. - Body container collapses both kind branches to max-w-[80rem], with --rc-max-width: 'none' forced on the editor surface so it fills the wider canvas instead of capping at the haklex default 700px. Pass mainClassName="flex flex-col" to ContentLayout so the inner Scroll's flex-1 takes a definite height; this fixes a regression where the editor's overscroll-contain prevented wheel events from reaching the panel-level scroller. Spec saved at docs/superpowers/specs/2026-05-26-write-page-editing-area-redesign.md.
…itor toolbar Upgrade @haklex/rich-plugin-toolbar to 0.16.0 to consume the newly exposed abstraction layer (useToolbarState hook, applyBlockType/applyFontFamily action helpers, FONT_FAMILIES/BLOCK_TYPE_LABELS constants, low-level ToolbarButton/Dropdown/Separator), then build a project-styled toolbar in rich-editor/components/EditorToolbar.tsx. The new toolbar matches the rest of the admin (neutral palette, ghost icon buttons, Base UI Menu dropdowns) and covers font family, heading dropdown, undo/redo, bold/italic/underline/strike/code, highlight, spoiler, lists, four alignment buttons, and dynamic insert items (first 5 inline plus an overflow dropdown for the rest). Mounted via the `header` slot of ShiroEditor across ReactEditorPane, NestedDocDialogEditor, and ShiroEditorBridge; the upstream stylesheet is no longer imported.
Introduce `present(Component, props, options?)` that returns a
PromiseLike `ModalHandle` ({ close, dismiss, update, then }) so any
caller — hook, event handler, async fn — can mount a dialog without
threading open state through JSX. Inside the body, `useModal()` exposes
the same handle.
ModalRoot subscribes a module-level stack store via useSyncExternalStore
and renders each instance through the existing `<Modal>` shell. It
enforces top-only dismissal (Base UI does not stack sibling dialogs),
cancels backdrop/ESC for non-top and non-dismissable modals via
eventDetails.cancel(), and uses a 10-step z-index stride so a modal's
descendants (popovers, nested dialogs) stay below the next modal.
Modal gains optional onOpenChange and onExitComplete props (additive;
existing onClose callsites unchanged). `onClose` is now optional.
Migrate DraftRecoveryDialog: drop onClose and the outer <Modal> wrap;
caller drives it via useEffect → present, with cleanup-dismiss to handle
route changes. publishedContent is now memoised to keep effect deps
stable.
Spec: docs/superpowers/specs/2026-05-26-imperative-modal-design.md
Below the lg breakpoint, route the aside slot into a BottomSheet so ContentLayoutSlot consumers continue to portal unchanged while phones get a native-feeling sheet UI. Desktop keeps the existing resizable PanelGroup unchanged.
Decompose 36 admin route view files from monolithic implementations (some 2000-4700 lines) into thin route skeletons that re-export from feature-scoped directories under apps/admin/src/features/<domain>/. Each feature now follows a routes/components/types/utils/constants boundary, with RouteViewContent as a thin orchestration layer: - snippets 1974 -> 390 lines - enrichment 1477 -> 397 - analyze 1287 -> 588 - dashboard 1122 -> 484 - comments 994 -> 393 - projects 957 -> 194 - webhooks 939 -> 200 - recently 867 -> 230 - cron 821 -> 305 - friends 806 -> 298 - setup 620 -> 120 Plus prior splits for settings, ai, posts, notes, pages, categories, topics, drafts, comments, says, recently, files, write, backup, markdown, search-index, subscribe, templates, readers, auth, debug. Route registration in src/routes.tsx is unchanged; public behavior preserved. Type-only declarations live in types/, query keys and option lists in constants.ts, pure transformations in utils/, and each non-trivial component in its own file. Validated via typecheck, lint, and production build.
- formatters/i18n type signatures accept null/undefined - draft-status-tag falls back to createdAt when updatedAt missing - extract DraftHintBanner from write page - minor spec doc table formatting
- New src/ui/codemirror/ module ported from master Vue editor: - Core: CodeMirrorEditor, useCodeMirror, editor-store (external store via useSyncExternalStore) - WYSIWYG block widgets (heading/list/image/codeblock/blockquote/details/math/...) - Floating selection toolbar + slash command menu - Drag/paste image upload via filesApi - Theme + font autosync via useThemeMode and localStorage settings - Wire CodeMirrorEditor into WriteRouteViewsContent markdown branch, add ImageDropZone - Drop unused post "一句话简介" inline subtitle input (summary still editable in meta panel) - Aside Scroll grids use grid-cols-[minmax(0,1fr)] to prevent intrinsic-content overflow - Related-reading section switches from checkbox list to SelectField + removable chips - Checkbox indicator no longer keepMounted (was showing stale check icon in dark mode)
…ure widgets
- src/ui/ now contains only four category dirs: primitives, feedback, layout, data
- forked editor packages move to src/vendor/{rich-editor,codemirror}
- business-coupled widgets move into their owning features (drafts, snippets,
analyze) or features/_shared/components/ when used by 2+ features
- hooks/utils/constants files relocate out of ui/ into existing top-level dirs
- ~178 importers + intra-ui refs updated; design spec added under docs/
Replace the 850-line manual route + sidebar registry with a Vite plugin
that scans `apps/admin/src/views/` for convention files and emits a
`virtual:admin-routes` module consumed by routes.tsx, App.tsx, and the
sidebar. Each route lives in a `page.tsx` (lazy) or `page.sync.tsx`
(eager) and exports `metadata` via `defineMetadata({...})`. Section
grouping is expressed with `(name)` route-group folders plus per-section
`meta.ts`. Static + functional redirects move to `views/redirects.ts`.
URL surface is preserved end-to-end; URLs that share prefixes like
`/extra-features/*` keep the prefix inside their parens-grouped folder
to avoid behavioural changes.
Spec covers a two-line row layout (title row + meta row), persistent dimmed action icons that brighten on hover (no layout shift), and a new ~/ui/overlay/context-menu primitive with singleton imperative store + render-prop ContextMenuTrigger, modelled after the existing modal-imperative pattern.
- Restructure ContentEntryListItem to a two-line layout (title + meta)
with persistent dimmed action icons that brighten on hover. No layout
shift, no inline-edit widgets.
- Add singleton imperative ContextMenu primitive in ~/ui/overlay/context-menu
modelled after lobe-ui: type-discriminated items (item / checkbox /
submenu / divider / group), cloneElement Trigger with data-popup-open
state, pointer-tracking store, ContextMenuHost mounted in providers.
- Add FloatLayerProvider + useFloatLayerContainer so menus Portal to a
known top-level container, escaping any ancestor stacking context.
- Add monotonic z-index manager (useLayerZIndex) so the latest opened
layer always sits on top — fixes submenus being trapped beneath their
parent positioner.
- Extract menu class strings to ~/ui/overlay/menu-styles for sharing
with the future DropdownMenu primitive.
- Drop NoteRow inline mood/weather InlineTextEdit; both move to a
present()-driven NoteMetaEditModal triggered from the context menu.
- buildPostMenuItems / buildNoteMenuItems compose menu items per row.
PostsRouteViewContent gains a pin mutation and the row category list is
remapped to { id, name }. NoteMetadataUpdate gains bookmark.
- FocusScope primitive (refcounted) tracks the active interaction area via global pointerdown/focusin; sidebar + each list view declare their scope. - useListSelection / useListShortcuts: single-source row selection (single / toggle / range) and Backspace/Enter/$mod+Enter actions gated on scope. - useScopeArrowNav: J/K + Arrow + Home/End move focus through visible items in the active scope, with checkVisibility-aware filtering for sidebar collapse. onItemFocus callback mirrors cursor into selection state. - Posts/notes row actions extracted into a ListAction registry so the right-click menu and shortcut bindings share a single source. - Row visuals: Vercel-style neutral selection palette, no transition-colors, data-id + tabIndex on the article so the cursor focus and cmd/shift modifier clicks behave consistently.
Adopts lobe-chat's slice pattern (externalized action class, flattenActions,
subscribeWithSelector + shallow). Each store gets initial-state / action /
store / index split into its own folder.
- focus-scope, context-menu, modal-imperative, codemirror/editor-store →
Zustand, with imperative wrappers preserved so callers like present.ts
and the ContextMenu trigger keep their existing API.
- theme, codemirror/editor-setting → Zustand + persist middleware. The
hand-rolled localStorage + custom-event broadcasts are gone; the matchMedia
listener in theme.ts stays on useSyncExternalStore (DOM source).
- codemirror/image-popover-state → Jotai atom (small, transient).
- Add src/store/{types,utils/flatten-actions}.ts shared by every slice.
useSyncExternalStore now only remains in theme.ts for matchMedia.
Left over from the Zustand migration in b419ed9 — the new editor-store/ folder fully replaces it.
Search now spans the available row width with no artificial cap; filter, sort, refresh, and selection collapse into a single right cluster. The selection control keeps a consistent visual register: a bare checkbox when empty, a neutral count chip + danger-tinted bulk-action chip when populated, replacing the previous inverted black pill.
…nslate() Move literal UI strings across all admin features into en-US/zh-CN resource bundles and route them through the new `~/i18n/translate` helper, enabling runtime locale switching via UI_LOCALE_STORAGE_KEY.
Move dashboard section spacing from root gap-6 to per-block mt-6 so each block owns its own rhythm. Soften the stats grid hairline color and pad the trailing empty tracks with panel-bg spacers to avoid the dark color patch on incomplete rows.
…ass name composition docs: add design document for List Focus Scope Abstraction & Master-Detail Migration refactor: update package.json scripts to use pnpm for admin app chore: remove turbo.json configuration and related dependencies from pnpm-lock.yaml Signed-off-by: Innei <tukon479@gmail.com>
- Replaced TopicFormDialog with TopicFormModal for better modal handling. - Updated TopicDetail to use presentAddNotesToTopic for adding notes. - Removed WebhookEditorDialog and implemented WebhookEditorModal for webhook editing. - Adjusted TopicsRouteViewContent and WebhooksRouteViewContent to utilize new modal components. - Enhanced MasterDetailLayout to support pixel-based resizing for list components. - Cleaned up unused code and improved overall structure for better maintainability. Signed-off-by: Innei <tukon479@gmail.com>
- Introduced new components for file management: FileThumbnail, FileListRow, FileDetailPane, UploadDropOverlay, and UploadProgressDock. - Implemented hooks for file uploading (useFileUploader) and file searching (useFileSearch). - Refactored existing routes to use the new components, improving code organization and maintainability. - Added utility functions for formatting bytes and validating color previews. - Updated constants and adapters for file types and statuses. - Enhanced UI with improved search functionality and upload overlays. - Updated translations for new UI elements and messages. - Documented the redesign goals, structure, and migration plan in a new design spec. Signed-off-by: Innei <tukon479@gmail.com>
- Replace top-level email/markdown tabs with MasterDetailLayout slim rail - Add view toggle (Split / Code / Preview) in detail pane header - Move sample props from static aside to editable JSON popover with live re-render - Register Monaco ejs-html language + completion provider sourced from props keys - Switch CodeEditor primitive to GitHub light/dark themes following app theme - Add Test SMTP button hooked into /health/email/test - Replace window.confirm with confirmDialog modal for reset - Provide admin-side fallback demo props per template type so previews stay resilient when backend response is missing keys (e.g. newsletter detail_link)
| ], | ||
|
|
||
| comment: [ | ||
| [/-->/, 'comment.html', '@pop'], |
…-grouped master-detail - Drop the legacy AiRouteViewContent six-tab wrapper for these three routes; each /ai/* page now self-mounts AppPage + PageHeader + MasterDetailLayout. - Share a generic ArticleGroupedRouteView engine driven by a per-surface ArticleGroupedConfig<TItem>; three thin route wrappers (Summary / Translation / Insights) supply queries, item shape, drawer body, and extra actions. - List pane: borderless search + infinite scroll + neutral type badges; drops the noisy "raw article id" row footer. Both article and item rows use FocusScope + useListKeyboard + ListRow with proper j/k highlight + click sync. - Detail pane: master-style article link + divider + section title + item rows; trash on hover; mobile back button. - Edit + generate flows live in a ContentLayout split (resizable + collapsible) inside the detail slot, defaulting to a 70% editor pane. - Embedded editors: CodeMirror for summary / insights / translation text; Lexical via a lazy-loaded LexicalEmbeddedEditor (with NestedDoc providers and nestedDocEditNodes) when the translation contentFormat is "lexical". - PageHeader: new `iconOnly` flag on button actions so refresh stays square on desktop without falling back to `kind: "custom"`. - ContentLayout: new optional `mainMinSize` prop so consumers can carve out more room for the aside. - Docs: `.claude/skills/master-detail-list-keyboard/` codifies the j/k/click/ drawer-sync recipe so the bug doesn't recur. Spec lives under `docs/superpowers/specs/2026-05-28-ai-article-grouped-redesign-design.md`.
…tries - Delete /ai/slug-backfill route, its surface (SlugBackfillSurface), the WriterGeneratePanel helper, the slug-backfill API functions, and all related i18n keys. The `AITaskType.SlugBackfill` enum value and the task type label are kept so historical tasks still render in /ai/tasks. - Drop the slug tab from `aiSurfaceTabs`, the slug branch from `getInitialAiSurface`, and the 'slug' variant from `AiSurface`. - Migrate /ai/translation-entries off the legacy AiRouteViewContent tab wrapper into a self-contained AiTranslationEntriesRouteView modelled on the /posts list shell: FocusScope + ContentListHeader + a custom toolbar strip (keyPath select / lang input / refresh) + Scroll table + pagination. URL-syncs page/keyPath/lang. The old TranslationEntriesSurface and its AiRouteViewContent branch are removed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Verification
Notes