diff --git a/app/javascript/components/user_mentor_memo.vue b/app/javascript/components/user_mentor_memo.vue deleted file mode 100644 index 7c59c0d3f54..00000000000 --- a/app/javascript/components/user_mentor_memo.vue +++ /dev/null @@ -1,174 +0,0 @@ - - - diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 0a07e55d1c1..9ee5b8eb3bf 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -55,6 +55,7 @@ import '../register-address.js' import '../upload-image-to-article.js' import '../header-dropdown.js' import '../editor-selection-form.js' +import '../user_mentor_memo.js' import VueMounter from '../VueMounter.js' import Announcements from '../components/announcements.vue' @@ -66,7 +67,6 @@ import UsersAnswers from '../components/users-answers.vue' import User from '../components/user.vue' import Watches from '../components/watches.vue' import WatchToggle from '../components/watch-toggle.vue' -import UserMentorMemo from '../components/user_mentor_memo.vue' import UserRecentReports from '../components/user-recent-reports.vue' import Footprints from '../components/footprints.vue' import QuestionAnswers from '../components/question-answers.vue' @@ -87,7 +87,6 @@ mounter.addComponent(UsersAnswers) mounter.addComponent(User) mounter.addComponent(Watches) mounter.addComponent(WatchToggle) -mounter.addComponent(UserMentorMemo) mounter.addComponent(UserRecentReports) mounter.addComponent(Footprints) mounter.addComponent(QuestionAnswers) diff --git a/app/javascript/user_mentor_memo.js b/app/javascript/user_mentor_memo.js new file mode 100644 index 00000000000..3aec4233a35 --- /dev/null +++ b/app/javascript/user_mentor_memo.js @@ -0,0 +1,144 @@ +import CSRF from 'csrf' +import TextareaInitializer from 'textarea-initializer' +import MarkdownInitializer from 'markdown-initializer' + +document.addEventListener('DOMContentLoaded', () => { + const mentorMemo = document.querySelector('.user-mentor-memo') + if (mentorMemo) { + TextareaInitializer.initialize('#js-user-mentor-memo') + const markdownInitializer = new MarkdownInitializer() + const userId = mentorMemo.dataset.user_id + let savedMemo = '' + + const memoDisplay = mentorMemo.querySelector('.memo-display') + const memoEditor = mentorMemo.querySelector('.memo-editor') + + const placeholder = memoDisplay.querySelector('.a-placeholder') + const emptyMessage = memoDisplay.querySelector('.o-empty-message') + const memoDisplayContent = memoDisplay.querySelector( + '.user-mentor-memo-content' + ) + const memoEditorPreview = memoEditor.querySelector( + '.a-markdown-input__preview' + ) + const editorTextarea = memoEditor.querySelector( + '.a-markdown-input__textarea' + ) + + fetch(`/api/users/${userId}.json`, { + method: 'GET', + headers: { + 'X-Requested-With': 'XMLHttpRequest' + }, + credentials: 'same-origin', + redirect: 'manual' + }) + .then((response) => { + return response.json() + }) + .then((json) => { + if (json.mentor_memo) { + savedMemo = json.mentor_memo + } + placeholder.classList.add('is-hidden') + if (savedMemo.length === 0) { + emptyMessage.classList.remove('is-hidden') + } else { + memoDisplayContent.classList.remove('is-hidden') + editorTextarea.value = savedMemo + switchMemoDisplay(memoDisplay, savedMemo) + memoDisplayContent.innerHTML = markdownInitializer.render(savedMemo) + memoEditorPreview.innerHTML = markdownInitializer.render(savedMemo) + } + }) + .catch((error) => { + console.warn(error) + }) + + const editButton = memoDisplay.querySelector('.card-footer-actions__action') + const modalElements = [memoDisplay, memoEditor] + editButton.addEventListener('click', () => + toggleClass(modalElements, 'is-hidden') + ) + + const saveButton = memoEditor.querySelector('.is-primary') + saveButton.addEventListener('click', () => { + toggleClass(modalElements, 'is-hidden') + savedMemo = editorTextarea.value + updateMemo(savedMemo, userId) + memoDisplayContent.innerHTML = markdownInitializer.render(savedMemo) + switchMemoDisplay(memoDisplay, savedMemo) + }) + + const cancelButton = memoEditor.querySelector('.is-secondary') + cancelButton.addEventListener('click', () => { + toggleClass(modalElements, 'is-hidden') + editorTextarea.value = savedMemo + memoEditorPreview.innerHTML = markdownInitializer.render(savedMemo) + }) + + editorTextarea.addEventListener('change', () => { + memoEditorPreview.innerHTML = markdownInitializer.render( + editorTextarea.value + ) + }) + + 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', () => + toggleClass(tabElements, 'is-active') + ) + } +}) + +function updateMemo(memo, userId) { + const params = { + user: { + mentor_memo: memo + } + } + fetch(`/api/mentor_memos/${userId}`, { + method: 'PUT', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Content-Type': 'application/json; charset=utf-8', + 'X-CSRF-Token': CSRF.getToken() + }, + credentials: 'same-origin', + redirect: 'manual', + body: JSON.stringify(params) + }) + .then((response) => { + return response + }) + .catch((error) => { + console.warn(error) + }) +} + +function switchMemoDisplay(memoDisplay, memo) { + const memoDisplayContent = memoDisplay.querySelector( + '.user-mentor-memo-content' + ) + const emptyMessage = memoDisplay.querySelector('.o-empty-message') + memoDisplayContent.classList.toggle('is-hidden', memo.length === 0) + emptyMessage.classList.toggle('is-hidden', memo.length !== 0) +} + +function toggleClass(elements, className) { + elements.forEach((element) => { + element.classList.toggle(className) + }) +} diff --git a/app/views/products/show.html.slim b/app/views/products/show.html.slim index 5c749382290..d8de8816aa7 100644 --- a/app/views/products/show.html.slim +++ b/app/views/products/show.html.slim @@ -79,7 +79,7 @@ header.page-header .user-info = render 'users/user_secret_attributes', user: @product.user = render 'users/metas', user: @product.user - div(data-vue="UserMentorMemo" data-vue-user-id:number="#{@product.user.id}" data-vue-products-mode:boolean="#{true}") + = render 'users/user_mentor_memo', user_id: @product.user.id .side-tabs-contents__item#side-tabs-content-4 div(data-vue="UserProducts" data-vue-user-id:number="#{@product.user.id}" data-vue-is-mentor:boolean="#{false}" data-vue-current-user-id:number="#{current_user.id}" data-vue-display-user-icon:boolean="false") diff --git a/app/views/reports/show.html.slim b/app/views/reports/show.html.slim index 3cc9691e9a0..eacbea64057 100644 --- a/app/views/reports/show.html.slim +++ b/app/views/reports/show.html.slim @@ -64,7 +64,7 @@ .user-info = render 'users/user_secret_attributes', user: @report.user = render 'users/metas', user: @report.user - div(data-vue="UserMentorMemo" data-vue-user-id:number="#{@report.user_id}") + = render 'users/user_mentor_memo', user_id: @report.user_id .side-tabs-contents__item#side-tabs-content-3.is-only-mentor .card-list.a-card - if @products.present? diff --git a/app/views/talks/show.html.slim b/app/views/talks/show.html.slim index 28c3d23f8c4..7a6cffb4c06 100644 --- a/app/views/talks/show.html.slim +++ b/app/views/talks/show.html.slim @@ -94,6 +94,6 @@ method: :patch, data: { confirm: '本当によろしいですか?' }, class: 'a-button is-sm is-danger is-block' - div(data-vue="UserMentorMemo" data-vue-user-id:number="#{@user.id}" data-vue-products-mode:boolean="#{true}") + = render 'users/user_mentor_memo', user_id: @user.id .side-tabs-contents__item#side-tabs-content-2 = react_component('Reports', userId: @user.id, displayUserIcon: false, displayPagination: false) diff --git a/app/views/users/_user_mentor_memo.html.slim b/app/views/users/_user_mentor_memo.html.slim new file mode 100644 index 00000000000..688207c2600 --- /dev/null +++ b/app/views/users/_user_mentor_memo.html.slim @@ -0,0 +1,53 @@ +section.a-card.is-memo.is-only-mentor.user-mentor-memo data-user_id=user_id + .memo-display + .description + header.card-header.is-sm + h2.card-header__title + | メンター向けユーザーメモ + hr.a-border-tint + .card-body + .card__description + .a-long-text.is-md.a-placeholder + p + p + p + p + p + p + .o-empty-message.is-hidden + .o-empty-message__icon + i.fa-regular.fa-sad-tear + .o-empty-message__text + | ユーザーメモはまだありません。 + .a-long-text.is-md.user-mentor-memo-content.is-hidden + hr.a-border-tint + footer.card-footer + .card-main-actions + .card-main-actions__items + .card-main-actions__item + button.card-footer-actions__action.a-button.is-sm.is-secondary.is-block + i.fa-solid.fa-pen + | 編集 + .memo-editor.is-hidden + .form-tabs.js-tabs + .form-tabs__tab.js-tabs__tab.editor-tab.is-active + | メモ + .form-tabs__tab.js-tabs__tab.preview-tab + | プレビュー + .card-body + .card__description + .a-markdown-input.js-markdown-parent + .a-markdown-input__inner.is-editor.js-tabs__content.is-active + textarea.a-text-input.a-markdown-input__textarea id='js-user-mentor-memo' data-preview='#user-mentor-memo-preview' name='user[memo]' + .a-markdown-input__inner.is-preview.js-tabs__content + .a-long-text.is-md.a-markdown-input__preview + hr.a-border-tint + .card-footer + .card-main-actions + .card-main-actions__items + .card-main-actions__item + button.a-button.is-sm.is-primary.is-block + | 保存する + .card-main-actions__item + button.a-button.is-sm.is-secondary.is-block + | キャンセル diff --git a/app/views/users/show.html.slim b/app/views/users/show.html.slim index 096237e91a0..e5c4af25c86 100644 --- a/app/views/users/show.html.slim +++ b/app/views/users/show.html.slim @@ -68,7 +68,7 @@ .col-xs-12.col-lg-6.col-xxl-6 - if admin_or_mentor_login? - div(data-vue="UserMentorMemo" data-vue-user-id:number="#{@user.id}") + = render 'users/user_mentor_memo', user_id: @user.id - unless @user.total_learning_time.zero? || @user.mentor? = react_component 'Grass', { userId: @user.id }, { class: 'a-card' } - if @user.student_or_trainee?