Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0e893c9
MentorMemoモデルを作成しUserモデルと関連付け
matuaya Jan 26, 2026
f32a3ea
メンターメモの初期表示をJSからRailsに変更
matuaya Feb 24, 2026
a67f0aa
メンターメモにメモの追加機能を実装
matuaya Mar 3, 2026
a876e8d
メンターメモの初期化処理をinitializeMentorMemo.jsに分離
matuaya Jun 2, 2026
48c3943
メンターメモにメモの編集機能を実装
matuaya Jun 2, 2026
712fdd1
メンターメモにメモの削除機能を実装
matuaya Jun 3, 2026
9a38e03
重複しているTextareaInitializerを削除
matuaya Jun 4, 2026
0d94bad
プレビューの表示をタブクリック時に実行するように変更
matuaya Jun 4, 2026
54d3a08
mentorMemoのチェックを早期リターンに変更
matuaya Jun 4, 2026
7c165df
リクエストを@rails/request.jsで処理するように変更
matuaya Jun 4, 2026
3dead7a
エディター再表示時のタブの状態を修正
matuaya Jun 4, 2026
b3d14fa
doorkeepr authorizationにcreateアクションを追加
matuaya Jun 5, 2026
fefc37d
ファイル名をuser_mentor_memo.jsからmentor-memos.jsに変更
matuaya Jun 5, 2026
ccb612b
パーシャルの呼び出しと渡すデータを修正
matuaya Jun 5, 2026
c8d9d16
不要なdivを削除
matuaya Jun 5, 2026
d3c9b46
メモの編集・削除に所有者チェックを追加
matuaya Jun 5, 2026
2cb8f1d
メモが存在しない時のメッセージを常時DOMに置き、表示の制御をJSで行うよう変更
matuaya Jun 5, 2026
1e8c81b
resetEditorTabsをDOMContentLoaded内に移動
matuaya Jun 6, 2026
11e194e
saveボタンのdisabled状態が正しくなるよう修正
matuaya Jun 6, 2026
a1c26d4
ユーザーフィクスチャからmentor_memo属性を削除
matuaya Jun 7, 2026
4ec5c32
メンターメモのテストをUser::MemoTestに集約し、重複テストを削除
matuaya Jun 7, 2026
08dbe04
クエリのスコープをmentor-memoに変更
matuaya Jun 9, 2026
235b0f4
メンター向けユーザーメモのデザインを整えた
machida Jun 16, 2026
6c3ea45
メンター向けユーザーメモのhtmlの構造を変更
machida Jun 16, 2026
e367840
プレビュータブが正しく切り替わるように修正
matuaya Jun 17, 2026
04f57ee
既存のメンターメモをMentorMemoにコピー
matuaya Jun 22, 2026
e1ce7c1
関数名をinitializeNewMemoからaddNewMemoに変更
matuaya Jun 24, 2026
6a42459
メモ追加処理をInitializeNewMentorMemoに分離
matuaya Jun 24, 2026
fd93424
空白のみの入力時にsaveボタンが無効になるよう修正
matuaya Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
@import "./application/blocks/user/_completed-practices-progress.css";
@import "./application/blocks/user/_following.css";
@import "./application/blocks/user/_group-company-name.css";
@import "./application/blocks/user/_mentor-memos.css";
@import "./application/blocks/user/_niconico-calendar.css";
@import "./application/blocks/user/_sns-links.css";
@import "./application/blocks/user/_user-data.css";
Expand Down
82 changes: 82 additions & 0 deletions app/assets/stylesheets/application/blocks/user/_mentor-memos.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
.mentor-memo-list {
display: flex;
flex-direction: column;
}

.mentor-memo {
font-size: 0.875rem;
line-height: 1.6;
}

.mentor-memo__inner {
padding: 1rem;
}

@media (width <= 47.9375em) {
.mentor-memo__inner {
padding: 0.75rem;
}
}

.mentor-memo:not(:last-child) {
border-bottom: solid 1px var(--border-tint);
}

.mentor-memo__meta {
display: flex;
align-items: center;
gap: 0.375rem;
margin-bottom: 0.5rem;
}

.mentor-memo__writer-link {
display: inline-flex;
flex: 0 0 auto;
line-height: 1;
}

.mentor-memo__writer {
font-size: 0.875rem;
line-height: 1.4;
font-weight: 600;
}

.mentor-memo__created-at {
margin-left: auto;
font-size: 0.75rem;
line-height: 1.4;
white-space: nowrap;
color: var(--muted-text);
}
Comment on lines +25 to +50

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

狭い画面でメタ情報が横にはみ出します。

.mentor-memo__meta が1行固定の flex で、__writer-link__created-at もほぼ縮まないため、ユーザー名が長いケースだと日時と衝突してカード幅を超えます。折り返しを許可するか、少なくともモバイルでは日時を次行へ落とせるようにしておいた方が安全です。

💡 修正例
 .mentor-memo__meta {
   display: flex;
   align-items: center;
+  flex-wrap: wrap;
   gap: 0.375rem;
   margin-bottom: 0.5rem;
 }

 .mentor-memo__writer-link {
   display: inline-flex;
-  flex: 0 0 auto;
+  flex: 0 1 auto;
+  min-width: 0;
   line-height: 1;
 }

