Commit Graph

68 Commits

Author SHA1 Message Date
Yutaka Kurosaki 7909c33074 Make DocumentService locale-aware
createDocument/updateDocument now accept a \$locale parameter and
write to document_translations. Adds addTranslation, deleteTranslation,
setDefaultLocale (with path/slug regen), distinct-document search,
and findByTitle that delegates to WikiLinkResolver.
2026-05-10 12:23:14 +09:00
Yutaka Kurosaki d7522f592d Add WikiLinkResolver with deterministic 5-step resolution
Prefers current locale, then document default_locale, then any locale
(lowest document_id), then slug match (legacy).
2026-05-10 12:19:57 +09:00
Yutaka Kurosaki 0c399c9f0f Refactor Document to read title/content via translations
Adds translations/defaultTranslation relations, current-locale accessors
with fallback to default_locale, isFallback/availableLocales helpers,
and search scope that delegates to DocumentTranslation.
2026-05-10 12:17:31 +09:00
Yutaka Kurosaki b7a70f74e5 Add DocumentTranslation model with renderMarkdown and search scope
Includes a minimal Document::translations() HasMany relation so that
DocumentFactory's afterCreating callback (which calls
$document->translations()->count()) works. The full Document model
refactor (accessors, fallback helpers, default-translation accessor)
lands in Task 4.
2026-05-10 12:14:24 +09:00
Yutaka Kurosaki 4a8622c385 Harden migration: transaction, chunking, lossy-down doc, data-preservation test
- Wrap the data copy in DB::transaction (FULLTEXT ALTER stays outside)
- Switch to chunkById(500) so the migration scales
- Document down() as irreversible for non-default-locale translations
- Add test_existing_documents_data_is_copied_to_translations to cover
  the data copy itself (the only previously-untested behavior)
- Drop unused Migrator import in DocumentMigrationTest
- Also restore title index in down() so up() can be re-run cleanly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 12:11:31 +09:00
Yutaka Kurosaki f2bdb6a069 Add document_translations table and migrate existing data
documents.{title,content,rendered_html} move to document_translations
keyed by (document_id, locale). Existing rows are copied to a single
translation in config('app.locale'). documents gains default_locale.

Also guard the original FULLTEXT ALTER TABLE with a MySQL driver check
so that the SQLite test environment can run all migrations cleanly.
2026-05-10 12:04:05 +09:00
Yutaka Kurosaki e83bd6981d Fix DocumentFactory withoutTranslations + path trailing period
afterCreating appends rather than replaces, so a no-op closure does not
override configure(). Use withoutAfterCreating() to actually clear the
translation-creation callback (otherwise DocumentTranslation::factory()
recurses through Document::factory()->withoutTranslations()).

