Skip to content
This repository was archived by the owner on Apr 17, 2026. It is now read-only.

Impl: vue editor - #27

Merged
kirikirisu merged 2 commits into
mainfrom
feature/vue-integate-tiptap
Mar 3, 2026
Merged

Impl: vue editor#27
kirikirisu merged 2 commits into
mainfrom
feature/vue-integate-tiptap

Conversation

@kirikirisu

@kirikirisu kirikirisu commented Mar 3, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

リリースノート

  • 新機能
    • プレスリリースエディターを追加しました。タイトル入力とリッチテキスト編集、保存/読み込みのUIを含みます。
  • スタイル
    • エディター向けの包括的なCSSスタイルを追加しました(レイアウト、ボタン、入力、編集領域のスタイリング)。
  • Chores
    • フロントエンドでのランタイム依存関係を追加し、クエリ管理とリッチテキスト編集をサポートしました。

@coderabbitai

coderabbitai Bot commented Mar 3, 2026

Copy link
Copy Markdown

Walkthrough

VueフロントエンドにTanStack Vue QueryとTipTap依存を追加し、プレスリリースの読み込み・編集・保存を行うApp.vueコンポーネント、グローバルCSS、およびVueQueryプラグイン登録を実装しました。

Changes

Cohort / File(s) Summary
依存関係の追加
webapp/frontend/vue/package.json
@tanstack/vue-query と TipTap 関連パッケージ(@tiptap/vue-3, @tiptap/extension-*, @tiptap/pm 等)を7件追加。
エディタコンポーネント実装
webapp/frontend/vue/src/App.vue
useQueryでデータ取得、useEditorでTipTap初期化、useMutationで保存処理(POST先: /press-releases/1)、タイトル入力・EditorContent・保存ボタンを実装。ローディング/エラー/保存中のUIも追加。
グローバルスタイル追加
webapp/frontend/vue/src/assets/main.css
タイポグラフィ、レイアウト、ヘッダー、エディタ領域、保存ボタン等を定義したスタイルシートを追加(+117行)。
アプリ初期化変更
webapp/frontend/vue/src/main.ts
VueQueryPlugin をインポートして app.use(VueQueryPlugin) を追加し、main.css を読み込み、明示的に app.mount('#app') を呼び出すように変更。

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as App.vue
    participant VQ as VueQuery
    participant API as API Server
    participant DB as Database

    User->>UI: ページ読み込み
    UI->>VQ: useQuery(queryKey) を実行
    VQ->>API: GET /press-releases/1
    API->>DB: データ取得
    DB-->>API: タイトル・コンテンツ返却
    API-->>VQ: JSON レスポンス
    VQ-->>UI: data, isPending, isError を提供
    UI->>UI: watcher で editor.setContent(data.content), title を設定
    Note over UI: ユーザーがタイトル/コンテンツを編集
    User->>UI: 保存ボタンをクリック
    UI->>VQ: mutate({ title, content }) を呼ぶ
    VQ->>API: POST /press-releases/1 (JSON)
    API->>DB: データ保存
    DB-->>API: 保存成功
    API-->>VQ: 成功レスポンス
    VQ->>VQ: queryKey を invalidate -> 再フェッチ
    VQ->>API: GET /press-releases/1 (最新取得)
    VQ-->>UI: 更新された data を返す
    UI-->>User: 保存完了を反映
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PRタイトル「Impl: vue editor」は、Vue.jsエディタの実装という主要な変更を明確に要約している。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/vue-integate-tiptap

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@webapp/frontend/vue/src/App.vue`:
- Around line 33-43: The watch callback uses title.value === '' as an
initialization flag and calls JSON.parse directly, which can overwrite
in-progress edits and throw on bad JSON; introduce an explicit initialization
ref (e.g., isInitialized) and use it instead of title.value to run the initial
fill only once (or until you intentionally reset), and wrap the JSON.parse of
newData.content in a try/catch (or use a safeParse helper) before calling
editor.value.commands.setContent so parse errors are handled without crashing;
update the watch to check isInitialized and set it true after the first
successful initialization and leave editor/title untouched on subsequent data
updates unless a deliberate reset occurs.
- Line 11: The BASE_URL constant is hardcoded to 'http://localhost:8080' (and
similar hardcoded URLs elsewhere), break out environment-specific configuration
and read the value from a Vite env var instead; replace occurrences of the
BASE_URL constant with something like reading import.meta.env.VITE_API_BASE_URL
(e.g. const BASE_URL = import.meta.env.VITE_API_BASE_URL ||
'http://localhost:8080') and ensure .env/.env.production define
VITE_API_BASE_URL so the app picks the correct backend per environment; update
any other references (the other hardcoded URL usages found in the file) to use
the same constant.
- Around line 82-106: The template currently renders nothing when isPending or
isError or !data, so add explicit fallback UIs: inside the same template
containing the <div v-if="!isPending && !isError && data" block, add
complementary v-else-if / v-else blocks that render a loading state when
isPending (e.g., spinner and "読み込み中..."), an error state when isError (e.g.,
error message and retry button that calls an existing retry or fetch method),
and a "no data" message when !data; ensure these blocks reference the same
reactive flags (isPending, isError, data) and reuse handleSave/isSaving or
EditorContent only in the main block to avoid showing editor when in fallback
states.

In `@webapp/frontend/vue/src/assets/main.css`:
- Around line 6-7: Remove the stray empty line and fix the casing of the
text-rendering value in main.css: delete the blank line before the property and
change the value used in the text-rendering declaration (the token
"optimizeLegibility") to the lowercase form required by stylelint so the rule
for value keyword case passes.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a7b24c and 1f7d307.

⛔ Files ignored due to path filters (1)
  • webapp/frontend/vue/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • webapp/frontend/vue/package.json
  • webapp/frontend/vue/src/App.vue
  • webapp/frontend/vue/src/assets/main.css
  • webapp/frontend/vue/src/main.ts

import Text from '@tiptap/extension-text'

const queryKey = ['fetch-press-release']
const BASE_URL = 'http://localhost:8080'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

APIベースURLのハードコードをやめてください。

Line [11] が localhost 固定のため、環境ごとの切り替えで取得・保存が壊れます。VITE_ 環境変数経由にしてください。

🔧 修正案
-const BASE_URL = 'http://localhost:8080'
+const BASE_URL = (import.meta.env.VITE_API_BASE_URL ?? '').replace(/\/$/, '')
+if (!BASE_URL) {
+  throw new Error('VITE_API_BASE_URL が未設定です')
+}

Also applies to: 16-16, 50-50

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/vue/src/App.vue` at line 11, The BASE_URL constant is
hardcoded to 'http://localhost:8080' (and similar hardcoded URLs elsewhere),
break out environment-specific configuration and read the value from a Vite env
var instead; replace occurrences of the BASE_URL constant with something like
reading import.meta.env.VITE_API_BASE_URL (e.g. const BASE_URL =
import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080') and ensure
.env/.env.production define VITE_API_BASE_URL so the app picks the correct
backend per environment; update any other references (the other hardcoded URL
usages found in the file) to use the same constant.

Comment on lines +33 to +43
watch(
data,
(newData) => {
if (newData && title.value === '') {
title.value = newData.title
if (editor.value) {
editor.value.commands.setContent(JSON.parse(newData.content))
}
}
},
{ immediate: true },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

初期化条件が脆く、再取得時に編集中データを上書きする可能性があります。

Line [36] の title.value === '' を初期化フラグに使うと、再取得時に意図せず内容を戻す挙動になります。加えて Line [39] の JSON.parse 失敗を未処理で落とすため、初期化フラグとパースガードを分離してください。

🔧 修正案
 const title = ref('')
+const isInitialized = ref(false)
+
+const parseContent = (raw: string) => {
+  try {
+    return JSON.parse(raw)
+  } catch {
+    return { type: 'doc', content: [{ type: 'paragraph' }] }
+  }
+}

 watch(
-  data,
-  (newData) => {
-    if (newData && title.value === '') {
-      title.value = newData.title
-      if (editor.value) {
-        editor.value.commands.setContent(JSON.parse(newData.content))
-      }
-    }
+  [data, editor],
+  ([newData, currentEditor]) => {
+    if (!newData || !currentEditor || isInitialized.value) return
+    title.value = newData.title ?? ''
+    currentEditor.commands.setContent(parseContent(newData.content))
+    isInitialized.value = true
   },
   { immediate: true },
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/vue/src/App.vue` around lines 33 - 43, The watch callback
uses title.value === '' as an initialization flag and calls JSON.parse directly,
which can overwrite in-progress edits and throw on bad JSON; introduce an
explicit initialization ref (e.g., isInitialized) and use it instead of
title.value to run the initial fill only once (or until you intentionally
reset), and wrap the JSON.parse of newData.content in a try/catch (or use a
safeParse helper) before calling editor.value.commands.setContent so parse
errors are handled without crashing; update the watch to check isInitialized and
set it true after the first successful initialization and leave editor/title
untouched on subsequent data updates unless a deliberate reset occurs.

Comment thread webapp/frontend/vue/src/App.vue Outdated
Comment on lines +6 to +7
font-synthesis: none;
text-rendering: optimizeLegibility;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Stylelintエラーを解消してください。

Line [6] の空行と Line [7] の text-rendering 値のケースで lint が失敗します。CI 通過のため修正が必要です。

🔧 修正案
 :root {
   font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
   line-height: 1.5;
   font-weight: 400;
-
   font-synthesis: none;
-  text-rendering: optimizeLegibility;
+  text-rendering: optimizelegibility;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
 }
📝 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.

Suggested change
font-synthesis: none;
text-rendering: optimizeLegibility;
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
font-synthesis: none;
text-rendering: optimizelegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
🧰 Tools
🪛 Stylelint (17.3.0)

[error] 6-6: Unexpected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)


[error] 7-7: Expected "optimizeLegibility" to be "optimizelegibility" (value-keyword-case)

(value-keyword-case)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/vue/src/assets/main.css` around lines 6 - 7, Remove the stray
empty line and fix the casing of the text-rendering value in main.css: delete
the blank line before the property and change the value used in the
text-rendering declaration (the token "optimizeLegibility") to the lowercase
form required by stylelint so the rule for value keyword case passes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
webapp/frontend/vue/src/App.vue (2)

11-11: ⚠️ Potential issue | 🟠 Major

APIベースURLのハードコードを解消してください。

Line [11] が localhost 固定のままなので、環境切り替え時に取得・保存の両方が壊れます。import.meta.env から読み込む形に統一してください。
Based on learnings: Applies to **/*.{ts,tsx,js,jsx} : Never commit secrets; use environment variables with PUBLIC_ prefix for client-visible values.

🔧 修正例
-const BASE_URL = 'http://localhost:8080'
+const BASE_URL = (
+  import.meta.env.PUBLIC_API_BASE_URL ??
+  import.meta.env.VITE_API_BASE_URL ??
+  ''
+).replace(/\/$/, '')
+
+if (!BASE_URL) {
+  throw new Error('API base URL が未設定です')
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/vue/src/App.vue` at line 11, Replace the hardcoded BASE_URL
constant with a value read from the client environment (use
import.meta.env.PUBLIC_API_BASE_URL) so the app uses environment-configured API
base URLs; update the declaration of BASE_URL (the const BASE_URL in App.vue) to
read import.meta.env.PUBLIC_API_BASE_URL with a sensible fallback (e.g.,
window.location.origin or empty string) and ensure any other modules referencing
BASE_URL (or importing it) use this new env-backed value; also update
documentation/env sample to include PUBLIC_API_BASE_URL so builds provide the
correct client-visible variable.

33-45: ⚠️ Potential issue | 🟠 Major

初期化ロジックが不安定で、再取得時に編集中データを上書きする可能性があります。

Line [33] 以降の watch(data, ...) は再フェッチのたびに title/本文を再代入するため、保存後の invalidateQueries や再取得で編集中内容を戻すリスクがあります。加えて Line [41] の JSON.parse 未ガードでクラッシュし得ます。editor の準備完了待ちも含めて初期化を一度だけに分離してください。

🔧 修正例
 const title = ref('')
+const isInitialized = ref(false)
+
+const parseContent = (raw: string) => {
+  try {
+    return JSON.parse(raw)
+  } catch {
+    return { type: 'doc', content: [{ type: 'paragraph' }] }
+  }
+}

-watch(
-  data,
-  (newData) => {
-    if (!newData) return
-
-    title.value = newData.title
-
-    if (editor.value) {
-      editor.value.commands.setContent(JSON.parse(newData.content))
-    }
-  },
-  { immediate: true },
-)
+watch(
+  [data, editor],
+  ([newData, currentEditor]) => {
+    if (!newData || !currentEditor || isInitialized.value) return
+    title.value = newData.title ?? ''
+    currentEditor.commands.setContent(parseContent(newData.content))
+    isInitialized.value = true
+  },
+  { immediate: true },
+)
#!/bin/bash
# 検証内容:
# 1) watch が data 単体監視かどうか
# 2) JSON.parse(newData.content) が未ガードかどうか
# 期待結果:
# - watch(data, ...) と JSON.parse(...) が見つかれば、指摘の再現性あり

rg -n -C3 'watch\(' webapp/frontend/vue/src/App.vue
rg -n -C2 'JSON\.parse\(newData\.content\)' webapp/frontend/vue/src/App.vue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@webapp/frontend/vue/src/App.vue` around lines 33 - 45, The watch on data
unconditionally reassigns title and editor content on every refetch and calls
JSON.parse(newData.content) without guarding, risking overwrites and crashes;
modify the watch(data, ...) logic so it only performs initial setup once and
only after the editor is ready: add an initialization flag (e.g. isInitialized)
or replace with a one-time effect that waits for editor.value to be truthy
before setting title.value and calling editor.value.commands.setContent; protect
JSON.parse(newData.content) with a try/catch and fallback to a safe default
(e.g. empty doc) and only call setContent when parse succeeds, referencing the
existing symbols data, title.value, editor.value,
editor.value.commands.setContent, newData.content, and watch to locate the code
to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@webapp/frontend/vue/src/App.vue`:
- Line 11: Replace the hardcoded BASE_URL constant with a value read from the
client environment (use import.meta.env.PUBLIC_API_BASE_URL) so the app uses
environment-configured API base URLs; update the declaration of BASE_URL (the
const BASE_URL in App.vue) to read import.meta.env.PUBLIC_API_BASE_URL with a
sensible fallback (e.g., window.location.origin or empty string) and ensure any
other modules referencing BASE_URL (or importing it) use this new env-backed
value; also update documentation/env sample to include PUBLIC_API_BASE_URL so
builds provide the correct client-visible variable.
- Around line 33-45: The watch on data unconditionally reassigns title and
editor content on every refetch and calls JSON.parse(newData.content) without
guarding, risking overwrites and crashes; modify the watch(data, ...) logic so
it only performs initial setup once and only after the editor is ready: add an
initialization flag (e.g. isInitialized) or replace with a one-time effect that
waits for editor.value to be truthy before setting title.value and calling
editor.value.commands.setContent; protect JSON.parse(newData.content) with a
try/catch and fallback to a safe default (e.g. empty doc) and only call
setContent when parse succeeds, referencing the existing symbols data,
title.value, editor.value, editor.value.commands.setContent, newData.content,
and watch to locate the code to change.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f7d307 and 6c6b58f.

📒 Files selected for processing (1)
  • webapp/frontend/vue/src/App.vue

@kirikirisu
kirikirisu merged commit 41d776a into main Mar 3, 2026
3 checks passed
@sucalul
sucalul deleted the feature/vue-integate-tiptap branch March 6, 2026 12:27
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant