diff --git a/.gitignore b/.gitignore index 46792764..c88bf684 100644 --- a/.gitignore +++ b/.gitignore @@ -49,7 +49,7 @@ dist/ # Instructions will be provided to download these from the game public/images/weapon* public/images/summon* -public/images/chara* +public/images/character* public/images/job* public/images/awakening* public/images/ax* diff --git a/README.md b/README.md index 537a8726..408da5e6 100644 --- a/README.md +++ b/README.md @@ -54,9 +54,9 @@ root ├─ accessory-square/ ├─ awakening/ ├─ ax/ -├─ chara-main/ -├─ chara-grid/ -├─ chara-square/ +├─ character-main/ +├─ character-grid/ +├─ character-square/ ├─ guidebooks/ ├─ jobs/ ├─ job-icons/ diff --git a/components/HovercardHeader/index.tsx b/components/HovercardHeader/index.tsx index e749b0a5..1acf04e8 100644 --- a/components/HovercardHeader/index.tsx +++ b/components/HovercardHeader/index.tsx @@ -51,7 +51,7 @@ const HovercardHeader = ({ gridObject, object, type, ...props }: Props) => { else if (gridCharacter.uncap_level == 5) suffix = '03' else if (gridCharacter.uncap_level > 2) suffix = '02' - return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_${suffix}.jpg` + return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-grid/${character.granblue_id}_${suffix}.jpg` } const summonImage = () => { diff --git a/components/MentionList/index.module.scss b/components/MentionList/index.module.scss new file mode 100644 index 00000000..a9365406 --- /dev/null +++ b/components/MentionList/index.module.scss @@ -0,0 +1,56 @@ +.items { + background: #fff; + border-radius: $item-corner; + box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.05), 0px 10px 20px rgba(0, 0, 0, 0.1); + color: rgba(0, 0, 0, 0.8); + overflow: hidden; + padding: $unit-half; + pointer-events: all; + position: relative; +} + +.item { + align-items: center; + background: transparent; + border: 1px solid transparent; + border-radius: $item-corner-small; + color: var(--text-tertiary); + font-size: $font-small; + font-weight: $medium; + display: flex; + gap: $unit; + margin: 0; + padding: $unit-half $unit; + text-align: left; + width: 100%; + + &:hover, + &.selected { + background: var(--menu-bg-item-hover); + color: var(--text-primary); + } + + .job { + display: flex; + align-items: center; + justify-content: center; + width: $unit-4x; + height: $unit-4x; + + img { + width: $unit-3x; + height: auto; + } + } + + img { + border-radius: $item-corner-small; + width: $unit-4x; + height: $unit-4x; + } +} + +.noResult { + padding: $unit; + color: var(--text-tertiary); +} diff --git a/components/MentionList/index.tsx b/components/MentionList/index.tsx new file mode 100644 index 00000000..499fb89a --- /dev/null +++ b/components/MentionList/index.tsx @@ -0,0 +1,124 @@ +import React, { + forwardRef, + useEffect, + useImperativeHandle, + useState, +} from 'react' +import { useTranslation } from 'next-i18next' +import { useRouter } from 'next/router' +import { SuggestionProps } from '@tiptap/suggestion' +import classNames from 'classnames' + +import styles from './index.module.scss' + +type Props = Pick + +export type MentionRef = { + onKeyDown: (props: { event: KeyboardEvent }) => boolean +} + +export type MentionSuggestion = { + granblue_id: string + name: { + [key: string]: string + en: string + ja: string + } + type: string + element: number +} + +interface MentionProps extends SuggestionProps { + items: MentionSuggestion[] +} + +export const MentionList = forwardRef( + ({ items, ...props }: Props, forwardedRef) => { + const router = useRouter() + const locale = router.locale || 'en' + + const { t } = useTranslation('common') + + const [selectedIndex, setSelectedIndex] = useState(0) + + const selectItem = (index: number) => { + const item = items[index] + + if (item) { + props.command({ id: item }) + } + } + + const upHandler = () => { + setSelectedIndex((selectedIndex + items.length - 1) % items.length) + } + + const downHandler = () => { + setSelectedIndex((selectedIndex + 1) % items.length) + } + + const enterHandler = () => { + selectItem(selectedIndex) + } + + useEffect(() => setSelectedIndex(0), [items]) + + useImperativeHandle(forwardedRef, () => ({ + onKeyDown: ({ event }) => { + if (event.key === 'ArrowUp') { + upHandler() + return true + } + + if (event.key === 'ArrowDown') { + downHandler() + return true + } + + if (event.key === 'Enter') { + enterHandler() + return true + } + + return false + }, + })) + + return ( +
+ {items.length ? ( + items.map((item, index) => ( + + )) + ) : ( +
+ {t('search.errors.no_results_generic')} +
+ )} +
+ ) + } +) + +MentionList.displayName = 'MentionList' diff --git a/components/about/ChangelogUnit/index.tsx b/components/about/ChangelogUnit/index.tsx index ade0b662..d63d8375 100644 --- a/components/about/ChangelogUnit/index.tsx +++ b/components/about/ChangelogUnit/index.tsx @@ -68,7 +68,7 @@ const ChangelogUnit = ({ id, type, image }: Props) => { switch (type) { case 'character': - src = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${id}_${image}.jpg` + src = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-grid/${id}_${image}.jpg` break case 'weapon': src = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${id}.jpg` diff --git a/components/character/CharacterConflictModal/index.tsx b/components/character/CharacterConflictModal/index.tsx index 5306fe39..5ad5acbb 100644 --- a/components/character/CharacterConflictModal/index.tsx +++ b/components/character/CharacterConflictModal/index.tsx @@ -60,7 +60,7 @@ const CharacterConflictModal = (props: Props) => { suffix = `${suffix}_0${element}` } - return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-square/${character?.granblue_id}_${suffix}.jpg` + return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-square/${character?.granblue_id}_${suffix}.jpg` } function openChange(open: boolean) { diff --git a/components/character/CharacterModal/index.tsx b/components/character/CharacterModal/index.tsx index d00ca5a9..f42b3114 100644 --- a/components/character/CharacterModal/index.tsx +++ b/components/character/CharacterModal/index.tsx @@ -364,7 +364,7 @@ const CharacterModal = ({ title={gridCharacter.object.name[locale]} subtitle={t('modals.characters.title')} image={{ - src: `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-square/${gridCharacter.object.granblue_id}_01.jpg`, + src: `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-square/${gridCharacter.object.granblue_id}_01.jpg`, alt: gridCharacter.object.name[locale], }} /> diff --git a/components/character/CharacterResult/index.tsx b/components/character/CharacterResult/index.tsx index 5ef46c6f..47ef8ea2 100644 --- a/components/character/CharacterResult/index.tsx +++ b/components/character/CharacterResult/index.tsx @@ -21,10 +21,10 @@ const CharacterResult = (props: Props) => { const character = props.data const characterUrl = () => { - let url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01.jpg` + let url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-grid/${character.granblue_id}_01.jpg` if (character.granblue_id === '3030182000') { - url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-grid/${character.granblue_id}_01_01.jpg` + url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-grid/${character.granblue_id}_01_01.jpg` } return url diff --git a/components/character/CharacterUnit/index.tsx b/components/character/CharacterUnit/index.tsx index b643bf35..83191784 100644 --- a/components/character/CharacterUnit/index.tsx +++ b/components/character/CharacterUnit/index.tsx @@ -203,7 +203,7 @@ const CharacterUnit = ({ suffix = `${suffix}_0${element}` } - imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-main/${character.granblue_id}_${suffix}.jpg` + imgSrc = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblue_id}_${suffix}.jpg` } setImageUrl(imgSrc) diff --git a/components/common/Editor/index.module.scss b/components/common/Editor/index.module.scss new file mode 100644 index 00000000..faebbf51 --- /dev/null +++ b/components/common/Editor/index.module.scss @@ -0,0 +1,234 @@ +.wrapper { + border-radius: $input-corner; + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: hidden; + + &.bound { + background-color: var(--input-bg); + height: 350px; // Temporary + } + + & > div { + display: flex; + flex-grow: 1; + overflow: hidden; + } + + .editor { + -webkit-font-smoothing: antialiased; + box-sizing: border-box; + color: var(--text-primary); + display: block; + flex-grow: 1; + font-family: system-ui, -apple-system, 'Helvetica Neue', Helvetica, Arial, + sans-serif; + font-size: $font-regular; + line-height: 1.4; + overflow: scroll; + padding: $unit * 1.5 $unit-2x; + white-space: pre-wrap; + width: 100%; + + &:focus { + // border: 2px solid $blue; + outline: none; + } + + &.bound { + background-color: var(--input-bound-bg); + + &:hover { + background-color: var(--input-bound-bg-hover); + } + } + + &.editParty { + border-bottom-left-radius: $input-corner; + border-bottom-right-radius: $input-corner; + } + + a:hover { + cursor: pointer; + } + + strong { + font-weight: $bold; + } + + em { + font-style: italic; + } + + iframe { + background: var(--input-bound-bg); + border-radius: $card-corner; + min-width: 200px; + min-height: 200px; + display: block; + outline: 0px solid transparent; + margin: $unit 0; + + &:hover { + background: var(--input-bound-bg-hover); + } + } + + .mention { + border-radius: $item-corner-small; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px rgba(0, 0, 0, 0.25); + background: var(--card-bg); + color: var(--text-primary); + font-weight: $medium; + font-size: 15px; + padding: 1px $unit-half; + + &:hover { + background: var(--card-bg-hover); + text-decoration: none; + cursor: pointer; + } + + &[data-element='fire'] { + background: var(--fire-bg); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--fire-shadow); + color: var(--fire-text); + + &:hover { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--fire-shadow-hover); + color: var(--fire-text-hover); + } + } + + &[data-element='water'] { + background: var(--water-bg); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--water-shadow); + color: var(--water-text); + + &:hover { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--water-shadow-hover); + color: var(--water-text-hover); + } + } + + &[data-element='earth'] { + background: var(--earth-bg); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--earth-shadow); + color: var(--earth-text); + + &:hover { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--earth-shadow-hover); + color: var(--earth-text-hover); + } + } + + &[data-element='wind'] { + background: var(--wind-bg); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--wind-shadow); + color: var(--wind-text); + + &:hover { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--wind-shadow-hover); + color: var(--wind-text-hover); + } + } + + &[data-element='dark'] { + background: var(--dark-bg); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--dark-shadow); + color: var(--dark-text); + + &:hover { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--dark-shadow-hover); + color: var(--dark-text-hover); + } + } + + &[data-element='light'] { + background: var(--light-bg); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--light-shadow); + color: var(--light-text); + + &:hover { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), + 0 1px 0px var(--light-shadow-hover); + color: var(--light-text-hover); + } + } + } + } + + .toolbar { + background: var(--toolbar-bg); + position: sticky; + align-items: center; + display: flex; + gap: $unit-half; + padding: $unit; + z-index: 10; + + button { + background: var(--toolbar-item-bg); + border-radius: $bubble-menu-item-corner; + color: var(--toolbar-item-text); + font-weight: $medium; + font-size: $font-small; + padding: $unit-half $unit; + + &:hover { + background: var(--toolbar-item-bg-hover); + color: var(--toolbar-item-text-hover); + cursor: pointer; + } + + &.active { + background: var(--toolbar-item-bg-active); + color: var(--toolbar-item-text-active); + } + } + + .divider { + background: var(--toolbar-divider-bg); + border-radius: $full-corner; + height: calc($unit-2x + $unit-half); + width: $unit-fourth; + } + } +} + +.menu { + background: var(--formatting-menu-bg); + border-radius: $bubble-menu-corner; + padding: $unit-half; + + button { + background: var(--formatting-menu-item-bg); + border-radius: $bubble-menu-item-corner; + color: var(--formatting-menu-item-text); + font-weight: $medium; + font-size: $font-small; + + &:hover { + background: var(--formatting-menu-item-bg-hover); + color: var(--formatting-menu-item-text-hover); + } + + &:active { + background: var(--formatting-menu-item-bg-active); + color: var(--formatting-menu-item-text-active); + } + } +} diff --git a/components/common/Editor/index.tsx b/components/common/Editor/index.tsx new file mode 100644 index 00000000..69620dbe --- /dev/null +++ b/components/common/Editor/index.tsx @@ -0,0 +1,180 @@ +import { ComponentProps, useCallback } from 'react' +import { useRouter } from 'next/router' +import { useEditor, EditorContent } from '@tiptap/react' +import StarterKit from '@tiptap/starter-kit' +import Link from '@tiptap/extension-link' +import Youtube from '@tiptap/extension-youtube' +import CustomMention from '~extensions/CustomMention' +import classNames from 'classnames' + +import { mentionSuggestionOptions } from '~utils/mentionSuggestions' +import type { JSONContent } from '@tiptap/core' + +import styles from './index.module.scss' + +interface Props extends ComponentProps<'div'> { + bound: boolean + editable?: boolean + content?: string + onUpdate?: (content: JSONContent) => void +} + +const Editor = ({ + bound, + className, + content, + editable, + onUpdate, + ...props +}: Props) => { + const router = useRouter() + const locale = router.locale || 'en' + + function isJSON(content?: string) { + if (!content) return false + + try { + JSON.parse(content) + } catch (e) { + return false + } + return true + } + + function formatContent(content?: string) { + if (!content) return '' + if (isJSON(content)) return JSON.parse(content) + else { + // Otherwise, create a new

tag after each double newline. + // Add < br /> tags for single newlines. + // Add a < br /> after each paragraph. + const paragraphs = content.split('\n\n') + const formatted = paragraphs + .map((p) => { + const lines = p.split('\n') + return lines.join('
') + }) + .join('


') + return formatted + } + } + + const editor = useEditor({ + content: formatContent(content), + editable: editable, + editorProps: { + attributes: { + class: classNames( + { + [styles.editor]: true, + [styles.bound]: bound, + }, + className?.split(' ').map((c) => styles[c]) + ), + }, + }, + extensions: [ + StarterKit, + Link, + CustomMention.configure({ + renderLabel({ options, node }) { + return `${node.attrs.id.name[locale] ?? node.attrs.id.granblue_en}` + }, + suggestion: mentionSuggestionOptions, + HTMLAttributes: { + class: classNames({ + [styles.mention]: true, + }), + }, + }), + Youtube.configure({ + inline: false, + modestBranding: true, + interfaceLanguage: locale, + }), + ], + onUpdate: ({ editor }) => { + const json = editor.getJSON() + if (onUpdate) onUpdate(json) + }, + }) + + const setLink = useCallback(() => { + const previousUrl = editor?.getAttributes('link').href + const url = window.prompt('URL', previousUrl) + + // cancelled + if (url === null) { + return + } + + // empty + if (url === '') { + editor?.chain().focus().extendMarkRange('link').unsetLink().run() + + return + } + + // update link + editor?.chain().focus().extendMarkRange('link').setLink({ href: url }).run() + }, [editor]) + + const addYoutubeVideo = () => { + const url = prompt('Enter YouTube URL') + + if (editor && url) { + editor.commands.setYoutubeVideo({ + src: url, + width: 320, + height: 180, + }) + } + } + + if (!editor) { + return null + } + + return ( +

+ {editor && editable === true && ( + + )} + +
+ ) +} + +Editor.defaultProps = { + bound: false, + editable: false, +} + +export default Editor diff --git a/components/common/InputTableField/index.tsx b/components/common/InputTableField/index.tsx index 8a53c4e6..2ec2a44c 100644 --- a/components/common/InputTableField/index.tsx +++ b/components/common/InputTableField/index.tsx @@ -14,7 +14,7 @@ interface Props imageAlt?: string imageClass?: string imageSrc?: string[] - onValueChange: (value?: string) => void + onValueChange: (value?: string | number | readonly string[]) => void } const InputTableField = ({ @@ -25,10 +25,12 @@ const InputTableField = ({ imageSrc, ...props }: Props) => { - const [inputValue, setInputValue] = useState('') + const [inputValue, setInputValue] = useState< + string | number | readonly string[] + >() useEffect(() => { - if (props.value) setInputValue(`${props.value}`) + if (props.value !== undefined) setInputValue(props.value) }, [props.value]) useEffect(() => { @@ -54,7 +56,7 @@ const InputTableField = ({ { setMinWeaponCount(value) } - function handleMaxButtonsCountValueChange(value?: string) { + function handleMaxButtonsCountValueChange( + value?: string | number | readonly string[] + ) { if (!value) return - setMaxButtonsCount(parseInt(value)) + setMaxButtonsCount(value as number) } - function handleMaxTurnsCountValueChange(value?: string) { + function handleMaxTurnsCountValueChange( + value?: string | number | readonly string[] + ) { if (!value) return - setMaxTurnsCount(parseInt(value)) + setMaxTurnsCount(value as number) } function handleNameQualityValueChange(value?: boolean) { diff --git a/components/party/EditPartyModal/index.tsx b/components/party/EditPartyModal/index.tsx index cdbcdcb1..8f346544 100644 --- a/components/party/EditPartyModal/index.tsx +++ b/components/party/EditPartyModal/index.tsx @@ -6,12 +6,13 @@ import classNames from 'classnames' import debounce from 'lodash.debounce' import Alert from '~components/common/Alert' +import Button from '~components/common/Button' import { Dialog, DialogTrigger } from '~components/common/Dialog' import DialogHeader from '~components/common/DialogHeader' import DialogFooter from '~components/common/DialogFooter' import DialogContent from '~components/common/DialogContent' -import Button from '~components/common/Button' import DurationInput from '~components/common/DurationInput' +import Editor from '~components/common/Editor' import Input from '~components/common/Input' import InputTableField from '~components/common/InputTableField' import RaidCombobox from '~components/raids/RaidCombobox' @@ -24,6 +25,7 @@ import Textarea from '~components/common/Textarea' import capitalizeFirstLetter from '~utils/capitalizeFirstLetter' import type { DetailsObject } from 'types' import type { DialogProps } from '@radix-ui/react-dialog' +import type { JSONContent } from '@tiptap/core' import { appState } from '~utils/appState' @@ -129,6 +131,10 @@ const EditPartyModal = ({ setErrors(newErrors) } + function handleEditorUpdate(content: JSONContent) { + setDescription(JSON.stringify(content)) + } + function handleChargeAttackChanged(checked: boolean) { setChargeAttack(checked) } @@ -153,22 +159,23 @@ const EditPartyModal = ({ if (!isNaN(value)) setClearTime(value) } - function handleTurnCountChanged(value?: string) { - if (!value) return - const numericalValue = parseInt(value) - if (!isNaN(numericalValue)) setTurnCount(numericalValue) + function handleTurnCountChanged(value?: string | number | readonly string[]) { + if (value === null || value === undefined) return + setTurnCount(value as number) } - function handleButtonCountChanged(value?: string) { - if (!value) return - const numericalValue = parseInt(value) - if (!isNaN(numericalValue)) setButtonCount(numericalValue) + function handleButtonCountChanged( + value?: string | number | readonly string[] + ) { + if (value === null || value === undefined) return + setButtonCount(value as number) } - function handleChainCountChanged(value?: string) { - if (!value) return - const numericalValue = parseInt(value) - if (!isNaN(numericalValue)) setChainCount(numericalValue) + function handleChainCountChanged( + value?: string | number | readonly string[] + ) { + if (value === null || value === undefined) return + setChainCount(value as number) } function handleTextAreaChanged(event: React.ChangeEvent) { @@ -291,7 +298,6 @@ const EditPartyModal = ({ function hasBeenModified() { const nameChanged = name !== party.name && !(name === '' && party.name === undefined) - const descriptionChanged = description !== party.description && !(description === '' && party.description === undefined) @@ -306,6 +312,21 @@ const EditPartyModal = ({ const buttonCountChanged = buttonCount !== party.buttonCount const chainCountChanged = chainCount !== party.chainCount + // Debugging for if you need to check if a value is being changed + // console.log(` + // nameChanged: ${nameChanged}\n + // descriptionChanged: ${descriptionChanged}\n + // raidChanged: ${raidChanged}\n + // chargeAttackChanged: ${chargeAttackChanged}\n + // fullAutoChanged: ${fullAutoChanged}\n + // autoGuardChanged: ${autoGuardChanged}\n + // autoSummonChanged: ${autoSummonChanged}\n + // clearTimeChanged: ${clearTimeChanged}\n + // turnCountChanged: ${turnCountChanged}\n + // buttonCountChanged: ${buttonCountChanged}\n + // chainCountChanged: ${chainCountChanged}\n + // `) + return ( nameChanged || descriptionChanged || @@ -332,13 +353,12 @@ const EditPartyModal = ({ setFullAuto(party.fullAuto) setChargeAttack(party.chargeAttack) setClearTime(party.clearTime) - if (party.turnCount) setTurnCount(party.turnCount) - if (party.buttonCount) setButtonCount(party.buttonCount) - if (party.chainCount) setChainCount(party.chainCount) + if (party.turnCount !== undefined) setTurnCount(party.turnCount) + if (party.buttonCount !== undefined) setButtonCount(party.buttonCount) + if (party.chainCount !== undefined) setChainCount(party.chainCount) } async function updateDetails(event: React.MouseEvent) { - const descriptionValue = descriptionInput.current?.innerHTML const details: DetailsObject = { fullAuto: fullAuto, autoGuard: autoGuard, @@ -349,7 +369,7 @@ const EditPartyModal = ({ turnCount: turnCount, chainCount: chainCount, name: name, - description: descriptionValue, + description: description, raid: raid, extra: extra, } @@ -457,6 +477,15 @@ const EditPartyModal = ({ /> ) + const editorField = ( + + ) + const chargeAttackField = ( ) diff --git a/components/party/PartyFooter/index.module.scss b/components/party/PartyFooter/index.module.scss index 7a88a9bb..5c2bea6b 100644 --- a/components/party/PartyFooter/index.module.scss +++ b/components/party/PartyFooter/index.module.scss @@ -2,7 +2,7 @@ display: flex; flex-direction: column; gap: $unit-2x; - margin: $unit-4x auto 0 auto; + margin: $unit-4x auto $unit-10x auto; max-width: $grid-width; @include breakpoint(phone) { @@ -22,8 +22,7 @@ line-height: 1.4; white-space: pre-wrap; margin: 0 auto $unit-2x; - margin-bottom: $unit-12x; - min-height: 10vh; + min-height: 20vh; max-width: $unit * 94; overflow: hidden; width: 100%; @@ -32,22 +31,6 @@ padding: 0 $unit; } - .noRemixes, - .noDescription { - align-items: center; - display: flex; - flex-direction: column; - gap: $unit-2x; - margin: 0 auto; - padding: $unit-4x 0; - - h3 { - font-size: $font-small; - font-weight: $medium; - text-align: center; - } - } - p { font-size: $font-regular; line-height: $font-regular * 1.2; @@ -147,6 +130,23 @@ } } + .noRemixes, + .noDescription { + align-items: center; + display: flex; + flex-direction: column; + min-height: 20vh; + gap: $unit-2x; + margin: 0 auto; + padding: $unit-4x 0; + + h3 { + font-size: $font-small; + font-weight: $medium; + text-align: center; + } + } + .PartyInfo { box-sizing: border-box; display: flex; diff --git a/components/party/PartyFooter/index.tsx b/components/party/PartyFooter/index.tsx index 5b05a218..0f1e882d 100644 --- a/components/party/PartyFooter/index.tsx +++ b/components/party/PartyFooter/index.tsx @@ -2,12 +2,8 @@ import React, { useEffect, useState } from 'react' import { useRouter } from 'next/router' import { useSnapshot } from 'valtio' import { useTranslation } from 'next-i18next' -import classNames from 'classnames' import clonedeep from 'lodash.clonedeep' - -import Linkify from 'react-linkify' -import LiteYouTubeEmbed from 'react-lite-youtube-embed' -import reactStringReplace from 'react-string-replace' +import DOMPurify from 'dompurify' import Button from '~components/common/Button' import SegmentedControl from '~components/common/SegmentedControl' @@ -27,6 +23,7 @@ import type { DetailsObject } from 'types' import RemixIcon from '~public/icons/Remix.svg' import EditIcon from '~public/icons/Edit.svg' import styles from './index.module.scss' +import Editor from '~components/common/Editor' // Props interface Props { @@ -55,42 +52,39 @@ const PartyFooter = (props: Props) => { // State: Data const [remixes, setRemixes] = useState([]) - const [embeddedDescription, setEmbeddedDescription] = - useState() + const [sanitizedDescription, setSanitizedDescription] = useState('') useEffect(() => { - // Extract the video IDs from the description if (partySnapshot.description) { - const videoIds = extractYoutubeVideoIds(partySnapshot.description) - - // Fetch the video titles for each ID - const fetchPromises = videoIds.map(({ id }) => fetchYoutubeData(id)) - - // Wait for all the video titles to be fetched - Promise.all(fetchPromises).then((videoTitles) => { - // Replace the video URLs in the description with LiteYoutubeEmbed elements - const newDescription = reactStringReplace( - partySnapshot.description, - youtubeUrlRegex, - (match, i) => ( - - ) - ) - - // Update the state with the new description - setEmbeddedDescription(newDescription) - }) + const purified = DOMPurify.sanitize(partySnapshot.description) + setSanitizedDescription(purified) } else { - setEmbeddedDescription('') + setSanitizedDescription('') } }, [partySnapshot.description]) + // Extract the video IDs from the description + // const videoIds = extractYoutubeVideoIds(partySnapshot.description) + // Fetch the video titles for each ID + // const fetchPromises = videoIds.map(({ id }) => fetchYoutubeData(id)) + // // Wait for all the video titles to be fetched + // Promise.all(fetchPromises).then((videoTitles) => { + // // Replace the video URLs in the description with LiteYoutubeEmbed elements + // const newDescription = reactStringReplace( + // partySnapshot.description, + // youtubeUrlRegex, + // (match, i) => ( + // + // ) + // ) + // Update the state with the new description + async function fetchYoutubeData(videoId: string) { return await youtube .getVideoById(videoId, { maxResults: 1 }) @@ -213,14 +207,14 @@ const PartyFooter = (props: Props) => { ) const descriptionSection = ( -
+ <> {partySnapshot && partySnapshot.description && partySnapshot.description.length > 0 && ( - {embeddedDescription} + )} {(!partySnapshot || !partySnapshot.description) && ( -
+

{t('footer.description.empty')}

{props.editable && ( { /> )} -
+
)} - + ) const remixesSection = ( diff --git a/components/reps/CharacterRep/index.tsx b/components/reps/CharacterRep/index.tsx index 0d34a753..14dcba66 100644 --- a/components/reps/CharacterRep/index.tsx +++ b/components/reps/CharacterRep/index.tsx @@ -95,9 +95,9 @@ const CharacterRep = (props: Props) => { else if (gridCharacter.uncap_level > 2) suffix = '02' if (character.element == 0) { - url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-main/${character.granblue_id}_${props.element}.jpg` + url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblue_id}_${props.element}.jpg` } else { - url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/chara-main/${character.granblue_id}_${suffix}.jpg` + url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblue_id}_${suffix}.jpg` } } diff --git a/extensions/CustomMention/index.tsx b/extensions/CustomMention/index.tsx new file mode 100644 index 00000000..a82e4305 --- /dev/null +++ b/extensions/CustomMention/index.tsx @@ -0,0 +1,25 @@ +import { mergeAttributes, Node } from '@tiptap/core' +import Mention from '@tiptap/extension-mention' + +export default Mention.extend({ + renderHTML({ node, HTMLAttributes }) { + return [ + 'a', + mergeAttributes( + { + href: `https://gbf.wiki/${node.attrs.id.name.en}`, + target: '_blank', + }, + { 'data-type': this.name }, + { 'data-element': node.attrs.id.element.slug }, + { tabindex: -1 }, + this.options.HTMLAttributes, + HTMLAttributes + ), + this.options.renderLabel({ + options: this.options, + node, + }), + ] + }, +}) diff --git a/package-lock.json b/package-lock.json index c561a55d..9139c0ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,11 +19,20 @@ "@radix-ui/react-toggle-group": "^1.0.1", "@radix-ui/react-tooltip": "^1.0.3", "@svgr/webpack": "^6.2.0", + "@tiptap/extension-bubble-menu": "^2.0.3", + "@tiptap/extension-link": "^2.0.3", + "@tiptap/extension-mention": "^2.0.3", + "@tiptap/extension-youtube": "^2.0.3", + "@tiptap/pm": "^2.0.3", + "@tiptap/react": "^2.0.3", + "@tiptap/starter-kit": "^2.0.3", + "@tiptap/suggestion": "2.0.0-beta.91", "axios": "^0.25.0", "classnames": "^2.3.1", "cmdk": "^0.2.0", "cookies-next": "^2.1.1", "date-fns": "^2.29.3", + "dompurify": "^3.0.4", "fast-deep-equal": "^3.1.3", "fix-date": "^1.1.6", "i18next": "^21.6.13", @@ -50,6 +59,7 @@ "resolve-url-loader": "^5.0.0", "sanitize-html": "^2.8.1", "sass": "^1.61.0", + "tippy.js": "^6.3.7", "usehooks-ts": "^2.9.1", "uuid": "^9.0.0", "valtio": "^1.3.0", @@ -65,6 +75,7 @@ "@storybook/nextjs": "latest", "@storybook/react": "latest", "@storybook/testing-library": "latest", + "@types/dompurify": "^3.0.2", "@types/lodash.clonedeep": "^4.5.6", "@types/lodash.debounce": "^4.0.6", "@types/node": "17.0.11", @@ -597,7 +608,6 @@ "version": "7.18.9", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.9", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" @@ -3090,6 +3100,62 @@ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==", "dev": true }, + "node_modules/@linaria/core": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/@linaria/core/-/core-4.2.9.tgz", + "integrity": "sha512-ELcu37VNVOT/PU0L6WDIN+aLzNFyJrqoBYT0CucGOCAmODbojUMCv8oJYRbWzA3N34w1t199dN4UFdfRWFG2rg==", + "dependencies": { + "@linaria/logger": "^4.0.0", + "@linaria/tags": "^4.3.4", + "@linaria/utils": "^4.3.3" + }, + "engines": { + "node": "^12.16.0 || >=13.7.0" + } + }, + "node_modules/@linaria/logger": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@linaria/logger/-/logger-4.0.0.tgz", + "integrity": "sha512-YnBq0JlDWMEkTOK+tMo5yEVR0f5V//6qMLToGcLhTyM9g9i+IDFn51Z+5q2hLk7RdG4NBPgbcCXYi2w4RKsPeg==", + "dependencies": { + "debug": "^4.1.1", + "picocolors": "^1.0.0" + }, + "engines": { + "node": "^12.16.0 || >=13.7.0" + } + }, + "node_modules/@linaria/tags": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@linaria/tags/-/tags-4.3.5.tgz", + "integrity": "sha512-PgaIi8Vv89YOjc6rpKL/uPg2w4k0rAwAYxcqeXqzKqsEAste5rgB8xp1/KUOG0oAOkPd3MRL6Duj+m0ZwJ3g+g==", + "dependencies": { + "@babel/generator": "^7.20.4", + "@linaria/logger": "^4.0.0", + "@linaria/utils": "^4.3.4" + }, + "engines": { + "node": "^12.16.0 || >=13.7.0" + } + }, + "node_modules/@linaria/utils": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@linaria/utils/-/utils-4.3.4.tgz", + "integrity": "sha512-vt6WJG54n+KANaqxOfzIIU7aSfFHEWFbnGLsgxL7nASHqO0zezrNA2y2Rrp80zSeTW+wSpbmDM4uJyC9UW1qoA==", + "dependencies": { + "@babel/core": "^7.20.2", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-modules-commonjs": "^7.19.6", + "@babel/traverse": "^7.20.1", + "@babel/types": "^7.20.2", + "@linaria/logger": "^4.0.0", + "babel-merge": "^3.0.0" + }, + "engines": { + "node": "^12.16.0 || >=13.7.0" + } + }, "node_modules/@mdx-js/react": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-2.3.0.tgz", @@ -3416,6 +3482,15 @@ "node": ">=8.9.0" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@radix-ui/number": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz", @@ -4384,6 +4459,52 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@remirror/core-constants": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.1.tgz", + "integrity": "sha512-ZR4aihtnnT9lMbhh5DEbsriJRlukRXmLZe7HmM+6ufJNNUDoazc75UX26xbgQlNUqgAqMcUdGFAnPc1JwgAdLQ==", + "dependencies": { + "@babel/runtime": "^7.21.0" + } + }, + "node_modules/@remirror/core-helpers": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@remirror/core-helpers/-/core-helpers-2.0.3.tgz", + "integrity": "sha512-LqIPF4stGG69l9qu/FFicv9d9B+YaItzgDMC5A0CEvDQfKkGD3BfabLmfpnuWbsc06oKGdTduilgWcALLZoYLg==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@linaria/core": "4.2.9", + "@remirror/core-constants": "^2.0.1", + "@remirror/types": "^1.0.1", + "@types/object.omit": "^3.0.0", + "@types/object.pick": "^1.3.2", + "@types/throttle-debounce": "^2.1.0", + "case-anything": "^2.1.10", + "dash-get": "^1.0.2", + "deepmerge": "^4.3.1", + "fast-deep-equal": "^3.1.3", + "make-error": "^1.3.6", + "object.omit": "^3.0.0", + "object.pick": "^1.3.0", + "throttle-debounce": "^3.0.1" + } + }, + "node_modules/@remirror/core-helpers/node_modules/throttle-debounce": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", + "integrity": "sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@remirror/types": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@remirror/types/-/types-1.0.1.tgz", + "integrity": "sha512-VlZQxwGnt1jtQ18D6JqdIF+uFZo525WEqrfp9BOc3COPpK4+AWCgdnAWL+ho6imWcoINlGjR/+3b6y5C1vBVEA==", + "dependencies": { + "type-fest": "^2.19.0" + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.0.tgz", @@ -6941,6 +7062,411 @@ "@testing-library/dom": ">=7.21.4" } }, + "node_modules/@tiptap/core": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.0.3.tgz", + "integrity": "sha512-jLyVIWAdjjlNzrsRhSE2lVL/7N8228/1R1QtaVU85UlMIwHFAcdzhD8FeiKkqxpTnGpaDVaTy7VNEtEgaYdCyA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-blockquote": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-2.0.3.tgz", + "integrity": "sha512-rkUcFv2iL6f86DBBHoa4XdKNG2StvkJ7tfY9GoMpT46k3nxOaMTqak9/qZOo79TWxMLYtXzoxtKIkmWsbbcj4A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-2.0.3.tgz", + "integrity": "sha512-OGT62fMRovSSayjehumygFWTg2Qn0IDbqyMpigg/RUAsnoOI2yBZFVrdM2gk1StyoSay7gTn2MLw97IUfr7FXg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.3.tgz", + "integrity": "sha512-lPt1ELrYCuoQrQEUukqjp9xt38EwgPUwaKHI3wwt2Rbv+C6q1gmRsK1yeO/KqCNmFxNqF2p9ZF9srOnug/RZDQ==", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-bullet-list": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-2.0.3.tgz", + "integrity": "sha512-RtaLiRvZbMTOje+FW5bn+mYogiIgNxOm065wmyLPypnTbLSeHeYkoqVSqzZeqUn+7GLnwgn1shirUe6csVE/BA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-2.0.3.tgz", + "integrity": "sha512-LsVCKVxgBtkstAr1FjxN8T3OjlC76a2X8ouoZpELMp+aXbjqyanCKzt+sjjUhE4H0yLFd4v+5v6UFoCv4EILiw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-code-block": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-2.0.3.tgz", + "integrity": "sha512-F4xMy18EwgpyY9f5Te7UuF7UwxRLptOtCq1p2c2DfxBvHDWhAjQqVqcW/sq/I/WuED7FwCnPLyyAasPiVPkLPw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-document": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-2.0.3.tgz", + "integrity": "sha512-PsYeNQQBYIU9ayz1R11Kv/kKNPFNIV8tApJ9pxelXjzcAhkjncNUazPN/dyho60mzo+WpsmS3ceTj/gK3bCtWA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-dropcursor": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-2.0.3.tgz", + "integrity": "sha512-McthMrfusn6PjcaynJLheZJcXto8TaIW5iVitYh8qQrDXr31MALC/5GvWuiswmQ8bAXiWPwlLDYE/OJfwtggaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.3.tgz", + "integrity": "sha512-zN1vRGRvyK3pO2aHRmQSOTpl4UJraXYwKYM009n6WviYKUNm0LPGo+VD4OAtdzUhPXyccnlsTv2p6LIqFty6Bg==", + "dependencies": { + "tippy.js": "^6.3.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-gapcursor": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-2.0.3.tgz", + "integrity": "sha512-6I9EzzsYOyyqDvDvxIK6Rv3EXB+fHKFj8ntHO8IXmeNJ6pkhOinuXVsW6Yo7TcDYoTj4D5I2MNFAW2rIkgassw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-2.0.3.tgz", + "integrity": "sha512-RCln6ARn16jvKTjhkcAD5KzYXYS0xRMc0/LrHeV8TKdCd4Yd0YYHe0PU4F9gAgAfPQn7Dgt4uTVJLN11ICl8sQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-heading": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-2.0.3.tgz", + "integrity": "sha512-f0IEv5ms6aCzL80WeZ1qLCXTkRVwbpRr1qAETjg3gG4eoJN18+lZNOJYpyZy3P92C5KwF2T3Av00eFyVLIbb8Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-history": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-history/-/extension-history-2.0.3.tgz", + "integrity": "sha512-00KHIcJ8kivn2ARI6NQYphv2LfllVCXViHGm0EhzDW6NQxCrriJKE3tKDcTFCu7LlC5doMpq9Z6KXdljc4oVeQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-2.0.3.tgz", + "integrity": "sha512-SZRUSh07b/M0kJHNKnfBwBMWrZBEm/E2LrK1NbluwT3DBhE+gvwiEdBxgB32zKHNxaDEXUJwUIPNC3JSbKvPUA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-2.0.3.tgz", + "integrity": "sha512-cfS5sW0gu7qf4ihwnLtW/QMTBrBEXaT0sJl3RwkhjIBg/65ywJKE5Nz9ewnQHmDeT18hvMJJ1VIb4j4ze9jj9A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-2.0.3.tgz", + "integrity": "sha512-H72tXQ5rkVCkAhFaf08fbEU7EBUCK0uocsqOF+4th9sOlrhfgyJtc8Jv5EXPDpxNgG5jixSqWBo0zKXQm9s9eg==", + "dependencies": { + "linkifyjs": "^4.1.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-list-item": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-2.0.3.tgz", + "integrity": "sha512-p7cUsk0LpM1PfdAuFE8wYBNJ3gvA0UhNGR08Lo++rt9UaCeFLSN1SXRxg97c0oa5+Ski7SrCjIJ5Ynhz0viTjQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-mention": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-2.0.3.tgz", + "integrity": "sha512-mT+tMJyf15gN3kW7UfZrP+J0jlhlBnR50SHj0PnDWqGnJ70qKSZTxcHfohrxU6On6yaOFsd+5Omn5seGK4XFWA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0", + "@tiptap/suggestion": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-ordered-list": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.0.3.tgz", + "integrity": "sha512-ZB3MpZh/GEy1zKgw7XDQF4FIwycZWNof1k9WbDZOI063Ch4qHZowhVttH2mTCELuyvTMM/o9a8CS7qMqQB48bw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-2.0.3.tgz", + "integrity": "sha512-a+tKtmj4bU3GVCH1NE8VHWnhVexxX5boTVxsHIr4yGG3UoKo1c5AO7YMaeX2W5xB5iIA+BQqOPCDPEAx34dd2A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-2.0.3.tgz", + "integrity": "sha512-RO4/EYe2iPD6ifDHORT8fF6O9tfdtnzxLGwZIKZXnEgtweH+MgoqevEzXYdS+54Wraq4TUQGNcsYhe49pv7Rlw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-2.0.3.tgz", + "integrity": "sha512-LvzChcTCcPSMNLUjZe/A9SHXWGDHtvk73fR7CBqAeNU0MxhBPEBI03GFQ6RzW3xX0CmDmjpZoDxFMB+hDEtW1A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/extension-youtube": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/extension-youtube/-/extension-youtube-2.0.3.tgz", + "integrity": "sha512-iZsMr+88I3hvfbJNLmiPsz2/8ZGpMucyCxRbrZGg1D6wBw4oiUhRPHzGJ3APlECzpanCjyQNMHIk/gvSDDX3ig==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/pm": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.0.3.tgz", + "integrity": "sha512-I9dsInD89Agdm1QjFRO9dmJtU1ldVSILNPW0pEhv9wYqYVvl4HUj/JMtYNqu2jWrCHNXQcaX/WkdSdvGJtmg5g==", + "dependencies": { + "prosemirror-changeset": "^2.2.0", + "prosemirror-collab": "^1.3.0", + "prosemirror-commands": "^1.3.1", + "prosemirror-dropcursor": "^1.5.0", + "prosemirror-gapcursor": "^1.3.1", + "prosemirror-history": "^1.3.0", + "prosemirror-inputrules": "^1.2.0", + "prosemirror-keymap": "^1.2.0", + "prosemirror-markdown": "^1.10.1", + "prosemirror-menu": "^1.2.1", + "prosemirror-model": "^1.18.1", + "prosemirror-schema-basic": "^1.2.0", + "prosemirror-schema-list": "^1.2.2", + "prosemirror-state": "^1.4.1", + "prosemirror-tables": "^1.3.0", + "prosemirror-trailing-node": "^2.0.2", + "prosemirror-transform": "^1.7.0", + "prosemirror-view": "^1.28.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0" + } + }, + "node_modules/@tiptap/react": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-2.0.3.tgz", + "integrity": "sha512-fiAh8Lk+/NBPAR/PE4Kc/aLiBUbUYI/CpAopz8DI9eInNyV8h8LAGa9uFILJQF/TNu0tclJ4rV0sWc7Se0FZMw==", + "dependencies": { + "@tiptap/extension-bubble-menu": "^2.0.3", + "@tiptap/extension-floating-menu": "^2.0.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0", + "@tiptap/pm": "^2.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.0.3.tgz", + "integrity": "sha512-t4WG4w93zTpL2VxhVyJJvl3kdLF001ZrhpOuEiZqEMBMUMbM56Uiigv1CnUQpTFrjDAh3IM8hkqzAh20TYw2iQ==", + "dependencies": { + "@tiptap/core": "^2.0.3", + "@tiptap/extension-blockquote": "^2.0.3", + "@tiptap/extension-bold": "^2.0.3", + "@tiptap/extension-bullet-list": "^2.0.3", + "@tiptap/extension-code": "^2.0.3", + "@tiptap/extension-code-block": "^2.0.3", + "@tiptap/extension-document": "^2.0.3", + "@tiptap/extension-dropcursor": "^2.0.3", + "@tiptap/extension-gapcursor": "^2.0.3", + "@tiptap/extension-hard-break": "^2.0.3", + "@tiptap/extension-heading": "^2.0.3", + "@tiptap/extension-history": "^2.0.3", + "@tiptap/extension-horizontal-rule": "^2.0.3", + "@tiptap/extension-italic": "^2.0.3", + "@tiptap/extension-list-item": "^2.0.3", + "@tiptap/extension-ordered-list": "^2.0.3", + "@tiptap/extension-paragraph": "^2.0.3", + "@tiptap/extension-strike": "^2.0.3", + "@tiptap/extension-text": "^2.0.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/suggestion": { + "version": "2.0.0-beta.91", + "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.0.0-beta.91.tgz", + "integrity": "sha512-G3TGwEsDadDL9eqHBI6PI3wczneEQ/P3JFeAvsPuSLlAo4ub9hXjoQz5KWwblLIH1xEQ0TgCPzITsdiF/e3XdQ==", + "dependencies": { + "prosemirror-model": "^1.16.1", + "prosemirror-state": "^1.3.4", + "prosemirror-view": "^1.23.6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.0.0-beta.1" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -7041,6 +7567,15 @@ "integrity": "sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==", "dev": true }, + "node_modules/@types/dompurify": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.2.tgz", + "integrity": "sha512-YBL4ziFebbbfQfH5mlC+QTJsvh0oJUrWbmxKMyEdL7emlHJqGR2Qb34TEFKj+VCayBvjKy3xczMFNhugThUsfQ==", + "dev": true, + "dependencies": { + "@types/trusted-types": "*" + } + }, "node_modules/@types/ejs": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.2.tgz", @@ -7269,6 +7804,16 @@ "integrity": "sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ==", "dev": true }, + "node_modules/@types/object.omit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/object.omit/-/object.omit-3.0.0.tgz", + "integrity": "sha512-I27IoPpH250TUzc9FzXd0P1BV/BMJuzqD3jOz98ehf9dQqGkxlq+hO1bIqZGWqCg5bVOy0g4AUVJtnxe0klDmw==" + }, + "node_modules/@types/object.pick": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/object.pick/-/object.pick-1.3.2.tgz", + "integrity": "sha512-sn7L+qQ6RLPdXRoiaE7bZ/Ek+o4uICma/lBFPyJEKDTPTBP1W8u0c4baj3EiS4DiqLs+Hk+KUGvMVJtAw3ePJg==" + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -7389,6 +7934,17 @@ "@types/node": "*" } }, + "node_modules/@types/throttle-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz", + "integrity": "sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz", + "integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==", + "dev": true + }, "node_modules/@types/unist": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", @@ -8395,6 +8951,26 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/babel-merge": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/babel-merge/-/babel-merge-3.0.0.tgz", + "integrity": "sha512-eBOBtHnzt9xvnjpYNI5HmaPp/b2vMveE5XggzqHnQeHJ8mFIBrBv6WZEVIj5jJ2uwTItkqKo9gWzEEcBxEq0yw==", + "dependencies": { + "deepmerge": "^2.2.1", + "object.omit": "^3.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-merge/node_modules/deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/babel-plugin-add-react-displayname": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz", @@ -9078,6 +9654,17 @@ } ] }, + "node_modules/case-anything": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz", + "integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -9955,6 +10542,11 @@ "sha.js": "^2.4.8" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "node_modules/cross-fetch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", @@ -10195,6 +10787,11 @@ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", "dev": true }, + "node_modules/dash-get": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dash-get/-/dash-get-1.0.2.tgz", + "integrity": "sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==" + }, "node_modules/date-fns": { "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", @@ -10577,6 +11174,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.4.tgz", + "integrity": "sha512-ae0mA+Qiqp6C29pqZX3fQgK+F91+F7wobM/v8DRzDqJdZJELXiFUx4PP4pK/mzUS0xkiSEx3Ncd9gr69jg3YsQ==" + }, "node_modules/domutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", @@ -13572,6 +14174,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -13910,7 +14534,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -14712,6 +15335,11 @@ "uc.micro": "^1.0.1" } }, + "node_modules/linkifyjs": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.1.tgz", + "integrity": "sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA==" + }, "node_modules/loader-runner": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", @@ -14912,6 +15540,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -14927,6 +15560,45 @@ "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", "dev": true }, + "node_modules/markdown-it": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-13.0.1.tgz", + "integrity": "sha512-lTlxriVoy2criHP0JKRhO2VDG9c2ypWCsT237eDiLqi09rmbKoUetyGHq2uOIRoRS//kfoJckS0eUzzkDR+k2Q==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~3.0.1", + "linkify-it": "^4.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/markdown-it/node_modules/linkify-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", + "integrity": "sha512-C7bfi1UZmoj8+PQx22XyeXCuBlokoyWQL5pWSP+EI6nzRylyThouddufc2c1NDIcP9k5agmN9fLpA7VNJfIiqw==", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, "node_modules/markdown-table": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", @@ -15186,6 +15858,11 @@ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -16664,6 +17341,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.omit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz", + "integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==", + "dependencies": { + "is-extendable": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object.values": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", @@ -16875,6 +17574,11 @@ "node": ">=8" } }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" + }, "node_modules/os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -17513,6 +18217,195 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/prosemirror-changeset": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.2.1.tgz", + "integrity": "sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.5.2.tgz", + "integrity": "sha512-hgLcPaakxH8tu6YvVAaILV2tXYsW3rAdDR8WNkeKGcgeMVQg3/TMhPdVoh7iAmfgVjZGtcOSjKiQaoeKjzd2mQ==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz", + "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", + "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.3.2.tgz", + "integrity": "sha512-/zm0XoU/N/+u7i5zepjmZAEnpvjDtzoPWW6VmKptcAnPadN/SStsBjMImdCEbb3seiNTpveziPTIrXQbHLtU1g==", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.2.1.tgz", + "integrity": "sha512-3LrWJX1+ULRh5SZvbIQlwZafOXqp1XuV21MGBu/i5xsztd+9VD15x6OtN6mdqSFI7/8Y77gYUbQ6vwwJ4mr6QQ==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz", + "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.11.1.tgz", + "integrity": "sha512-CLOieKoaSSEusKyYcXIj8v2qHGLW+tnuffci+8678Sen48NEFQE7M3o0Nx0gj9v63iVDj+yLibj2gCe8eb3jIw==", + "dependencies": { + "markdown-it": "^13.0.1", + "prosemirror-model": "^1.0.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.2.tgz", + "integrity": "sha512-437HIWTq4F9cTX+kPfqZWWm+luJm95Aut/mLUy+9OMrOml0bmWDS26ceC6SNfb2/S94et1sZ186vLO7pDHzxSw==", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.19.2.tgz", + "integrity": "sha512-RXl0Waiss4YtJAUY3NzKH0xkJmsZupCIccqcIFoLTIKFlKNbIvFDRl27/kQy1FP8iUAxrjRRfIVvOebnnXJgqQ==", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.2.tgz", + "integrity": "sha512-/dT4JFEGyO7QnNTe9UaKUhjDXbTNkiWTq/N4VpKaF79bBjSExVV2NXmJpcM7z/gD7mbqNjxbmWW5nf1iNSSGnw==", + "dependencies": { + "prosemirror-model": "^1.19.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.3.0.tgz", + "integrity": "sha512-Hz/7gM4skaaYfRPNgr421CU4GSwotmEwBVvJh5ltGiffUJwm7C8GfN/Bc6DR1EKEp5pDKhODmdXXyi9uIsZl5A==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", + "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.3.4.tgz", + "integrity": "sha512-z6uLSQ1BLC3rgbGwZmpfb+xkdvD7W/UOsURDfognZFYaTtc0gsk7u/t71Yijp2eLflVpffMk6X0u0+u+MMDvIw==", + "dependencies": { + "prosemirror-keymap": "^1.1.2", + "prosemirror-model": "^1.8.1", + "prosemirror-state": "^1.3.1", + "prosemirror-transform": "^1.2.1", + "prosemirror-view": "^1.13.3" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.4.tgz", + "integrity": "sha512-0Yl9w7IdHkaCdqR+NE3FOucePME4OmiGcybnF1iasarEILP5U8+4xTnl53yafULjmwcg1SrSG65Hg7Zk2H2v3g==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@remirror/core-constants": "^2.0.1", + "@remirror/core-helpers": "^2.0.2", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.19.0", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.30.2" + } + }, + "node_modules/prosemirror-trailing-node/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.7.3.tgz", + "integrity": "sha512-qDapyx5lqYfxVeUWEw0xTGgeP2S8346QtE7DxkalsXlX89lpzkY6GZfulgfHyk1n4tf74sZ7CcXgcaCcGjsUtA==", + "dependencies": { + "prosemirror-model": "^1.0.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.31.5", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.31.5.tgz", + "integrity": "sha512-tobRCDeCp61elR1d97XE/JTL9FDIfswZpWeNs7GKJjAJvWyMGHWYFCq29850p6bbG2bckP+i9n1vT56RifosbA==", + "dependencies": { + "prosemirror-model": "^1.16.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -18653,6 +19546,11 @@ "inherits": "^2.0.1" } }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -19958,6 +20856,14 @@ "node": ">=0.6.0" } }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/tlds": { "version": "1.238.0", "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.238.0.tgz", @@ -20187,7 +21093,6 @@ "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, "engines": { "node": ">=12.20" }, @@ -20777,6 +21682,11 @@ "node": ">=0.10.0" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index e791144c..e3d72855 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,20 @@ "@radix-ui/react-toggle-group": "^1.0.1", "@radix-ui/react-tooltip": "^1.0.3", "@svgr/webpack": "^6.2.0", + "@tiptap/extension-bubble-menu": "^2.0.3", + "@tiptap/extension-link": "^2.0.3", + "@tiptap/extension-mention": "^2.0.3", + "@tiptap/extension-youtube": "^2.0.3", + "@tiptap/pm": "^2.0.3", + "@tiptap/react": "^2.0.3", + "@tiptap/starter-kit": "^2.0.3", + "@tiptap/suggestion": "2.0.0-beta.91", "axios": "^0.25.0", "classnames": "^2.3.1", "cmdk": "^0.2.0", "cookies-next": "^2.1.1", "date-fns": "^2.29.3", + "dompurify": "^3.0.4", "fast-deep-equal": "^3.1.3", "fix-date": "^1.1.6", "i18next": "^21.6.13", @@ -57,6 +66,7 @@ "resolve-url-loader": "^5.0.0", "sanitize-html": "^2.8.1", "sass": "^1.61.0", + "tippy.js": "^6.3.7", "usehooks-ts": "^2.9.1", "uuid": "^9.0.0", "valtio": "^1.3.0", @@ -72,6 +82,7 @@ "@storybook/nextjs": "latest", "@storybook/react": "latest", "@storybook/testing-library": "latest", + "@types/dompurify": "^3.0.2", "@types/lodash.clonedeep": "^4.5.6", "@types/lodash.debounce": "^4.0.6", "@types/node": "17.0.11", @@ -90,5 +101,10 @@ "sass-loader": "^13.2.2", "storybook": "latest", "typescript": "^4.5.5" + }, + "overrides": { + "@tiptap/extension-mention": { + "@tiptap/suggestion": "2.0.0-beta.91" + } } } diff --git a/public/locales/en/common.json b/public/locales/en/common.json index a64abe28..149ded99 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -487,6 +487,7 @@ "start_typing": "Start typing the name of a {{object}}", "min_length": "Type at least 3 characters", "no_results": "No results found for '{{query}}'", + "no_results_generic": "No results found", "end_results": "No more results" }, "placeholders": { diff --git a/public/locales/ja/common.json b/public/locales/ja/common.json index 01dd00e1..94169382 100644 --- a/public/locales/ja/common.json +++ b/public/locales/ja/common.json @@ -485,6 +485,7 @@ "start_typing": "{{object}}名を入力してください", "min_length": "3文字以上を入力してください", "no_results": "'{{query}}'の検索結果が見つかりませんでした", + "no_results_generic": "検索結果が見つかりませんでした", "end_results": "検索結果これ以上ありません" }, "placeholders": { diff --git a/styles/globals.scss b/styles/globals.scss index 45cc6507..a32a947a 100644 --- a/styles/globals.scss +++ b/styles/globals.scss @@ -4,7 +4,7 @@ html { background-color: var(--background); font-size: 62.5%; - height: 100%; + // height: 100%; } body { diff --git a/styles/themes.scss b/styles/themes.scss index 4edee375..6ebab807 100644 --- a/styles/themes.scss +++ b/styles/themes.scss @@ -44,6 +44,25 @@ --menu-bg-item-hover: #{$menu--item--bg--light--hover}; --menu-text-hover: #{$menu--text--light--hover}; + // Light - Formatting menu + --formatting-menu-bg: #{$formatting--menu--bg--light}; + --formatting-menu-item-bg: #{$formatting--menu--item--bg--light}; + --formatting-menu-item-bg-hover: #{$formatting--menu--item--bg--light--hover}; + --formatting-menu-item-bg-active: #{$formatting--menu--item--bg--dark--active}; + --formatting-menu-item-text: #{$formatting--menu--item--text--light}; + --formatting-menu-item-text-hover: #{$formatting--menu--item--text--light--hover}; + --formatting-menu-item-text-active: #{$formatting--menu--item--text--dark--active}; + + // Light - Toolbar + --toolbar-bg: #{$toolbar--bg--light}; + --toolbar-divider-bg: #{$toolbar--divider--bg--light}; + --toolbar-item-bg: #{$toolbar--item--bg--light}; + --toolbar-item-bg-hover: #{$toolbar--item--bg--light--hover}; + --toolbar-item-bg-active: #{$toolbar--item--bg--light--active}; + --toolbar-item-text: #{$toolbar--item--text--light}; + --toolbar-item-text-hover: #{$toolbar--item--text--light--hover}; + --toolbar-item-text-active: #{$toolbar--item--text--light--active}; + // Light - Placeholders --placeholder-bound-bg: #{$placeholder--bound--bg--light}; --placeholder-bound-bg-hover: #{$placeholder--bound--bg--light--hover}; @@ -139,36 +158,48 @@ --wind-text: #{$wind--text--light}; --wind-raid-text: #{$wind--text--raid--light}; --wind-text-hover: #{$wind--text--hover--light}; + --wind-shadow: #{$wind--shadow--light}; + --wind-shadow-hover: #{$wind--shadow--light--hover}; --fire-bg: #{$fire--bg--light}; --fire-bg-hover: #{$fire--bg--hover--light}; --fire-text: #{$fire--text--light}; --fire-raid-text: #{$fire--text--raid--light}; --fire-text-hover: #{$fire--text--hover--light}; + --fire-shadow: #{$fire--shadow--light}; + --fire-shadow-hover: #{$fire--shadow--light--hover}; --water-bg: #{$water--bg--light}; --water-bg-hover: #{$water--bg--hover--light}; --water-text: #{$water--text--light}; --water-raid-text: #{$water--text--raid--light}; --water-text-hover: #{$water--text--hover--light}; + --water-shadow: #{$water--shadow--light}; + --water-shadow-hover: #{$water--shadow--light--hover}; --earth-bg: #{$earth--bg--light}; --earth-bg-hover: #{$earth--bg--hover--light}; --earth-text: #{$earth--text--light}; --earth-raid-text: #{$earth--text--raid--light}; --earth-text-hover: #{$earth--text--hover--light}; + --earth-shadow: #{$earth--shadow--light}; + --earth-shadow-hover: #{$earth--shadow--light--hover}; --dark-bg: #{$dark--bg--light}; --dark-bg-hover: #{$dark--bg--hover--light}; --dark-text: #{$dark--text--light}; --dark-raid-text: #{$dark--text--raid--light}; --dark-text-hover: #{$dark--text--hover--light}; + --dark-shadow: #{$dark--shadow--light}; + --dark-shadow-hover: #{$dark--shadow--light--hover}; --light-bg: #{$light--bg--light}; --light-bg-hover: #{$light--bg--hover--light}; --light-text: #{$light--text--light}; --light-raid-text: #{$light--text--raid--light}; --light-text-hover: #{$light--text--hover--light}; + --light-shadow: #{$light--shadow--light}; + --light-shadow-hover: #{$light--shadow--light--hover}; // Gradients --hero-gradient: #{$hero--gradient--light}; @@ -221,6 +252,25 @@ --menu-bg-item-hover: #{$menu--item--bg--dark--hover}; --menu-text-hover: #{$menu--text--dark--hover}; + // Dark - Formatting menu + --formatting-menu-bg: #{$formatting--menu--bg--dark}; + --formatting-menu-item-bg: #{$formatting--menu--item--bg--dark}; + --formatting-menu-item-bg-hover: #{$formatting--menu--item--bg--dark--hover}; + --formatting-menu-item-bg-active: #{$formatting--menu--item--bg--dark--active}; + --formatting-menu-item-text: #{$formatting--menu--item--text--dark}; + --formatting-menu-item-text-hover: #{$formatting--menu--item--text--dark--hover}; + --formatting-menu-item-text-active: #{$formatting--menu--item--text--dark--active}; + + // Dark - Toolbar + --toolbar-bg: #{$toolbar--bg--dark}; + --toolbar-divider-bg: #{$toolbar--divider--bg--dark}; + --toolbar-item-bg: #{$toolbar--item--bg--dark}; + --toolbar-item-bg-hover: #{$toolbar--item--bg--dark--hover}; + --toolbar-item-bg-active: #{$toolbar--item--bg--dark--active}; + --toolbar-item-text: #{$toolbar--item--text--dark}; + --toolbar-item-text-hover: #{$toolbar--item--text--dark--hover}; + --toolbar-item-text-active: #{$toolbar--item--text--dark--active}; + // Dark - Placeholders --placeholder-bound-bg: #{$placeholder--bound--bg--dark}; --placeholder-bound-bg-hover: #{$placeholder--bound--bg--dark--hover}; @@ -316,36 +366,48 @@ --wind-text: #{$wind--text--dark}; --wind-raid-text: #{$wind--text--raid--dark}; --wind-text-hover: #{$wind--text--hover--dark}; + --wind-shadow: #{$wind--shadow--dark}; + --wind-shadow-hover: #{$wind--shadow--dark--hover}; --fire-bg: #{$fire--bg--dark}; --fire-bg-hover: #{$fire--bg--hover--dark}; --fire-text: #{$fire--text--dark}; --fire-raid-text: #{$fire--text--raid--dark}; --fire-text-hover: #{$fire--text--hover--dark}; + --fire-shadow: #{$fire--shadow--dark}; + --fire-shadow-hover: #{$fire--shadow--dark--hover}; --water-bg: #{$water--bg--dark}; --water-bg-hover: #{$water--bg--hover--dark}; --water-text: #{$water--text--dark}; --water-raid-text: #{$water--text--raid--dark}; --water-text-hover: #{$water--text--hover--dark}; + --water-shadow: #{$water--shadow--dark}; + --water-shadow-hover: #{$water--shadow--dark--hover}; --earth-bg: #{$earth--bg--dark}; --earth-bg-hover: #{$earth--bg--hover--dark}; --earth-text: #{$earth--text--dark}; --earth-raid-text: #{$earth--text--raid--dark}; --earth-text-hover: #{$earth--text--hover--dark}; + --earth-shadow: #{$earth--shadow--dark}; + --earth-shadow-hover: #{$earth--shadow--dark--hover}; --dark-bg: #{$dark--bg--dark}; --dark-bg-hover: #{$dark--bg--hover--dark}; --dark-text: #{$dark--text--dark}; --dark-raid-text: #{$dark--text--raid--dark}; --dark-text-hover: #{$dark--text--hover--dark}; + --dark-shadow: #{$dark--shadow--dark}; + --dark-shadow-hover: #{$dark--shadow--dark--hover}; --light-bg: #{$light--bg--dark}; --light-bg-hover: #{$light--bg--hover--dark}; --light-text: #{$light--text--dark}; --light-raid-text: #{$light--text--raid--dark}; --light-text-hover: #{$light--text--hover--dark}; + --light-shadow: #{$light--shadow--dark}; + --light-shadow-hover: #{$light--shadow--dark--hover}; // Gradients --hero-gradient: #{$hero--gradient--dark}; diff --git a/styles/variables.scss b/styles/variables.scss index fa7f7242..2b9ccd41 100644 --- a/styles/variables.scss +++ b/styles/variables.scss @@ -336,6 +336,53 @@ $pill--bg--dark--hover: $grey-50; $pill--text--dark: $grey-100; $pill--text--dark--hover: $grey-00; +// Color Definitions: Formatting menu +$formatting--menu--bg--light: $grey-30; +$formatting--menu--bg--dark: $grey-10; + +$formatting--menu--item--bg--light: $grey-30; +$formatting--menu--item--bg--dark: $grey-20; + +$formatting--menu--item--bg--light--hover: $grey-40; +$formatting--menu--item--bg--dark--hover: $grey-30; + +$formatting--menu--item--bg--light--active: $grey-50; +$formatting--menu--item--bg--dark--active: $grey-40; + +$formatting--menu--item--text--light: $grey-100; +$formatting--menu--item--text--dark: $grey-00; + +$formatting--menu--item--text--light--hover: $grey-100; +$formatting--menu--item--text--dark--hover: $grey-00; + +$formatting--menu--item--text--light--active: $grey-100; +$formatting--menu--item--text--dark--active: $grey-00; + +// Color Definitions: Toolbar +$toolbar--bg--light: $grey-75; +$toolbar--bg--dark: $grey-10; + +$toolbar--divider--bg--light: $grey-70; +$toolbar--divider--bg--dark: $grey-20; + +$toolbar--item--bg--light: $grey-75; +$toolbar--item--bg--dark: $grey-20; + +$toolbar--item--bg--light--hover: $grey-70; +$toolbar--item--bg--dark--hover: $grey-30; + +$toolbar--item--bg--light--active: $accent--blue--light; +$toolbar--item--bg--dark--active: $accent--blue--dark; + +$toolbar--item--text--light: $grey-40; +$toolbar--item--text--dark: $grey-80; + +$toolbar--item--text--light--hover: $grey-30; +$toolbar--item--text--dark--hover: $grey-90; + +$toolbar--item--text--light--active: $grey-100; +$toolbar--item--text--dark--active: $grey-00; + // Color Definitions: Element Toggle $toggle--bg--light: $grey-90; $toggle--bg--dark: $grey-15; @@ -390,6 +437,12 @@ $wind--text--raid--dark: $wind-bg-10; $wind--text--hover--light: $wind-text-00; $wind--text--hover--dark: $wind-text-00; +$wind--shadow--light: fade-out($wind-text-20, 0.3); +$wind--shadow--dark: fade-out($wind-text-20, 0.3); + +$wind--shadow--light--hover: fade-out($wind-text-00, 0.3); +$wind--shadow--dark--hover: fade-out($wind-text-00, 0.3); + // Color Definitions: Element / Fire $fire--bg--light: $fire-bg-10; $fire--bg--dark: $fire-bg-10; @@ -406,6 +459,12 @@ $fire--text--raid--dark: $fire-bg-10; $fire--text--hover--light: $fire-text-00; $fire--text--hover--dark: $fire-text-00; +$fire--shadow--light: fade-out($fire-text-20, 0.3); +$fire--shadow--dark: fade-out($fire-text-20, 0.3); + +$fire--shadow--light--hover: fade-out($fire-text-00, 0.3); +$fire--shadow--dark--hover: fade-out($fire-text-00, 0.3); + // Color Definitions: Element / Water $water--bg--light: $water-bg-10; $water--bg--dark: $water-bg-10; @@ -422,6 +481,12 @@ $water--text--raid--dark: $water-bg-10; $water--text--hover--light: $water-text-00; $water--text--hover--dark: $water-text-00; +$water--shadow--light: fade-out($water-text-20, 0.3); +$water--shadow--dark: fade-out($water-text-20, 0.3); + +$water--shadow--light--hover: fade-out($water-text-00, 0.3); +$water--shadow--dark--hover: fade-out($water-text-00, 0.3); + // Color Definitions: Element / Earth $earth--bg--light: $earth-bg-10; $earth--bg--dark: $earth-bg-10; @@ -438,6 +503,12 @@ $earth--text--raid--dark: $earth-bg-10; $earth--text--hover--light: $earth-text-00; $earth--text--hover--dark: $earth-text-00; +$earth--shadow--light: fade-out($earth-text-20, 0.3); +$earth--shadow--dark: fade-out($earth-text-20, 0.3); + +$earth--shadow--light--hover: fade-out($earth-text-00, 0.3); +$earth--shadow--dark--hover: fade-out($earth-text-00, 0.3); + // Color Definitions: Element / Dark $dark--bg--light: $dark-bg-10; $dark--bg--dark: $dark-bg-10; @@ -454,6 +525,12 @@ $dark--text--raid--dark: $dark-bg-10; $dark--text--hover--light: $dark-text-00; $dark--text--hover--dark: $dark-text-00; +$dark--shadow--light: fade-out($dark-text-20, 0.3); +$dark--shadow--dark: fade-out($dark-text-20, 0.3); + +$dark--shadow--light--hover: fade-out($dark-text-00, 0.3); +$dark--shadow--dark--hover: fade-out($dark-text-00, 0.3); + // Color Definitions: Element / Light $light--bg--light: $light-bg-10; $light--bg--dark: $light-bg-10; @@ -470,6 +547,12 @@ $light--text--raid--dark: $light-bg-10; $light--text--hover--light: $light-text-00; $light--text--hover--dark: $light-text-00; +$light--shadow--light: fade-out($light-text-20, 0.3); +$light--shadow--dark: fade-out($light-text-20, 0.3); + +$light--shadow--light--hover: fade-out($light-text-00, 0.3); +$light--shadow--dark--hover: fade-out($light-text-00, 0.3); + // Font-weight $normal: 400; $medium: 500; @@ -497,6 +580,9 @@ $input-corner: $unit; $item-corner: $unit; $item-corner-small: $unit-half; +$bubble-menu-corner: $unit; +$bubble-menu-item-corner: $unit-half * 1.5; + // Shadows $hover-stroke: 1px solid rgba(0, 0, 0, 0.1); $hover-shadow: rgba(0, 0, 0, 0.08) 0px 0px 14px; diff --git a/types/GranblueElement.d.ts b/types/GranblueElement.d.ts new file mode 100644 index 00000000..fe8c1c93 --- /dev/null +++ b/types/GranblueElement.d.ts @@ -0,0 +1,10 @@ +interface GranblueElement { + [key: string]: any + id: number + weaknessId: number + name: { + en: string + ja: string + } + slug: string +} diff --git a/utils/api.tsx b/utils/api.tsx index bba56760..bd55cd0b 100644 --- a/utils/api.tsx +++ b/utils/api.tsx @@ -69,6 +69,16 @@ class Api { } }) } + + searchAll(query: string, locale: string) { + const resourceUrl = `${this.url}/search` + return axios.post(`${resourceUrl}`, { + search: { + query: query, + locale: locale + } + }) + } check(resource: string, value: string) { const resourceUrl = `${this.url}/check/${resource}` diff --git a/utils/elements.tsx b/utils/elements.tsx new file mode 100644 index 00000000..a7d7d9c3 --- /dev/null +++ b/utils/elements.tsx @@ -0,0 +1,73 @@ +export const elements: GranblueElement[] = [ + { + id: 0, + weaknessId: 0, + name: { + en: 'Null', + ja: '無', + }, + slug: 'null', + }, + { + id: 1, + weaknessId: 2, + name: { + en: 'Wind', + ja: '風', + }, + slug: 'wind', + }, + { + id: 2, + weaknessId: 3, + name: { + en: 'Fire', + ja: '火', + }, + slug: 'fire', + }, + { + id: 3, + weaknessId: 4, + name: { + en: 'Water', + ja: '水', + }, + slug: 'water', + }, + { + id: 4, + weaknessId: 1, + name: { + en: 'Earth', + ja: '土', + }, + slug: 'earth', + }, + { + id: 5, + weaknessId: 5, + name: { + en: 'Dark', + ja: '闇', + }, + slug: 'dark', + }, + { + id: 6, + weaknessId: 6, + name: { + en: 'Light', + ja: '光', + }, + slug: 'light', + }, +] + +export function numberToElement(value: number) { + return elements.find((element) => element.id === value) || elements[0] +} + +export function stringToElement(value: string) { + return elements.find((element) => element.name.en === value) +} diff --git a/utils/mentionSuggestions.tsx b/utils/mentionSuggestions.tsx new file mode 100644 index 00000000..18e4b8a9 --- /dev/null +++ b/utils/mentionSuggestions.tsx @@ -0,0 +1,129 @@ +import { ReactRenderer } from '@tiptap/react' +import { MentionOptions } from '@tiptap/extension-mention' +import { SuggestionKeyDownProps, SuggestionProps } from '@tiptap/suggestion' +import tippy, { Instance as TippyInstance } from 'tippy.js' +import { getCookie } from 'cookies-next' + +import { + MentionList, + MentionRef, + MentionSuggestion, +} from '~components/MentionList' +import api from '~utils/api' +import { numberToElement } from '~utils/elements' +import { get } from 'http' + +interface RawSearchResponse { + searchable_type: string + granblue_id: string + name_en: string + name_jp: string + element: number +} + +interface SearchResponse { + name: { + [key: string]: string + en: string + ja: string + } + type: string + granblue_id: string + element: GranblueElement +} + +function transform(object: RawSearchResponse) { + const result: SearchResponse = { + name: { + en: object.name_en, + ja: object.name_jp, + }, + type: object.searchable_type.toLowerCase(), + granblue_id: object.granblue_id, + element: numberToElement(object.element), + } + return result +} + +export const mentionSuggestionOptions: MentionOptions['suggestion'] = { + items: async ({ query }): Promise => { + const locale = getCookie('NEXT_LOCALE') + ? (getCookie('NEXT_LOCALE') as string) + : 'en' + const response = await api.searchAll(query, locale) + const results = response.data.results + + return results + .map((rawObject: RawSearchResponse, index: number) => { + const object = transform(rawObject) + return { + granblue_id: object.granblue_id, + element: object.element, + type: object.type, + name: { + en: object.name.en, + ja: object.name.ja, + }, + } + }) + .slice(0, 7) + }, + + render: () => { + let component: ReactRenderer | undefined + let popup: TippyInstance | undefined + + return { + onStart: (props) => { + component = new ReactRenderer(MentionList, { + props, + editor: props.editor, + }) + + popup = tippy('body', { + getReferenceClientRect: props.clientRect, + appendTo: () => document.body, + content: component.element, + showOnCreate: true, + interactive: true, + trigger: 'manual', + placement: 'bottom-start', + })[0] + }, + + onUpdate(props) { + component?.updateProps(props) + + popup?.setProps({ + getReferenceClientRect: props.clientRect, + }) + }, + + onKeyDown(props) { + if (props.event.key === 'Escape') { + popup?.hide() + return true + } + + if (!component?.ref) { + return false + } + + return component?.ref.onKeyDown(props) + }, + + onExit() { + popup?.destroy() + component?.destroy() + + // Remove references to the old popup and component upon destruction/exit. + // (This should prevent redundant calls to `popup.destroy()`, which Tippy + // warns in the console is a sign of a memory leak, as the `suggestion` + // plugin seems to call `onExit` both when a suggestion menu is closed after + // a user chooses an option, *and* when the editor itself is destroyed.) + popup = undefined + component = undefined + }, + } + }, +}