Also use words() instead of sentence() to avoid Faker's trailing period
producing paths like "Foo bar..md".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 11:58:41 +09:00
Yutaka Kurosaki 7f2f8a2248 Add Document and DocumentTranslation factories 2026-05-10 11:53:33 +09:00
Yutaka Kurosaki ab846b71b2 Add implementation plan for article-level i18n
12 TDD tasks: factories, migration with data move, DocumentTranslation
model, Document refactor with locale-aware accessors, WikiLinkResolver,
DocumentService rewrite, syncLinks/processLinks via resolver, translation
CRUD routes/controller, viewer fallback banner, editor locale tabs,
QuickSwitcher cross-locale search, and seeder cleanup. Each task includes
exact file paths, failing tests, minimal implementation, and a commit step.
2026-05-10 10:10:42 +09:00
Yutaka Kurosaki 01fb8b9fcf Add design spec for article-level i18n with default-locale fallback
Documents the data model (1 doc + document_translations table), service
layer changes, routing/editor UX, wiki-link resolution order, search,
and migration plan. Article URLs stay locale-independent; display falls
back to each document's default_locale when the requested locale lacks
a translation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 09:46:29 +09:00
Yutaka Kurosaki ba25b544f5 Add implementation plan for admin media manager
Eleven TDD tasks from migration through editor integration: model,
service (store/rename/delete + auto-suffix), admin CRUD controller,
real Blade UI, auth-gated download, listener Phase-1 logical-path
rewrite for Image+Link nodes, and ImageUploadController rewiring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:54:54 +09:00
Yutaka Kurosaki 3c185fac37 Add design spec for admin media manager
Defines logical/physical filename split, MD reference resolution policy,
component structure, data flow, and test coverage for the new
upload/rename/download/delete flows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:42:18 +09:00
Yutaka Kurosaki b924564c22 Upgrade to Laravel 13
Bump laravel/framework ^12.0 → ^13.0, laravel/tinker ^2.10 → ^3.0,
phpunit/phpunit ^11.5 → ^12.0, and php ^8.2 → ^8.3 (Laravel 13
minimum). No app code changes required: codebase has no
VerifyCsrfToken, JobAttempted/QueueBusy listeners, custom
Manager::extend, custom queue drivers, or model boot()
instantiation that the v13 breaking changes touch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 14:25:12 +09:00
Yutaka Kurosaki def78d4754 Address final review: Vimeo regex boundary + spec accuracy
- Vimeo regex now rejects URLs like vimeo.com/123abc that were
  silently truncated to ID 123 and produced broken iframes. Negative
  lookahead (?![A-Za-z0-9]) ensures the captured digits are not
  followed by alphanumerics. Two false-positive test cases added.
- Spec corrected: HtmlInline nodes ARE filtered regardless of
  insertion path; the implementation uses a dedicated MediaEmbedNode
  + renderer to bypass the filter only for trusted programmatic embeds.
  Components list updated to include the two extra files.
- Plan Task 6 regex updated for consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:18:26 +09:00
Yutaka Kurosaki 81efac4a53 Add integration tests for mixed media in Markdown rendering
Covers image+video coexistence, multiple videos in one paragraph,
videos inside list items, wiki link non-interference, YouTube
timestamps end-to-end, and audio rendering through renderMarkdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:08:06 +09:00
Yutaka Kurosaki f26b930b5f Wire MediaEmbedExtension into Document::renderMarkdown
The extension registers a DocumentParsedEvent listener that walks the
AST, finds Image nodes whose URLs match media patterns (via
MediaUrlResolver), and replaces them with MediaEmbedNode instances
containing the appropriate <video>/<audio>/<iframe> markup.

