Skip to content

Commit

Permalink
Merge pull request #5942 from fjordllc/chore/convert-tags-vue-compone…
Browse files Browse the repository at this point in the history
…nt-to-react

tags-input.vue, tags.vueをreactに対応させる
  • Loading branch information
komagata authored May 17, 2023
2 parents df93b74 + 9995871 commit b81c523
Show file tree
Hide file tree
Showing 27 changed files with 406 additions and 290 deletions.
161 changes: 161 additions & 0 deletions app/javascript/components/Tags/Tags.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import React, { useState, useCallback } from 'react'
import TagifyTags from '@yaireo/tagify/dist/react.tagify'
import '@yaireo/tagify/dist/tagify.css' // Tagify CSS
import useSWR from 'swr'
import fetcher from '../../fetcher'
import Token from '../../token'
import transformHeadSharp from './transform-head-sharp'
import validateTagName from './validate-tag-name'
import headIsSharpOrOctothorpe from './head-is-sharp-or-octothorpe'
import parseTags from './parse_tags'

export default function Tags({
tagsInitialValue,
tagsParamName,
tagsInputId,
tagsType,
tagsTypeId,
tagsEditable = true
}) {
const [unSavedTags, setUnSavedTags] = useState(parseTags(tagsInitialValue))
const [tags, setTags] = useState(parseTags(tagsInitialValue))
const [editing, setEditing] = useState(false)
const [isSharp, setIsSharp] = useState(false)
const { data, error } = useSWR(
`/api/tags.json?taggable_type=${tagsType}`,
fetcher
)

if (error) {
console.warn('使われているタグリストの読み込みに失敗しました', error)
}

const onChange = useCallback((e) => {
setTags(
e.detail.tagify.value
.filter((tag) => tag.__isValid)
.map((tag) => tag.value)
)
setIsSharp(false)
}, [])

const onInput = useCallback((e) => {
setIsSharp(headIsSharpOrOctothorpe(e.detail.value))
}, [])

const onCancel = useCallback(
(e) => {
e.preventDefault()
setTags(unSavedTags)
setEditing(false)
},
[unSavedTags]
)

const onInvalid = useCallback((e) => {
alert(e.detail.message)
setIsSharp(false)
}, [])

const updateTag = (e) => {
e.preventDefault()
const params = {
[tagsType.toLowerCase()]: {
tag_list: tags.join()
}
}

fetch(`/api/${tagsType.toLowerCase()}s/${tagsTypeId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json; charset=utf-8',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': Token.getToken()
},
credentials: 'same-origin',
redirect: 'manual',
body: JSON.stringify(params)
})
.catch((error) => {
alert('タグの更新に失敗しました')
console.warn(error)
})
.then(() => {
setUnSavedTags(tags)
})
.finally(() => {
setEditing(false)
})
}

return (
<div className="tag-links">
{!editing && (
<ul className="tag-links__items">
{tags.map((tag) => {
return (
<li key={tag} className="tag-links__item">
<a
className="tag-links__item-link"
href={`tags/${encodeURIComponent(tag)}`}>
{tag}
</a>
</li>
)
})}
{tagsEditable && (
<li className="tag-links__item">
<div
className="tag-links__item-edit"
onClick={() => setEditing(true)}>
タグ編集
</div>
</li>
)}
</ul>
)}
<form className={`${editing ? '' : 'hidden'}`}>
<div className="form__items">
<div className="form-item">
<TagifyTags
settings={{
validate: validateTagName,
transformTag: transformHeadSharp
}}
value={tags}
whitelist={data ? data.map((tag) => tag.value) : []}
onChange={onChange}
onInput={onInput}
onInvalid={onInvalid}
/>
<input
type="hidden"
value={tags.join()}
name={tagsParamName}
id={tagsInputId}
/>
</div>
</div>
{isSharp && <div>先頭の記号は無視されます</div>}
{tagsEditable && (
<div className="form-actions">
<ul className="form-actions__items">
<li className="form-actions__item is-main">
<button
className="a-button is-primary is-sm is-block"
onClick={updateTag}>
保存する
</button>
</li>
<li className="form-actions__item">
<button className="a-button is-sm is-text" onClick={onCancel}>
キャンセル
</button>
</li>
</ul>
</div>
)}
</form>
</div>
)
}
62 changes: 62 additions & 0 deletions app/javascript/components/Tags/TagsInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useState, useCallback } from 'react'
import TagifyTags from '@yaireo/tagify/dist/react.tagify'
import '@yaireo/tagify/dist/tagify.css' // Tagify CSS
import useSWR from 'swr'
import fetcher from '../../fetcher'
import transformHeadSharp from './transform-head-sharp.js'
import validateTagName from './validate-tag-name'
import headIsSharpOrOctothorpe from './head-is-sharp-or-octothorpe'
import parseTags from './parse_tags'

export default function TagsInput({
tagsInitialValue,
tagsParamName,
taggableType
}) {
const [tags, setTags] = useState(parseTags(tagsInitialValue))
const [isSharp, setIsSharp] = useState(false)
const { data, error } = useSWR(
`/api/tags.json?taggable_type=${taggableType}`,
fetcher
)

if (error) {
console.warn('使われているタグリストの読み込みに失敗しました', error)
}

const onInput = useCallback((e) => {
setIsSharp(headIsSharpOrOctothorpe(e.detail.value))
}, [])

const onChange = useCallback((e) => {
setTags(
e.detail.tagify.value
.filter((tag) => tag.__isValid)
.map((tag) => tag.value)
)
setIsSharp(false)
}, [])

const onInvalid = useCallback((e) => {
alert(e.detail.message)
setIsSharp(false)
}, [])

return (
<>
<TagifyTags
settings={{
validate: validateTagName,
transformTag: transformHeadSharp
}}
value={tags}
whitelist={data ? data.map((tag) => tag.value) : []}
onInput={onInput}
onChange={onChange}
onInvalid={onInvalid}
/>
<input type="hidden" value={tags.join()} name={tagsParamName} />
{isSharp && <div>先頭の記号は無視されます</div>}
</>
)
}
4 changes: 4 additions & 0 deletions app/javascript/components/Tags/head-is-sharp-or-octothorpe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default function headIsSharpOrOctothorpe(text) {
const regex = /^(#||).*/
return regex.test(text)
}
5 changes: 5 additions & 0 deletions app/javascript/components/Tags/parse_tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default function parseTags(value) {
if (value === '') return []

return value.split(',').map((value) => value)
}
8 changes: 8 additions & 0 deletions app/javascript/components/Tags/transform-head-sharp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function transformHeadSharp(text) {
if (/^(#||)/.test(text.value)) {
if (text.length === 1) {
return
}
text.value = text.value.substr(1)
}
}
11 changes: 11 additions & 0 deletions app/javascript/components/Tags/validate-tag-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default function validateTagName(text) {
if (/\s+/.test(text.value)) {
return 'スペースを含むタグは作成できません'
} else if (text.value === '.') {
return 'ドット1つだけのタグは作成できません'
} else if (/^(#||)/.test(text.value)) {
return '#を先頭に含むタグは作成できません'
} else {
return true
}
}
3 changes: 0 additions & 3 deletions app/javascript/packs/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ import '../js-select2.js'
import '../warning.js'
import '../date-input-toggler'
import '../github_grass'
import '../tags-input.js'
import '../page_tags.js'
import '../following.js'
import '../user_tags.js'
import '../hide-user.js'
import '../categories-practice.js'
import '../notifications.js'
Expand Down
26 changes: 0 additions & 26 deletions app/javascript/page_tags.js

This file was deleted.

1 change: 1 addition & 0 deletions app/javascript/stylesheets/_common-imports.sass
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
@import "modules/select2"
@import "modules/stripe"
@import "modules/toasts"
@import "modules/tagify"

////////////
// style
Expand Down
Loading

0 comments on commit b81c523

Please sign in to comment.