+@media (width <= 47.9375em) {
+  .mentor-memo__created-at {
+    margin-left: 0;
+  }
+}
📝 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
.mentor-memo__meta {
display: flex;
align-items: center;
gap: 0.375rem;
margin-bottom: 0.5rem;
}
.mentor-memo__writer-link {
display: inline-flex;
flex: 0 0 auto;
line-height: 1;
}
.mentor-memo__writer {
font-size: 0.875rem;
line-height: 1.4;
font-weight: 600;
}
.mentor-memo__created-at {
margin-left: auto;
font-size: 0.75rem;
line-height: 1.4;
white-space: nowrap;
color: var(--muted-text);
}
.mentor-memo__meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.375rem;
margin-bottom: 0.5rem;
}
.mentor-memo__writer-link {
display: inline-flex;
flex: 0 1 auto;
min-width: 0;
line-height: 1;
}
.mentor-memo__writer {
font-size: 0.875rem;
line-height: 1.4;
font-weight: 600;
}
.mentor-memo__created-at {
margin-left: auto;
font-size: 0.75rem;
line-height: 1.4;
white-space: nowrap;
color: var(--muted-text);
}
`@media` (width <= 47.9375em) {
.mentor-memo__created-at {
margin-left: 0;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/assets/stylesheets/application/blocks/user/_mentor-memos.css` around
lines 25 - 50, The `.mentor-memo__meta` flex row is too rigid for narrow
screens, causing the writer and `__created-at` metadata to overflow or collide.
Update the styling in `mentor-memos.css` so the meta block can wrap or stack on
small widths, and make `__writer-link`/`__created-at` less restrictive (for
example by allowing wrapping or moving the date to a new line on mobile). Use
the existing `.mentor-memo__meta`, `.mentor-memo__writer-link`, and
`.mentor-memo__created-at` selectors to locate the fix.


.mentor-memo__body {
margin-bottom: 0.5rem;
}

.mentor-memo__actions {
display: flex;
align-items: center;
gap: 0.75rem;
}

.mentor-memo__action {
font-size: 0.75rem;
line-height: 1;
display: inline-flex;
align-items: center;
gap: 0.25rem;
cursor: pointer;
background-color: transparent;
border: none;
padding: 0;
color: var(--muted-text);
transition: color 0.2s ease-in;
}

.mentor-memo__action:hover {
color: var(--link-text);
}

.mentor-memo__action.is-danger:hover {
color: var(--danger);
}
44 changes: 37 additions & 7 deletions app/controllers/api/mentor_memos_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,55 @@

class API::MentorMemosController < API::BaseController
before_action :require_mentor_login_for_api
before_action -> { doorkeeper_authorize! :write }, only: %i[update], if: -> { doorkeeper_token.present? }
before_action -> { doorkeeper_authorize! :mentor }, only: %i[update], if: -> { doorkeeper_token.present? }
before_action :set_user, only: %i[update]
before_action -> { doorkeeper_authorize! :write }, only: %i[create update destroy], if: -> { doorkeeper_token.present? }
before_action -> { doorkeeper_authorize! :mentor }, only: %i[create update destroy], if: -> { doorkeeper_token.present? }
before_action :set_user, only: %i[create update destroy]
before_action :set_memo, only: %i[update destroy]
before_action :authorize_memo, only: %i[update destroy]

def create
memo = MentorMemo.new(mentor_memo_params)
memo.writer = current_user
memo.recipient = @user

if memo.save
render partial: 'users/mentor_memo', locals: { memo: }, status: :created
else
head :bad_request
end
end

def update
if @user.update_mentor_memo(user_params[:mentor_memo])
if @mentor_memo.update(mentor_memo_params)
head :ok
else
head :bad_request
end
end

def destroy
@mentor_memo.destroy

head :no_content
end
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private

def set_user
@user = User.find(params[:id])
@user = User.find(params[:user_id])
end

def set_memo
@mentor_memo = @user.received_memos.find(params[:id])
end

def user_params
params.require(:user).permit(:mentor_memo)
def mentor_memo_params
params.require(:mentor_memo).permit(:body)
end

def authorize_memo
return if @mentor_memo.writer == current_user || current_user.admin?

head :forbidden
end
end
1 change: 1 addition & 0 deletions app/controllers/products_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def show
Footprint.find_or_create_for(@product, current_user)
@footprints = Footprint.fetch_for_resource(@product)
@comments = @product.comments.order(:created_at)
@mentor_memos = @product.user.received_memos.includes(:writer).order(created_at: :desc)
respond_to do |format|
format.html
format.md
Expand Down
1 change: 1 addition & 0 deletions app/controllers/reports_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def show
@recent_reports = Report.list.where(user_id: @report.user.id).limit(10)
Footprint.find_or_create_for(@report, current_user)
@footprints = Footprint.fetch_for_resource(@report)
@mentor_memos = @report.user.received_memos.includes(:writer).order(created_at: :desc)
respond_to do |format|
format.html
format.md
Expand Down
1 change: 1 addition & 0 deletions app/controllers/talks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def index
def show
@comments = @talk.comments.order(:created_at)
@reports = @user.reports.list.limit(20)
@mentor_memos = @user.received_memos.includes(:writer).order(created_at: :desc)
end

private
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ def show
reports = @user.reports_with_learning_times
@study_streak = StudyStreak.new(reports, include_wip: false)

@mentor_memos = @user.received_memos.includes(:writer).order(created_at: :desc)

if logged_in?
render :show
else
Expand Down
2 changes: 1 addition & 1 deletion app/javascript/importmap_application.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import 'upload-image-to-article'
import 'header-dropdown'
import 'postal-code-address'
import 'editor-selection-form'
import 'user_mentor_memo'
import 'mentor-memos'
import 'invitation-url-updater'
import 'movie-fileinput'
import 'payment-methods-check-boxes'
Expand Down
134 changes: 134 additions & 0 deletions app/javascript/initializeMentorMemo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import TextareaInitializer from 'textarea-initializer'
import MarkdownInitializer from 'markdown-initializer'
import { put, destroy } from '@rails/request.js'

export default function initializeMemo(memo, userId) {
const memoId = memo.dataset.memo_id
const memoBody = memo.dataset.memo_body
let savedMemo = memoBody || ''

TextareaInitializer.initialize(`#js-memo-${memoId}`)
const markdownInitializer = new MarkdownInitializer()

const memoDisplay = memo.querySelector('.memo-display')
const memoDisplayContent = memo.querySelector('.memo-text')

const memoEditor = memo.querySelector('.memo-editor')
const editorTextarea = memo.querySelector('.a-markdown-input__textarea')
const editorPreview = memo.querySelector('.a-markdown-input__preview')

if (memoBody) {
memoDisplayContent.innerHTML = markdownInitializer.render(savedMemo)
editorPreview.innerHTML = markdownInitializer.render(savedMemo)
}

const editButton = memo.querySelector('.js-edit-memo')
if (editButton) {
editButton.addEventListener('click', () => {
toggleEditor()
resetEditorTabs()
})
}

const saveButton = memo.querySelector('.js-save-memo')
editorTextarea.addEventListener('input', () => {
saveButton.disabled = editorTextarea.value.trim().length === 0
})

saveButton.addEventListener('click', async () => {
try {
const editedMemo = editorTextarea.value
await updateMemo(memoId, editedMemo, userId)
savedMemo = editedMemo
toggleEditor()
memoDisplayContent.innerHTML = markdownInitializer.render(savedMemo)
} catch (error) {
console.warn(error)
}
})

const cancelButton = memo.querySelector('.js-cancel-memo')
cancelButton.addEventListener('click', () => {
toggleEditor()
editorTextarea.value = savedMemo
editorPreview.innerHTML = markdownInitializer.render(savedMemo)
saveButton.disabled = editorTextarea.value.trim().length === 0
})

const deleteButton = memo.querySelector('.js-delete-memo')
if (deleteButton) {
deleteButton.addEventListener('click', async () => {
if (window.confirm('本当によろしいですか?')) {
try {
await deleteMemo(memoId, userId)
memo.remove()

const mentorMemos = document.querySelector('.mentor-memos')
const emptyMessage = mentorMemos.querySelector('.o-empty-message')
const memoList = mentorMemos.querySelectorAll('.mentor-memo')

if (!memoList.length) emptyMessage.classList.remove('is-hidden')
} catch (error) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
console.warn(error)
}
}
})
}

const editorTab = memoEditor.querySelector('.editor-tab')
const editorTabContent = memoEditor.querySelector('.is-editor')
const previewTab = memoEditor.querySelector('.preview-tab')
const previewTabContent = memoEditor.querySelector('.is-preview')

const tabElements = [
editorTab,
editorTabContent,
previewTab,
previewTabContent
]
editorTab.addEventListener('click', () =>
toggleClass(tabElements, 'is-active')
)
previewTab.addEventListener('click', () => {
editorPreview.innerHTML = markdownInitializer.render(editorTextarea.value)
toggleClass(tabElements, 'is-active')
})

async function updateMemo(memoId, memoContent, userId) {
const params = {
mentor_memo: {
body: memoContent
}
}
const response = await put(`/api/users/${userId}/mentor_memos/${memoId}`, {
body: params
})

if (!response.ok) throw new Error('Failed to update')
}

async function deleteMemo(memoId, userId) {
const response = await destroy(
`/api/users/${userId}/mentor_memos/${memoId}`
)

if (!response.ok) throw new Error('Failed to delete')
}

function toggleEditor() {
toggleClass([memoDisplay, memoEditor], 'is-hidden')
}

function toggleClass(elements, className) {
elements.forEach((element) => {
element.classList.toggle(className)
})
}

function resetEditorTabs() {
editorTab.classList.add('is-active')
editorTabContent.classList.add('is-active')
previewTab.classList.remove('is-active')
previewTabContent.classList.remove('is-active')
}
}
Loading
Loading