A custom MediaEmbedNode + MediaEmbedNodeRenderer pair bypasses the
html_input filter (which would strip raw HTML when set to 'strip'),
allowing programmatically generated embed HTML to pass through safely
while user-authored raw HTML remains stripped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 11:03:09 +09:00
Yutaka Kurosaki 6ee4dcfc21 Detect Vimeo URLs and emit iframe with dnt=1
Recognizes vimeo.com/{id} and player.vimeo.com/video/{id}. Preserves
timestamps from #t=30s and ?t=30s as #t=30s on the embed URL (Vimeo
convention).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:57:05 +09:00
Yutaka Kurosaki 9486d97c73 Normalize YouTube timestamp parameters to ?start=N
Accepts ?t=30s, ?t=30, ?t=1m20s, ?t=1h2m3s, and ?start=N. Converts to
seconds and emits as ?start=N on the embed URL. ?t= takes priority over
?start= when both are present.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:52:49 +09:00
Yutaka Kurosaki 6debaf93bc Fix regex delimiter in plan Task 5
Task 4 implementer discovered that # delimiter conflicts with literal #
inside [/?#] and [&#] character classes (PHP PCRE terminates the regex
early). Same patterns repeat in Task 5; pre-update so a re-execution
does not hit the same bug. Vimeo regex in Task 6 is unaffected (no
literal # in pattern body).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:51:48 +09:00
Yutaka Kurosaki 5b6e344ee9 Detect YouTube URLs and emit privacy-enhanced iframe
Recognizes youtu.be, watch?v=, shorts, embed, and mobile variants.
Emits an iframe pointing to youtube-nocookie.com with lazy loading,
strict-origin referrer policy, and allowfullscreen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:47:46 +09:00
Yutaka Kurosaki bb9843fd47 Detect local audio URLs in MediaUrlResolver
Recognizes mp3/wav/ogg/m4a and emits <audio controls class="kb-audio">.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:44:35 +09:00
Yutaka Kurosaki 7e445eb2fe Detect local video URLs in MediaUrlResolver
Recognizes mp4/webm/ogv/mov/m4v on URL path (case-insensitive, ignoring
query strings) and emits <video controls class="kb-video">.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:41:29 +09:00
Yutaka Kurosaki 6daa001388 Scaffold MediaUrlResolver with null fallback
Initial skeleton returning null for any non-media URL. Subsequent commits
add detection for video, audio, YouTube, and Vimeo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:38:19 +09:00
Yutaka Kurosaki 1563aff964 Add implementation plan for Markdown media embed
Plan breaks the work into 9 TDD tasks: scaffold resolver, video
detection, audio detection, YouTube URL detection, YouTube timestamps,
Vimeo detection, listener+extension wiring, integration tests, full
test suite verification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:25:08 +09:00
Yutaka Kurosaki 692f4d5492 Restrict document edit/delete to owners and close public registration
Adds DocumentPolicy gating update/delete to the creator (admins bypass via
before()), invokes $this->authorize() in DocumentEditor mount/save/delete,
applies can:update,document on the edit route, hides the edit button for
non-owners, and removes the open /register routes so accounts must be
provisioned via the admin panel.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:22:18 +09:00
Yutaka Kurosaki 01a11328ec Add design spec for Markdown media embed extension
Approved design for extending image syntax `![](url)` to render videos,
audio, YouTube, and Vimeo embeds. Preserves html_input=>strip safety and
existing image/Wiki-link behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 10:21:10 +09:00
Yutaka Kurosaki 85f67871fa Update dependencies to fix security vulnerabilities
- npm audit fix: resolves axios, postcss, vite, rollup, picomatch, follow-redirects advisories
- composer update: refreshes PHP dependencies

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:18:38 +09:00
y963admin 80deff661d Add image upload support to document editor
- Create ImageUploadController to handle image uploads
- Store images in storage/app/public/images with UUID filenames
- Integrate with EasyMDE editor for drag-drop, paste, and toolbar upload
- Use original filename as alt text in markdown

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 20:08:40 +09:00
y963admin 8ea8b3f6b6 Improve current document highlighting with better URL matching
- Add removal of previous highlighting before applying new one
- Support multiple URL matching strategies (exact, full URL, ends with)
- Add debug logging to troubleshoot highlighting issues
- Reset all links before applying highlight to ensure clean state
2025-12-04 02:45:24 +09:00
y963admin d52968e697 Add current document highlighting in sidebar navigation
- Highlight active document with indigo background and bold font
- Change icon color to indigo for active document
- Use JavaScript to match current URL path with sidebar links
- Update highlighting on page load and Alpine navigation events
- Active document is visually distinct from other items
2025-12-04 02:42:20 +09:00
y963admin bed7137e43 Remove debug console logs from sidebar scroll preservation
- Remove all console.log statements
- Clean up code for production use
- Preserve scroll position functionality without debug output
2025-12-04 02:37:07 +09:00
y963admin 028e0b11c7 Fix sidebar scroll preservation by correcting link selector
- Change selector from 'a[x-navigate]' to 'a' (no x-navigate attribute)
- Remove sessionStorage.removeItem to prevent clearing saved scroll position
- Add '0' check to prevent restoring scroll position to top
- Add debug logging for troubleshooting
- Now works correctly in all browsers including Chrome
2025-12-04 02:35:29 +09:00
y963admin 5bf43abab9 Remove x-navigate directive, use native HTML links with scroll preservation
- Remove x-navigate.preserve-scroll from sidebar links
- Use standard <a> tags for navigation
- Implement manual JavaScript-based scroll position management
- Save scroll position to sessionStorage before navigation
- Restore scroll position after page load
- Works consistently across all browsers including Chrome
2025-12-04 02:30:48 +09:00
y963admin f96ad4d14f Use manual scroll position management instead of x-navigate.preserve-scroll
- Add JavaScript to save sidebar scroll position before navigation
- Restore scroll position after page load using sessionStorage
- Works consistently in Chrome and other browsers
- Handles both DOMContentLoaded and window load events
- Compatible with Alpine navigate and standard navigation
2025-12-04 02:24:00 +09:00
y963admin a4aff43091 Add preserve-scroll modifier to x-navigate directive
- Use x-navigate.preserve-scroll to maintain sidebar scroll position during navigation
- Prevents page from scrolling to top after clicking sidebar links
- Alpine navigate automatically saves and restores scroll position
2025-12-04 02:17:13 +09:00
y963admin 1e20982e00 Simplify sidebar scroll preservation using only x-navigate directive
- Remove custom sessionStorage scroll management logic
- Rely solely on x-navigate directive from Alpine for scroll preservation
- x-navigate handles automatic scroll position saving and restoring
- Cleaner and simpler implementation
- Keep x-navigate directive on all sidebar links
2025-12-04 02:13:28 +09:00
y963admin ec7aaf44a9 Fix sidebar scroll preservation per page with x-navigate directive
- Add x-navigate directive to all sidebar document links for Alpine navigation
- Store scroll position per page using URL path as key in sessionStorage
- Each page now maintains its own scroll position in the sidebar
- Save scroll position before navigation and restore after navigation
- Scroll position is preserved when clicking links in the sidebar
- Works correctly with Alpine navigate events triggered by x-navigate directive
2025-12-04 02:11:06 +09:00
y963admin 00a5951654 Improve sidebar scroll position preservation with sessionStorage fallback
- Replace localStorage with sessionStorage for session-based scroll restoration
- Add console logging for debugging scroll behavior
- Support both Livewire and Alpine navigate events
- Intercept sidebar link clicks to ensure scroll position is saved before navigation
- Use setTimeout for smoother DOM restoration timing
- Restore scroll position on page load and window load events
- Sidebar now maintains scroll position consistently across navigation
2025-12-04 01:55:59 +09:00
y963admin 8dba510a6c Fix sidebar scroll position preservation during page navigation
- Replace unstable x-navigate:scroll directive with custom Alpine event handlers
- Use alpine:navigating event to save sidebar scroll position to localStorage
- Use alpine:navigated event to restore sidebar scroll position after navigation
- Sidebar now maintains scroll position when clicking document links
- Fixed 'Element not found' error that was preventing scroll restoration
- Uses requestAnimationFrame for smooth DOM restoration
2025-12-04 01:47:51 +09:00
y963admin e66ece71e3 Preserve sidebar scroll position when navigating between documents
- Add x-navigate:scroll directive to sidebar container to maintain scroll position
- Add x-navigate:scroll to all document links in sidebar (tree-item.blade.php)
- Add x-navigate:scroll to 'New Document' button
- When clicking a link in sidebar, the sidebar scroll position is now preserved during page navigation
- Fixes issue where sidebar would scroll to top after loading a document
2025-12-04 01:37:19 +09:00
y963admin b96012f598 Add resizable sidebar feature and increase default width to 320px
- Add resizable handle on the right edge of sidebar in desktop view (lg and above)
- Allow dragging to adjust sidebar width between 200px and 600px
- Persist resize settings in localStorage across page reloads
- Keep mobile mode unchanged with fixed 256px width
- Increase default width from 256px to 320px
- Implement visual feedback with color change on mouse hover
2025-12-04 01:24:47 +09:00
y963admin e50ed261e1 Show timezone information 2025-11-30 13:58:03 +09:00
y963admin 79a09430aa Fix timezone issue: Set default timezone to Asia/Tokyo
- Update config/app.php to use APP_TIMEZONE from .env with Asia/Tokyo as default
- Add APP_TIMEZONE to .env.example
- Fixes issue where timestamps were displayed 9 hours behind (UTC vs JST)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 13:46:16 +09:00
y963admin 33fef93ce0 Update README with comprehensive project documentation
- Add detailed feature overview (wiki-links, folders, multi-language, responsive)
- Include complete installation and setup instructions
- Document project structure and key concepts
- Add development workflow and common commands
- Include troubleshooting section
- Update technology stack and credits

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 13:19:43 +09:00
y963admin e14cc5dd43 Implement responsive design for mobile and tablet devices
- Add hamburger menu for mobile sidebar with slide-out animation
- Make header navigation responsive with icon-only buttons on mobile
- Adjust document viewer, editor, and quick switcher layouts for smaller screens
- Preserve all existing functionality including localStorage folder state persistence
- Use Tailwind responsive utilities (sm:, md:, lg:) for progressive enhancement

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 13:17:50 +09:00
y963admin c9fe1f6ed0 Reorder language dropdown for better UX
New order:
1. Primary: English, Japanese
2. Chinese: Simplified, Traditional
3. Korean
4. Other Asian: Hindi, Vietnamese, Turkish
5. European: German, French, Spanish, Portuguese, Russian,
   Ukrainian, Italian, Polish

This prioritizes the most commonly used languages in the target
market while maintaining a logical regional grouping.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 13:02:05 +09:00
y963admin 01c92aeb7c Add translation files for 8 new languages
Created complete message translation files for:
- pt-BR: Portuguese (Brazil) - 250M+ speakers
- ru: Russian - 260M+ speakers
- uk: Ukrainian - 40M+ speakers
- it: Italian - 67M speakers
- hi: Hindi - 600M+ speakers
- vi: Vietnamese - 95M speakers
- tr: Turkish - 80M speakers
- pl: Polish - 45M speakers

All translation files include complete translations for:
- Navigation, documents, quick switcher
- Admin panel, settings, common terms
- Authentication, errors, profile management

Total supported languages: 16 (LTR only)
These translations provide native language support for
1.5+ billion additional speakers worldwide.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 13:01:12 +09:00
y963admin 211867e2eb Add 8 major languages (LTR only)
Added languages:
- pt-BR: Português (Brasil) - 250M+ speakers
- ru: Русский - 260M+ speakers
- uk: Українська - 40M+ speakers
- it: Italiano - 67M speakers
- hi: हिन्दी - 600M+ speakers
- vi: Tiếng Việt - 95M speakers
- tr: Türkçe - 80M speakers
- pl: Polski - 45M speakers

Total supported languages: 16
RTL languages (Arabic, Hebrew, etc.) excluded for now,
as they require additional layout implementation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 12:54:14 +09:00
y963admin f57d235651 Remove language setting from profile page
Since language switching is now available in the header for all users,
the separate language setting section in the profile page is redundant.

Changes:
- Remove update-locale-form include from profile/edit.blade.php
- Delete profile/partials/update-locale-form.blade.php

Users can now change language using the header dropdown instead.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 12:36:53 +09:00
y963admin b419991940 Store guest user language preference in long-lived cookie
Changes:
- LocaleController: Set 1-year cookie when language is changed
- SetLocale middleware: Check cookie after session, before default
  Priority: User DB > Session > Cookie > Default

This allows guest users to retain their language preference
even after closing the browser (persists for 1 year).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 12:32:01 +09:00