diff --git a/app/javascript/components/question-answers.vue b/app/javascript/components/question-answers.vue
deleted file mode 100644
index 331217f50aa..00000000000
--- a/app/javascript/components/question-answers.vue
+++ /dev/null
@@ -1,114 +0,0 @@
-
-div
- template(v-if='question === null || currentUser === null')
- commentPlaceholder(v-for='num in placeholderCount', :key='num')
- template(v-else)
- template(v-if='hasAiQuestion && isAdminOrMentor()')
- ai_answer(:text='question.ai_answer')
- answers(
- :questionId='questionId',
- :questionUser='question.user',
- :currentUser='currentUser',
- @updateAnswerCount='updateAnswerCount',
- @solveQuestion='solveQuestion',
- @cancelSolveQuestion='cancelSolveQuestion')
-
-
diff --git a/app/javascript/debounce.js b/app/javascript/debounce.js
new file mode 100644
index 00000000000..4a8eaee20fb
--- /dev/null
+++ b/app/javascript/debounce.js
@@ -0,0 +1,7 @@
+export function debounce(func, wait) {
+ let timeout
+ return function (...args) {
+ clearTimeout(timeout)
+ timeout = setTimeout(() => func.apply(this, args), wait)
+ }
+}
diff --git a/app/javascript/initializeAnswer.js b/app/javascript/initializeAnswer.js
new file mode 100644
index 00000000000..af2cfd3d72c
--- /dev/null
+++ b/app/javascript/initializeAnswer.js
@@ -0,0 +1,274 @@
+import CSRF from 'csrf'
+import updateAnswerCount from './updateAnswerCount'
+import TextareaInitializer from 'textarea-initializer'
+import MarkdownInitializer from 'markdown-initializer'
+
+export default function initializeAnswer(answer) {
+ const questionId = answer.dataset.question_id
+ const answerId = answer.dataset.answer_id
+ const answerDescription = answer.dataset.answer_description
+ let savedAnswer = ''
+ TextareaInitializer.initialize(`#js-comment-${answerId}`)
+ const markdownInitializer = new MarkdownInitializer()
+
+ const answerDisplay = answer.querySelector('.answer-display')
+ const answerEditor = answer.querySelector('.answer-editor')
+ const answerDisplayContent = answerDisplay.querySelector('.a-long-text')
+
+ const answerEditorPreview = answerEditor.querySelector(
+ '.a-markdown-input__preview'
+ )
+ const editorTextarea = answerEditor.querySelector(
+ '.a-markdown-input__textarea'
+ )
+
+ if (answerDescription) {
+ answerDisplayContent.innerHTML =
+ markdownInitializer.render(answerDescription)
+ answerEditorPreview.innerHTML =
+ markdownInitializer.render(answerDescription)
+ }
+
+ const editButton = answerDisplay.querySelector('.card-main-actions__action')
+ const modalElements = [answerDisplay, answerEditor]
+ if (editButton) {
+ editButton.addEventListener('click', () => {
+ if (!savedAnswer) {
+ savedAnswer = editorTextarea.value
+ }
+ toggleVisibility(modalElements, 'is-hidden')
+ })
+ }
+
+ const saveButton = answerEditor.querySelector('.is-primary')
+ if (saveButton) {
+ saveButton.addEventListener('click', () => {
+ toggleVisibility(modalElements, 'is-hidden')
+ savedAnswer = editorTextarea.value
+ updateAnswer(answerId, savedAnswer)
+ answerDisplayContent.innerHTML = markdownInitializer.render(savedAnswer)
+ })
+ }
+
+ const cancelButton = answerEditor.querySelector('.is-secondary')
+ cancelButton.addEventListener('click', () => {
+ toggleVisibility(modalElements, 'is-hidden')
+ editorTextarea.value = savedAnswer
+ answerEditorPreview.innerHTML = markdownInitializer.render(savedAnswer)
+ })
+
+ editorTextarea.addEventListener('input', () => {
+ answerEditorPreview.innerHTML = markdownInitializer.render(
+ editorTextarea.value
+ )
+ })
+
+ const makeBestAnswerButton = answerDisplay.querySelector('.is-warning')
+ const cancelBestAnswerButton = answerDisplay.querySelector('.is-muted')
+ const answerBadgeElement = answerDisplay.querySelector('.answer-badge')
+ if (makeBestAnswerButton) {
+ makeBestAnswerButton.addEventListener('click', () => {
+ if (window.confirm('本当に宜しいですか?')) {
+ makeToBestAnswer(answerId, questionId)
+ answerBadgeElement.classList.remove('is-hidden')
+ answerBadgeElement.classList.add('correct-answer')
+ const parentElements = [
+ makeBestAnswerButton.parentNode,
+ cancelBestAnswerButton.parentNode
+ ]
+ toggleVisibility(parentElements, 'is-hidden')
+ const otherMakeBestAnswerButtons = document.querySelectorAll(
+ '.make-best-answer-button'
+ )
+ otherMakeBestAnswerButtons.forEach((button) => {
+ if (button.closest('.answer').dataset.answer_id !== answerId) {
+ button.classList.add('is-hidden')
+ }
+ })
+ }
+ })
+ }
+ if (cancelBestAnswerButton) {
+ cancelBestAnswerButton.addEventListener('click', () => {
+ if (window.confirm('本当に宜しいですか?')) {
+ cancelBestAnswer(answerId, questionId)
+ answerBadgeElement.classList.remove('correct-answer')
+ answerBadgeElement.classList.add('is-hidden')
+ const parentElements = [
+ makeBestAnswerButton.parentNode,
+ cancelBestAnswerButton.parentNode
+ ]
+ toggleVisibility(parentElements, 'is-hidden')
+ const otherCancelBestAnswerButtons = document.querySelectorAll(
+ '.make-best-answer-button'
+ )
+ otherCancelBestAnswerButtons.forEach((button) => {
+ if (button.closest('.answer').dataset.answer_id !== answerId) {
+ button.classList.remove('is-hidden')
+ }
+ })
+ }
+ })
+ }
+
+ const deleteButton = answerDisplay.querySelector(
+ '.card-main-actions__muted-action'
+ )
+ if (deleteButton) {
+ deleteButton.addEventListener('click', () => {
+ if (window.confirm('本当に宜しいですか?')) {
+ deleteAnswer(answerId)
+ if (answerBadgeElement.classList.contains('correct-answer')) {
+ cancelBestAnswer(answerId, questionId)
+ const otherCancelBestAnswerButtons = document.querySelectorAll(
+ '.make-best-answer-button'
+ )
+ otherCancelBestAnswerButtons.forEach((button) => {
+ if (button.closest('.answer').dataset.answer_id !== answerId) {
+ button.classList.remove('is-hidden')
+ }
+ })
+ }
+ }
+ })
+ }
+
+ const editTab = answerEditor.querySelector('.edit-answer-tab')
+ const editorTabContent = answerEditor.querySelector('.is-editor')
+ const previewTab = answerEditor.querySelector('.answer-preview-tab')
+ const previewTabContent = answerEditor.querySelector('.is-preview')
+
+ const tabElements = [editTab, editorTabContent, previewTab, previewTabContent]
+ editTab.addEventListener('click', () =>
+ toggleVisibility(tabElements, 'is-active')
+ )
+
+ previewTab.addEventListener('click', () =>
+ toggleVisibility(tabElements, 'is-active')
+ )
+
+ const createdAtElement = answer.querySelector('.thread-comment__created-at')
+ if (createdAtElement && navigator.clipboard) {
+ createdAtElement.addEventListener('click', () => {
+ const answerURL = location.href.split('#')[0] + '#answer_' + answerId
+ navigator.clipboard
+ .writeText(answerURL)
+ .then(() => {
+ createdAtElement.classList.add('is-active')
+ setTimeout(() => {
+ createdAtElement.classList.remove('is-active')
+ }, 4000)
+ })
+ .catch((error) => {
+ console.error(error)
+ })
+ })
+ }
+
+ function toggleVisibility(elements, className) {
+ elements.forEach((element) => {
+ element.classList.toggle(className)
+ })
+ }
+
+ function updateAnswer(answerId, description) {
+ if (description.length < 1) {
+ return null
+ }
+ const params = {
+ id: answerId,
+ answer: { description: description }
+ }
+ fetch(`/api/answers/${answerId}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-CSRF-Token': CSRF.getToken()
+ },
+ credentials: 'same-origin',
+ redirect: 'manual',
+ body: JSON.stringify(params)
+ }).catch((error) => {
+ console.warn(error)
+ })
+ }
+
+ function deleteAnswer(answerId) {
+ fetch(`/api/answers/${answerId}.json`, {
+ method: 'DELETE',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-CSRF-Token': CSRF.getToken()
+ },
+ credentials: 'same-origin',
+ redirect: 'manual'
+ })
+ .then(() => {
+ const deletedAnswer = document.querySelector(
+ `.thread-comment.answer[data-answer_id='${answerId}']`
+ )
+
+ if (deletedAnswer) {
+ deletedAnswer.parentNode.removeChild(deletedAnswer)
+ }
+
+ updateAnswerCount(false)
+ })
+ .catch((error) => {
+ console.warn(error)
+ })
+ }
+
+ function makeToBestAnswer(answerId, questionId) {
+ requestSolveQuestion(answerId, questionId, false)
+ .then(() => {
+ solveQuestion()
+ })
+ .catch((error) => {
+ console.warn(error)
+ })
+ }
+
+ function cancelBestAnswer(answerId, questionId) {
+ requestSolveQuestion(answerId, questionId, true)
+ .then(() => {
+ cancelSolveQuestion()
+ })
+ .catch((error) => {
+ console.warn(error)
+ })
+ }
+
+ function requestSolveQuestion(answerId, questionId, isCancel) {
+ const params = {
+ question_id: questionId
+ }
+
+ return fetch(`/api/answers/${answerId}/correct_answer`, {
+ method: isCancel ? 'PATCH' : 'POST',
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-CSRF-Token': CSRF.getToken()
+ },
+ credentials: 'same-origin',
+ redirect: 'manual',
+ body: JSON.stringify(params)
+ })
+ }
+
+ function solveQuestion() {
+ const statusLabel = document.querySelector('.js-solved-status')
+ statusLabel.classList.remove('is-danger')
+ statusLabel.classList.add('is-success')
+ statusLabel.textContent = '解決済'
+ }
+
+ function cancelSolveQuestion() {
+ const statusLabel = document.querySelector('.js-solved-status')
+ statusLabel.classList.remove('is-success')
+ statusLabel.classList.add('is-danger')
+ statusLabel.textContent = '未解決'
+ }
+}
diff --git a/app/javascript/learning.js b/app/javascript/learning.js
index f8961d32d15..afdbd42c157 100644
--- a/app/javascript/learning.js
+++ b/app/javascript/learning.js
@@ -18,10 +18,16 @@ document.addEventListener('DOMContentLoaded', () => {
redirect: 'manual',
body: params
})
- .then(() => {
- completeButton.classList.add('is-disabled')
- completeButton.textContent = '修了しています'
- document.getElementById('modal-learning_completion').checked = true // 修了モーダル表示のためのフラグを立てる
+ .then((response) => {
+ if (response.ok) {
+ completeButton.classList.add('is-disabled')
+ completeButton.textContent = '修了しています'
+ document.getElementById('modal-learning_completion').checked = true // 修了モーダル表示のためのフラグを立てる
+ } else {
+ response.json().then((data) => {
+ alert(data.error)
+ })
+ }
})
.catch((error) => {
console.warn(error)
diff --git a/app/javascript/micro-report-form-tabs.js b/app/javascript/micro-report-form-tabs.js
new file mode 100644
index 00000000000..30e7e90559b
--- /dev/null
+++ b/app/javascript/micro-report-form-tabs.js
@@ -0,0 +1,17 @@
+document.addEventListener('DOMContentLoaded', function () {
+ const tabLinks = document.querySelectorAll('.js-tab-link')
+ const tabPanes = document.querySelectorAll('.js-tab-pane')
+
+ tabLinks.forEach((tabLink) => {
+ tabLink.addEventListener('click', function (event) {
+ event.preventDefault()
+
+ tabLinks.forEach((l) => l.classList.remove('is-active'))
+ tabPanes.forEach((p) => p.classList.add('hidden'))
+
+ tabLink.classList.add('is-active')
+ const targetId = tabLink.dataset.target
+ document.getElementById(targetId).classList.remove('hidden')
+ })
+ })
+})
diff --git a/app/javascript/micro-reports.js b/app/javascript/micro-reports.js
new file mode 100644
index 00000000000..44fde6459ef
--- /dev/null
+++ b/app/javascript/micro-reports.js
@@ -0,0 +1,26 @@
+import { debounce } from './debounce.js'
+
+window.addEventListener('DOMContentLoaded', function () {
+ const textarea = document.getElementById('js-micro-report-textarea')
+ const microReports = document.getElementById('js-micro-reports')
+ const form = document.getElementById('js-micro-report-form')
+ const submitButton = document.getElementById('js-shortcut-submit')
+ let prevHeight = 0
+
+ const adjustPadding = () => {
+ const height = form.scrollHeight
+ if (height !== prevHeight) {
+ microReports.style.paddingBottom = height + 'px'
+ prevHeight = height
+ }
+ }
+
+ if (form) {
+ // Event listener for textarea input
+ submitButton.disabled = true
+ textarea.addEventListener('input', function () {
+ debounce(adjustPadding, 100)()
+ submitButton.disabled = textarea.value.trim() === ''
+ })
+ }
+})
diff --git a/app/javascript/new-answer.js b/app/javascript/new-answer.js
new file mode 100644
index 00000000000..532515f3280
--- /dev/null
+++ b/app/javascript/new-answer.js
@@ -0,0 +1,139 @@
+import CSRF from 'csrf'
+import TextareaInitializer from 'textarea-initializer'
+import MarkdownInitializer from 'markdown-initializer'
+import { toast } from './vanillaToast.js'
+import updateAnswerCount from './updateAnswerCount.js'
+import initializeAnswer from './initializeAnswer.js'
+import { initializeReaction } from './reaction.js'
+import store from './check-store.js'
+
+document.addEventListener('DOMContentLoaded', () => {
+ const newAnswer = document.querySelector('.new-answer')
+ if (newAnswer) {
+ TextareaInitializer.initialize('#js-new-comment')
+ const defaultTextareaSize =
+ document.getElementById('js-new-comment').scrollHeight
+ const markdownInitializer = new MarkdownInitializer()
+ const questionId = newAnswer.dataset.question_id
+ let savedAnswer = ''
+
+ const answerEditor = newAnswer.querySelector('.answer-editor')
+ const answerEditorPreview = answerEditor.querySelector(
+ '.a-markdown-input__preview'
+ )
+ const editorTextarea = answerEditor.querySelector(
+ '.a-markdown-input__textarea'
+ )
+
+ const saveButton = answerEditor.querySelector('.is-primary')
+ editorTextarea.addEventListener('input', () => {
+ answerEditorPreview.innerHTML = markdownInitializer.render(
+ editorTextarea.value
+ )
+ saveButton.disabled = editorTextarea.value.length === 0
+ })
+
+ saveButton.addEventListener('click', async () => {
+ savedAnswer = editorTextarea.value
+ const answerCreated = await createAnswer(savedAnswer, questionId)
+ if (answerCreated) {
+ editorTextarea.value = ''
+ answerEditorPreview.innerHTML = markdownInitializer.render(
+ editorTextarea.value
+ )
+ saveButton.disabled = true
+ updateAnswerCount(true)
+ updateWatchable(questionId)
+ if (previewTab.classList.contains('is-active')) {
+ toggleVisibility(tabElements, 'is-active')
+ }
+ resizeTextarea(editorTextarea, defaultTextareaSize)
+ }
+ })
+
+ const editTab = answerEditor.querySelector('.edit-answer-tab')
+ const editorTabContent = answerEditor.querySelector('.is-editor')
+ const previewTab = answerEditor.querySelector('.answer-preview-tab')
+ const previewTabContent = answerEditor.querySelector('.is-preview')
+
+ const tabElements = [
+ editTab,
+ editorTabContent,
+ previewTab,
+ previewTabContent
+ ]
+ editTab.addEventListener('click', () =>
+ toggleVisibility(tabElements, 'is-active')
+ )
+
+ previewTab.addEventListener('click', () =>
+ toggleVisibility(tabElements, 'is-active')
+ )
+ }
+})
+
+async function createAnswer(description, questionId) {
+ if (description.length < 1) {
+ return false
+ }
+ const params = {
+ question_id: questionId,
+ answer: {
+ description: description
+ }
+ }
+ try {
+ const response = await fetch('/api/answers', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-CSRF-Token': CSRF.getToken()
+ },
+ credentials: 'same-origin',
+ redirect: 'manual',
+ body: JSON.stringify(params)
+ })
+
+ if (response.ok) {
+ const html = await response.text()
+ initializeNewAnswer(html)
+ toast('回答を投稿しました!')
+ return true
+ } else {
+ const data = await response.json()
+ throw new Error(data.errors.join(', '))
+ }
+ } catch (error) {
+ console.warn(error)
+ return false
+ }
+}
+
+function toggleVisibility(elements, className) {
+ elements.forEach((element) => {
+ element.classList.toggle(className)
+ })
+}
+
+function updateWatchable(questionId) {
+ store.dispatch('setWatchable', {
+ watchableId: questionId,
+ watchableType: 'Question'
+ })
+}
+
+function resizeTextarea(textarea, defaultTextareaSize) {
+ textarea.style.height = `${defaultTextareaSize}px`
+}
+
+function initializeNewAnswer(html) {
+ const answersList = document.querySelector('.answers-list')
+ const answerDiv = document.createElement('div')
+ answerDiv.innerHTML = html
+ const newAnswerElement = answerDiv.firstElementChild
+ answersList.appendChild(newAnswerElement)
+ initializeAnswer(newAnswerElement)
+ const reactionElement = newAnswerElement.querySelector('.js-reactions')
+ initializeReaction(reactionElement)
+}
diff --git a/app/javascript/onbrowserjudge.js b/app/javascript/onbrowserjudge.js
new file mode 100644
index 00000000000..ee092894c55
--- /dev/null
+++ b/app/javascript/onbrowserjudge.js
@@ -0,0 +1,231 @@
+const OnBrowserJudge = {
+ dict: {
+ ready: '▶Run',
+ running: '■Stop',
+ preparation: 'In preparation',
+ case_name: 'Case Name',
+ status: 'Status',
+ AC: 'AC',
+ WA: 'WA',
+ RE: 'RE',
+ CE: 'CE',
+ IE: 'IE',
+ TLE: 'TLE',
+ MLE: 'MLE',
+ OLE: 'OLE',
+ WJ: 'WJ'
+ },
+
+ timeLimit: 2000,
+
+ initialData: null,
+
+ congratulations: () => {},
+
+ process: (program, _casename, _input) => program,
+
+ assertEqual: (expected, actual) => expected === actual.trimEnd(),
+
+ status: 'preparation',
+
+ updateStatus: function (status) {
+ this.status = status
+ const button = document.getElementById('run')
+ button.disabled = status === 'preparation'
+ button.innerHTML = this.dict[status]
+ },
+
+ runButtonPressed: function () {
+ switch (this.status) {
+ case 'ready':
+ this.run()
+ break
+ case 'running':
+ this.stop()
+ break
+ }
+ },
+
+ worker: null,
+
+ timer: null,
+
+ workerEvent: function (e) {
+ let d = null
+ switch (e.data[0]) {
+ case 'init':
+ this.worker.postMessage(['init', this.initialData])
+ break
+ case 'ready':
+ this.updateStatus('ready')
+ break
+ case 'executed':
+ d = e.data[1]
+ this.executed(d.testCase, d.output, d.error, d.errorMessage, d.execTime)
+ break
+ }
+ },
+
+ loadWorker: function (path) {
+ const baseURL = window.location.href
+ .replaceAll('\\', '/')
+ .replace(/\/[^/]*$/, '/')
+ const array = [`importScripts("${baseURL}${path}");`]
+ const blob = new Blob(array, { type: 'text/javascript' })
+ const url = window.URL.createObjectURL(blob)
+ return new Worker(url)
+ },
+
+ resetWorker: function () {
+ if (this.worker) this.worker.terminate()
+ this.worker = this.loadWorker(this.workerFile)
+ this.worker.addEventListener(
+ 'message',
+ (event) => {
+ this.workerEvent(event)
+ },
+ false
+ )
+ },
+
+ run: async function () {
+ if (this.status !== 'ready') return
+ this.updateStatus('running')
+ const autocopy = document.getElementById('autocopy')
+ if (!autocopy || autocopy.checked) this.copyProgram()
+ this.initializeResult()
+ this.restTests = Array.from(this.tests)
+ this.allPassed = true
+ this.program = this.getProgram()
+ this.nextTest()
+ },
+
+ nextTest: function () {
+ const testCase = this.restTests.shift()
+ const input = document.getElementById(`${testCase}_input`).innerText.trim()
+ const program = this.process(this.getProgram(), testCase, input)
+ this.timer = setTimeout(() => this.tle(testCase), this.timeLimit * 2)
+ this.worker.postMessage(['execute', { testCase, program, input }])
+ },
+
+ tle: function (testCase) {
+ this.updateResult(testCase, 'TLE', '', '', '', this.timeLimit * 2)
+ this.stop()
+ },
+
+ executed: function (testCase, output, error, errorMessage, execTime) {
+ clearTimeout(this.timer)
+ let result = 'AC'
+ if (error !== 0) {
+ result = error === 1 ? 'CE' : 'RE'
+ } else {
+ if (execTime > this.timeLimit) result = 'TLE'
+ const expected = document
+ .getElementById(`${testCase}_output`)
+ .innerText.trim()
+ if (!this.assertEqual(expected, output)) result = 'WA'
+ }
+
+ console.log('output:', output)
+ console.log('error:', error)
+ console.log('errorMessage:', errorMessage)
+
+ this.updateResult(testCase, result, output, error, errorMessage, execTime)
+ if (result !== 'AC') this.allPassed = false
+ if (this.restTests.length === 0) {
+ if (this.allPassed) setTimeout(this.congratulations, 20)
+ this.updateStatus('ready')
+ } else {
+ this.nextTest()
+ }
+ },
+
+ initializeResult: function () {
+ document.getElementById('result').innerHTML = `
+
+ ${this.dict.case_name} |
+ 出力 |
+ エラー |
+ ${this.dict.status} |
+
`
+
+ for (const testCase of OnBrowserJudge.tests) {
+ const tr = document.createElement('tr')
+ tr.innerHTML = `
+
${testCase} |
+
|
+
|
+
${this.dict.WJ} | `
+ document.getElementById('result').appendChild(tr)
+ tr.id = `${testCase}__items`
+ }
+ document.getElementById('result').scrollIntoView({ behavior: 'smooth' })
+ },
+
+ updateResult: function (
+ testCase,
+ result,
+ output,
+ _error,
+ errorMessage,
+ _execTime
+ ) {
+ document.getElementById(
+ `${testCase}_std_output`
+ ).innerHTML = `
${output}
`
+ document.getElementById(
+ `${testCase}_std_error`
+ ).innerHTML = `
${errorMessage}
`
+ const span =
+ `
${this.dict[result]}`
+ document.getElementById(`${testCase}_status`).innerHTML = span
+ document.getElementById(
+ `${testCase}__items`
+ ).className = `is-${result.toLowerCase()}`
+ },
+
+ stop: function () {
+ window.clearTimeout(this.timer)
+ Array.from(document.getElementsByClassName('wj')).forEach((elm) => {
+ elm.innerText = ''
+ })
+
+ this.updateStatus('preparation')
+ this.resetWorker()
+ },
+
+ copyProgram: function () {
+ navigator.clipboard.writeText(this.getProgram())
+ }
+}
+
+window.addEventListener('DOMContentLoaded', () => {
+ const editor = document.getElementById('code_editor')
+ if (!editor) return null
+
+ function getTestNames() {
+ return Array.from(document.getElementsByTagName('pre'))
+ .map((elm) => elm.id)
+ .filter(
+ (id) =>
+ id.match(/_input$/) &&
+ document.getElementById(id.replace(/_input$/, '_output'))
+ )
+ .map((id) => id.replace(/_input$/, ''))
+ }
+ OnBrowserJudge.tests = getTestNames()
+
+ function trimAllSampleCases() {
+ const samples = document.getElementsByClassName('sample')
+ for (const elm of samples) elm.innerText = elm.innerText.trim()
+ }
+ trimAllSampleCases()
+
+ OnBrowserJudge.updateStatus('preparation')
+ OnBrowserJudge.resetWorker()
+ document.getElementById('run').onclick = () =>
+ OnBrowserJudge.runButtonPressed()
+})
+
+export { OnBrowserJudge }
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 4fbed6ad80c..ac1dd67cd9f 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -27,7 +27,6 @@ import '../github_grass'
import '../following.js'
import '../hide-user.js'
import '../categories-practice.js'
-import '../products.js'
import '../courses-categories.js'
import '../courses-practices.js'
import '../no_learn.js'
@@ -55,10 +54,16 @@ import '../editor-selection-form.js'
import '../user_mentor_memo.js'
import '../invitation-url-updater.js'
import '../payment-methods-check-boxes.js'
-import '../product-checker.js'
+import '../product_checker.js'
+import '../product_checker_button.js'
import '../user-follow.js'
import '../sort-faq.js'
import '../sort-faq-category.js'
+import '../micro-report-form-tabs.js'
+import '../micro-reports.js'
+import '../answer.js'
+import '../new-answer.js'
+import '../coding-test.js'
import VueMounter from '../VueMounter.js'
import Questions from '../components/questions.vue'
@@ -67,7 +72,6 @@ import User from '../components/user.vue'
import Watches from '../components/watches.vue'
import WatchToggle from '../components/watch-toggle.vue'
import Footprints from '../components/footprints.vue'
-import QuestionAnswers from '../components/question-answers.vue'
import SadReports from '../components/sad_reports.vue'
import UserProducts from '../components/user-products.vue'
import ActionCompletedButton from '../components/action-completed-button.vue'
@@ -82,7 +86,6 @@ mounter.addComponent(User)
mounter.addComponent(Watches)
mounter.addComponent(WatchToggle)
mounter.addComponent(Footprints)
-mounter.addComponent(QuestionAnswers)
mounter.addComponent(SadReports)
mounter.addComponent(UserProducts)
mounter.addComponent(ActionCompletedButton)
diff --git a/app/javascript/product_checker.js b/app/javascript/product_checker.js
new file mode 100644
index 00000000000..ba7c9d2e438
--- /dev/null
+++ b/app/javascript/product_checker.js
@@ -0,0 +1,83 @@
+import Swal from 'sweetalert2'
+
+export default class ProductChecker {
+ constructor(productId, currentUserId, url, method, token) {
+ this.productId = productId
+ this.currentUserId = currentUserId
+ this.url = url
+ this.method = method
+ this.token = token
+ }
+
+ toast(title, status = 'success') {
+ Swal.fire({
+ title: title,
+ toast: true,
+ position: 'top-end',
+ showConfirmButton: false,
+ timer: 3000,
+ timerProgressBar: true,
+ customClass: { popup: `is-${status}` }
+ })
+ }
+
+ updateButton(button) {
+ let buttonClass, buttonLabel, dataMethod, fasClass
+
+ if (this.id !== null) {
+ buttonClass = 'is-warning'
+ buttonLabel = '担当から外れる'
+ dataMethod = 'DELETE'
+ fasClass = 'fa-times'
+ } else {
+ buttonClass = 'is-secondary'
+ buttonLabel = '担当する'
+ dataMethod = 'PATCH'
+ fasClass = 'fa-hand-paper'
+ }
+ button.className = `a-button is-block is-sm ${buttonClass}`
+ button.setAttribute('data-method', dataMethod)
+ const icon = button.querySelector('i')
+ icon.className = `fas ${fasClass}`
+ icon.nextSibling.textContent = ` ${buttonLabel}`
+ }
+
+ checkProduct(button) {
+ const params = {
+ product_id: this.productId,
+ current_user_id: this.currentUserId
+ }
+
+ fetch(this.url, {
+ method: this.method,
+ headers: {
+ 'Content-Type': 'application/json; charset=utf-8',
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-CSRF-Token': this.token
+ },
+ credentials: 'same-origin',
+ redirect: 'manual',
+ body: JSON.stringify(params)
+ })
+ .then((response) => {
+ return response.json()
+ })
+ .then((json) => {
+ if (json.message) {
+ alert(json.message)
+ } else {
+ this.id = json.checker_id
+ this.name = json.checker_name
+ if (this.id !== null) {
+ this.toast('担当になりました。')
+ } else {
+ this.toast('担当から外れました。')
+ }
+ this.updateButton(button)
+ }
+ })
+ .catch((error) => {
+ console.warn(error)
+ })
+ }
+}
diff --git a/app/javascript/product_checker_button.js b/app/javascript/product_checker_button.js
new file mode 100644
index 00000000000..758de5a9939
--- /dev/null
+++ b/app/javascript/product_checker_button.js
@@ -0,0 +1,23 @@
+import ProductChecker from './product_checker.js'
+
+document.addEventListener('DOMContentLoaded', () => {
+ const buttons = document.querySelectorAll('.check-product-button')
+ buttons.forEach((button) => {
+ button.addEventListener('click', () => {
+ const productId = button.getAttribute('data-product-id')
+ const currentUserId = button.getAttribute('data-current-user-id')
+ const url = button.getAttribute('data-url')
+ const method = button.getAttribute('data-method')
+ const token = button.getAttribute('data-token')
+
+ const checker = new ProductChecker(
+ productId,
+ currentUserId,
+ url,
+ method,
+ token
+ )
+ checker.checkProduct(button)
+ })
+ })
+})
diff --git a/app/javascript/products.js b/app/javascript/products.js
deleted file mode 100644
index b8277af68d9..00000000000
--- a/app/javascript/products.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import Vue from 'vue'
-import Products from 'products.vue'
-import store from './check-store.js'
-
-document.addEventListener('DOMContentLoaded', () => {
- const selector = '#js-products'
- const products = document.querySelector(selector)
- if (products) {
- const title = products.getAttribute('data-title')
- const isMentor = products.getAttribute('data-mentor-login')
- const currentUserId = Number(products.getAttribute('data-current-user-id'))
- const productDeadlineDays = Number(
- products.getAttribute('data-product-deadline-days')
- )
- new Vue({
- store,
- render: (h) =>
- h(Products, {
- props: {
- title: title,
- isMentor: isMentor === 'true',
- currentUserId: currentUserId,
- productDeadlineDays: productDeadlineDays
- }
- })
- }).$mount(selector)
- }
-})
diff --git a/app/javascript/products.vue b/app/javascript/products.vue
deleted file mode 100644
index c360f7ce2dc..00000000000
--- a/app/javascript/products.vue
+++ /dev/null
@@ -1,267 +0,0 @@
-
-.page-content.is-products(v-if='!loaded')
- div
- loadingListPlaceholder
-
-//- ダッシュボード
-.is-vue(v-else-if='isDashboard')
- template(v-for='product_n_days_passed in filteredProducts')
- .a-card.h-auto(
- v-if='!isDashboard || (isDashboard && product_n_days_passed.elapsed_days >= 0 && product_n_days_passed.elapsed_days <= selectedDays + 2)')
- //- TODO 以下を共通化する
- //- prettier-ignore: need space between v-if and id
- header.card-header.a-elapsed-days(
- v-if='product_n_days_passed.elapsed_days === 0', id='0days-elapsed'
- )
- h2.card-header__title
- | 今日提出
- span.card-header__count
- | ({{ countProductsGroupedBy(product_n_days_passed) }})
- //- prettier-ignore: need space between v-else-if and id
- header.card-header.a-elapsed-days.is-reply-warning(
- v-else-if='product_n_days_passed.elapsed_days === selectedDays', id='first-alert'
- )
- h2.card-header__title
- | {{ selectedDays }}日経過
- span.card-header__count
- | ({{ countProductsGroupedBy(product_n_days_passed) }})
- //- prettier-ignore: need space between v-else-if and id
- header.card-header.a-elapsed-days.is-reply-alert(
- v-else-if='product_n_days_passed.elapsed_days === selectedDays + 1', id='second-alert'
- )
- h2.card-header__title
- | {{ selectedDays + 1 }}日経過
- span.card-header__count
- | ({{ countProductsGroupedBy(product_n_days_passed) }})
- //- prettier-ignore: need space between v-else-if and id
- header.card-header.a-elapsed-days.is-reply-deadline(
- v-else-if='product_n_days_passed.elapsed_days === selectedDays + 2', id='deadline-alert'
- )
- h2.card-header__title
- | {{ selectedDays + 2 }}日以上経過
- span.card-header__count
- | ({{ countProductsGroupedBy(product_n_days_passed) }})
- header.card-header.a-elapsed-days(
- v-else,
- :id='elapsedDaysId(product_n_days_passed.elapsed_days)')
- h2.card-header__title
- | {{ product_n_days_passed.elapsed_days }}日経過
- span.card-header__count
- | ({{ countProductsGroupedBy(product_n_days_passed) }})
- //- 共通化ここまで
- .card-list
- .card-list__items
- product(
- v-for='product in product_n_days_passed.products',
- :key='product.id',
- :product='product',
- :currentUserId='currentUserId',
- :isMentor='isMentor',
- :display-user-icon='displayUserIcon')
-
- .under-cards
- .under-cards__links.mt-4.text-center.leading-normal.text-sm
- a.divide-indigo-800.block.p-3.border.rounded.border-solid.text-indigo-800.a-hover-link(
- class='hover\:bg-black',
- v-bind:href='`/products/unassigned#${selectedDays - 1}days-elapsed`',
- v-if='filteredProducts.length === 0 && countAlmostPassedSelectedDays() === 0')
- | しばらく{{ selectedDays }}日経過に到達する
提出物はありません。
- a.divide-indigo-800.block.p-3.border.rounded.border-solid.text-indigo-800.a-hover-link(
- class='hover\:bg-black',
- v-bind:href='`/products/unassigned#${selectedDays - 1}days-elapsed`',
- v-else-if='countAlmostPassedSelectedDays() > 0')
- | {{ countAlmostPassedSelectedDays() }}件の提出物が、
8時間以内に{{ selectedDays }}日経過に到達します。
- a.divide-indigo-800.block.p-3.border.rounded.border-solid.text-indigo-800.a-hover-link(
- class='hover\:bg-black',
- v-bind:href='`/products/unassigned#${selectedDays - 1}days-elapsed`',
- v-else)
- | しばらく{{ selectedDays }}日経過に到達する
提出物はありません。
-
-
-
diff --git a/app/javascript/reaction.js b/app/javascript/reaction.js
index 64f7820e865..c1558c52cde 100644
--- a/app/javascript/reaction.js
+++ b/app/javascript/reaction.js
@@ -5,118 +5,115 @@ document.addEventListener('DOMContentLoaded', () => {
return
}
- const requestReaction = (url, method, callback) => {
- fetch(url, {
- method: method,
- credentials: 'same-origin',
- headers: {
- 'X-Requested-With': 'XMLHttpRequest',
- 'X-CSRF-Token': $.rails.csrfToken()
- }
+ reactions.forEach((reaction) => {
+ initializeReaction(reaction)
+ })
+})
+
+export function initializeReaction(reaction) {
+ const loginName = reaction.dataset.reactionLoginName
+ const reactionableId = reaction.dataset.reactionReactionableId
+
+ const dropdown = reaction.querySelector('.js-reaction-dropdown')
+ if (dropdown) {
+ dropdown.addEventListener('click', (e) => {
+ const reactionEmoji = e.currentTarget.querySelector('.js-reaction')
+ reactionEmoji.hidden = !reactionEmoji.hidden
})
- .then((response) => {
- return response.json()
- })
- .then((json) => {
- callback(json)
- })
- .catch((error) => {
- console.warn(error)
- })
}
- const updateReactionCount = (element, count) => {
- const reactionCount = element.querySelector('.js-reaction-count')
+ reaction.querySelectorAll('li').forEach((element) => {
+ element.addEventListener('click', (e) => {
+ const kind = e.currentTarget.dataset.reactionKind
+ const reactionId = e.currentTarget.dataset.reactionId
- if (!reactionCount) {
- return
+ if (reactionId) {
+ destroyReaction(reaction, kind, loginName, reactionId)
+ } else {
+ createReaction(reaction, kind, loginName, reactionableId)
+ }
+ })
+ })
+}
+
+function requestReaction(url, method, callback) {
+ fetch(url, {
+ method: method,
+ credentials: 'same-origin',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest',
+ 'X-CSRF-Token': $.rails.csrfToken()
}
+ })
+ .then((response) => response.json())
+ .then((json) => callback(json))
+ .catch((error) => console.warn(error))
+}
- reactionCount.textContent = Number(reactionCount.textContent) + count
- switch (reactionCount.textContent) {
- case '0':
- element.hidden = true
- break
- case '1':
- element.hidden = false
- break
- }
+function updateReactionCount(element, count) {
+ const reactionCount = element.querySelector('.js-reaction-count')
+
+ if (!reactionCount) {
+ return
}
- const updateReactionLoginNames = (element, loginName) => {
- const reactionLoginNames = element.querySelector('.js-reaction-login-names')
+ reactionCount.textContent = Number(reactionCount.textContent) + count
+ switch (reactionCount.textContent) {
+ case '0':
+ element.hidden = true
+ break
+ case '1':
+ element.hidden = false
+ break
+ }
+}
- if (!reactionLoginNames) {
- return
- }
+function updateReactionLoginNames(element, loginName) {
+ const reactionLoginNames = element.querySelector('.js-reaction-login-names')
- const reactionLoginName = Array.from(reactionLoginNames.children).find(
- (li) => {
- return li.textContent === loginName
- }
- )
-
- if (reactionLoginName) {
- reactionLoginNames.removeChild(reactionLoginName)
- } else {
- const li = document.createElement('li')
- li.textContent = loginName
- reactionLoginNames.appendChild(li)
- }
+ if (!reactionLoginNames) {
+ return
}
- const createReaction = (reaction, kind, loginName, reactionableId) => {
- const url = `/api/reactions?reactionable_id=${reactionableId}&kind=${kind}`
-
- requestReaction(url, 'POST', (json) => {
- reaction
- .querySelectorAll(`[data-reaction-kind="${kind}"]`)
- .forEach((element) => {
- element.classList.add('is-reacted')
- element.dataset.reactionId = json.id
- updateReactionCount(element, 1)
- updateReactionLoginNames(element, loginName)
- })
- })
- }
+ const reactionLoginName = Array.from(reactionLoginNames.children).find(
+ (li) => li.textContent === loginName
+ )
- const destroyReaction = (reaction, kind, loginName, reactionId) => {
- const url = `/api/reactions/${reactionId}`
-
- requestReaction(url, 'DELETE', () => {
- reaction
- .querySelectorAll(`[data-reaction-kind="${kind}"]`)
- .forEach((element) => {
- element.classList.remove('is-reacted')
- delete element.dataset.reactionId
- updateReactionCount(element, -1)
- updateReactionLoginNames(element, loginName)
- })
- })
+ if (reactionLoginName) {
+ reactionLoginNames.removeChild(reactionLoginName)
+ } else {
+ const li = document.createElement('li')
+ li.textContent = loginName
+ reactionLoginNames.appendChild(li)
}
-
- reactions.forEach((reaction) => {
- const loginName = reaction.dataset.reactionLoginName
- const reactionableId = reaction.dataset.reactionReactionableId
-
- reaction.querySelectorAll('li').forEach((element) => {
- element.addEventListener('click', (e) => {
- const kind = e.currentTarget.dataset.reactionKind
- const reactionId = e.currentTarget.dataset.reactionId
-
- if (reactionId) {
- destroyReaction(reaction, kind, loginName, reactionId)
- } else {
- createReaction(reaction, kind, loginName, reactionableId)
- }
+}
+
+function createReaction(reaction, kind, loginName, reactionableId) {
+ const url = `/api/reactions?reactionable_id=${reactionableId}&kind=${kind}`
+
+ requestReaction(url, 'POST', (json) => {
+ reaction
+ .querySelectorAll(`[data-reaction-kind="${kind}"]`)
+ .forEach((element) => {
+ element.classList.add('is-reacted')
+ element.dataset.reactionId = json.id
+ updateReactionCount(element, 1)
+ updateReactionLoginNames(element, loginName)
})
- })
})
-
- document.querySelectorAll('.js-reaction-dropdown').forEach((dropdown) => {
- dropdown.addEventListener('click', (e) => {
- const reaction = e.currentTarget.querySelector('.js-reaction')
- reaction.hidden = !reaction.hidden
- })
+}
+
+function destroyReaction(reaction, kind, loginName, reactionId) {
+ const url = `/api/reactions/${reactionId}`
+
+ requestReaction(url, 'DELETE', () => {
+ reaction
+ .querySelectorAll(`[data-reaction-kind="${kind}"]`)
+ .forEach((element) => {
+ element.classList.remove('is-reacted')
+ delete element.dataset.reactionId
+ updateReactionCount(element, -1)
+ updateReactionLoginNames(element, loginName)
+ })
})
-})
+}
diff --git a/app/javascript/stylesheets/_common-imports.sass b/app/javascript/stylesheets/_common-imports.sass
index e416f535afc..f3b03ec1a40 100644
--- a/app/javascript/stylesheets/_common-imports.sass
+++ b/app/javascript/stylesheets/_common-imports.sass
@@ -123,6 +123,7 @@
@import "atoms/a-icon-label"
@import "atoms/a-side-nav"
@import "atoms/a-notice-block"
+@import "atoms/a-table"
////////////
// layouts
@@ -163,6 +164,7 @@
@import "shared/blocks/card/congrats-card-body"
@import "shared/blocks/card/practice-books"
@import "shared/blocks/card/thumbnail-card"
+@import "shared/blocks/card/tags-highlight"
@import "shared/blocks/card-list/card-list-item-actions"
@import "shared/blocks/card-list/card-list-item-meta"
diff --git a/app/javascript/stylesheets/application.sass b/app/javascript/stylesheets/application.sass
index e0590a1cef2..6081b3a737f 100644
--- a/app/javascript/stylesheets/application.sass
+++ b/app/javascript/stylesheets/application.sass
@@ -117,3 +117,14 @@
@import application/blocks/user/user-profile
@import application/blocks/user/user-secret-attributes
@import application/blocks/user/users-item
+
+@import application/blocks/coding-test/code-editor
+@import application/blocks/coding-test/io-sample
+@import application/blocks/coding-test/test-case
+@import application/blocks/coding-test/coding-test
+@import application/blocks/coding-test/result-table
+@import application/blocks/coding-test/coding-tests-item
+
+@import application/blocks/micro-report/micro-reports
+@import application/blocks/micro-report/micro-report-form
+@import application/blocks/micro-report/micro-report-form-tabs
diff --git a/app/javascript/stylesheets/application/base/_base.sass b/app/javascript/stylesheets/application/base/_base.sass
index 4ff86c8b8ff..2f0da20b454 100644
--- a/app/javascript/stylesheets/application/base/_base.sass
+++ b/app/javascript/stylesheets/application/base/_base.sass
@@ -7,6 +7,13 @@ html.is-application
body.is-application
background-color: var(--background)
+body.is-no-scroll
+ overflow-y: hidden
+ .header
+ position: static
+ .wrapper
+ padding-top: 0
+
body.is-piyo-background
&::before
content: ''
diff --git a/app/javascript/stylesheets/application/blocks/coding-test/_code-editor.sass b/app/javascript/stylesheets/application/blocks/coding-test/_code-editor.sass
new file mode 100644
index 00000000000..4b1a736e91b
--- /dev/null
+++ b/app/javascript/stylesheets/application/blocks/coding-test/_code-editor.sass
@@ -0,0 +1,5 @@
+.code-editor
+ font-size: .875rem
+ line-height: 1.5
+ field-sizing: content
+ min-height: 20lh
diff --git a/app/javascript/stylesheets/application/blocks/coding-test/_coding-test.sass b/app/javascript/stylesheets/application/blocks/coding-test/_coding-test.sass
new file mode 100644
index 00000000000..34f43f8bb4e
--- /dev/null
+++ b/app/javascript/stylesheets/application/blocks/coding-test/_coding-test.sass
@@ -0,0 +1,2 @@
+.coding-test__language
+ +text-block(.875rem 1.4)
diff --git a/app/javascript/stylesheets/application/blocks/coding-test/_coding-tests-item.sass b/app/javascript/stylesheets/application/blocks/coding-test/_coding-tests-item.sass
new file mode 100644
index 00000000000..0bc2c501dff
--- /dev/null
+++ b/app/javascript/stylesheets/application/blocks/coding-test/_coding-tests-item.sass
@@ -0,0 +1,7 @@
+.coding-tests-item
+ display: flex
+ gap: .5rem
+ align-items: center
+
+.coding-tests-item__test-link
+ +text-block(.875rem 1.4)
diff --git a/app/javascript/stylesheets/application/blocks/coding-test/_io-sample.sass b/app/javascript/stylesheets/application/blocks/coding-test/_io-sample.sass
new file mode 100644
index 00000000000..560be672b55
--- /dev/null
+++ b/app/javascript/stylesheets/application/blocks/coding-test/_io-sample.sass
@@ -0,0 +1,11 @@
+.io-sample__title
+ +text-block(.875rem 1.4, 700)
+
+.io-sample__body
+ margin-top: .5rem
+ font-size: .875rem
+
+.io-sample__code
+ padding: .75rem
+ border-radius: .25rem
+ font-family: var(--monospace)
diff --git a/app/javascript/stylesheets/application/blocks/coding-test/_result-table.sass b/app/javascript/stylesheets/application/blocks/coding-test/_result-table.sass
new file mode 100644
index 00000000000..3c69388f3bf
--- /dev/null
+++ b/app/javascript/stylesheets/application/blocks/coding-test/_result-table.sass
@@ -0,0 +1,15 @@
+.result-table
+ tr
+ &.is-wa td,
+ &.is-re td
+ background-color: var(--danger-tint)
+ &.is-ac td
+ background-color: var(--success-tint)
+ .status.wa,
+ .status.re
+ color: var(--danger-text)
+ .status.ac
+ color: var(--success-text)
+ pre,
+ code
+ font-family: var(--monospace)
diff --git a/app/javascript/stylesheets/application/blocks/coding-test/_test-case.sass b/app/javascript/stylesheets/application/blocks/coding-test/_test-case.sass
new file mode 100644
index 00000000000..1fb93121911
--- /dev/null
+++ b/app/javascript/stylesheets/application/blocks/coding-test/_test-case.sass
@@ -0,0 +1,2 @@
+.test-case__header
+ position: relative
diff --git a/app/javascript/stylesheets/application/blocks/header/_header.sass b/app/javascript/stylesheets/application/blocks/header/_header.sass
index 756eb8714f5..5a290be8ae0 100644
--- a/app/javascript/stylesheets/application/blocks/header/_header.sass
+++ b/app/javascript/stylesheets/application/blocks/header/_header.sass
@@ -1,6 +1,6 @@
.header
background-color: var(--base)
- z-index: 3
+ z-index: 5
+media-breakpoint-up(md)
+position(fixed, top 0, right 0)
left: var(--global-nav-width)
diff --git a/app/javascript/stylesheets/application/blocks/micro-report/_micro-report-form-tabs.sass b/app/javascript/stylesheets/application/blocks/micro-report/_micro-report-form-tabs.sass
new file mode 100644
index 00000000000..4bf00870f76
--- /dev/null
+++ b/app/javascript/stylesheets/application/blocks/micro-report/_micro-report-form-tabs.sass
@@ -0,0 +1,11 @@
+.micro-report-form-tabs__items
+ display: flex
+
+.micro-report-form-tabs__item-link
+ +flex-link
+ padding: .5rem .75rem 1rem
+ +text-block(.875rem 1.4)
+
+.micro-report-form__preview
+ min-height: 3.75rem
+ padding: .5rem
diff --git a/app/javascript/stylesheets/application/blocks/micro-report/_micro-report-form.sass b/app/javascript/stylesheets/application/blocks/micro-report/_micro-report-form.sass
new file mode 100644
index 00000000000..4dd69131e19
--- /dev/null
+++ b/app/javascript/stylesheets/application/blocks/micro-report/_micro-report-form.sass
@@ -0,0 +1,21 @@
+.micro-report-form
+ padding-bottom: 1rem
+ position: fixed
+ bottom: 0
+ left: 0
+ right: 0
+ background-color: var(--background)
+ +media-breakpoint-up(md)
+ margin-left: 5rem
+
+.micro-report-form__inner
+ display: flex
+ align-items: flex-end
+ gap: .75rem
+
+.micro-report-form__start
+ flex: 1
+
+.micro-report-form__text-area.a-text-input
+ height: 3.75rem
+ min-height: 0
diff --git a/app/javascript/stylesheets/application/blocks/micro-report/_micro-reports.sass b/app/javascript/stylesheets/application/blocks/micro-report/_micro-reports.sass
new file mode 100644
index 00000000000..ee280d437c1
--- /dev/null
+++ b/app/javascript/stylesheets/application/blocks/micro-report/_micro-reports.sass
@@ -0,0 +1,18 @@
+.micro-reports
+ max-height: calc(100vh - 10.5rem)
+ overflow: hidden
+ display: flex
+ flex-direction: column
+ .thread-comment:last-child
+ margin-bottom: 0
+
+.micro-reports__start
+ height: 100%
+ overflow-y: scroll
+ padding-bottom: .5rem
+
+.micro-reports-with-form
+ padding-bottom: 135px
+
+.micro-reports-without-form
+ padding-bottom: 0
diff --git a/app/javascript/stylesheets/application/blocks/page/_page-main-header.sass b/app/javascript/stylesheets/application/blocks/page/_page-main-header.sass
index 848d81a93b0..c706cb8eecb 100644
--- a/app/javascript/stylesheets/application/blocks/page/_page-main-header.sass
+++ b/app/javascript/stylesheets/application/blocks/page/_page-main-header.sass
@@ -1,11 +1,12 @@
.page-main-header__inner
padding-block: .5rem
+ display: flex
+media-breakpoint-up(md)
min-height: calc(3.5rem - 1px)
- display: flex
align-items: center
+media-breakpoint-down(sm)
- display: block
+ flex-direction: column
+ gap: .5rem
.a-page-notice + .page-main &
padding-top: .5em
diff --git a/app/javascript/stylesheets/application/blocks/practice/_practice-content-actions.sass b/app/javascript/stylesheets/application/blocks/practice/_practice-content-actions.sass
index 697c50a5362..73ec6863d15 100644
--- a/app/javascript/stylesheets/application/blocks/practice/_practice-content-actions.sass
+++ b/app/javascript/stylesheets/application/blocks/practice/_practice-content-actions.sass
@@ -11,7 +11,7 @@
.practice-content-actions__item
padding-inline: .375rem
> *
- width: 160px
+ width: 10rem
max-width: 100%
+media-breakpoint-down(sm)
width: 100%
diff --git a/app/javascript/stylesheets/application/blocks/report/_stamp.sass b/app/javascript/stylesheets/application/blocks/report/_stamp.sass
index 56997064b44..c2a3b38c73d 100644
--- a/app/javascript/stylesheets/application/blocks/report/_stamp.sass
+++ b/app/javascript/stylesheets/application/blocks/report/_stamp.sass
@@ -21,6 +21,13 @@
font-weight: 800
font-family: serif
justify-content: center
+ &.is-cleared
+ display: flex
+ flex-direction: column
+ font-size: 1.125rem
+ font-weight: 800
+ font-family: serif
+ justify-content: center
&.is-sm
+size(4.125em 2.25rem)
+position(absolute, right .5rem, top .5rem)
diff --git a/app/javascript/stylesheets/atoms/_a-table.sass b/app/javascript/stylesheets/atoms/_a-table.sass
new file mode 100644
index 00000000000..d7a737303a0
--- /dev/null
+++ b/app/javascript/stylesheets/atoms/_a-table.sass
@@ -0,0 +1,34 @@
+.a-table
+ +media-breakpoint-down(sm)
+ overflow-x: auto
+
+.a-table table
+ border-radius: .25rem
+ width: 100%
+
+.a-table thead
+ background-color: var(--background-semi-shade)
+ border: solid 1px var(--border-shade)
+ +border-radius(top, .25rem)
+
+.a-table tr
+ +border(horizontal, solid 1px var(--border))
+
+.a-table th
+ +text-block(.75rem 1.4, center 600)
+ border: solid 1px var(--border-shade)
+ white-space: nowrap
+ height: 2rem
+ padding-inline: .5rem
+
+.a-table td
+ transition: all .2s ease-out
+ background-color: var(--base)
+ padding: .375rem .75rem
+ border: solid 1px var(--border)
+ +text-block(.8125rem 1.45)
+
+.card-body
+ >.a-table table
+ margin: -1px
+ width: calc(100% + 2px)
diff --git a/app/javascript/stylesheets/config/mixins/_badge.sass b/app/javascript/stylesheets/config/mixins/_badge.sass
index c577db020a4..4834bdbe0c4 100644
--- a/app/javascript/stylesheets/config/mixins/_badge.sass
+++ b/app/javascript/stylesheets/config/mixins/_badge.sass
@@ -12,6 +12,12 @@
margin-left: .1875em
&:not(:last-child)
margin-right: .1875em
+ &.is-block
+ width: 100%
+ display: flex
+ .a-badge__inner
+ overflow: hidden
+ text-overflow: ellipsis
=badge-color($color)
background-color: $color
diff --git a/app/javascript/stylesheets/config/mixins/_grid.sass b/app/javascript/stylesheets/config/mixins/_grid.sass
index 8104ffc60cc..b3f3147ac39 100644
--- a/app/javascript/stylesheets/config/mixins/_grid.sass
+++ b/app/javascript/stylesheets/config/mixins/_grid.sass
@@ -1,8 +1,7 @@
@use "sass:math"
.row
+make-row
-
-+make-grid-columns
+ +make-grid-columns
=grid($gutter-size)
&.row
diff --git a/app/javascript/stylesheets/config/mixins/_long-text-style.sass b/app/javascript/stylesheets/config/mixins/_long-text-style.sass
index 1149a340715..d8d537ed10f 100644
--- a/app/javascript/stylesheets/config/mixins/_long-text-style.sass
+++ b/app/javascript/stylesheets/config/mixins/_long-text-style.sass
@@ -71,15 +71,23 @@
h3
+text-block(1.375em 1.6, 600)
margin-bottom: .625em
+ & + *
+ margin-top: 0 !important
h4
+text-block(1.25em 1.6, 600)
margin-bottom: .625em
+ & + *
+ margin-top: 0 !important
h5
+text-block(1.125em 1.6, 600)
margin-bottom: .625em
+ & + *
+ margin-top: 0 !important
h6
+text-block(1em 1.6, 600)
margin-bottom: .625em
+ & + *
+ margin-top: 0 !important
>img
margin-block: 1.5em
p
@@ -91,6 +99,13 @@
&:hover
img
border-color: mix($font, $background, 16%)
+ a,
+ a:link,
+ a:visited,
+ a:hover
+ pre,
+ code
+ text-decoration: none !important
dl
margin-block: 1.5em
border: 1px solid mix($font, $background, 20%)
diff --git a/app/javascript/stylesheets/config/mixins/grid/_grid.sass b/app/javascript/stylesheets/config/mixins/grid/_grid.sass
index fcf3936700d..e997d0932f7 100644
--- a/app/javascript/stylesheets/config/mixins/grid/_grid.sass
+++ b/app/javascript/stylesheets/config/mixins/grid/_grid.sass
@@ -7,5 +7,5 @@
@each $breakpoint in map-keys($breakpoints)
+media-breakpoint-up($breakpoint)
@for $i from 1 through $columns
- .col-#{$breakpoint}-#{$i}
+ >.col-#{$breakpoint}-#{$i}
+make-col-span($i, $columns, $gutter)
diff --git a/app/javascript/stylesheets/config/variables/_colors.sass b/app/javascript/stylesheets/config/variables/_colors.sass
index 563e567bf89..97fe85f7a2b 100644
--- a/app/javascript/stylesheets/config/variables/_colors.sass
+++ b/app/javascript/stylesheets/config/variables/_colors.sass
@@ -3,7 +3,7 @@ $accent: hsl(44, 96%, 54%)
$base: white
// completion
-$completion: hsl(197, 83%, 49%)
+$completion: hsl(197deg 83% 49%)
$completion-dark: rgb(0 0 0 / 20%)
// background
diff --git a/app/javascript/stylesheets/initializers/_ress.scss b/app/javascript/stylesheets/initializers/_ress.scss
index 6aa755268b7..b5f49d559fe 100644
--- a/app/javascript/stylesheets/initializers/_ress.scss
+++ b/app/javascript/stylesheets/initializers/_ress.scss
@@ -10,10 +10,14 @@
html {
box-sizing: border-box;
- overflow-y: scroll; /* All browsers without overlaying scrollbars */
+ //overflow-y: scroll; /* All browsers without overlaying scrollbars */
-webkit-text-size-adjust: 100%; /* iOS 8+ */
}
+body {
+ overflow-y: scroll; /* All browsers without overlaying scrollbars */
+}
+
*,
::before,
::after {
diff --git a/app/javascript/stylesheets/shared/blocks/_page-nav.sass b/app/javascript/stylesheets/shared/blocks/_page-nav.sass
index ae66ff8f897..a7656b3fc24 100644
--- a/app/javascript/stylesheets/shared/blocks/_page-nav.sass
+++ b/app/javascript/stylesheets/shared/blocks/_page-nav.sass
@@ -103,3 +103,8 @@ a.page-nav__title-inner
display: flex
flex-wrap: wrap
gap: .25rem .75rem
+
+.page-nav__item-link-inner
+ +flex-link
+ gap: .5rem
+ align-items: center
diff --git a/app/javascript/stylesheets/shared/blocks/_pagination.sass b/app/javascript/stylesheets/shared/blocks/_pagination.sass
index e5727aed439..2701fe455cf 100644
--- a/app/javascript/stylesheets/shared/blocks/_pagination.sass
+++ b/app/javascript/stylesheets/shared/blocks/_pagination.sass
@@ -41,3 +41,6 @@
.pagination__item.is-active &
background-color: var(--main)
color: var(--reversal-text)
+ &.is-disabled
+ opacity: .4
+ pointer-events: none
diff --git a/app/javascript/stylesheets/shared/blocks/card-list/_card-list-item.sass b/app/javascript/stylesheets/shared/blocks/card-list/_card-list-item.sass
index 6fa91841580..78491eadcca 100644
--- a/app/javascript/stylesheets/shared/blocks/card-list/_card-list-item.sass
+++ b/app/javascript/stylesheets/shared/blocks/card-list/_card-list-item.sass
@@ -192,3 +192,8 @@ a.card-list-item__inner
.card-list-item__badge
margin-right: .25rem
+
+.card-list-item__small-link
+ display: flex
+ +text-block(.75rem 1.4)
+ margin-top: .5rem
diff --git a/app/javascript/stylesheets/shared/blocks/card/_tags-highlight.sass b/app/javascript/stylesheets/shared/blocks/card/_tags-highlight.sass
new file mode 100644
index 00000000000..6ea472d4404
--- /dev/null
+++ b/app/javascript/stylesheets/shared/blocks/card/_tags-highlight.sass
@@ -0,0 +1,15 @@
+.tags-highlight
+ position: absolute
+ left: 0
+ top: 0
+ padding: .5rem .75rem
+ display: flex
+ gap: .25rem
+ width: 100%
+ flex-wrap: wrap
+
+.tags-highlight__item
+ width: 25%
+ max-width: 6rem
+ .a-badge
+ border: solid 1px var(--base)
diff --git a/app/javascript/stylesheets/shared/blocks/card/_thumbnail-card.sass b/app/javascript/stylesheets/shared/blocks/card/_thumbnail-card.sass
index 791a19db191..f9a13625322 100644
--- a/app/javascript/stylesheets/shared/blocks/card/_thumbnail-card.sass
+++ b/app/javascript/stylesheets/shared/blocks/card/_thumbnail-card.sass
@@ -1,4 +1,5 @@
.thumbnail-card
+ padding: 1rem
+media-breakpoint-up(md)
height: 100%
+media-breakpoint-down(sm)
@@ -6,10 +7,10 @@
height: 100%
.thumbnail-card__inner
- padding: 1rem
display: flex
flex-direction: column
gap: 1rem
+ position: relative
a.thumbnail-card__inner
+flex-link
diff --git a/app/javascript/stylesheets/shared/blocks/form/_form-tabs.sass b/app/javascript/stylesheets/shared/blocks/form/_form-tabs.sass
index 5a3110da3d3..74810ba8cc2 100644
--- a/app/javascript/stylesheets/shared/blocks/form/_form-tabs.sass
+++ b/app/javascript/stylesheets/shared/blocks/form/_form-tabs.sass
@@ -10,8 +10,9 @@
.form-tabs__tab
width: 5.5rem
padding-bottom: 1rem
- +text-block(.8125rem 1)
+ +text-block(.8125rem 1, flex)
display: flex
+ white-space: nowrap
color: var(--muted-text)
align-items: center
justify-content: center
diff --git a/app/javascript/updateAnswerCount.js b/app/javascript/updateAnswerCount.js
new file mode 100644
index 00000000000..95532cab81c
--- /dev/null
+++ b/app/javascript/updateAnswerCount.js
@@ -0,0 +1,12 @@
+export default function updateAnswerCount(isCreated) {
+ const answerCountElement = document.querySelector('.js-answer-count')
+ const currentCount = parseInt(answerCountElement.textContent, 10)
+ const newCount = currentCount + (isCreated ? 1 : -1)
+
+ answerCountElement.textContent = newCount
+ if (currentCount === 0) {
+ answerCountElement.classList.remove('is-zero')
+ } else if (newCount === 0) {
+ answerCountElement.classList.add('is-zero')
+ }
+}
diff --git a/app/mailers/activity_mailer.rb b/app/mailers/activity_mailer.rb
index 4790e4e0f0a..177d811f730 100644
--- a/app/mailers/activity_mailer.rb
+++ b/app/mailers/activity_mailer.rb
@@ -287,6 +287,7 @@ def hibernated(args = {})
link: "/users/#{@sender.id}",
kind: Notification.kinds[:hibernated]
)
+ @hibernation = Hibernation.find_by(user_id: @sender.id)
subject = "[FBC] #{@sender.login_name}さんが休会しました。"
message = mail(to: @user.email, subject:)
diff --git a/app/models/coding_test.rb b/app/models/coding_test.rb
new file mode 100644
index 00000000000..3212e0d1b8a
--- /dev/null
+++ b/app/models/coding_test.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class CodingTest < ApplicationRecord
+ enum language: {
+ ruby: 1,
+ javascript: 2
+ }, _prefix: true
+
+ belongs_to :practice
+ belongs_to :user
+ has_many :coding_test_cases, dependent: :destroy
+ has_many :coding_test_submissions, dependent: :destroy
+
+ accepts_nested_attributes_for :coding_test_cases
+
+ acts_as_list scope: :practice
+
+ validates :language, presence: true
+ validates :title, presence: true
+ validates :description, presence: true
+
+ validate :no_test_cases
+
+ def passed_by?(user)
+ coding_test_submissions.exists?(user:)
+ end
+
+ private
+
+ def no_test_cases
+ return if coding_test_cases.any?
+
+ errors.add(:base, 'テストケースがありません')
+ end
+end
diff --git a/app/models/coding_test_case.rb b/app/models/coding_test_case.rb
new file mode 100644
index 00000000000..6f526d151aa
--- /dev/null
+++ b/app/models/coding_test_case.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class CodingTestCase < ApplicationRecord
+ belongs_to :coding_test
+end
diff --git a/app/models/coding_test_submission.rb b/app/models/coding_test_submission.rb
new file mode 100644
index 00000000000..29255cd9c74
--- /dev/null
+++ b/app/models/coding_test_submission.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class CodingTestSubmission < ApplicationRecord
+ belongs_to :coding_test
+ belongs_to :user
+
+ has_one :practice, through: :coding_test
+
+ validates :coding_test_id, uniqueness: { scope: :user_id }
+end
diff --git a/app/models/comment.rb b/app/models/comment.rb
index 383074b2d85..d674b4b8897 100644
--- a/app/models/comment.rb
+++ b/app/models/comment.rb
@@ -18,6 +18,8 @@ class Comment < ApplicationRecord
mentionable_as :description, hook_name: :after_commit
+ scope :without_talk, -> { where.not(commentable_type: 'Talk') }
+
class << self
def commented_users
User.with_attached_avatar
diff --git a/app/models/learning.rb b/app/models/learning.rb
index f77fc8555c8..df300c582c0 100644
--- a/app/models/learning.rb
+++ b/app/models/learning.rb
@@ -10,6 +10,7 @@ class Learning < ApplicationRecord
presence: true,
uniqueness: { scope: :user_id }
validate :startable_practice
+ validate :submission_checked_for_completion, if: -> { practice.submission && status == 'complete' }
private
@@ -18,4 +19,10 @@ def startable_practice
errors.add :error, "すでに着手しているプラクティスがあります。\n提出物を提出するか修了すると新しいプラクティスを開始できます。"
end
+
+ def submission_checked_for_completion
+ return if practice.product(user)&.checked?
+
+ errors.add :error, '提出物がチェックされていないため、修了にできません'
+ end
end
diff --git a/app/models/micro_report.rb b/app/models/micro_report.rb
new file mode 100644
index 00000000000..3e36ce3bf97
--- /dev/null
+++ b/app/models/micro_report.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class MicroReport < ApplicationRecord
+ include Reactionable
+
+ belongs_to :user
+ validates :content, presence: true
+end
diff --git a/app/models/practice.rb b/app/models/practice.rb
index 3103daa6be0..0e9a5bda5aa 100644
--- a/app/models/practice.rb
+++ b/app/models/practice.rb
@@ -37,11 +37,16 @@ class Practice < ApplicationRecord
has_one_attached :ogp_image
has_one_attached :completion_image
- has_many :books, through: :practices_books
has_many :practices_books, dependent: :destroy
+ has_many :books, through: :practices_books
accepts_nested_attributes_for :practices_books, reject_if: :all_blank, allow_destroy: true
has_one :submission_answer, dependent: :destroy
+ has_many :coding_tests, dependent: :nullify
+
+ has_many :coding_test_submissions,
+ through: :coding_tests,
+ source: :coding_test_submissions
validates :title, presence: true
validates :description, presence: true
diff --git a/app/models/product.rb b/app/models/product.rb
index 86cd9d3d415..4a941f1239a 100644
--- a/app/models/product.rb
+++ b/app/models/product.rb
@@ -108,6 +108,22 @@ def self.self_assigned_no_replied_products(user_id)
.order(published_at: :asc, id: :asc)
end
+ def self.require_assignment_products
+ Product.all
+ .unassigned
+ .unchecked
+ .not_wip
+ .list
+ .ascending_by_date_of_publishing_and_id
+ end
+
+ def self.group_by_elapsed_days(products)
+ reply_deadline_days = PRODUCT_DEADLINE + 2
+ products.group_by do |product|
+ product.elapsed_days >= reply_deadline_days ? reply_deadline_days : product.elapsed_days
+ end
+ end
+
def completed?(user)
checks.where(user:).present?
end
diff --git a/app/models/regular_event.rb b/app/models/regular_event.rb
index 1867ec7e87a..e96fbc5f1da 100644
--- a/app/models/regular_event.rb
+++ b/app/models/regular_event.rb
@@ -38,7 +38,7 @@ class RegularEvent < ApplicationRecord # rubocop:disable Metrics/ClassLength
others: 4
}, _prefix: true
- validates :title, presence: true
+ validates :title, presence: true, markdown_prohibited: true
validates :user_ids, presence: true
validates :start_at, presence: true
validates :end_at, presence: true
@@ -79,7 +79,7 @@ def scheduled_on?(date)
def next_event_date
event_dates =
- hold_national_holiday ? feature_scheduled_dates : feature_scheduled_dates.reject { |d| HolidayJp.holiday?(d) }
+ hold_national_holiday ? upcoming_scheduled_dates : upcoming_scheduled_dates.reject { |d| HolidayJp.holiday?(d) }
event_dates.min
end
@@ -109,8 +109,8 @@ def assign_admin_as_organizer_if_none
end
def all_scheduled_dates(
- from: Date.new(Time.current.year, 1, 1),
- to: Date.new(Time.current.year, 12, 31)
+ from: Date.current,
+ to: Date.current.next_year
)
(from..to).filter { |d| date_match_the_rules?(d, regular_event_repeat_rules) }
end
@@ -129,10 +129,7 @@ def transform_for_subscription(event_date)
def self.fetch_participated_regular_events(user)
participated_regular_events = []
user.participated_regular_event_ids.find_each do |regular_event|
- regular_event.all_scheduled_dates(
- from: Time.current.to_date,
- to: Time.current.to_date.next_year
- ).each do |event_date|
+ regular_event.all_scheduled_dates.each do |event_date|
participated_regular_events << regular_event.transform_for_subscription(event_date)
end
end
@@ -148,7 +145,7 @@ def end_at_be_greater_than_start_at
errors.add(:end_at, ': イベント終了時刻はイベント開始時刻よりも後の時刻にしてください。')
end
- def feature_scheduled_dates
+ def upcoming_scheduled_dates
# 時刻が過ぎたイベントを排除するためだけに、一時的にstart_timeを与える。後でDate型に戻す。
event_dates_with_start_time = all_scheduled_dates.map { |d| d.in_time_zone.change(hour: start_at.hour, min: start_at.min) }
@@ -170,11 +167,8 @@ def nth_wday(date)
end
def parse_event_time(event_date, event_time)
- tz = ActiveSupport::TimeZone['Asia/Tokyo']
-
- time = event_time ? event_time.strftime('%H:%M') : '00:00'
- date_time = DateTime.parse("#{event_date} #{time}")
-
- tz.local_to_utc(date_time)
+ str_date = event_date.strftime('%F')
+ str_time = event_time.strftime('%R')
+ Time.zone.parse([str_date, str_time].join(' '))
end
end
diff --git a/app/models/regular_event_update_notifier.rb b/app/models/regular_event_update_notifier.rb
index a1271ec0cf6..b751716f66e 100644
--- a/app/models/regular_event_update_notifier.rb
+++ b/app/models/regular_event_update_notifier.rb
@@ -3,10 +3,11 @@
class RegularEventUpdateNotifier
def call(payload)
regular_event = payload[:regular_event]
+ sender = payload[:sender]
participants = regular_event.participants
participants.each do |participant|
- ActivityDelivery.with(regular_event:, receiver: participant).notify(:update_regular_event) if regular_event.user != participant
+ ActivityDelivery.with(regular_event:, sender:, receiver: participant).notify(:update_regular_event) if regular_event.user != participant
end
end
end
diff --git a/app/models/report.rb b/app/models/report.rb
index 333f7f7f325..b792ed2ffb1 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -32,7 +32,7 @@ class Report < ApplicationRecord
validates :user, presence: true
validates :reported_on, presence: true, uniqueness: { scope: :user }
validates :emotion, presence: true
- validate :reported_on_or_before_today
+ validate :limited_date_within_range
after_create ReportCallbacks.new
after_destroy ReportCallbacks.new
@@ -114,10 +114,6 @@ def total_learning_time
(learning_times.sum(&:diff) / 60).to_i
end
- def reported_on_or_before_today
- errors.add(:reported_on, 'は今日以前の日付にしてください') if reported_on > Date.current
- end
-
def latest_of_user?
self == Report.not_wip
.where(user:, wip: false)
@@ -134,4 +130,13 @@ def not_wip_previous_of_user
.order(reported_on: :desc)
.second
end
+
+ private
+
+ def limited_date_within_range
+ min_date = Date.new(2013, 1, 1)
+ return if min_date <= reported_on && reported_on <= Date.current
+
+ errors.add(:reported_on, "は#{I18n.l min_date}から今日以前の間の日付にしてください")
+ end
end
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index d8c66f66b58..7beb89b9b25 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -26,6 +26,8 @@ def create(customer_id, idempotency_key = SecureRandom.uuid, trial: 3)
end
def destroy(subscription_id)
+ return true if retrieve(subscription_id).status == 'canceled'
+
Stripe::Subscription.update(subscription_id, cancel_at_period_end: true)
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 175286e02be..b388bc9ee75 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -104,12 +104,15 @@ class User < ApplicationRecord
has_many :surveys, dependent: :destroy
has_many :survey_questions, dependent: :destroy
has_many :external_entries, dependent: :destroy
+ has_many :coding_tests, dependent: :destroy
+ has_many :coding_test_submissions, dependent: :destroy
has_one :report_template, dependent: :destroy
has_one :talk, dependent: :destroy
has_one :discord_profile, dependent: :destroy
accepts_nested_attributes_for :discord_profile, allow_destroy: true
has_many :request_retirements, dependent: :destroy
has_one :targeted_request_retirement, class_name: 'RequestRetirement', foreign_key: 'target_user_id', dependent: :destroy, inverse_of: :target_user
+ has_many :micro_reports, dependent: :destroy
has_many :participate_events,
through: :participations,
@@ -171,6 +174,8 @@ class User < ApplicationRecord
through: :regular_event_participations,
source: :regular_event
+ has_many :coding_test_submissions, dependent: :destroy
+
has_one_attached :avatar
has_one_attached :profile_image
@@ -517,6 +522,10 @@ def by_area(area)
end
end
+ def submitted?(coding_test)
+ coding_test_submissions.exists?(coding_test_id: coding_test.id)
+ end
+
def away?
last_activity_at && (last_activity_at <= 10.minutes.ago)
end
@@ -879,6 +888,10 @@ def area
end
end
+ def latest_micro_report_page
+ [micro_reports.page.total_pages, 1].max
+ end
+
private
def password_required?
diff --git a/app/notifiers/activity_notifier.rb b/app/notifiers/activity_notifier.rb
index 0ab2e4df326..543295f73da 100644
--- a/app/notifiers/activity_notifier.rb
+++ b/app/notifiers/activity_notifier.rb
@@ -297,12 +297,13 @@ def update_regular_event(params = {})
params.merge!(@params)
regular_event = params[:regular_event]
receiver = params[:receiver]
+ sender = params[:sender]
notification(
body: "定期イベント【#{regular_event.title}】が更新されました。",
kind: :regular_event_updated,
receiver:,
- sender: regular_event.user,
+ sender:,
link: Rails.application.routes.url_helpers.polymorphic_path(regular_event),
read: false
)
diff --git a/app/notifiers/discord_notifier.rb b/app/notifiers/discord_notifier.rb
index afa5c293222..1b90e91add4 100644
--- a/app/notifiers/discord_notifier.rb
+++ b/app/notifiers/discord_notifier.rb
@@ -117,7 +117,7 @@ def product_review_not_completed(params = {})
product = comment.commentable
body = <<~TEXT.chomp
- ⚠️ #{comment.user.login_name}さんの「#{comment.commentable.practice.title}」の提出物が、最後のコメントから5日経過しました。
+ ⚠️ #{comment.user.login_name}さんの「#{comment.commentable.practice.title}」の提出物が、最後のコメントから3日経過しました。
担当:#{product_checker_discord_name}さん
URL: <#{Rails.application.routes.url_helpers.product_url(product)}>
TEXT
diff --git a/app/validators/markdown_prohibited_validator.rb b/app/validators/markdown_prohibited_validator.rb
new file mode 100644
index 00000000000..a21d32addc3
--- /dev/null
+++ b/app/validators/markdown_prohibited_validator.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class MarkdownProhibitedValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ invalid_format = /\A([-+#*>`]+|\d+\.)\s+|([~|_*])\S.*?\2/.match(value)
+ return unless invalid_format
+
+ record.errors.add(attribute, "にマークダウン記法は使用できません: #{invalid_format}")
+ end
+end
diff --git a/app/views/activity_mailer/hibernated.html.slim b/app/views/activity_mailer/hibernated.html.slim
index ff607cf258c..2b461eb4ed2 100644
--- a/app/views/activity_mailer/hibernated.html.slim
+++ b/app/views/activity_mailer/hibernated.html.slim
@@ -1,3 +1,10 @@
= render '/notification_mailer/notification_mailer_template',
title: "#{@sender.login_name}さんが休会しました。",
link_url: @link_url, link_text: "#{@sender.login_name}さんのページへ" do
+ .a-long-text
+ h2
+ = Hibernation.human_attribute_name('scheduled_return_on')
+ = @hibernation.scheduled_return_on
+ h2
+ = Hibernation.human_attribute_name('reason')
+ = md2html(@hibernation.reason)
diff --git a/app/views/admin/companies/index.html.slim b/app/views/admin/companies/index.html.slim
index 4125c0d83c3..1cf7151b53c 100644
--- a/app/views/admin/companies/index.html.slim
+++ b/app/views/admin/companies/index.html.slim
@@ -27,4 +27,28 @@ main.page-main
hr.a-border
.page-body
.container.is-lg
- = react_component 'AdminCompanies'
+ = paginate @companies if @companies.total_pages > 1
+ .admin-table
+ table.admin-table__table
+ thead.admin-table__header
+ tr.admin-table__labels
+ - %w[名前 ロゴ ウェブサイト アドバイザー招待リンク 研修生招待リンク 編集].each do |label|
+ th.admin-table__label = label
+ tbody.admin-table__items
+ - @companies.each do |company|
+ tr.admin-table__item
+ td.admin-table__item-value.company-name
+ = link_to company.name, company_path(company)
+ td.admin-table__item-value
+ = image_tag company.logo_url, class: 'admin-table__item-logo-image'
+ td.admin-table__item-value = company.website
+ td.admin-table__item-value.is-text-align-center
+ = link_to company.adviser_sign_up_url, class: 'a-button is-sm is-secondary is-icon', title: 'アドバイザー参加登録'
+ i.fa-solid.fa-user-plus
+ td.admin-table__item-value.is-text-align-center
+ = link_to company.trainee_sign_up_url, class: 'a-button is-sm is-secondary is-icon', title: '研修生参加登録' do
+ i.fa-solid.fa-user-plus
+ td.admin-table__item-value.is-text-align-center
+ = link_to edit_admin_company_path(company), class: 'a-button is-sm is-secondary is-icon', title: '企業編集' do
+ i.fa-solid.fa-pen
+ = paginate @companies if @companies.total_pages > 1
diff --git a/app/views/admin/faqs/show.html.slim b/app/views/admin/faqs/show.html.slim
index 142bab1b034..cacc84f369f 100644
--- a/app/views/admin/faqs/show.html.slim
+++ b/app/views/admin/faqs/show.html.slim
@@ -51,7 +51,7 @@ main.page-main
.a-card
.card-body
.card-body__description
- .js-markdown-view.a-long-text.is-md
+ .a-long-text.js-markdown-view
= @faq.answer
hr.a-border-tint
.card-footer
diff --git a/app/views/admin/users/_table.html.slim b/app/views/admin/users/_table.html.slim
index 09f8e7d0da3..879c58e4fea 100644
--- a/app/views/admin/users/_table.html.slim
+++ b/app/views/admin/users/_table.html.slim
@@ -126,7 +126,7 @@
header.card-header
h2.card-header__title
| 全員のメアド
- hr.a-border
+ hr.a-border-tint
.card-body
.card-body__description
textarea.a-text-input
diff --git a/app/views/api/admin/companies/_company.json.jbuilder b/app/views/api/admin/companies/_company.json.jbuilder
deleted file mode 100644
index 2b0c853bc14..00000000000
--- a/app/views/api/admin/companies/_company.json.jbuilder
+++ /dev/null
@@ -1,8 +0,0 @@
-json.id company.id
-json.name company.name
-json.logo_url company.logo_url
-json.description company.description
-json.website company.website
-json.blog_url company.blog_url
-json.adviser_sign_up_url company.adviser_sign_up_url
-json.trainee_sign_up_url company.trainee_sign_up_url
diff --git a/app/views/api/admin/companies/index.json.jbuilder b/app/views/api/admin/companies/index.json.jbuilder
deleted file mode 100644
index 80c857909a5..00000000000
--- a/app/views/api/admin/companies/index.json.jbuilder
+++ /dev/null
@@ -1,6 +0,0 @@
-json.companies do
- json.array! @companies do |company|
- json.partial! "api/admin/companies/company", company: company
- end
-end
-json.total_pages @companies.total_pages
diff --git a/app/views/api/products/unassigned/counts.text.erb b/app/views/api/products/unassigned/counts.text.erb
index 9c7d66a98aa..2469a0c6a14 100644
--- a/app/views/api/products/unassigned/counts.text.erb
+++ b/app/views/api/products/unassigned/counts.text.erb
@@ -13,6 +13,6 @@
<% end %>
<% else %>
-提出されてから<%= @first_alert %>日以上経過している提出物はありません。
+提出されてから<%= @product_deadline_day %>日以上経過している提出物はありません。
メンターの皆様、日々の提出物の確認ありがとうございます。
<% end %>
diff --git a/app/views/application/_mentor_menu.html.slim b/app/views/application/_mentor_menu.html.slim
index d0a2a7957f5..838dade47f4 100644
--- a/app/views/application/_mentor_menu.html.slim
+++ b/app/views/application/_mentor_menu.html.slim
@@ -6,6 +6,10 @@
= link_to mentor_root_path,
class: 'header-dropdown__item-link' do
| メンターページ
+ li.header-dropdown__item
+ = link_to [:mentor, :coding_tests],
+ class: 'header-dropdown__item-link' do
+ | コーディングテスト
li.header-dropdown__item
= link_to mentor_practices_path,
class: 'header-dropdown__item-link' do
diff --git a/app/views/application/footer/_footer.html.slim b/app/views/application/footer/_footer.html.slim
index 0a11e276d76..39bd0da52f2 100644
--- a/app/views/application/footer/_footer.html.slim
+++ b/app/views/application/footer/_footer.html.slim
@@ -58,6 +58,9 @@ footer.footer
li.footer-nav__item
= link_to 'https://circlecast.net/channels/9', class: 'footer-nav__item-link', target: '_blank', rel: 'noopener' do
| フィヨルドブートキャスト
+ li.footer-nav__item
+ = link_to 'https://roulette-talk.com/', class: 'footer-nav__item-link', target: '_blank', rel: 'noopener' do
+ | RouletteTalk
li.footer-nav__item
= link_to '/articles.atom', class: 'a-button is-sm is-secondary is-icon', target: '_blank', rel: 'noopener', title: 'フィヨルドブートキャンプブログフィード' do
i.fa-solid.fa-rss
diff --git a/app/views/articles/_articles.html.slim b/app/views/articles/_articles.html.slim
index 547beee24e5..57cd5c4f6bd 100644
--- a/app/views/articles/_articles.html.slim
+++ b/app/views/articles/_articles.html.slim
@@ -35,6 +35,12 @@ hr.a-border-tint
.col-lg-4.col-md-6.col-xs-12
.thumbnail-card.a-card class=(article.wip? ? ' is-wip' : '')
= link_to article, class: 'thumbnail-card__inner' do
+ - if current_user&.admin_or_mentor_login? && feature_tag?(article)
+ .tags-highlight
+ .tags-highlight__item
+ .a-badge.is-sm.is-primary.is-block
+ .a-badge__inner
+ | 注目の記事
.thumbnail-card__row
- if article.prepared_thumbnail?
= image_tag article.prepared_thumbnail_url, class: 'thumbnail-card__image', alt: "ブログ記事「#{article.title}」のアイキャッチ画像"
diff --git a/app/views/coding_tests/coding_test_submissions/_coding_test_submission.html.slim b/app/views/coding_tests/coding_test_submissions/_coding_test_submission.html.slim
new file mode 100644
index 00000000000..507950dd509
--- /dev/null
+++ b/app/views/coding_tests/coding_test_submissions/_coding_test_submission.html.slim
@@ -0,0 +1,32 @@
+- cts = coding_test_submission
+- coding_test = cts.coding_test
+
+.card-list-item
+ .card-list-item__inner
+ .card-list-item__user
+ = render 'users/icon',
+ user: cts.user,
+ link_class: 'card-list-item__user-link',
+ image_class: 'card-list-item__user-icon'
+ .card-list-item__rows
+ .card-list-item__row
+ .card-list-item-meta
+ .card-list-item-meta__items
+ .card-list-item-meta__item
+ = link_to cts.user, class: 'a-user-name' do
+ = cts.user.long_name
+ .card-list-item-meta__item
+ time.a-meta(datetime="#{cts.updated_at}")
+ = l cts.updated_at
+ .card-list-item__row
+ .a-long-text
+ details
+ summary
+ | #{cts.user.long_name}さんの回答
+ pre(class="language-#{@coding_test.language}")
+ code
+ = cts.code
+ .card-list-item__row
+ .card-list-item__small-link
+ = link_to [coding_test, cts], class: 'a-text-link' do
+ | 回答ページ
diff --git a/app/views/coding_tests/coding_test_submissions/index.html.slim b/app/views/coding_tests/coding_test_submissions/index.html.slim
new file mode 100644
index 00000000000..046ae470b61
--- /dev/null
+++ b/app/views/coding_tests/coding_test_submissions/index.html.slim
@@ -0,0 +1,44 @@
+- title "「#{@coding_test.title}」の回答"
+- description "コーディングテスト「#{@coding_test.title}」の回答の一覧ページです。"
+- practice = @coding_test.practice
+
+= render '/practices/page_header', title: practice.title, practice: practice
+
+= practice_page_tabs(practice, active_tab: 'コーディングテスト')
+
+.page-main
+ header.page-main-header
+ .container
+ .page-main-header__inner
+ .page-main-header__start
+ h2.page-main-header__title
+ | #{@coding_test.title}(#{@coding_test.language})
+ .page-main-header__end
+ .page-main-header-actions
+ ul.page-main-header-actions__items
+ li.page-main-header-actions__item
+ = link_to coding_test_coding_test_submissions_path(@coding_test),
+ class: 'a-button is-md is-secondary is-block is-back' do
+ | テスト一覧
+ - if current_user.submitted?(@coding_test)
+ li.page-main-header-actions__item
+ = link_to @coding_test, class: 'a-button is-md is-secondary is-block is-back' do
+ | 自分の回答
+ hr.a-border
+
+ .page-body
+ .page-content
+ .container.is-md
+ - if @coding_test_submissions.present?
+ .page-content.coding-test-submissions
+ = paginate @coding_test_submissions
+ .card-list.a-card
+ .card-list__items
+ = render @coding_test_submissions
+ = paginate @coding_test_submissions
+ - else
+ .o-empty-message
+ .o-empty-message__icon
+ i.fa-regular.fa-sad-tear
+ .o-empty-message__text
+ | 回答コードはまだありません。
diff --git a/app/views/coding_tests/coding_test_submissions/show.html.slim b/app/views/coding_tests/coding_test_submissions/show.html.slim
new file mode 100644
index 00000000000..063dd008b00
--- /dev/null
+++ b/app/views/coding_tests/coding_test_submissions/show.html.slim
@@ -0,0 +1,65 @@
+ruby:
+ cts = @coding_test_submission
+ practice = cts.practice
+ title "「#{@coding_test.title}」の回答コード"
+ description "#{cts.user.login_name}さんの回答コードです。"
+
+= render '/practices/page_header', title: practice.title, practice: practice
+
+= practice_page_tabs(practice, active_tab: 'コーディングテスト')
+
+.page-main
+
+ header.page-main-header
+ .container
+ .page-main-header__inner
+ .page-main-header__start
+ h2.page-main-header__title
+ | #{@coding_test.title}(#{@coding_test.language})
+ .page-main-header__end
+ .page-main-header-actions
+ ul.page-main-header-actions__items
+ li.page-main-header-actions__item
+ = link_to practice_coding_tests_path(@coding_test_submission.practice), class: 'a-button is-md is-secondary is-block is-back' do
+ | テスト一覧
+ li.page-main-header-actions__item
+ = link_to [@coding_test, :coding_test_submissions], class: 'a-button is-md is-secondary is-block is-back' do
+ | みんなの回答一覧
+ hr.a-border
+
+ .page-body
+ .page-content
+ .container.is-md
+ header.page-content-header
+ .page-content-header__start
+ .page-content-header__user
+ = link_to cts.user,
+ itemprop: 'url',
+ class: 'page-content-header__user-link' do
+ span class="a-user-role is-#{cts.user.primary_role}"
+ = image_tag cts.user.avatar_url,
+ title: cts.user.icon_title,
+ class: 'page-content-header__user-icon a-user-icon'
+ .page-content-header__end
+ .page-content-header__row
+ .page-content-header__before-title
+ = link_to cts.user, class: 'a-user-name' do
+ = cts.user.login_name
+ h1.page-content-header__title
+ = title
+ .page-content-header__row
+ .page-content-header-metas
+ .page-content-header-metas__start
+ .page-content-header-metas__meta
+ .a-meta
+ .a-meta__label 提出日
+ time.a-meta__value(datetime="#{cts.created_at}" pubdate='pubdate')
+ = l cts.created_at
+
+ .a-card
+ .card-body
+ .card-body__description
+ .a-long-text
+ pre(class="language-#{@coding_test.language}")
+ code
+ = cts.code
diff --git a/app/views/coding_tests/show.html.slim b/app/views/coding_tests/show.html.slim
new file mode 100644
index 00000000000..4dd07d4af55
--- /dev/null
+++ b/app/views/coding_tests/show.html.slim
@@ -0,0 +1,167 @@
+ruby:
+ title @coding_test.title
+ practice = @coding_test.practice
+ description "#{@coding_test.title}のコーディングテストです。"
+
+= render '/practices/page_header', title: practice.title, practice: practice
+
+= practice_page_tabs(@practice, active_tab: 'コーディングテスト')
+
+.page-main
+ header.page-main-header
+ .container
+ .page-main-header__inner
+ .page-main-header__start
+ h2.page-main-header__title
+ | #{@coding_test.title}(#{@coding_test.language})
+ .page-main-header__end
+ .page-main-header-actions
+ ul.page-main-header-actions__items
+ li.page-main-header-actions__item
+ = link_to [@practice, :coding_tests], class: 'a-button is-md is-secondary is-block is-back' do
+ | テスト一覧
+ - if current_user.submitted?(@coding_test)
+ li.page-main-header-actions__item
+ = link_to [@coding_test, :coding_test_submissions], class: 'a-button is-md is-secondary is-block is-back' do
+ | みんなの回答一覧
+ hr.a-border
+
+ .page-body
+ .page-content
+
+ - if current_user.submitted?(@coding_test)
+ .a-page-notice
+ .container
+ .a-page-notice__inner
+ p
+ | このコーディングテストはクリアしました🎉
+ | みんなの回答が確認できます。
+
+ .container.is-xl
+ .row.is-gutter-width-32
+ .col-lg-9.col-xs-12
+
+ .coding-test.page-content
+ header.page-content-header
+ .page-content-header__end
+ .page-content-header__row
+ .page-content-header__before-title
+ p.coding-test__language
+ = @coding_test.language
+ h1.page-content-header__title
+ = @coding_test.title
+
+ .a-card
+ header.card-header
+ h2.card-header__title
+ = CodingTest.human_attribute_name :description
+ hr.a-border-tint
+ .card-body
+ .card-body__description
+ .a-long-text.is-md.js-markdown-view
+ = @coding_test.description
+
+ - @coding_test.coding_test_cases.each_with_index do |coding_test_case, i|
+ .a-card.overflow-y-hidden
+ header.card-header.is-sm
+ h2.card-header__title
+ = "入力・出力例#{i + 1}"
+ hr.a-border-tint
+ .card-body
+ .row.is-gutter-width-0
+ .col-lg-6.col-xs-12
+ .card-body__description(class="#{coding_test_case.input.present? ? '' : 'hidden'}")
+ section.io-sample
+ header.io-sample__header
+ h3.io-sample__title
+ = "入力例#{i + 1}"
+ .io-sample__body
+ pre.io-sample__code.sample.coding_test_case-input(id="coding_test_case_#{coding_test_case.id}_input" class="language-#{@coding_test.language}")
+ = coding_test_case.input
+ .col-lg-6.col-xs-12
+ .card-body__description(class="#{coding_test_case.output.present? ? '' : 'hidden'}")
+ .io-sample
+ header.io-sample__header
+ h3.io-sample__title
+ = "出力例#{i + 1}"
+ .io-sample__body
+ pre.io-sample__code.sample.coding_test_case-output(id="coding_test_case_#{coding_test_case.id}_output" class="language-#{@coding_test.language}")
+ = coding_test_case.output
+
+ - if current_user.submitted?(@coding_test)
+ .a-card
+ header.card-header
+ h2.card-header__title
+ | 自分の回答
+ hr.a-border-tint
+ .card-body
+ .card-body__description
+ .a-long-text
+ pre(class="language-#{@coding_test.language}")
+ code
+ = current_user.coding_test_submissions.find_by(coding_test: @coding_test).code
+ - else
+ .a-card
+ header.card-header.is-sm
+ h2.card-header__title
+ | 回答を入力
+ hr.a-border-tint
+ .card-body
+ #code_editor.code-editor(
+ data-language="#{@coding_test.language}"
+ data-coding-test-id="#{@coding_test.id}"
+ data-practice-id="#{@coding_test.practice_id}"
+ )
+
+ hr.a-border-tint
+ footer.card-footer
+ .card-main-actions
+ ul.card-main-actions__items
+ li.card-main-actions__item
+ button#run.a-button.is-md.is-primary.is-block
+ | 実行
+
+ .a-card
+ header.card-header
+ h2.card-header__title
+ | 結果
+ .card-body
+ .a-table
+ table.result-table#result
+
+ - if current_user.admin_or_mentor?
+ .a-card.is-only-mentor
+ header.card-header.is-sm
+ h2.card-header__title 管理者・メンター用メニュー
+ hr.a-border-tint
+ footer.card-footer
+ .card-main-actions
+ ul.card-main-actions__items
+ li.card-main-actions__item
+ = link_to edit_mentor_coding_test_path(@coding_test), class: 'a-button is-md is-secondary is-block' do
+ i.fa-solid.fa-pen
+ span
+ | 問題を編集
+ li.card-main-actions__item
+ = link_to [@coding_test, :coding_test_submissions], class: 'a-button is-md is-secondary is-block' do
+ | みんなのコード
+
+ .col-lg-3.col-xs-12
+ nav.page-nav.a-card
+ header.page-nav__header
+ h2.page-nav__title
+ = link_to @practice,
+ class: 'page-nav__title-inner' do
+ = @practice.title
+ hr.a-border-tint
+
+ ul.page-nav__items
+ - @practice.coding_tests.each do |coding_test|
+ li.page-nav__item(class="#{@coding_test == coding_test ? 'is-current' : ''}")
+ = link_to coding_test, class: 'page-nav__item-link' do
+ .page-nav__item-link-inner
+ - if coding_test.passed_by?(current_user)
+ .coding-tests-item__passed
+ .a-badge.is-success.is-xs
+ | クリア
+ = coding_test.title
diff --git a/app/views/companies/users/_company_users.html.slim b/app/views/companies/users/_company_users.html.slim
index a0993ba00ae..666a5b1f1b5 100644
--- a/app/views/companies/users/_company_users.html.slim
+++ b/app/views/companies/users/_company_users.html.slim
@@ -1,6 +1,5 @@
.page-body
- nav.pagination
- = paginate users
+ = paginate users
.container
.users
- if users.empty?
@@ -13,5 +12,4 @@
- else
.row
= render users
- nav.pagination
- = paginate users
+ = paginate users
diff --git a/app/views/events/_event.html.slim b/app/views/events/_event.html.slim
index 44de0831766..ebe6d5478c7 100644
--- a/app/views/events/_event.html.slim
+++ b/app/views/events/_event.html.slim
@@ -99,7 +99,7 @@
header.card-header.is-sm
h2.card-header__title
| 補欠者(#{event.waitlist.count}名)
- hr.a-border
+ hr.a-border-tint
.card-body
.card-body__description
ul.user-icons__items
diff --git a/app/views/home/_mentor_dashboard.html.slim b/app/views/home/_mentor_dashboard.html.slim
index 853125dffaf..6b7fce5b3bd 100644
--- a/app/views/home/_mentor_dashboard.html.slim
+++ b/app/views/home/_mentor_dashboard.html.slim
@@ -8,7 +8,13 @@
- if unchecked_report_count > 100
= render 'unchecked_report_alert', unchecked_report_count: unchecked_report_count
.dashboard-category__body
- #js-products(data-title="#{title}" data-mentor-login="#{mentor_login?}" data-current-user-id="#{current_user.id}" data-product-deadline-days="#{@product_deadline_day}")
+ = render(Products::UnassignedProductsComponent.new( \
+ products:,
+ products_grouped_by_elapsed_days:,
+ is_mentor: mentor_login?,
+ is_admin: admin_login?,
+ current_user_id: current_user.id,
+ reply_warning_days: @product_deadline_day))
.dashboard-contents__col.is-main
.dashboard-contents__categories
.dashboard-category
diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim
index fbc3ebe9f54..68b20e7a864 100644
--- a/app/views/home/index.html.slim
+++ b/app/views/home/index.html.slim
@@ -15,7 +15,7 @@
- if current_user.adviser?
= render 'adviser_dashboard'
- elsif current_user.mentor?
- = render 'mentor_dashboard'
+ = render 'mentor_dashboard', products: @products, products_grouped_by_elapsed_days: @products_grouped_by_elapsed_days
- else
.columns
.container.is-xl
diff --git a/app/views/kaminari/_first_page.html.slim b/app/views/kaminari/_first_page.html.slim
index 28f31c723c1..38c44635364 100644
--- a/app/views/kaminari/_first_page.html.slim
+++ b/app/views/kaminari/_first_page.html.slim
@@ -1,4 +1,7 @@
-- # rubocop:disable Rails/OutputSafety
li.pagination__item.is-first
- = link_to_unless current_page.first?, raw('
'), url, remote:, class: 'pagination__item-link is-first'
-- # rubocop:enable Rails/OutputSafety
+ - if current_page.first?
+ span.pagination__item-link.is-first.is-disabled
+ i.fas.fa-angle-double-left
+ - else
+ = link_to url, remote:, class: 'pagination__item-link is-first' do
+ i.fas.fa-angle-double-left
diff --git a/app/views/kaminari/_paginator.html.slim b/app/views/kaminari/_paginator.html.slim
index bf21aecb1d9..6b19a26eb01 100644
--- a/app/views/kaminari/_paginator.html.slim
+++ b/app/views/kaminari/_paginator.html.slim
@@ -2,7 +2,7 @@
nav.pagination
.container
ul.pagination__items
- == first_page_tag unless current_page.first?
+ == first_page_tag
== prev_page_tag unless current_page.first?
- each_page do |page|
- if page.left_outer? || page.right_outer? || page.inside_window?
diff --git a/app/views/mentor/_mentor_page_tabs.html.slim b/app/views/mentor/_mentor_page_tabs.html.slim
index e4f3cadb4fe..66d6f857aad 100644
--- a/app/views/mentor/_mentor_page_tabs.html.slim
+++ b/app/views/mentor/_mentor_page_tabs.html.slim
@@ -5,6 +5,10 @@
= link_to 'メンターページ',
mentor_root_path,
class: "page-tabs__item-link #{current_link(/^mentor-home/)}"
+ li.page-tabs__item
+ = link_to 'コーディングテスト',
+ mentor_coding_tests_path,
+ class: "page-tabs__item-link #{current_link(/^mentor-coding_tests/)}"
li.page-tabs__item
= link_to 'プラクティス',
mentor_practices_path,
diff --git a/app/views/mentor/categories/show.html.slim b/app/views/mentor/categories/show.html.slim
index 1ad413610ea..27ed1b71c90 100644
--- a/app/views/mentor/categories/show.html.slim
+++ b/app/views/mentor/categories/show.html.slim
@@ -80,7 +80,7 @@ header.page-header
h2.card-header__title
| プラクティス
| (#{@category.practices.count})
- hr.a-border
+ hr.a-border-tint
- if @category.practices.present?
.card-list__items
- @category.practices.each do |practice|
diff --git a/app/views/mentor/coding_tests/_coding_test_case_fields.html.slim b/app/views/mentor/coding_tests/_coding_test_case_fields.html.slim
new file mode 100644
index 00000000000..51e3d04c868
--- /dev/null
+++ b/app/views/mentor/coding_tests/_coding_test_case_fields.html.slim
@@ -0,0 +1,20 @@
+.nested-fields.test-case.mt-8
+ .a-card
+ header.card-header.is-sm.relative
+ h2.card-header__title
+ | テストケース
+ .books-form__delete
+ = link_to_remove_association f, class: 'books-form__delete-link' do
+ i.fas.fa-times
+ .a-border-tint
+ .card-body
+ .card-body__description
+ .row
+ .col-lg-6.col-xs-12
+ .form-item
+ = f.label :input, class: 'a-form-label'
+ = f.text_area :input, class: 'a-text-input test_case_input'
+ .col-lg-6.col-xs-12
+ .form-item
+ = f.label :output, class: 'a-form-label'
+ = f.text_area :output, class: 'a-text-input test_case_output'
diff --git a/app/views/mentor/coding_tests/_form.html.slim b/app/views/mentor/coding_tests/_form.html.slim
new file mode 100644
index 00000000000..0aa3085a8d9
--- /dev/null
+++ b/app/views/mentor/coding_tests/_form.html.slim
@@ -0,0 +1,64 @@
+= form_with model: [:mentor, coding_test], local: true, html: { name: 'coding_test', class: 'form' } do |f|
+ = render 'errors', object: coding_test
+ = f.hidden_field :course_id, value: params[:course_id]
+ .form__items
+ .form-item
+ .row
+ .col-lg-6.col-xs-12
+ = f.label :practice, class: 'a-form-label'
+ .select-practices
+ = f.collection_select :practice_id,
+ Practice.all,
+ :id,
+ :title,
+ { include_blank: 'プラクティスを選択してください' },
+ { id: 'js-choices-single-select' }
+ .col-md-3.col-xs-6
+ = f.label :language, class: 'a-form-label'
+ .select-users
+ = f.select :language,
+ CodingTest.languages.keys,
+ { include_blank: '言語を選択してください' },
+ { class: 'js-select2' }
+ .form-item
+ .row
+ .col-md-6.col-xs-12
+ = f.label :title, class: 'a-form-label'
+ = f.text_field :title, class: 'a-text-input js-warning-form', placeholder: '文字列操作'
+ .col-md-3.col-xs-6
+ = f.label :user, class: 'a-form-label'
+ .select-users
+ = f.select :user_id,
+ User.where(retired_on: nil).pluck(:login_name, :id).sort,
+ { include_blank: '作成者を選択してください' },
+ { class: 'js-select2' }
+ .form-item
+ .row.js-markdown-parent
+ .col-md-6.col-xs-12
+ = f.label :description, class: 'a-form-label'
+ = f.text_area :description,
+ class: 'a-text-input js-warning-form js-markdown markdown-form__text-area practices-edit__input',
+ data: { 'preview': '.js-preview' }
+ .col-md-6.col-xs-12
+ .a-form-label プレビュー
+ .js-preview.a-long-text.is-md.practices-edit__input.markdown-form__preview
+
+ .form__items
+ .test-cases
+ = f.fields_for :coding_test_cases do |coding_test_case|
+ = render 'coding_test_case_fields', f: coding_test_case
+ .test-cases__add.mt-6
+ = link_to_add_association f, :coding_test_cases, class: 'a-button is-md is-primary' do
+ i.fa-regular.fa-plus
+ span
+ | テストケースを追加
+
+ .form-actions
+ ul.form-actions__items
+ li.form-actions__item.is-main
+ = f.submit nil, class: 'a-button is-lg is-primary is-block'
+ li.form-actions__item.is-sub
+ = link_to 'キャンセル', mentor_categories_path, class: 'a-button is-sm is-text'
+ - if coding_test.id.present?
+ li.form-actions__item.is-muted
+ = link_to '削除', mentor_coding_test_path(coding_test), method: :delete, data: { confirm: '本当によろしいですか?' }, class: 'a-button is-sm is-muted-text'
diff --git a/app/views/mentor/coding_tests/edit.html.slim b/app/views/mentor/coding_tests/edit.html.slim
new file mode 100644
index 00000000000..764168b46ed
--- /dev/null
+++ b/app/views/mentor/coding_tests/edit.html.slim
@@ -0,0 +1,28 @@
+- title 'コーディングテスト編集'
+
+header.page-header
+ .container
+ .page-header__inner
+ .page-header__start
+ .page-header__title メンターページ
+
+= render 'mentor/mentor_page_tabs'
+
+.page-main
+ header.page-main-header
+ .container
+ .page-main-header__inner
+ .page-main-header__start
+ h1.page-main-header__title
+ = title
+ .page-main-header__end
+ .page-main-header-actions
+ .page-main-header-actions__items
+ .page-main-header-actions__item
+ = link_to mentor_coding_tests_path,
+ class: 'a-button is-md is-secondary is-block is-back' do
+ | コーディングテスト
+ hr.a-border
+ .page-body
+ .container.is-xxl
+ = render 'form', coding_test: @coding_test
diff --git a/app/views/mentor/coding_tests/index.html.slim b/app/views/mentor/coding_tests/index.html.slim
new file mode 100644
index 00000000000..307e4784e65
--- /dev/null
+++ b/app/views/mentor/coding_tests/index.html.slim
@@ -0,0 +1,72 @@
+- title 'コーディングテスト'
+
+header.page-header
+ .container
+ .page-header__inner
+ .page-header__start
+ h2.page-header__title メンターページ
+
+= render 'mentor/mentor_page_tabs'
+
+main.page-main
+ header.page-main-header
+ .container
+ .page-main-header__inner
+ .page-main-header__start
+ h1.page-main-header__title = title
+ .page-main-header__end
+ .page-header-actions
+ .page-header-actions__items
+ .page-header-actions__item
+ = link_to new_mentor_coding_test_path,
+ class: 'a-button is-md is-secondary is-block' do
+ i.fa-regular.fa-plus
+ | コーディングテスト作成
+
+ hr.a-border
+ .page-body
+ .container.is-lg
+ .admin-table.is-grab id='mentor-practices'
+
+ = paginate @coding_tests
+
+ table.admin-table__table
+ thead.admin-table__header
+ tr.admin-table__labels
+ th.admin-table__label
+ = CodingTest.human_attribute_name :title
+ th.admin-table__label
+ = CodingTest.human_attribute_name :language
+ th.admin-table__label
+ | 入力例・出力例
+ th.admin-table__label
+ = CodingTest.human_attribute_name :practice
+ th.admin-table__label
+ = CodingTest.human_attribute_name :coding_test_submissions
+ th.admin-table__label.actions 編集
+ tbody.admin-table__items
+ - @coding_tests.each do |coding_test|
+ tr.admin-table__item
+ td.admin-table__item-value
+ = link_to coding_test.title, coding_test
+ td.admin-table__item-value
+ = coding_test.language
+ td.admin-table__item-value
+ ul
+ - coding_test.coding_test_cases.each do |coding_test_case|
+ li
+ = coding_test_case.input
+ = ' / '
+ = coding_test_case.output
+ td.admin-table__item-value
+ = link_to coding_test.practice.title, coding_test.practice
+ td.admin-table__item-value.is-text-align-center
+ = link_to '一覧', [coding_test, :coding_test_submissions]
+ td.admin-table__item-value.is-text-align-center
+ ul.is-inline-buttons
+ li
+ = link_to edit_mentor_coding_test_path(coding_test),
+ class: 'a-button is-sm is-secondary is-icon'
+ i.fa-solid.fa-pen
+
+ = paginate @coding_tests
diff --git a/app/views/mentor/coding_tests/new.html.slim b/app/views/mentor/coding_tests/new.html.slim
new file mode 100644
index 00000000000..e2d8d2b31e3
--- /dev/null
+++ b/app/views/mentor/coding_tests/new.html.slim
@@ -0,0 +1,28 @@
+- title 'メンターページ'
+
+header.page-header
+ .container
+ .page-header__inner
+ .page-header__start
+ h2.page-header__title
+ = title
+
+= render 'mentor/mentor_page_tabs'
+
+.page-main
+ header.page-main-header
+ .container
+ .page-main-header__inner
+ .page-main-header__start
+ h1.page-main-header__title コーディングテスト作成
+ .page-main-header__end
+ .page-main-header-actions
+ .page-main-header-actions__items
+ .page-main-header-actions__item
+ = link_to mentor_coding_tests_path,
+ class: 'a-button is-md is-secondary is-block is-back' do
+ | コーディングテスト
+ hr.a-border
+ .page-body
+ .container.is-xxl
+ = render 'form', coding_test: @coding_test
diff --git a/app/views/mentor/practices/submission_answer/edit.html.slim b/app/views/mentor/practices/submission_answer/edit.html.slim
index cd9f344f533..eb0f2e2d6e9 100644
--- a/app/views/mentor/practices/submission_answer/edit.html.slim
+++ b/app/views/mentor/practices/submission_answer/edit.html.slim
@@ -5,7 +5,11 @@ ruby:
- title "「#{practice_title}」の模範解答編集"
- set_meta_tags description: "プラクティス「#{practice_title}」の模範解答編集ページです。"
-= render '/practices/page_header', title: practice_title, category: category
+= render '/practices/page_header',
+ title: practice_title,
+ category: category,
+ practice: @practice
+
= practice_page_tabs(@practice, active_tab: '模範解答')
.page-main
diff --git a/app/views/mentor/practices/submission_answer/new.html.slim b/app/views/mentor/practices/submission_answer/new.html.slim
index e4056c2e079..d3e3cfc5333 100644
--- a/app/views/mentor/practices/submission_answer/new.html.slim
+++ b/app/views/mentor/practices/submission_answer/new.html.slim
@@ -5,7 +5,10 @@ ruby:
- title "「#{practice_title}」の模範解答作成"
- set_meta_tags description: "プラクティス「#{practice_title}」の模範解答作成ページです。"
-= render '/practices/page_header', title: practice_title, category: category
+= render '/practices/page_header',
+ title: practice_title,
+ category: category,
+ practice: @practice
= practice_page_tabs(@practice, active_tab: '模範解答')
.page-main
diff --git a/app/views/practices/_coding_tests.html.slim b/app/views/practices/_coding_tests.html.slim
new file mode 100644
index 00000000000..242bae68100
--- /dev/null
+++ b/app/views/practices/_coding_tests.html.slim
@@ -0,0 +1,15 @@
+.a-card.coding-tests
+ header.card-header
+ h2.card-header__title
+ | コーディングテスト
+ hr.a-border-tint
+ .card-list
+ - coding_tests.order(:position).each do |coding_test|
+ .card-list-item
+ .coding-tests-item
+ - if coding_test.passed_by?(current_user)
+ .coding-tests-item__passed
+ .a-badge.is-success.is-sm
+ | クリア
+ = link_to coding_test, class: 'coding-tests-item__test-link' do
+ = coding_test.title
diff --git a/app/views/practices/_description.html.slim b/app/views/practices/_description.html.slim
index 5a9748af412..a7f940bc1f7 100644
--- a/app/views/practices/_description.html.slim
+++ b/app/views/practices/_description.html.slim
@@ -2,7 +2,7 @@
header.card-header
h2.card-header__title
= Practice.human_attribute_name :description
- hr.a-border
+ hr.a-border-tint
.card-body.is-practice
.card-body__description
.a-long-text.is-md.js-markdown-view
diff --git a/app/views/practices/_memo.html.slim b/app/views/practices/_memo.html.slim
index 9eea83d5ac1..b8e0ca89c24 100644
--- a/app/views/practices/_memo.html.slim
+++ b/app/views/practices/_memo.html.slim
@@ -2,7 +2,7 @@
header.card-header
h2.card-header__title
= Practice.human_attribute_name :memo
- hr.a-border
+ hr.a-border-tint
.card-body.is-memo
.card-body__description
.a-long-text.is-md.js-markdown-view
diff --git a/app/views/practices/_page_header.html.slim b/app/views/practices/_page_header.html.slim
index c10bfff489a..0b204453718 100644
--- a/app/views/practices/_page_header.html.slim
+++ b/app/views/practices/_page_header.html.slim
@@ -1,3 +1,4 @@
+- category = practice.category(current_user.course)
header.page-header
.container
.page-header__inner
@@ -7,9 +8,9 @@ header.page-header
.page-header__end
.page-header-actions
ul.page-header-actions__items
- - if current_user.mentor? && @practice.submission && !@practice.submission_answer
+ - if current_user.mentor? && practice.submission && !practice.submission_answer
li.page-header-actions__item.is-hidden-sm-down.is-only-mentor
- = link_to new_mentor_practice_submission_answer_path(@practice), class: 'a-button is-md is-secondary is-block' do
+ = link_to new_mentor_practice_submission_answer_path(practice), class: 'a-button is-md is-secondary is-block' do
i.fa-regular.fa-plus
span
| 模範解答作成
diff --git a/app/views/practices/_summary.html.slim b/app/views/practices/_summary.html.slim
index a0ee8e44744..5dd2e66bcd0 100644
--- a/app/views/practices/_summary.html.slim
+++ b/app/views/practices/_summary.html.slim
@@ -2,7 +2,7 @@
header.card-header
h2.card-header__title
= Practice.human_attribute_name :summary
- hr.a-border
+ hr.a-border-tint
.card-body.is-practice
- if practice.ogp_image.attached?
= image_tag practice.ogp_image
diff --git a/app/views/practices/coding_tests/_coding_test.html.slim b/app/views/practices/coding_tests/_coding_test.html.slim
new file mode 100644
index 00000000000..faa834f2675
--- /dev/null
+++ b/app/views/practices/coding_tests/_coding_test.html.slim
@@ -0,0 +1,21 @@
+.card-list-item
+ .card-list-item__inner
+ .card-list-item__rows
+ .card-list-item__row
+ .card-list-item-title
+ h2.card-list-item-title__title
+ = link_to coding_test.title,
+ coding_test,
+ class: 'card-list-item-title__link a-text-link'
+ .card-list-item__row
+ .card-list-item-meta
+ .card-list-item-meta__items
+ .card-list-item-meta__item
+ .a-meta
+ span.a-meta__label
+ | 更新
+ span.a-meta__value
+ | #{l coding_test.updated_at}
+ - if coding_test.passed_by?(current_user)
+ .stamp.is-circle.is-cleared
+ .stamp__content.is-icon クリア
diff --git a/app/views/practices/coding_tests/index.html.slim b/app/views/practices/coding_tests/index.html.slim
new file mode 100644
index 00000000000..119b1372e1d
--- /dev/null
+++ b/app/views/practices/coding_tests/index.html.slim
@@ -0,0 +1,29 @@
+- title "#{@practice.title}のコーディングテスト"
+- description 'コーディングテストの一覧ページです。'
+
+= render '/practices/page_header', title: @practice.title, practice: @practice
+
+= practice_page_tabs(@practice, active_tab: 'コーディングテスト')
+
+.page-main
+ header.page-main-header
+ .container
+ .page-main-header__inner
+ .page-main-header__start
+ h2.page-main-header__title
+ | #{@practice.title}のコーディングテスト
+ .page-main-header__end
+ hr.a-border
+
+ .page-body
+ .container.is-md
+ - if @coding_tests.empty?
+ .o-empty-message
+ .o-empty-message__icon
+ i.fa-regular.fa-sad-tear
+ p.o-empty-message__text
+ | コーディングテストはまだありません
+ - else
+ .card-list.a-card
+ .card-list__items
+ = render @coding_tests
diff --git a/app/views/practices/pages/index.html.slim b/app/views/practices/pages/index.html.slim
index a54449969b5..51469b6f82b 100644
--- a/app/views/practices/pages/index.html.slim
+++ b/app/views/practices/pages/index.html.slim
@@ -2,7 +2,11 @@
- set_meta_tags description: "プラクティス「#{@practice.title}」に関するドキュメント一覧です。"
- category = @practice.category(current_user.course)
-= render '/practices/page_header', title: @practice.title, category: category
+= render '/practices/page_header',
+ title: @practice.title,
+ category: category,
+ practice: @practice
+
= practice_page_tabs(@practice, active_tab: 'Docs')
.page-body
diff --git a/app/views/practices/products/index.html.slim b/app/views/practices/products/index.html.slim
index 051650282ac..4a6b2a5fe75 100644
--- a/app/views/practices/products/index.html.slim
+++ b/app/views/practices/products/index.html.slim
@@ -5,7 +5,11 @@
- set_meta_tags description: "プラクティス「#{@practice.title}」の提出物一覧です。"
- category = @practice.category(current_user.course)
-= render '/practices/page_header', title: @practice.title, category: category
+= render '/practices/page_header',
+ title: @practice.title,
+ category: category,
+ practice: @practice
+
= practice_page_tabs(@practice, active_tab: '提出物')
.page-body
diff --git a/app/views/practices/questions/index.html.slim b/app/views/practices/questions/index.html.slim
index cd471e2c963..e7f8c9bfc8a 100644
--- a/app/views/practices/questions/index.html.slim
+++ b/app/views/practices/questions/index.html.slim
@@ -2,7 +2,11 @@
- set_meta_tags description: "プラクティス「#{@practice.title}」に関するQ&A一覧です。"
- category = @practice.category(current_user.course)
-= render '/practices/page_header', title: @practice.title, category: category
+= render '/practices/page_header',
+ title: @practice.title,
+ category: category,
+ practice: @practice
+
= practice_page_tabs(@practice, active_tab: '質問')
nav.tab-nav
diff --git a/app/views/practices/reports/index.html.slim b/app/views/practices/reports/index.html.slim
index d7a6f6d2060..0bf6945533b 100644
--- a/app/views/practices/reports/index.html.slim
+++ b/app/views/practices/reports/index.html.slim
@@ -2,7 +2,11 @@
- set_meta_tags description: "プラクティス「#{@practice.title}」に関する日報一覧です。"
- category = @practice.category(current_user.course)
-= render '/practices/page_header', title: @practice.title, category: category
+= render '/practices/page_header',
+ title: @practice.title,
+ category: category,
+ practice: @practice
+
= practice_page_tabs(@practice, active_tab: '日報')
.page-body
diff --git a/app/views/practices/show.html.slim b/app/views/practices/show.html.slim
index 0937c44643c..c638c64a71a 100644
--- a/app/views/practices/show.html.slim
+++ b/app/views/practices/show.html.slim
@@ -3,7 +3,12 @@
- category = @practice.category(current_user.course)
= render '/shared/modal_learning_completion', practice: @practice, tweet_url: @tweet_url, should_display_message_automatically: false
-= render '/practices/page_header', title: @practice.title, category: category
+
+= render '/practices/page_header',
+ title: @practice.title,
+ category: category,
+ practice: @practice
+
= practice_page_tabs(@practice, active_tab: 'プラクティス')
.page-body
@@ -85,7 +90,7 @@
header.card-header
h2.card-header__title
= Practice.human_attribute_name :goal
- hr.a-border
+ hr.a-border-tint
.card-body.is-goal
.card-body__description
.a-long-text.is-md.js-markdown-view
@@ -111,13 +116,16 @@
br
| 終了条件をクリアしたら修了にしてください。
+ - if @practice.coding_tests.present?
+ = render partial: 'coding_tests', locals: { coding_tests: @practice.coding_tests }
+
- if current_user.admin_or_mentor?
= render 'memo', practice: @practice
.a-card.is-only-mentor
header.card-header
h2.card-header__title 管理者・メンター用メニュー
- hr.a-border
+ hr.a-border-tint
footer.card-footer
.card-main-actions
ul.card-main-actions__items
diff --git a/app/views/practices/submission_answer/_admin_or_mentor_menu.html.slim b/app/views/practices/submission_answer/_admin_or_mentor_menu.html.slim
index 8e49aca01d0..eb4f762ea79 100644
--- a/app/views/practices/submission_answer/_admin_or_mentor_menu.html.slim
+++ b/app/views/practices/submission_answer/_admin_or_mentor_menu.html.slim
@@ -3,7 +3,7 @@
header.card-header
h2.card-header__title
| 管理者・メンター用メニュー
- hr.a-border
+ hr.a-border-tint
footer.card-footer
.card-main-actions
ul.card-main-actions__items
diff --git a/app/views/practices/submission_answer/_description.html.slim b/app/views/practices/submission_answer/_description.html.slim
index bffab9e0b1a..249ef06b8f2 100644
--- a/app/views/practices/submission_answer/_description.html.slim
+++ b/app/views/practices/submission_answer/_description.html.slim
@@ -3,8 +3,8 @@
header.card-header
h2.card-header__title
= SubmissionAnswer.human_attribute_name :description
- hr.a-border
- .card-body.is-practice
- .card-body__description
- .a-long-text.is-md.js-markdown-view
- = submission_answer.description
+ hr.a-border-tint
+ .card-body.is-practice
+ .card-body__description
+ .a-long-text.is-md.js-markdown-view
+ = submission_answer.description
diff --git a/app/views/practices/submission_answer/show.html.slim b/app/views/practices/submission_answer/show.html.slim
index 61df30b0487..785fe92ea4f 100644
--- a/app/views/practices/submission_answer/show.html.slim
+++ b/app/views/practices/submission_answer/show.html.slim
@@ -5,7 +5,11 @@ ruby:
- title "「#{practice_title}」の模範解答"
- set_meta_tags description: "プラクティス「#{practice_title}」の模範解答です。"
-= render '/practices/page_header', title: practice_title, category: category
+= render '/practices/page_header',
+ title: practice_title,
+ category: category,
+ practice: @practice
+
= practice_page_tabs(@practice, active_tab: '模範解答')
.page-body
diff --git a/app/javascript/components/ai-answer.vue b/app/views/questions/_ai_answer.html.slim
similarity index 65%
rename from app/javascript/components/ai-answer.vue
rename to app/views/questions/_ai_answer.html.slim
index 915bafa175a..ea8729458f4 100644
--- a/app/javascript/components/ai-answer.vue
+++ b/app/views/questions/_ai_answer.html.slim
@@ -1,4 +1,3 @@
-
.thread-comments.is-only-mentor
header.thread-comments__header
h2.thread-comments__title
@@ -19,21 +18,5 @@
.thread-comment__end
.a-card.is-answer
.thread-comment__description
- .a-long-text.is-md(v-html='markdownDescription')
-
-
+ .a-long-text.is-md.js-markdown-view
+ = question.ai_answer
diff --git a/app/views/questions/_answer.html.slim b/app/views/questions/_answer.html.slim
new file mode 100644
index 00000000000..9928be7db75
--- /dev/null
+++ b/app/views/questions/_answer.html.slim
@@ -0,0 +1,84 @@
+.thread-comment.answer id="answer_#{answer.id}" data-question_id="#{question.id}" data-answer_id="#{answer.id}" data-answer_description="#{answer.description}"
+ .thread-comment__start
+ a.thread-comment__user-link href="#{answer.user.url}"
+ span class="a-user-role is-#{answer.user.primary_role}"
+ img.thread-comment__user-icon.a-user-icon src="#{answer.user.avatar_url}"
+ - if answer.user.company && answer.user.adviser?
+ a.thread-comment__company-link href="#{company_path(answer.user.company)}"
+ img.thread-comment__company-logo src="#{answer.user.company.logo_url}"
+ .thread-comment__end
+ .a-card.is-answer.answer-display
+ .answer-badge class=(answer.type == 'CorrectAnswer' ? 'correct-answer' : 'is-hidden')
+ .answer-badge__icon
+ i.fa-solid.fa-star
+ .answer-badge__label
+ | ベストアンサー
+ header.card-header
+ h2.thread-comment__title
+ a.thread-comment__title-user-link.is-hidden-md-up href="#{answer.user.url}"
+ img.thread-comment__title-user-icon.a-user-icon src="#{answer.user.avatar_url}"
+ a.thread-comment__title-link.a-text-link href="#{answer.user.url}"
+ = answer.user.login_name
+ time.thread-comment__created-at
+ = l(answer.created_at)
+ hr.a-border-tint
+ .thread-comment__description
+ - if answer.user.company && answer.user.adviser
+ a.thread-comment__company-link.is-hidden-md-up href="#{company_path(answer.user.company)}"
+ img.thread-comment__company-logo src="#{answer.user.company.logo_url}"
+ .a-long-text.is-md
+ = answer.description
+ hr.a-border-tint
+ .thread-comment__reactions
+ = render 'reactions/reactions', reactionable: answer
+ hr.a-border-tint
+ footer.card-footer
+ .card-main-actions
+ ul.card-main-actions__items
+ - if answer.user.id == user.id || user.admin?
+ li.card-main-actions__item
+ button.card-main-actions__action.a-button.is-sm.is-secondary.is-block
+ i.fa-solid.fa-pen
+ | 内容修正
+ - if user.mentor? || user.id == question.user.id
+ - make_button_hidden = (question.correct_answer && answer.type != 'CorrectAnswer') || (answer.type == 'CorrectAnswer')
+ - cancel_button_hidden = (question.correct_answer && answer.type != 'CorrectAnswer') || !question.correct_answer
+ li.card-main-actions__item.make-best-answer-button class=(make_button_hidden ? 'is-hidden' : '')
+ button.card-main-actions__action.a-button.is-sm.is-warning.is-block
+ | ベストアンサーにする
+ li.card-main-actions__item.cancel-best-answer-button class=(cancel_button_hidden ? 'is-hidden' : '')
+ button.card-main-actions__action.a-button.is-sm.is-muted.is-block
+ | ベストアンサーを取り消す
+ - if answer.user.id == user.id || user.mentor?
+ li.card-main-actions__item.is-sub
+ button.card-main-actions__muted-action
+ | 削除する
+ .a-card.is-answer.answer-editor.is-hidden
+ .thread-comment-form__form
+ .a-form-tabs.js-tabs
+ .a-form-tabs__tab.js-tabs__tab.edit-answer-tab.is-active
+ | コメント
+ .a-form-tabs__tab.js-tabs__tab.answer-preview-tab
+ | プレビュー
+ .a-markdown-input.js-markdown-parent
+ .a-markdown-input__inner.is-editor.js-tabs__content.is-active
+ .form-textarea
+ .form-textarea__body
+ textarea.a-text-input.a-markdown-input__textarea id="js-comment-#{answer.id}" data-preview="#js-comment-preview-#{answer.id}" data-input=".js-comment-file-input-#{answer.id}" name='answer[description]'
+ = answer.description
+ .form-textarea__footer
+ .form-textarea__insert
+ label.a-file-insert.a-button.is-xs.is-text-reversal.is-block
+ | ファイルを挿入
+ input(class="js-comment-file-input-#{answer.id}" type='file' multiple)
+ .a-markdown-input__inner.is-preview.js-tabs__content
+ .js-preview.a-long-text.is-md.a-markdown-input__preview id="js-comment-preview-#{answer.id}"
+ .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/questions/_comment_placeholder.html.slim b/app/views/questions/_comment_placeholder.html.slim
new file mode 100644
index 00000000000..c5ae65ba6a8
--- /dev/null
+++ b/app/views/questions/_comment_placeholder.html.slim
@@ -0,0 +1,18 @@
+.thread-comment
+ .thread-comment__start
+ .thread-comment__user-icon.a-user-icon.a-placeholder
+ .thread-comment__end
+ .a-card.is-loading
+ .card-header
+ .thread-comment__title
+ .thread-comment__title-link.a-placeholder
+ .thread-comment__created-at.a-placeholder
+ hr.a-border-tint
+ .thread-comment__description
+ .a-long-text.is-md.a-placeholder
+ p
+ p
+ p
+ p
+ p
+ p
diff --git a/app/views/questions/_new_answer.html.slim b/app/views/questions/_new_answer.html.slim
new file mode 100644
index 00000000000..b3174590acd
--- /dev/null
+++ b/app/views/questions/_new_answer.html.slim
@@ -0,0 +1,30 @@
+.thread-comment-form.new-answer data-question_id="#{question.id}"
+ .thread-comment__start
+ span class="a-user-role is-#{user.primary_role}"
+ img.thread-comment__user-icon.a-user-icon src="#{user.avatar_url}" alt="#{current_user.icon_title}"
+ .thread-comment__end
+ .answer-editor
+ .thread-comment-form__form.a-card
+ .a-form-tabs.js-tabs
+ .a-form-tabs__tab.js-tabs__tab.edit-answer-tab.is-active
+ | コメント
+ .a-form-tabs__tab.js-tabs__tab.answer-preview-tab
+ | プレビュー
+ .a-markdown-input.js-markdown-parent
+ .a-markdown-input__inner.is-editor.js-tabs__content.is-active
+ .form-textarea
+ .form-textarea__body
+ textarea#js-new-comment.a-text-input.js-warning-form.a-markdown-input__textarea name='answer[description]' data-preview='#new-comment-preview' data-input='.new-comment-file-input'
+ .form-textarea__footer
+ .form-textarea__insert
+ label.a-file-insert.a-button.is-xs.is-text-reversal.is-block
+ | ファイルを挿入
+ input.new-comment-file-input(type='file' multiple)
+ .a-markdown-input__inner.is-preview.js-tabs__content
+ #new-comment-preview.a-long-text.is-md.a-markdown-input__preview
+ .card-footer
+ .card-main-actions
+ .card-main-actions__items
+ .card-main-actions__item
+ button#js-shortcut-post-comment.a-button.is-sm.is-primary.is-block disabled=true
+ | コメントする
diff --git a/app/views/questions/show.html.slim b/app/views/questions/show.html.slim
index 7f27fe2ea06..d21f193efde 100644
--- a/app/views/questions/show.html.slim
+++ b/app/views/questions/show.html.slim
@@ -29,8 +29,19 @@ hr.a-border
.question.page-content
= render 'question_header', question: @question
= render 'question_body', question: @question
- div(data-vue="QuestionAnswers" data-vue-current-user-id:number="#{current_user.id}" data-vue-question-id="#{@question.id}")
-
+ .loading-content
+ - 3.times do
+ = render 'questions/comment_placeholder'
+ .answer-content style='display: none;'
+ - if @question.ai_answer
+ = render 'ai_answer', question: @question
+ header.thread-comments__header
+ h2.thread-comments__title
+ | 回答・コメント
+ .answers-list
+ - @answers.each do |answer|
+ = render 'answer', question: @question, user: current_user, answer: answer
+ = render 'new_answer', question: @question, user: current_user
nav.a-side-nav
.a-side-nav__inner
header.a-side-nav__header
diff --git a/app/views/regular_events/_regular_event.html.slim b/app/views/regular_events/_regular_event.html.slim
index cb73723bbc8..227ab71cb3c 100644
--- a/app/views/regular_events/_regular_event.html.slim
+++ b/app/views/regular_events/_regular_event.html.slim
@@ -48,7 +48,7 @@
.card-header.is-sm
h2.card-header__title
| イベント内容
- hr.a-border
+ hr.a-border-tint
- if regular_event.all
.card-message.is-notice
| この定期イベントは全員参加のため参加登録は不要です。
diff --git a/app/views/reports/_form.html.slim b/app/views/reports/_form.html.slim
index 8a52a3792dc..94274f02c0c 100644
--- a/app/views/reports/_form.html.slim
+++ b/app/views/reports/_form.html.slim
@@ -20,7 +20,7 @@
.form-item
= f.label :reported_on, class: 'a-form-label'
- = f.date_field :reported_on, class: 'a-text-input'
+ = f.date_field :reported_on, min: '2013-01-01', class: 'a-text-input'
.form-item.is-sm
= f.label :emotion, class: 'a-form-label'
ul.block-checks.is-3-items.is-inline
diff --git a/app/views/reports/_recent_reports.html.slim b/app/views/reports/_recent_reports.html.slim
index 5768aa4fb68..241f180fb6a 100644
--- a/app/views/reports/_recent_reports.html.slim
+++ b/app/views/reports/_recent_reports.html.slim
@@ -4,7 +4,7 @@
.card-header.is-sm
h2.card-header__title
| 直近の日報
- hr.a-border
+ hr.a-border-tint
- if @recent_reports.any?
.card-list__items
= render partial: 'reports/report', collection: @recent_reports, locals: { user_icon_display: false, actions_display: false }
diff --git a/app/views/shared/_not_logged_in_footer.html.slim b/app/views/shared/_not_logged_in_footer.html.slim
index e8bcb0218f4..4e61d12fec5 100644
--- a/app/views/shared/_not_logged_in_footer.html.slim
+++ b/app/views/shared/_not_logged_in_footer.html.slim
@@ -14,6 +14,9 @@ footer.not-logged-in-footer
li.not-logged-in-footer__nav-item
= link_to coc_path, class: 'not-logged-in-footer__nav-item-link' do
| アンチハラスメントポリシー
+ li.not-logged-in-footer__nav-item
+ = link_to 'https://github.com/fjordllc/bootcamp', class: 'not-logged-in-footer__nav-item-link' do
+ | ソースコード
li.not-logged-in-footer__nav-item
= link_to law_path, class: 'not-logged-in-footer__nav-item-link' do
| 特定商取引法に基づく表記
@@ -23,6 +26,9 @@ footer.not-logged-in-footer
li.not-logged-in-footer__nav-item
= link_to new_comeback_path, class: 'not-logged-in-footer__nav-item-link' do
| 休会からの復帰
+ li.not-logged-in-footer__nav-item
+ = link_to press_kit_path, class: 'not-logged-in-footer__nav-item-link' do
+ | プレスキット
- if admin_login?
// TODO リスキル講座 公開したら if を外す
li.not-logged-in-footer__nav-item
diff --git a/app/views/users/_activity_counts.html.slim b/app/views/users/_activity_counts.html.slim
index 7f95326824b..61338eacc30 100644
--- a/app/views/users/_activity_counts.html.slim
+++ b/app/views/users/_activity_counts.html.slim
@@ -18,8 +18,8 @@ dl.card-counts__items
dt.card-counts__item-label
| コメント
dd.card-counts__item-value
- = link_to_if !user.comments.empty?,
- user.comments.size, user_comments_path(user)
+ = link_to_if !user.comments.without_talk.empty?,
+ user.comments.without_talk.size, user_comments_path(user)
.card-counts__item
.card-counts__item-inner
dt.card-counts__item-label
diff --git a/app/views/users/_page_tabs.html.slim b/app/views/users/_page_tabs.html.slim
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/app/views/users/_sad_emotion_report.html.slim b/app/views/users/_sad_emotion_report.html.slim
index 4dd362b86dc..3b26238cc50 100644
--- a/app/views/users/_sad_emotion_report.html.slim
+++ b/app/views/users/_sad_emotion_report.html.slim
@@ -5,6 +5,6 @@
| 2回連続
= image_tag Report.faces['sad'], id: 'sad', alt: 'sad', class: 'card-header__title-emotion-image'
| のユーザー
- hr.a-border
+ hr.a-border-tint
.card-list
div(data-vue="SadReports")
diff --git a/app/views/users/micro_reports/index.html.slim b/app/views/users/micro_reports/index.html.slim
new file mode 100644
index 00000000000..3d2d5225984
--- /dev/null
+++ b/app/views/users/micro_reports/index.html.slim
@@ -0,0 +1,35 @@
+- title "#{@user.login_name}の分報一覧"
+- set_meta_tags description: "#{@user.login_name}さんの分報一覧ページです。"
+- content_for(:extra_body_classes, 'no-footer no-scroll')
+
+= render 'users/page_title', user: @user
+
+= user_page_tabs(@user, active_tab: '分報')
+
+.page-body.pb-0
+ .page-content
+ - if current_user == @user
+ .micro-reports__end
+ = render(Users::MicroReports::FormComponent.new(user: @user))
+ - micro_reports_class = 'micro-reports-with-form'
+ - else
+ - micro_reports_class = 'micro-reports-without-form'
+ .micro-reports#js-micro-reports class=micro_reports_class
+ .micro-reports__start
+ .container.is-md
+ .micro-reports-posts
+ - if @micro_reports.empty?
+ .o-empty-message
+ .o-empty-message__icon
+ .o-empty-message
+ .o-empty-message__icon
+ i.fa-regular.fa-sad-tear
+ .o-empty-message__text
+ | 分報の投稿はまだありません。
+ - else
+ = paginate @micro_reports
+ - @micro_reports.each_with_index do |micro_report, index|
+ - if index == @micro_reports.size - 1
+ #latest-micro-report
+ = render(Users::MicroReports::MicroReportComponent.new(user: @user, current_user: current_user, micro_report: micro_report))
+ = paginate @micro_reports, params: { anchor: 'latest-micro-report' }
diff --git a/app/views/welcome/_pricing_features.html.slim b/app/views/welcome/_pricing_features.html.slim
index 8530bc2d246..3f0dd5dd970 100644
--- a/app/views/welcome/_pricing_features.html.slim
+++ b/app/views/welcome/_pricing_features.html.slim
@@ -14,7 +14,7 @@
| メンターや運営は受講生の学習のサポートに注力するために、
| それ以外の手間は最小限に抑えるよう、アプリの改善を続けています。
| 新規入会があった際の運営側の手続きが自動化されており、
- | 運営側の運営側に追加のコストがかからないため、入会金はかかりません。
+ | 運営側に追加のコストがかからないため、入会金はかかりません。
.col-xs-12.col-md-4
section.lp-top-image-section
diff --git a/app/views/welcome/_unlimited.html.slim b/app/views/welcome/_unlimited.html.slim
index 473f0178a4d..b9097ab3b85 100644
--- a/app/views/welcome/_unlimited.html.slim
+++ b/app/views/welcome/_unlimited.html.slim
@@ -41,4 +41,4 @@ section.lp-content-section
p
| プログラミング経験が全くない人を想定したカリキュラムを用意していますので、
| プログラミング未経験でもご安心ください。
- | ほとんどの方はプログラミング経験です。
+ | ほとんどの方はプログラミング未経験です。
diff --git a/app/views/welcome/alumni_voices.html.slim b/app/views/welcome/alumni_voices.html.slim
index 5bd5fc5d298..7f591443546 100644
--- a/app/views/welcome/alumni_voices.html.slim
+++ b/app/views/welcome/alumni_voices.html.slim
@@ -1,7 +1,7 @@
- content_for :extra_body_classes, 'welcome'
-- title 'FAQ'
+- title '卒業生の声'
- set_meta_tags(site: 'FJORD BOOT CAMP(フィヨルドブートキャンプ)',
- description: 'フィヨルドブートキャンプに寄せられたよくあるお問い合わせとその回答の一覧です。')
+ description: 'フィヨルドブートキャンプ卒業生のインタビューやプレゼンテーションの記事を紹介しています。')
article.lp
header.lp-content.is-lp-bg-main.is-hero
diff --git a/app/views/welcome/logo.html.slim b/app/views/welcome/logo.html.slim
new file mode 100644
index 00000000000..c36da9ad530
--- /dev/null
+++ b/app/views/welcome/logo.html.slim
@@ -0,0 +1,198 @@
+- title 'ロゴ・ガイドライン'
+- set_meta_tags(site: 'FJORD BOOT CAMP(フィヨルドブートキャンプ)',
+ description: '株式会社ロッカ(以下「当社」といいます)の提供する「フィヨルドブートキャンプ」(以下「本サービス」といいます。)における、ユーザーの個人情報の取扱いについて、このページにあるとおりプライバシーポリシー(以下、「本ポリシー」といいます。)を定めます。')
+
+header.lp-page-header
+ .l-container
+ .lp-page-header__inner
+ .lp-page-header__start
+ h1.lp-page-header__title
+ = title
+ .lp-page-header__end
+hr.a-border
+.lp
+ .lp-content.is-lp-bg-1.is-top-title
+ .l-container.is-lg
+ .lp-content__inner
+ .lp-content__start
+ header.lp-content__header
+ h2.lp-content-title.text-center.is-border-bottom
+ = title
+ .lp-content__end
+ .a-card
+ .card-body
+ .card-body__inner
+ .a-long-text.is-md
+ h2
+ | ロゴデータ
+ ul
+ li
+ = link_to '/images/logo/rgb.ai', class: 'a-text-link' do
+ | RGB
+ li
+ = link_to '/images/logo/cmyk.ai', class: 'a-text-link' do
+ | CMYK
+
+ .card-body__inner
+ .a-long-text.is-md
+ h2 ロゴ使用ガイドライン
+
+ h3 概要
+ p
+ | 株式会社ロッカが提供するプログラミングスクール「フィヨルドブートキャンプ」のロゴは、
+ | そのアイデンティティとして重要な役割を果たしています。本ガイドラインは、
+ | 当社サービスに関する商標やロゴの使用方法を明確にし、
+ | 皆様に安心してご活用いただくために設定されています。
+
+ h3 権利帰属
+ p
+ | 当社ロゴに関するすべての権利(著作権、商標権など)は株式会社ロッカに帰属します。
+
+ h3 使用許可の条件
+ p
+ | ロゴは、フィヨルドブートキャンプの紹介目的、
+ | 当社と提携関係にある使用者が提携プロジェクトや企画において当社の許可を得て使用する場合、
+ | および当社サービスの受講生、卒業生、メンター、アドバイザーが自身の所属を示す場合に限り、使用可能です。
+
+ h3 使用者の責任と免責事項
+ p ロゴの不適切な使用により当社に損害が発生した場合、使用者は当社の請求に応じて補償を行うものとします。当社はロゴ使用に関連する直接的または間接的な損害に対して責任を負いません。
+
+ h3 ロゴ使用ルール及びガイドラインの変更
+ p 当社は、ロゴ使用ルールやガイドラインを必要に応じて更新することがあります。変更は当社ウェブサイトに掲載された時点で効力を発揮します。
+
+ h2 具体的使用ルール
+
+ h3 バリエーション
+ p
+ | 縦組と横組みのバリエーションがあります。
+ | 表示領域に合わせてより視認性が高い方を使用してください。
+ .inline-grid.gap-8.w-auto.mt-0(class='grid-cols-[auto,auto]')
+ .m-0
+ h5
+ | 縦組み
+ p
+ = image_tag('press-kit/blue-vertical.png', alt: 'logo', class: 'block w-80')
+ .m-0
+ h5
+ | 横組み
+ p
+ = image_tag('press-kit/blue-horizontal.png', alt: 'logo', class: 'block w-100')
+
+ h3 色
+ p ロゴはブルー、背景色が指定できる場合はホワイトを使用してください。
+
+ .inline-grid.gap-8.w-auto.mt-0(class='grid-cols-[auto,auto]')
+ .m-0
+ h5 ブルー
+ .grid.gap-4.m-0(class='grid-cols-[auto,1fr]')
+ .m-0
+ .h-10.w-10(class='bg-[#4843c2] md:h-16 md:w-16')
+ .m-0
+ ul
+ li
+ | HEX #4843c2
+ li
+ | RGB(72, 67, 194)
+ li
+ | CMYK(82, 74, 0, 0)
+ li
+ | DIC 185
+ .m-0
+ h5 ホワイト
+ .grid.gap-4.m-0(class='grid-cols-[auto,1fr]')
+ .m-0
+ .border.border-gray-200.bg-white.border-solid.h-10.w-10(class='md:h-16 md:w-16')
+ .m-0
+ ul
+ li
+ | HEX #fff
+ li
+ | RGB(255, 255, 255)
+ li
+ | CMYK(0, 0, 0, 0)
+ li
+ | DIC 583
+
+ h3 サイズ
+ p 読み取り可能な最小サイズを守ってください。
+ ul
+ li
+ h5
+ | 横組み
+ p 最小幅を100ピクセル、または25ミリメートル。
+ li
+ h5
+ | 縦組み
+ p 最小幅を80ピクセル、または20ミリメートル。
+
+ h3 余白
+ p ロゴの周囲には、「FBC」の高さの1/3以上の余白を確保してください。
+
+ .inline-grid.gap-8.w-auto.mt-0(class='grid-cols-[auto,auto]')
+ .m-0
+ p
+ = image_tag('press-kit/isolation-vertical.png', alt: 'logo', class: 'block w-80')
+ .m-0
+ p
+ = image_tag('press-kit/isolation-horizontal.png', alt: 'logo', class: 'block w-100')
+
+ h3 背景
+ p
+ | 背景によってロゴの視認性が悪くなる場合、ホワイトにするか、
+ | ホワイトで縁取りを施してください。
+ .inline-grid.gap-8.w-auto.mt-0(class='grid-cols-[auto,auto]')
+ .m-0
+ p
+ = image_tag('press-kit/background-blue.png', alt: 'logo', class: 'block w-80')
+ .m-0
+ p
+ = image_tag('press-kit/background-people.png', alt: 'logo', class: 'block w-80')
+
+ h3 使用禁止例
+ p 下記のような色や割合、配列など、ロゴの要素変更をしないようにしてください。
+ .grid.gap-8.grid-cols-3(class='md:grid-cols-4')
+ .m-0
+ p.m-0
+ = image_tag('press-kit/ng-1.png', alt: 'logo', class: 'block w-80')
+ p.mt-2.leading-snug
+ | 縦横比を変える。
+ .m-0
+ p.m-0
+ = image_tag('press-kit/ng-2.png', alt: 'logo', class: 'block w-80')
+ p.mt-2.leading-snug
+ | 色を変える。
+ .m-0
+ p.m-0
+ = image_tag('press-kit/ng-3.png', alt: 'logo', class: 'block w-80')
+ p.mt-2.leading-snug
+ | 回転する。
+ .m-0
+ p.m-0
+ = image_tag('press-kit/ng-4.png', alt: 'logo', class: 'block w-80')
+ p.mt-2.leading-snug
+ | 半透明にする。
+ .m-0
+ p.m-0
+ = image_tag('press-kit/ng-5.png', alt: 'logo', class: 'block w-80')
+ p.mt-2.leading-snug
+ | 影を付ける。
+ .m-0
+ p.m-0
+ = image_tag('press-kit/ng-6.png', alt: 'logo', class: 'block w-80')
+ p.mt-2.leading-snug
+ | トリミングする。
+ .m-0
+ p.m-0
+ = image_tag('press-kit/ng-7.png', alt: 'logo', class: 'block w-80')
+ p.mt-2.leading-snug
+ | ぼかす。
+ .m-0
+ p.m-0
+ = image_tag('press-kit/ng-8.png', alt: 'logo', class: 'block w-80')
+ p.mt-2.leading-snug
+ | 文章の一部に使う。
+ .m-0
+ p.m-0
+ = image_tag('press-kit/ng-9.png', alt: 'logo', class: 'block w-80')
+ p.mt-2.leading-snug
+ | 視認性が悪い。
diff --git a/app/views/welcome/press_kit.html.slim b/app/views/welcome/press_kit.html.slim
new file mode 100644
index 00000000000..e628e1f4aaf
--- /dev/null
+++ b/app/views/welcome/press_kit.html.slim
@@ -0,0 +1,78 @@
+- title 'プレスキット'
+- set_meta_tags(site: 'FJORD BOOT CAMP(フィヨルドブートキャンプ)',
+ description: '株式会社ロッカ(以下「当社」といいます)の提供する「フィヨルドブートキャンプ」(以下「本サービス」といいます。)における、ユーザーの個人情報の取扱いについて、このページにあるとおりプライバシーポリシー(以下、「本ポリシー」といいます。)を定めます。')
+
+header.lp-page-header
+ .l-container
+ .lp-page-header__inner
+ .lp-page-header__start
+ h1.lp-page-header__title
+ = title
+ .lp-page-header__end
+hr.a-border
+.lp
+ .lp-content.is-lp-bg-1.is-top-title
+ .l-container.is-lg
+ .lp-content__inner
+ .lp-content__start
+ header.lp-content__header
+ h2.lp-content-title.text-center.is-border-bottom
+ = title
+ .lp-content__end
+ .a-card
+ .card-body
+ .card-body__inner
+ .a-long-text.is-md
+ h2
+ | ロゴ
+ ul
+ li
+ = link_to logo_path, class: 'a-text-link' do
+ | ロゴ・ガイドライン
+ h2
+ | 運営会社
+ p
+ strong
+ | 社名 :
+ span
+ | 株式会社ロッカ(Lokka, Inc)
+ br
+ strong
+ | 所在地 :
+ span
+ | 〒150-0041 東京都 渋谷区 神南1-12-14 渋谷宮田ビル710号
+ br
+ strong
+ | 設立 :
+ span
+ | 2021年7月27日
+ br
+ strong
+ | 資本金 :
+ span
+ | 900万円
+ br
+ strong
+ | 従業員 :
+ span
+ | 3人
+ br
+ strong
+ | 代表取締役 :
+ span
+ | 駒形真幸(コマガタマサキ)、町田哲平(マチダテッペイ)
+ br
+ strong
+ | 取材に関するお問い合わせ :
+ span
+ = link_to new_inquiry_path, class: 'a-text-link' do
+ | お問い合わせフォーム
+ h2
+ | SNS
+ ul
+ li
+ = link_to 'https://github.com/fjordllc', class: 'a-text-link' do
+ | GitHub
+ li
+ = link_to 'https://x.com/fjordbootcamp', class: 'a-text-link' do
+ | X
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index 2a40437c96a..687672c41df 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -49,6 +49,7 @@ ja:
request_retirement: 退会申請
faq: FAQ
faq_category: FAQカテゴリ
+ coding_test: コーディングテスト
attributes:
user:
login_name: アカウント
@@ -311,6 +312,16 @@ ja:
faq_category: カテゴリー
faq_category:
name: カテゴリー名
+ coding_test:
+ title: タイトル
+ description: 問題文
+ user: 作成者
+ practice: 所属プラクティス
+ language: 言語
+ coding_test_submissions: 回答コード
+ coding_test_case:
+ input: 入力
+ output: 出力
enums:
user:
job:
diff --git a/config/routes.rb b/config/routes.rb
index 7cf4e1118a5..b9e5927bc18 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -18,6 +18,8 @@
get "pp", to: "welcome#pp", as: "pp"
get "law", to: "welcome#law", as: "law"
get "coc", to: "welcome#coc", as: "coc"
+ get "press_kit", to: "welcome#press_kit", as: "press_kit"
+ get "logo", to: "welcome#logo", as: "logo"
get 'certified_reskill_courses/rails_developer_course',
to: 'welcome#rails_developer_course',
as: :certified_reskill_courses_rails_developer_course_root
@@ -54,6 +56,12 @@
resources :pages, only: %i(index), controller: "practices/pages"
resource :completion, only: %i(show), controller: "practices/completion"
resource :submission_answer, only: %i(show), controller: "practices/submission_answer"
+ resources :coding_tests, only: %i(index), controller: "practices/coding_tests"
+ end
+ resources :coding_tests, only: %i(show) do
+ resources :coding_test_submissions,
+ only: %i(index show show),
+ controller: "coding_tests/coding_test_submissions"
end
resources :pages, param: :slug_or_id
namespace :notification do
diff --git a/config/routes/api.rb b/config/routes/api.rb
index c1c8f4901cd..923881d2a81 100644
--- a/config/routes/api.rb
+++ b/config/routes/api.rb
@@ -4,7 +4,6 @@
namespace 'api' do
namespace 'admin' do
resource :count, controller: 'count', only: %i(show)
- resources :companies, only: %i(index destroy)
end
namespace 'mentor' do
resources :practices, only: %i(index)
@@ -43,6 +42,7 @@
resource :completion_message, only: %i(update), controller: "practices/learning/completion_message"
end
end
+ resources :coding_test_submissions, only: %i(create)
resources :reports, only: %i(index)
namespace "reports" do
resources :unchecked, only: %i(index) do
diff --git a/config/routes/mentor.rb b/config/routes/mentor.rb
index fba2e993ac6..f3059fc1a1d 100644
--- a/config/routes/mentor.rb
+++ b/config/routes/mentor.rb
@@ -9,6 +9,7 @@
resources :practices, only: %i(index new edit create update) do
resource :submission_answer, only: %i(new edit create update), controller: "practices/submission_answer"
end
+ resources :coding_tests, only: %i(index new edit create update destroy)
resources :courses, only: %i(index new edit create update) do
resources :categories, only: %i(index), controller: "courses/categories"
end
diff --git a/config/routes/users.rb b/config/routes/users.rb
index 1aa85eb422b..945ca97ce4e 100644
--- a/config/routes/users.rb
+++ b/config/routes/users.rb
@@ -16,6 +16,7 @@
resources :products, only: %i(index), controller: "users/products"
resources :questions, only: %i(index), controller: "users/questions"
resources :answers, only: %i(index), controller: "users/answers"
+ resources :micro_reports, only: %i[index create], controller: "users/micro_reports"
get "portfolio" => "users/works#index", as: :portfolio
patch "graduation", to: "graduation#update", as: :graduation
patch "job_seek", to: "job_seek#update"
diff --git a/config/slim_lint.yml b/config/slim_lint.yml
index 6d014220efa..86fbce7ddc6 100644
--- a/config/slim_lint.yml
+++ b/config/slim_lint.yml
@@ -43,4 +43,4 @@ linters:
- Style/Next
- Style/WhileUntilDo
- Style/WhileUntilModifier
- - Style/HashSyntax # 検討の結果、残す。(詳細は issue #7253 参照)
\ No newline at end of file
+ - Style/HashSyntax # 検討の結果、残す。(詳細は issue #7253 参照)
diff --git a/db/fixtures/coding_test_cases.yml b/db/fixtures/coding_test_cases.yml
new file mode 100644
index 00000000000..a14884aab97
--- /dev/null
+++ b/db/fixtures/coding_test_cases.yml
@@ -0,0 +1,81 @@
+coding_test_case1:
+ coding_test: coding_test1
+ input: ""
+ output: hello
+
+coding_test_case2:
+ coding_test: coding_test2
+ input: world
+ output: hello world
+
+coding_test_case3:
+ coding_test: coding_test2
+ input: programming
+ output: hello programming
+
+coding_test_case4:
+ coding_test: coding_test3
+ input: 1
+ output: 1
+
+coding_test_case5:
+ coding_test: coding_test3
+ input: 3
+ output: Fizz
+
+coding_test_case6:
+ coding_test: coding_test3
+ input: 5
+ output: Buzz
+
+coding_test_case7:
+ coding_test: coding_test3
+ input: 15
+ output: FizzBuzz
+
+coding_test_case8:
+ coding_test: coding_test4
+ input: a b c d
+ output: d c b a
+
+coding_test_case9:
+ coding_test: coding_test4
+ input: hello
+ output: olleh
+
+coding_test_case10:
+ coding_test: coding_test5
+ input: hello
+ output: h e l l o
+
+coding_test_case11:
+ coding_test: coding_test5
+ input: world
+ output: w o r l d
+
+coding_test_case12:
+ coding_test: coding_test6
+ input: ""
+ output: hello
+
+coding_test_case13:
+ coding_test: coding_test7
+ input: 3
+ output: Fizz
+
+coding_test_case14:
+ coding_test: coding_test7
+ input: 5
+ output: Buzz
+
+coding_test_case15:
+ coding_test: coding_test7
+ input: 15
+ output: FizzBuzz
+
+<% 16.upto(29) do |i| %>
+coding_test_case<%= i %>:
+ coding_test: coding_test<%= i - 8 %>
+ input: ""
+ output: hello
+<% end %>
diff --git a/db/fixtures/coding_test_submissions.yml b/db/fixtures/coding_test_submissions.yml
new file mode 100644
index 00000000000..02515c94071
--- /dev/null
+++ b/db/fixtures/coding_test_submissions.yml
@@ -0,0 +1,38 @@
+coding_test_submission1:
+ coding_test: coding_test1
+ user: hajime
+ code: |-
+ console.log(\"hello\")
+
+coding_test_submission6:
+ coding_test: coding_test6
+ user: hajime
+ code: |-
+ puts 'hello'
+
+coding_test_submission7:
+ coding_test: coding_test7
+ user: hajime
+ code: |-
+ input = gets.to_i
+
+ output =
+ if input % 15 == 0
+ 'FizzBuzz'
+ elsif input % 3 == 0
+ 'Fizz'
+ elsif input % 5 == 0
+ 'Buzz'
+ else
+ input.to_s
+ end
+
+ puts output
+
+<% 8.upto(29) do |i| %>
+coding_test_submission<%= i %>:
+ coding_test: coding_test1
+ user: marumarushain<%= i - 7 %>
+ code: |-
+ console.log(\"hello\")
+<% end %>
diff --git a/db/fixtures/coding_tests.yml b/db/fixtures/coding_tests.yml
new file mode 100644
index 00000000000..b29e377adca
--- /dev/null
+++ b/db/fixtures/coding_tests.yml
@@ -0,0 +1,92 @@
+coding_test1:
+ practice: practice62
+ user: komagata
+ language: javascript
+ title: 最初の出力
+ description: |-
+ `hello`という文字列を出力しなさい。
+ hint: ""
+ position: 1
+
+coding_test2:
+ practice: practice62
+ user: komagata
+ language: javascript
+ title: 最初の入力
+ description: |-
+ 文字列sが与えられます。
+ `hello `とsをつなげて出力しなさい。
+ hint: ""
+ position: 2
+
+coding_test3:
+ practice: practice62
+ user: komagata
+ language: javascript
+ title: 条件分岐
+ description: |-
+ 正の整数aが与えられます。
+ aが3の倍数なら`Fizz`と出力してください。
+ aが5の倍数なら`Buzz`と出力してください。
+ aが3の倍数かつ5の倍数なら`FizzBuzz`と出力してください。
+ どれでもないならaを出力してください。
+ hint: 条件分岐にはifが使えます。
+ position: 3
+
+coding_test4:
+ practice: practice62
+ user: komagata
+ language: javascript
+ title: 配列を並び替え
+ description: |-
+ 文字a, b, c, dが与えられます。
+ 逆に並び替えて`d c b a`と出力してください。
+ hint: 逆に並び替えるにはreverseが使えます。
+ position: 4
+
+coding_test5:
+ practice: practice62
+ user: komagata
+ language: javascript
+ title: スペース区切り
+ description: |-
+ 文字列aが与えられます。
+ 1文字ずつスペースを入れて出力してください。
+ hint: 文字列を分割するにはsplitが使えます。
+ position: 5
+
+coding_test6:
+ practice: practice26
+ user: komagata
+ language: ruby
+ title: 最初の出力
+ description: |-
+ `hello`という文字列を出力しなさい。
+ hint: ""
+ position: 1
+
+coding_test7:
+ practice: practice26
+ user: komagata
+ language: ruby
+ title: 条件分岐
+ description: |-
+ 正の整数aが与えられます。
+ aが3の倍数なら`Fizz`と出力してください。
+ aが5の倍数なら`Buzz`と出力してください。
+ aが3の倍数かつ5の倍数なら`FizzBuzz`と出力してください。
+ どれでもないならaを出力してください。
+ hint: 条件分岐にはifが使えます。
+ position: 2
+
+<% 8.upto(21) do |i| %>
+coding_test<%= i %>:
+ practice: practice62
+ user: komagata
+ language: javascript
+ title: 最初の出力
+ description: |-
+ `hello`という文字列を出力しなさい。
+ hint: ""
+ position: <%= i - 6 %>
+<% end %>
diff --git a/db/migrate/20240601203433_create_coding_tests.rb b/db/migrate/20240601203433_create_coding_tests.rb
new file mode 100644
index 00000000000..81474dec4c8
--- /dev/null
+++ b/db/migrate/20240601203433_create_coding_tests.rb
@@ -0,0 +1,15 @@
+class CreateCodingTests < ActiveRecord::Migration[6.1]
+ def change
+ create_table :coding_tests do |t|
+ t.integer :language, null: false
+ t.string :title, null: false
+ t.text :description
+ t.text :hint
+ t.integer :position
+ t.references :practice, null: false, foreign_key: true
+ t.references :user, null: false, foreign_key: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240601212831_create_coding_test_cases.rb b/db/migrate/20240601212831_create_coding_test_cases.rb
new file mode 100644
index 00000000000..554511703ee
--- /dev/null
+++ b/db/migrate/20240601212831_create_coding_test_cases.rb
@@ -0,0 +1,11 @@
+class CreateCodingTestCases < ActiveRecord::Migration[6.1]
+ def change
+ create_table :coding_test_cases do |t|
+ t.text :input
+ t.text :output
+ t.references :coding_test, null: false, foreign_key: true
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20240601212854_create_coding_test_submissions.rb b/db/migrate/20240601212854_create_coding_test_submissions.rb
new file mode 100644
index 00000000000..06063ea0cdb
--- /dev/null
+++ b/db/migrate/20240601212854_create_coding_test_submissions.rb
@@ -0,0 +1,12 @@
+class CreateCodingTestSubmissions < ActiveRecord::Migration[6.1]
+ def change
+ create_table :coding_test_submissions do |t|
+ t.text :code, null: false
+ t.references :coding_test, null: false, foreign_key: true
+ t.references :user, null: false, foreign_key: true
+
+ t.timestamps
+ end
+ add_index :coding_test_submissions, [:coding_test_id, :user_id], unique: true
+ end
+end
diff --git a/db/migrate/20240619125427_create_micro_reports.rb b/db/migrate/20240619125427_create_micro_reports.rb
new file mode 100644
index 00000000000..6ccb0c654e4
--- /dev/null
+++ b/db/migrate/20240619125427_create_micro_reports.rb
@@ -0,0 +1,10 @@
+class CreateMicroReports < ActiveRecord::Migration[6.1]
+ def change
+ create_table :micro_reports do |t|
+ t.references :user, null: false, foreign_key: true
+ t.text :content, null: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20250129033027_add_source_id_to_practices.rb b/db/migrate/20250129033027_add_source_id_to_practices.rb
new file mode 100644
index 00000000000..d008b3c96f6
--- /dev/null
+++ b/db/migrate/20250129033027_add_source_id_to_practices.rb
@@ -0,0 +1,5 @@
+class AddSourceIdToPractices < ActiveRecord::Migration[6.1]
+ def change
+ add_column :practices, :source_id, :integer
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 1feea419347..4453c144460 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2024_11_03_082456) do
+ActiveRecord::Schema.define(version: 2025_01_29_033027) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
@@ -173,6 +173,40 @@
t.index ["user_id"], name: "index_checks_on_user_id"
end
+ create_table "coding_test_cases", force: :cascade do |t|
+ t.text "input"
+ t.text "output"
+ t.bigint "coding_test_id", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["coding_test_id"], name: "index_coding_test_cases_on_coding_test_id"
+ end
+
+ create_table "coding_test_submissions", force: :cascade do |t|
+ t.text "code", null: false
+ t.bigint "coding_test_id", null: false
+ t.bigint "user_id", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["coding_test_id", "user_id"], name: "index_coding_test_submissions_on_coding_test_id_and_user_id", unique: true
+ t.index ["coding_test_id"], name: "index_coding_test_submissions_on_coding_test_id"
+ t.index ["user_id"], name: "index_coding_test_submissions_on_user_id"
+ end
+
+ create_table "coding_tests", force: :cascade do |t|
+ t.integer "language", null: false
+ t.string "title", null: false
+ t.text "description"
+ t.text "hint"
+ t.integer "position"
+ t.bigint "practice_id", null: false
+ t.bigint "user_id", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["practice_id"], name: "index_coding_tests_on_practice_id"
+ t.index ["user_id"], name: "index_coding_tests_on_user_id"
+ end
+
create_table "comments", id: :serial, force: :cascade do |t|
t.text "description"
t.integer "user_id"
@@ -433,6 +467,14 @@
t.index ["survey_question_id"], name: "index_linear_scales_on_survey_question_id"
end
+ create_table "micro_reports", force: :cascade do |t|
+ t.bigint "user_id", null: false
+ t.text "content", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["user_id"], name: "index_micro_reports_on_user_id"
+ end
+
create_table "notifications", force: :cascade do |t|
t.integer "kind", default: 0, null: false
t.bigint "user_id"
@@ -497,6 +539,7 @@
t.text "memo"
t.integer "last_updated_user_id"
t.text "summary"
+ t.integer "source_id"
t.index ["category_id"], name: "index_practices_on_category_id"
end
@@ -827,6 +870,11 @@
add_foreign_key "categories_practices", "practices"
add_foreign_key "check_box_choices", "check_boxes"
add_foreign_key "check_boxes", "survey_questions"
+ add_foreign_key "coding_test_cases", "coding_tests"
+ add_foreign_key "coding_test_submissions", "coding_tests"
+ add_foreign_key "coding_test_submissions", "users"
+ add_foreign_key "coding_tests", "practices"
+ add_foreign_key "coding_tests", "users"
add_foreign_key "discord_profiles", "users"
add_foreign_key "external_entries", "users"
add_foreign_key "faqs", "faq_categories"
@@ -835,6 +883,7 @@
add_foreign_key "learning_minute_statistics", "practices"
add_foreign_key "learning_times", "reports"
add_foreign_key "linear_scales", "survey_questions"
+ add_foreign_key "micro_reports", "users"
add_foreign_key "notifications", "users"
add_foreign_key "notifications", "users", column: "sender_id"
add_foreign_key "organizers", "regular_events"
diff --git a/db/seeds.rb b/db/seeds.rb
index ac04df7c734..e7dcecb167a 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -62,6 +62,9 @@
survey_question_listings
buzzes
inquiries
+ coding_tests
+ coding_test_cases
+ coding_test_submissions
]
ActiveRecord::FixtureSet.create_fixtures 'db/fixtures', tables
diff --git a/lib/tasks/bootcamp.rake b/lib/tasks/bootcamp.rake
index f40995d8e5a..d85a65d5f51 100644
--- a/lib/tasks/bootcamp.rake
+++ b/lib/tasks/bootcamp.rake
@@ -51,21 +51,66 @@ namespace :bootcamp do
end
end
+ desc 'Copy practices from rails course to reskill course.'
+ task copy_practices: :environment do
+ sufix = '(Reスキル)'
+ slug = '-reskill'
+ rails_course = Course.find_by(title: 'Railsエンジニア')
+ reskill_course = Course.find_by(title: 'Railsエンジニア(Reスキル講座認定)')
+
+ if rails_course && reskill_course
+ Course.transaction do
+ # Copy categories
+ rails_course.categories.each do |category|
+ Category.exists?(name: category.name + sufix) && next
+
+ new_category = category.dup
+ new_category.id = nil
+ new_category.name = category.name + sufix
+ new_category.slug = category.slug + slug
+ puts "Copying category: #{new_category.name}"
+ reskill_course.categories << new_category
+ new_category.save!
+
+ # Copy practices
+ category.practices.each do |practice|
+ Practice.exists?(title: practice.title + sufix) && next
+
+ new_practice = practice.dup
+ new_practice.id = nil
+ new_practice.title = practice.title + sufix
+ new_practice.category_id = new_category.id
+ new_practice.source_id = practice.id
+
+ new_practice.categories << new_category
+
+ puts "Copying practice: #{new_practice.title}"
+ new_practice.save!
+
+ # Copy reports
+ practice.reports.each do |report|
+ new_practice.reports << report
+ end
+
+ # Copy books
+ practice.books.each do |book|
+ new_practice.books << book
+ end
+ end
+ end
+ puts 'Practices copied successfully.'
+ end
+ else
+ puts 'One or both courses not found.'
+ end
+ end
+
namespace :oneshot do
desc 'Cloud Build Task'
task cloudbuild: :environment do
puts '== START Cloud Build Task =='
- watches = []
- Watch.find_each do |watch|
- w = "#{watch.watchable_type}-#{watch.watchable_id}-#{watch.user_id}"
- if watches.include?(w)
- puts w
- watch.destroy
- else
- watches << w
- end
- end
+ Rake::Task['bootcamp:copy_practices'].invoke
puts '== END Cloud Build Task =='
end
diff --git a/package.json b/package.json
index b9de4ef1407..42c6b3389e9 100644
--- a/package.json
+++ b/package.json
@@ -6,9 +6,11 @@
},
"dependencies": {
"@babel/preset-react": "^7.18.6",
+ "@codemirror/basic-setup": "^0.20.0",
"@johmun/vue-tags-input": "^2.0.1",
"@rails/webpacker": "5.4.3",
"@yaireo/tagify": "^4.17.6",
+ "ace-builds": "^1.35.0",
"autosize": "^4.0.2",
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
"choices.js": "^10.1.0",
diff --git a/public/images/logo/cmyk.ai b/public/images/logo/cmyk.ai
new file mode 100644
index 00000000000..2a3dd73d440
--- /dev/null
+++ b/public/images/logo/cmyk.ai
@@ -0,0 +1,5229 @@
+%PDF-1.6
%
+1 0 obj
<>/OCGs[36 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<>stream
+
+
+
+
+ application/pdf
+
+
+ cmyk
+
+
+ Adobe Illustrator 29.0 (Macintosh)
+ 2024-11-23T01:35:49+09:00
+ 2024-11-23T01:35:49+09:00
+ 2024-11-23T01:35:49+09:00
+
+
+
+ 256
+ 76
+ JPEG
+ /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgATAEAAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9P5JXYq7FXYq7FXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FVyqWNBiq70W8
RgtXei3iMbV3ot4jG1d6LeIxtXei3iMbV3ot4jG1d6LeIxtXei3iMbV3ot4jG1WtGV64bVbirYFS
B44qv9FvEYLV3ot4jG1d6LeIxtXei3iMbV3ot4jG1d6LeIxtXei3iMbV3ot4jG1d6LeIxtXei3iM
bV3ot4jG1d6LeIxtXei3iMbV3ot4jG1d6LeIxtXei3iMbVaylTQ4VW4q7FVSH7R+WAqvnnit4JJ5
m4xRKzyNuaKoqTt7YgXsryaT/nJPymrsq6bfOoJCvSIVHjQvmx/kyfeGvxQqWf8Azkf5NmuEjuLK
+tomNGnZI3VfdgrlqfIE+2CXZmQDYhfECdecPzm8reWbm0t3jmv3vIFu43tQhQQyf3bcnZQeVDSm
VYdFPICeVJlMBj//AEMp5V/6td9/yR/5ry/+TJ94R4oZz5J/MHy95xtJZtKd0mt6C5tJlCyx8q8S
aFlINDQg5h59PLEd2UZAoDzd+b3kzyvdNZXk8l1fpT1bSzQSOlf5yzIgPtyr7ZPDo8mQWNh5qZgI
by3+dPk7W72Gxb6xpl1c0+qrfRrGktTReDqzrv2rSuSy6GcBfP3IEwWe5hs1OfoMIVRwquT7Y+eK
sU/NLz9ceS9Etr63tEu5rm4ECpIxVVHBnLGgqfs0zI0mnGWVE0xlKnmif85G+ZZIJZ00K2aGEqJX
DyUXnULX50zP/kyHLiLDxPJPIv8AnIATeTbrVk0xRq1pcQ28tq0h9IrOHZZFanKn7pgV7Gm+Uns6
sgjexT4m1pNH/wA5D+bJLOa+j8vQvZ27Kk9wplMaNJXgGYbDlTbLT2bC64t0eIe5nX5afmza+brS
/N7brp11pqCa4PPlCYTWsgY0K8ePxA/f4Ymq0ZxkVuCyjO2PT/n1e6p5gj0byfon6ReVykMs7mMy
8QSWCCnBaCtWbp1Ay4dniMeKcqR4m+zJ/IP5r6d5nv59HuoBp+uW3MNbiRZYpfTNHMMq0DU608Nx
UVyjUaQ4xxDeLKMrZ3mGydirsVdirsVdirsVdirsVUZvtD5YQqnhV2KqkP2j8sBVDeYP+ODqX/ML
P/ybbJY/qHvV8T51TiPWbn8ttD826zFPonmfTluL6CF/0b/u5ZEgX1RxU7n4Cx2r1rmuGpljj6on
bq2mIJ5sf/NvQm0HWtL0d5vrDWWl28TTBeIYh5CaCp8cu0eTjiZd8mMxSV3mueXpfIOn6NFYcNet
r2Se41Dio5wMpAXkPiNSV2PTj75ZHHIZDK/TXJBIqmaflRa6t5b8q+ZfO5CpCtk1tp4JBLzM60cr
XYI3Hr1rmLqzGc44/PdnAUCXnOiIuoeZLBL1jKt3eQrcs5qWEkoDlmPjU75nZNomugaxzZb+ej3P
/KyL+F+Qt7eK2jskpRVi9BGog6cfUZunfMbQV4Q+LLJze+flXqt/qv5f6Ne35Zrp4mjd3ryYQyNE
jknqWVAa9802rgI5SBybomwyefoMoDJRwquT7Y+eKvKf+clP+UV0v/mO/wCZL5sezPrPua8vJ5B5
EuZwdRs18ty+Zre7ij9e0h9cNH6cnJX5W6s4327ZstQOR4uGvx1aonyejfmb5T8v6H+VdtdaXpJ0
i51O4spr61eSWV0f0JG9NjMzN+7LkdswdLmlPMQTxVbZIAReYaZdea08ma1b2UTN5clmtjqsoRSF
cPWIcj8QqwFae3jvsJiHiAn6t6axdJ35YvNGt/y281RWjyjzDNHb/WQ1An1P6yiN6VCa7uA9fEU7
5VljI5Y39P6aSORUvyb1Sx0/z3aG9mW3huYprYXDkKqPLGQhJNOrbfTh1sCcZpcZ3Zb+Xv5Ped9H
892F9fxJbWFjK0hvEmif1VVSKIit6lHrQ8lGxzG1OtxyxkDmWUYEFf5o8+/mAmpeZtSsbydPL+nX
cunwPGIBEsg/cgBmHqchy5giuDFp8VRBHqItJkd1zfn55uspL20ewtZfqCNCWlWX11liZYvVuKMi
kO53VQKH8X+T4GjZ3/Gy+IVvmr82/wAwVvE8u3aW2kXzta+vNaep6yevSUcGZ3SgjZVb/KrTHDo8
VcQuQ35qZnklXl787vPdsmoXV1OmoRiNfSS4iHpQyPMqhmaFY24hOQ413yzJocZoDZAmU7f/AJyB
838dKVNItPWu4+boVn/0gmdoVFuA3w14ftct/uyr+Tob7nb8bp8QpTf/AJuefxFcx3GqQWQmv54z
PBCzvb/V0B9Fea8fTkZl49X2PKgyyOjx7UL2RxlH2f5++arO7sLG9is7qFIovr14ySpK5dOZbYxo
hAI24U2+6Euz4EEiwnjNqlr+fvnX14Rdafp3omW1WYxrMGCXKmTasrCvAde3viez8fQnqomWWflL
+aPmTzjqt1a6jZ2sdrb23rG4tVlXjKZQqxv6juPiSrCnh92NrNJDEAQTzTCRL0ub7Q+WYAbFPCrs
VVIftH5YCqG8wf8AHB1L/mFn/wCTbZLH9Q96vifOqcR9WeRvyu8m6ONM12ytHGprbK4neWRqNNFx
duJPHcMe2c9n1eSVxJ2tyRAB4x+fOqafqHn6Q2U6zra28dtOyGoWVGcslehK8t6d9s2nZ8DHHu05
Dukd7P5OP5dafBbxgea1v5GvJOL1NsValW+zT+7oPEN71tiJ+Kb+ikGq80x/Kex1TWdR1Xy5bORa
arp863KmpjV4xyglYf5M3EV8Cchq5CIEzzBTDfZh97ZahpWoyWt1G9rfWknGSNqq6Opr/aDmTGQk
LHIsOT0Vvzrh1CxhXzH5XsNc1W3XhDqE4Vdq1HKP03rv1CsB7ZhfkeE+iRiO5s8TvD6F8r3NzdeX
dNuLmyGnTy28bPYqKLFVR8AWg4gD9nt0zS5QBIgG24I+foMgEqOFVyfbHzxVg350+TNc81eXrO10
aNJbm2uhM8TuI6oY2TYtRdi3jmXoc8cciZdzCcbCSfkl+XPmjytqOp3etQx26XEKRQosiSMxDcif
gLAAe5y3XamGQARRCJCN/Oryv578zQWWm6HbwzaUh9e5rIkcnrrVVqZCPhCttx79e2R0OXHjsy5p
mCXnFt+VH5w22i3miwWyJpt+8cl3AJ7f42hNU3LVG9Dt4DM06vAZCV7j3tfBKqZf+Vn5Lajpkmo3
XmhIwl7ayWK2COHJSUjm7svwj7I40P3ZjavXCVCHQ2yhCubE/MH/ADj15wtL510ZotSsWNYXaRYZ
QvhIr8VqPFTv7dMycfaMCPVsWJxFmP5Wfk1rWia1Dr3mG6X17RWFnZQyF6M6lCZH6UAOyrX59sxd
XrYzjwxHNnGFc3o+v+TfL+vm1XU4DLDaSmdLdXaON5DT4pFQrzpTvmDjzyhddWZFps1naM7u0EZe
SnqMVWrcenI03plfEUtyW1tI3KSJHbb4mUE7dOvzxsqt+p2npvF6EfpybSJwXiw9xTfHiKt/VLSs
R9GOsH9z8I+Dt8O3w/RjxFXSWdpIrLJBG6uwZlZVILDoTUdceIq42loS5MMZMhBkPEfEQKAnbeg2
xsqh7/RdKv7K4srq2R7a6jaGdAOBZGUqRyTiw+E02OSjOQNgrS/TNL0/S7GGxsIRBa26LHFGKmio
oVasxLNsOpNcE5mRs81AVJvtD5YAqnhV2KqkP2j8sBVUkjjljeKRQ8bgq6MKgqRQgg+OAFXzzaT/
AJAXN6lNJvYIjKRJLNMREqKruz8Y55HKjhSlKmu1c3ZGpA5j8fBp9L1VPzb/AC1ht7cJqyJDJEXt
0WC42jiJQ/CI/hpwOx/Vmu/J5SeX3NnGHlV9qf5D3erTzSaPfypdXTLHc2zyem5bizP6TSROgrJs
oUn2zYxjqBHmOTWeFODof/OOAtrm5+tEw2kqQTsJL0/HJy4hABVwfTbdKjKvE1VgVz9yaimmh+eP
yh8ni/tfL1pcPMGhXnDFJLJdvKpZEjkkblRQOjcRv8NcryYM+WjI/sSCByRGv+YPyT81ei+vAR6h
9W+s0khuIrlIghk4tJCCG4qD8PJvbBjx6jH9PK/KkkxPNAeX77/nHbSLg31hJE1zbgSiaeK8mKfG
EDKsqMoIZhSgr3yWSOqkKP6EDhD1XRNb0vW9Lg1TS5/rNhc8jDMFZOXBijfC4VhRlI3Ga+cDA0eb
MG0VP0GRCVHCramhB8MVVPX/AMn8cFK71/8AJ/HGld6/+T+ONK71/wDJ/HGld6/+T+ONK71/8n8c
aV3r/wCT+ONK71/8n8caV3r/AOT+ONK71/8AJ/HGld6/+T+ONK71/wDJ/HGld6/+T+ONK71/8n8c
aV3r/wCT+ONK71/8n8caVY78jWlMKrcVdiqpD9o/LAVXzwrNDJCxIWRSjEGhowoaHEGledv+SXl1
9Stto49Fs7SW3js44gs8klwpV5prgGrMB9n4dszfz0qP84n8bMOAJhb/AJOeR4ILiFLaUi7tfqc7
tKzOyGQTF+R35lxUn6OmQOtyH4G08IQsX5F+QI54ZvRuWML+oFadiGblyq3c1yR1+TyXgCsfyU8h
G1ltxbTKJZln9RZnDrx50jQ/sp+8bYDv8sH57Jd2vAF99+TPkW79QNbTRJIYP3cUzqqi2i9FAtan
ePZvHBHW5AvAFC6/I38vridpltp4GZQgWKeSgRYxHxHIttxGEa/KBzXgCE1T8iPK11Pby2k89pxn
Et38TSNLFzMjRq3JeBZyPj3Ipk4doTHPdBgGeaHounaJpVtpWmxejZWq8Yo6ljuSzEk7ksxJOYeS
ZnLiPMsgKRU/QZEJUcKuxV2KuxV2KuxV2KuxVxIAJJoB1OKuxV2KuqK0ruO2KuxV1RWldx2xV2Ku
JA6mn9uKuxV2KuxV2KuxVcj8TWlcSqp648MFK71x4Y0rvXHhjSu9ceGNK71x4Y0rvXHhjSu9ceGN
K71x4Y0rvXHhjSrHk5AbUwgKsxV2KuxV2KpD5t8l6V5ohtYtQmuYltHeSI2sphJLoYyHoDyWjfZO
x71G2AhQWK2f5D+U7O2tYLe/1KP6nPaXUEqSwpIJbCKSKEkpCtRSXkQf2hX+arwptD2X/OPPky0l
hdb7U5RC0bhJZYHVmjMBBb9x1P1RKkb7t448K2ynyv8Al7o3luy1C0sppnj1FY0mLiBCqxQCBeAg
ihWvEVLMCSe+ICLYhL/zjn5PECJbX9/HLBEsVqZTbyovCCS3T1F9FHdeEx5LzFfEYOFNp1D+T2gW
0OpWlndT2mnavZNZX1vAsELkmVphLHJFGhBLTS81YMp5ADiq8SaW1Rvyj8uSaEmhSSzLptt9ZWyW
HhFJGl7FxuOUnFubPKzy1oBUgUoN3hW0V5S/LjT/ACzqGp3Nrf3dzHqiKsiXMgaRWDyOT6qBGIHq
0Su6b/FvsgKSktr+Qvk2CaeRri+mS4QRyW7yQrBQPCzMsUcMaK8gtY1kcDkwG5JZiXhW0LB/zj35
Ti1F7wX1632TCrGEstJUmZXdozzQyRAqKApybifs8Rwradv+Vto3mSDzQNTuI9fAjW+niWKOG4Cw
pBLWJVDKzRq3BhJVCxptthpbS+L8ktIig0uFNUu+OiXf1nS6rD+7jeSKSSMhUUBi0TFXj4ULCobj
uOFbTXzH+Vnl/X9ZudWvLi6juLq3jtZFhMKqEibmpBaJn69VLFO/Gu+EhQUDrv5JeUdbWIXk10np
QpBS3+rQqwjt3tVYxpAIwfTk6KoUUFABtjwrbPLa3S2toreOvpwosaV60UUHSnhhQqYq7FXYq7FX
Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqr/uv
bArv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv
3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xti
rv3Xtirv3Xtirv3Xtirv3Xtirv3Xtirv3Xtir//Z
+
+
+
+ proof:pdf
+ uuid:65E6390686CF11DBA6E2D887CEACB407
+ xmp.did:b57c09fd-f0cc-41ca-8d3a-9150aa874f13
+ uuid:e31a4487-60f3-2e43-b83b-f094227e9a37
+
+ uuid:a9cf553b-7801-e547-b02f-3a7ffcebac92
+ xmp.did:8272c999-9888-4edc-b712-c3c254973673
+ uuid:65E6390686CF11DBA6E2D887CEACB407
+ default
+
+
+
+
+ saved
+ xmp.iid:8272c999-9888-4edc-b712-c3c254973673
+ 2024-11-23T01:17:25+09:00
+ Adobe Illustrator 29.0 (Macintosh)
+ /
+
+
+ saved
+ xmp.iid:b57c09fd-f0cc-41ca-8d3a-9150aa874f13
+ 2024-11-23T01:20:08+09:00
+ Adobe Illustrator 29.0 (Macintosh)
+ /
+
+
+
+ Web
+ Document
+ AIRobin
+ 1
+ False
+ False
+
+ 970.000000
+ 277.000000
+ Pixels
+
+
+
+
+ KozGoPr6N-Regular
+ 小塚ゴシック Pr6N
+ R
+ Open Type
+ Version 6.020;PS 6.008;hotconv 1.0.70;makeotf.lib2.5.5900
+ False
+ KozGoPr6N-Regular.otf
+
+
+
+
+
+ Cyan
+ Magenta
+ Yellow
+ Black
+
+
+
+
+
+ 初期設定のスウォッチグループ
+ 0
+
+
+
+ ホワイト
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+
+
+ ブラック
+ CMYK
+ PROCESS
+ 92.578125
+ 87.500000
+ 88.671875
+ 79.687500
+
+
+ RGB レッド
+ CMYK
+ PROCESS
+ 0.000000
+ 95.703125
+ 94.140625
+ 0.000000
+
+
+ RGB イエロー
+ CMYK
+ PROCESS
+ 10.156250
+ 0.000000
+ 82.421875
+ 0.000000
+
+
+ RGB グリーン
+ CMYK
+ PROCESS
+ 61.328125
+ 0.000000
+ 100.000000
+ 0.000000
+
+
+ RGB シアン
+ CMYK
+ PROCESS
+ 54.687500
+ 0.000000
+ 17.578125
+ 0.000000
+
+
+ RGB ブルー
+ CMYK
+ PROCESS
+ 92.187500
+ 74.218750
+ 0.000000
+ 0.000000
+
+
+ RGB マゼンタ
+ CMYK
+ PROCESS
+ 37.109375
+ 76.171875
+ 0.000000
+ 0.000000
+
+
+ R=193 G=39 B=45
+ CMYK
+ PROCESS
+ 30.468750
+ 96.093750
+ 89.062500
+ 0.390625
+
+
+ R=237 G=28 B=36
+ CMYK
+ PROCESS
+ 6.640625
+ 94.921875
+ 87.890625
+ 0.000000
+
+
+ R=241 G=90 B=36
+ CMYK
+ PROCESS
+ 4.296875
+ 78.125000
+ 87.109375
+ 0.000000
+
+
+ R=247 G=147 B=30
+ CMYK
+ PROCESS
+ 3.125000
+ 53.515625
+ 87.500000
+ 0.000000
+
+
+ R=251 G=176 B=59
+ CMYK
+ PROCESS
+ 3.125000
+ 40.234375
+ 78.906250
+ 0.000000
+
+
+ R=252 G=238 B=33
+ CMYK
+ PROCESS
+ 8.593750
+ 4.296875
+ 83.984375
+ 0.000000
+
+
+ R=217 G=224 B=33
+ CMYK
+ PROCESS
+ 24.218750
+ 4.687500
+ 87.890625
+ 0.000000
+
+
+ R=140 G=198 B=63
+ CMYK
+ PROCESS
+ 52.343750
+ 4.296875
+ 88.281250
+ 0.000000
+
+
+ R=57 G=181 B=74
+ CMYK
+ PROCESS
+ 71.484375
+ 3.906250
+ 89.453125
+ 0.000000
+
+
+ R=0 G=146 B=69
+ CMYK
+ PROCESS
+ 81.640625
+ 25.781250
+ 94.140625
+ 0.000000
+
+
+ R=0 G=104 B=55
+ CMYK
+ PROCESS
+ 88.671875
+ 48.437500
+ 100.000000
+ 12.890625
+
+
+ R=34 G=181 B=115
+ CMYK
+ PROCESS
+ 72.656250
+ 3.906250
+ 69.921875
+ 0.000000
+
+
+ R=0 G=169 B=157
+ CMYK
+ PROCESS
+ 75.781250
+ 13.671875
+ 46.484375
+ 0.000000
+
+
+ R=41 G=171 B=226
+ CMYK
+ PROCESS
+ 71.093750
+ 18.750000
+ 7.031250
+ 0.000000
+
+
+ R=0 G=113 B=188
+ CMYK
+ PROCESS
+ 85.546875
+ 52.343750
+ 5.468750
+ 0.000000
+
+
+ R=46 G=49 B=146
+ CMYK
+ PROCESS
+ 93.750000
+ 91.796875
+ 6.640625
+ 0.000000
+
+
+ R=27 G=20 B=100
+ CMYK
+ PROCESS
+ 100.000000
+ 100.000000
+ 55.859375
+ 7.031250
+
+
+ R=102 G=45 B=145
+ CMYK
+ PROCESS
+ 74.218750
+ 92.578125
+ 4.687500
+ 0.000000
+
+
+ R=147 G=39 B=143
+ CMYK
+ PROCESS
+ 54.296875
+ 93.750000
+ 5.468750
+ 0.000000
+
+
+ R=158 G=0 B=93
+ CMYK
+ PROCESS
+ 48.828125
+ 100.000000
+ 45.312500
+ 1.171875
+
+
+ R=212 G=20 B=90
+ CMYK
+ PROCESS
+ 21.093750
+ 96.484375
+ 47.656250
+ 0.000000
+
+
+ R=237 G=30 B=121
+ CMYK
+ PROCESS
+ 7.421875
+ 92.578125
+ 23.437500
+ 0.000000
+
+
+ R=199 G=178 B=153
+ CMYK
+ PROCESS
+ 26.953125
+ 31.250000
+ 39.453125
+ 0.000000
+
+
+ R=153 G=134 B=117
+ CMYK
+ PROCESS
+ 47.265625
+ 48.437500
+ 52.734375
+ 0.000000
+
+
+ R=115 G=99 B=87
+ CMYK
+ PROCESS
+ 61.328125
+ 60.937500
+ 64.453125
+ 9.375000
+
+
+ R=83 G=71 B=65
+ CMYK
+ PROCESS
+ 69.140625
+ 68.750000
+ 69.921875
+ 28.906250
+
+
+ R=198 G=156 B=109
+ CMYK
+ PROCESS
+ 28.125000
+ 42.968750
+ 59.375000
+ 0.000000
+
+
+ R=166 G=124 B=82
+ CMYK
+ PROCESS
+ 42.968750
+ 55.468750
+ 72.265625
+ 0.390625
+
+
+ R=140 G=98 B=57
+ CMYK
+ PROCESS
+ 50.781250
+ 64.453125
+ 85.546875
+ 9.765625
+
+
+ R=117 G=76 B=36
+ CMYK
+ PROCESS
+ 55.468750
+ 70.703125
+ 97.656250
+ 23.828125
+
+
+ R=96 G=56 B=19
+ CMYK
+ PROCESS
+ 58.593750
+ 76.171875
+ 100.000000
+ 38.281250
+
+
+ R=66 G=33 B=11
+ CMYK
+ PROCESS
+ 64.843750
+ 82.421875
+ 100.000000
+ 56.640625
+
+
+
+
+
+ グレー
+ 1
+
+
+
+ R=0 G=0 B=0
+ CMYK
+ PROCESS
+ 92.578125
+ 87.500000
+ 88.671875
+ 79.687500
+
+
+ R=26 G=26 B=26
+ CMYK
+ PROCESS
+ 83.984375
+ 79.687500
+ 78.515625
+ 65.234375
+
+
+ R=51 G=51 B=51
+ CMYK
+ PROCESS
+ 78.906250
+ 73.437500
+ 71.093750
+ 44.921875
+
+
+ R=77 G=77 B=77
+ CMYK
+ PROCESS
+ 73.437500
+ 66.796875
+ 63.671875
+ 22.656250
+
+
+ R=102 G=102 B=102
+ CMYK
+ PROCESS
+ 67.187500
+ 58.593750
+ 55.859375
+ 5.859375
+
+
+ R=128 G=128 B=128
+ CMYK
+ PROCESS
+ 57.421875
+ 48.437500
+ 45.312500
+ 0.000000
+
+
+ R=153 G=153 B=153
+ CMYK
+ PROCESS
+ 46.093750
+ 37.500000
+ 35.156250
+ 0.000000
+
+
+ R=179 G=179 B=179
+ CMYK
+ PROCESS
+ 34.375000
+ 27.343750
+ 25.781250
+ 0.000000
+
+
+ R=204 G=204 B=204
+ CMYK
+ PROCESS
+ 23.437500
+ 17.578125
+ 17.187500
+ 0.000000
+
+
+ R=230 G=230 B=230
+ CMYK
+ PROCESS
+ 11.718750
+ 8.984375
+ 8.984375
+ 0.000000
+
+
+ R=242 G=242 B=242
+ CMYK
+ PROCESS
+ 6.250000
+ 4.687500
+ 4.687500
+ 0.000000
+
+
+
+
+
+ Web カラーグループ
+ 1
+
+
+
+ R=63 G=169 B=245
+ CMYK
+ PROCESS
+ 67.187500
+ 23.437500
+ 0.000000
+ 0.000000
+
+
+ R=122 G=201 B=67
+ CMYK
+ PROCESS
+ 56.640625
+ 0.000000
+ 87.109375
+ 0.000000
+
+
+ R=255 G=147 B=30
+ CMYK
+ PROCESS
+ 0.000000
+ 54.687500
+ 85.937500
+ 0.000000
+
+
+ R=255 G=29 B=37
+ CMYK
+ PROCESS
+ 0.000000
+ 92.968750
+ 81.250000
+ 0.000000
+
+
+ R=255 G=123 B=172
+ CMYK
+ PROCESS
+ 0.000000
+ 66.406250
+ 7.031250
+ 0.000000
+
+
+ R=189 G=204 B=212
+ CMYK
+ PROCESS
+ 30.468750
+ 15.625000
+ 14.453125
+ 0.000000
+
+
+
+
+
+
+ Adobe PDF library 17.00
+ 21.0.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
endstream
endobj
3 0 obj
<>
endobj
5 0 obj
<>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/Thumb 41 0 R/TrimBox[0.0 0.0 970.0 277.0]/Type/Page/PieceInfo<>>>
endobj
38 0 obj
<>stream
+HWˮKWve+k
+bC_qY=slXB]Y:Gd~<>c9~?(g](w|xO_?8|7_>Ṩru㭖r،O/2.xx+g8NOGZb8ZC?jq?L=k9cm?oxh#^1ϊ͊XQ7;רG0qּ/w+ lOVqz< Ʉx{NqpubQEқ2moI;pܡN_~36snX