diff --git a/app/controllers/api/bookmarks_controller.rb b/app/controllers/api/bookmarks_controller.rb
index ff6830baf51..1767fd7c066 100644
--- a/app/controllers/api/bookmarks_controller.rb
+++ b/app/controllers/api/bookmarks_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class API::BookmarksController < API::BaseController
- PAGER_NUMBER = 25
+ PAGER_NUMBER = 20
def index
per = params[:per] || PAGER_NUMBER
diff --git a/app/controllers/current_user/bookmarks_controller.rb b/app/controllers/current_user/bookmarks_controller.rb
index 6e89ba1a89c..d24d54732e6 100644
--- a/app/controllers/current_user/bookmarks_controller.rb
+++ b/app/controllers/current_user/bookmarks_controller.rb
@@ -1,7 +1,20 @@
# frozen_string_literal: true
class CurrentUser::BookmarksController < ApplicationController
- def index
- @user = current_user
+ PAGER_NUMBER = 20
+
+ before_action :set_bookmarks, only: [:index]
+
+ def index; end
+
+ def destroy
+ current_user.bookmarks.find(params[:id]).destroy
+ head :no_content
+ end
+
+ private
+
+ def set_bookmarks
+ @bookmarks = current_user.bookmarks.preload(bookmarkable: :user).order(created_at: :desc, id: :desc).page(params[:page]).per(PAGER_NUMBER)
end
end
diff --git a/app/javascript/bookmarks-delete-button-visibility.js b/app/javascript/bookmarks-delete-button-visibility.js
new file mode 100644
index 00000000000..47ad0e9f8a5
--- /dev/null
+++ b/app/javascript/bookmarks-delete-button-visibility.js
@@ -0,0 +1,6 @@
+export function toggleDeleteButtonVisibility(editToggle, deleteButtons) {
+ const displayStyle = editToggle.checked ? 'block' : 'none'
+ for (const button of deleteButtons) {
+ button.style.display = displayStyle
+ }
+}
diff --git a/app/javascript/bookmarks-edit-button.js b/app/javascript/bookmarks-edit-button.js
deleted file mode 100644
index 76407116d92..00000000000
--- a/app/javascript/bookmarks-edit-button.js
+++ /dev/null
@@ -1,23 +0,0 @@
-document.addEventListener('DOMContentLoaded', () => {
- const bookMarksEditButton = document.getElementById('bookmark_edit')
- const bookmarkDeleteButton = document.getElementsByClassName(
- 'js-bookmark-delete-button'
- )
- if (bookMarksEditButton && bookmarkDeleteButton) {
- for (let i = 0; i < bookmarkDeleteButton.length; i++) {
- bookmarkDeleteButton[i].style.display = 'none'
- }
-
- bookMarksEditButton.addEventListener('click', () => {
- if (bookMarksEditButton.checked) {
- for (let i = 0; i < bookmarkDeleteButton.length; i++) {
- bookmarkDeleteButton[i].style.display = 'block'
- }
- } else {
- for (let i = 0; i < bookmarkDeleteButton.length; i++) {
- bookmarkDeleteButton[i].style.display = 'none'
- }
- }
- })
- }
-})
diff --git a/app/javascript/bookmarks.js b/app/javascript/bookmarks.js
new file mode 100644
index 00000000000..7033ef8c2f9
--- /dev/null
+++ b/app/javascript/bookmarks.js
@@ -0,0 +1,83 @@
+import { get, destroy } from '@rails/request.js'
+import { toggleDeleteButtonVisibility } from './bookmarks-delete-button-visibility'
+
+const EDIT_MODE_KEY = 'bookmark_edit_mode'
+const DELETE_BUTTON_CLASS = 'js-bookmark-delete-button'
+
+document.addEventListener('DOMContentLoaded', () => {
+ const editButton = document.getElementById('bookmark_edit')
+ const pageBody = document.querySelector('.page-body')
+ if (!pageBody) return
+
+ const savedValue = sessionStorage.getItem(EDIT_MODE_KEY)
+ const savedMode = savedValue === 'true'
+ initialize(savedMode)
+
+ if (editButton) {
+ editButton.addEventListener('change', () => {
+ const deleteButtons = document.getElementsByClassName(DELETE_BUTTON_CLASS)
+ toggleDeleteButtonVisibility(editButton, deleteButtons)
+ sessionStorage.setItem(EDIT_MODE_KEY, editButton.checked)
+ })
+ }
+
+ document.addEventListener('click', async (event) => {
+ const deleteButton = event.target.closest('.bookmark-delete-button')
+ if (!deleteButton) return
+
+ deleteButton.disabled = true
+
+ try {
+ const url = deleteButton.dataset.url
+ const response = await destroy(url)
+
+ if (!response.ok) {
+ throw new Error(`削除に失敗しました。(ステータス: ${response.status})`)
+ }
+
+ const params = new URLSearchParams(location.search)
+ const currentPage = parseInt(params.get('page') || '1', 10)
+ const newPageMain = await fetchPageMain(currentPage)
+
+ // 空ページの場合は1ページ前にフォールバック
+ let pageToShow = newPageMain
+ if (currentPage > 1 && newPageMain.querySelector('.o-empty-message')) {
+ pageToShow = await fetchPageMain(currentPage - 1)
+ }
+ document.querySelector('.page-body').replaceWith(pageToShow)
+
+ const savedModeAfterDelete =
+ sessionStorage.getItem(EDIT_MODE_KEY) === 'true'
+ initialize(savedModeAfterDelete)
+ } catch (error) {
+ console.warn(error)
+ deleteButton.disabled = false
+ }
+ })
+})
+
+window.addEventListener('beforeunload', () => {
+ const isBookmarkPage = location.pathname.includes('/current_user/bookmarks')
+ if (!isBookmarkPage) {
+ sessionStorage.removeItem(EDIT_MODE_KEY)
+ }
+})
+
+const fetchPageMain = async (page) => {
+ const bookmarkUrl = `/current_user/bookmarks?page=${page}`
+ const response = await get(bookmarkUrl, { responseKind: 'html' })
+ const html = await response.text
+ const parser = new DOMParser()
+ const parsedDocument = parser.parseFromString(html, 'text/html')
+ return parsedDocument.querySelector('.page-body')
+}
+
+const initialize = (deleteMode = false) => {
+ const editButton = document.getElementById('bookmark_edit')
+ const deleteButtons = document.getElementsByClassName(DELETE_BUTTON_CLASS)
+
+ if (editButton && deleteButtons.length > 0) {
+ editButton.checked = deleteMode
+ toggleDeleteButtonVisibility(editButton, deleteButtons)
+ }
+}
diff --git a/app/javascript/components/Bookmarks.jsx b/app/javascript/components/Bookmarks.jsx
deleted file mode 100644
index db737cd07f3..00000000000
--- a/app/javascript/components/Bookmarks.jsx
+++ /dev/null
@@ -1,240 +0,0 @@
-import React, { useState, useEffect, useRef } from 'react'
-import useSWR, { useSWRConfig } from 'swr'
-import fetcher from '../fetcher'
-import { destroy } from '@rails/request.js'
-import userIcon from '../user-icon.js'
-import Pagination from './Pagination'
-import usePage from './hooks/usePage'
-import { formatDateToJapanese } from '../dateFormatter'
-
-export default function Bookmarks() {
- const [editable, setEditable] = useState(false)
- const per = 20
- const { page, setPage } = usePage()
- const bookmarksUrl = `/api/bookmarks.json?page=${page}&per=${per}`
-
- const { data, error } = useSWR(bookmarksUrl, fetcher)
- if (error) return <>エラーが発生しました。>
- if (!data) return <>ロード中…>
-
- if (data.totalPages === 0) {
- return
- } else {
- return (
-
-
-
- {/* .page-main-header */}
-
-
-
- {data.totalPages > 1 && (
-
- )}
-
-
- {data.bookmarks.map((bookmark) => {
- return (
-
- )
- })}
-
-
- {data.totalPages > 1 && (
-
- )}
-
- {/* .container */}
-
- {/* .page-body */}
-
- {/* .page-main */}
-
- )
- }
-}
-
-const NoBookmarks = () => {
- return (
-
-
- {/* .page-main-header */}
-
-
-
-
-
-
- ブックマークはまだありません。
-
-
-
-
-
- )
-}
-
-const EditButton = ({ editable, setEditable }) => {
- return (
- <>
-
-
- >
- )
-}
-
-const Bookmark = ({ bookmark, editable, bookmarksUrl }) => {
- // userIconの非React化により、useRef,useEffectを導入している。
- const userIconRef = useRef(null)
- useEffect(() => {
- const linkClass = 'card-list-item__user-link'
- const imgClasses = ['card-list-item__user-icon', 'a-user-icon']
-
- const userIconElement = userIcon({
- user: bookmark.user,
- linkClass,
- imgClasses
- })
-
- if (userIconRef.current) {
- userIconRef.current.innerHTML = ''
- userIconRef.current.appendChild(userIconElement)
- }
- }, [bookmark.user])
-
- const date = bookmark.reported_on || bookmark.created_at
- const createdAt = formatDateToJapanese(date)
- const { mutate } = useSWRConfig()
- const afterDelete = async (id) => {
- try {
- const response = await destroy(`/api/bookmarks/${id}.json`)
- if (response.ok) {
- mutate(bookmarksUrl)
- } else {
- console.warn('削除に失敗しました。')
- }
- } catch (error) {
- console.warn(error)
- }
- }
-
- return (
-
-
- {bookmark.modelName === 'Talk' ? (
-
- ) : (
-
{bookmark.modelNameI18n}
- )}
-
-
- {bookmark.modelName !== 'Talk' && (
-
-
-
-
-
-
-
-
-
-
-
- )}
-
- {editable && (
-
- )}
-
-
- )
-}
-
-const DeleteButton = ({ id, afterDelete }) => {
- return (
-
-
afterDelete(id)}>
- 削除
-
-
- )
-}
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 42c89b21ff5..c8de1feb0c6 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -44,7 +44,7 @@ import '../learning-completion-message.js'
import '../choices-ui.js'
import '../training-info-toggler.js'
import '../welcome_message_for_adviser.js'
-import '../bookmarks-edit-button.js'
+import '../bookmarks.js'
import '../hibernation_agreements.js'
import '../current-date-time-setter.js'
import '../modal-switcher.js'
diff --git a/app/views/current_user/bookmarks/_empty.html.slim b/app/views/current_user/bookmarks/_empty.html.slim
new file mode 100644
index 00000000000..2cf4dcbc87b
--- /dev/null
+++ b/app/views/current_user/bookmarks/_empty.html.slim
@@ -0,0 +1,11 @@
+.page-main-header
+ .container
+ .page-main-header__inner
+ .page-main-header__start
+ h1.page-main-header__title ブックマーク
+hr.a-border
+.page-body
+ .o-empty-message
+ .o-empty-message__icon
+ i.fa-regular.fa-face-sad-tear
+ p.o-empty-message__text ブックマークはまだありません。
diff --git a/app/views/current_user/bookmarks/_list.html.slim b/app/views/current_user/bookmarks/_list.html.slim
new file mode 100644
index 00000000000..6d6d8a817c8
--- /dev/null
+++ b/app/views/current_user/bookmarks/_list.html.slim
@@ -0,0 +1,59 @@
+.page-main-header
+ .container
+ .page-main-header__inner
+ .page-main-header__start
+ h1.page-main-header__title ブックマーク
+ .page-main-header__end
+ .page-header-actions
+ .page-header-actions__items
+ .page-header-actions__item.js-bookmark-edit-toggle
+ .form-item.is-inline
+ label.a-form-label for="card-list-tools__action"
+ | 編集
+ label.a-on-off-checkbox.is-sm
+ input#bookmark_edit[name="card-list-tools__action" type="checkbox"]
+ span#spec-edit-mode
+hr.a-border
+.page-body
+ .container.is-md
+ - if bookmarks.total_pages > 1
+ = paginate bookmarks
+
+ .card-list.a-card
+ .card-list__items
+ - bookmarks.each do |bookmark|
+ .card-list-item class="is-#{bookmark.bookmarkable_type.downcase}" id="bookmark-#{bookmark.id}"
+ .card-list-item__inner
+ - if bookmark.bookmarkable_type == 'Talk'
+ = image_tag bookmark.bookmarkable.user.avatar_url, class: 'card-list-item__user-icon a-user-icon'
+ - else
+ .card-list-item__label = bookmark.bookmarkable.model_name.human
+
+ .card-list-item__rows
+ .card-list-item__row
+ .card-list-item-title
+ .card-list-item-title__title
+ = link_to polymorphic_path(bookmark.bookmarkable), class: 'card-list-item-title__link a-text-link' do
+ = bookmark.bookmarkable_type == 'Talk' ? "#{bookmark.bookmarkable.user.long_name} さんの相談部屋" : bookmark.bookmarkable.title
+
+ - if bookmark.bookmarkable_type != 'Talk'
+ .card-list-item__row
+ .card-list-item__summary
+ p = truncate(search_summary(bookmark.bookmarkable, ''), length: 100)
+
+ .card-list-item__row
+ .card-list-item-meta
+ .card-list-item-meta__item
+ = link_to bookmark.bookmarkable.user.url, class: 'a-user-name' do
+ = "#{bookmark.bookmarkable.user.login_name}(#{bookmark.bookmarkable.user.name_kana})"
+ .card-list-item-meta__item
+ - date = bookmark.bookmarkable_type == 'Report' ? bookmark.bookmarkable.reported_on.to_time : bookmark.bookmarkable.created_at
+ time.a-meta datetime=date
+ = l(date, format: :default)
+
+ .card-list-item__option
+ .js-bookmark-delete-button
+ button.bookmark-delete-button.a-bookmark-button.a-button.is-sm.is-block.is-main[type="button" data-url=current_user_bookmark_path(bookmark)] 削除
+
+ - if bookmarks.total_pages > 1
+ = paginate bookmarks
diff --git a/app/views/current_user/bookmarks/index.html.slim b/app/views/current_user/bookmarks/index.html.slim
index e054472fc3c..94670f816d6 100644
--- a/app/views/current_user/bookmarks/index.html.slim
+++ b/app/views/current_user/bookmarks/index.html.slim
@@ -4,4 +4,8 @@
= render 'home/page_header'
= dashboard_page_tabs(active_tab: 'ブックマーク')
-= react_component 'Bookmarks'
+.page-main
+ - if @bookmarks.empty?
+ = render 'current_user/bookmarks/empty'
+ - else
+ = render 'current_user/bookmarks/list', bookmarks: @bookmarks
diff --git a/config/routes/current_user.rb b/config/routes/current_user.rb
index dda7d75dc03..ac7e86d9f7e 100644
--- a/config/routes/current_user.rb
+++ b/config/routes/current_user.rb
@@ -5,6 +5,6 @@
resources :reports, only: %i(index)
resources :products, only: %i(index)
resources :watches, only: %i(index)
- resources :bookmarks, only: %i(index)
+ resources :bookmarks, only: %i(index destroy)
end
end
diff --git a/test/system/bookmark/talks_test.rb b/test/system/bookmark/talks_test.rb
index 43aa50aee15..69de2e18c2f 100644
--- a/test/system/bookmark/talks_test.rb
+++ b/test/system/bookmark/talks_test.rb
@@ -11,6 +11,8 @@ class Bookmark::TalkTest < ApplicationSystemTestCase
test 'show talk bookmark on lists' do
visit_with_auth '/current_user/bookmarks', 'komagata'
+ find_all('a.pagination__item-link', text: '2').first.click if !page.has_text?("#{@decorated_user.long_name} さんの相談部屋")
+
assert_text "#{@decorated_user.long_name} さんの相談部屋"
end
diff --git a/test/system/current_user/bookmarks_test.rb b/test/system/current_user/bookmarks_test.rb
index e8b56d01d97..b702c462b1d 100644
--- a/test/system/current_user/bookmarks_test.rb
+++ b/test/system/current_user/bookmarks_test.rb
@@ -26,7 +26,7 @@ class CurrentUser::BookmarksTest < ApplicationSystemTestCase
visit_with_auth '/current_user/bookmarks', 'kimura'
assert_selector 'label', text: '編集'
- assert_selector 'input#card-list-tools__action', visible: false
+ assert_selector 'input#bookmark_edit', visible: false
assert_text '作業週1日目'
assert_selector '.card-list-item__label', text: '日報'
@@ -104,8 +104,7 @@ class CurrentUser::BookmarksTest < ApplicationSystemTestCase
user_with_some_bookmarks.bookmarks.create!(bookmarkable_id: reports("report#{n}".to_sym).id, bookmarkable_type: 'Report')
end
visit_with_auth '/current_user/bookmarks', user_with_some_bookmarks.login_name
- # ページ遷移直後なのでreactコンポーネントが表示されるまで待つ
- within "[data-testid='bookmarks']" do
+ within '.page-main' do
assert_no_selector 'nav.pagination'
end
end
@@ -127,8 +126,7 @@ class CurrentUser::BookmarksTest < ApplicationSystemTestCase
user_with_many_bookmarks.bookmarks.create!(bookmarkable_id: reports("report#{n}".to_sym).id, bookmarkable_type: 'Report')
end
visit_with_auth '/current_user/bookmarks', user_with_many_bookmarks.login_name
- # ページ遷移直後なのでreactコンポーネントが表示されるまで待つ
- within "[data-testid='bookmarks']" do
+ within '.page-main' do
assert_selector 'nav.pagination', count: 2
end