Browse Source

#171

feature/171
みてるぞ 2 weeks ago
parent
commit
49c82c626a
5 changed files with 81 additions and 37 deletions
  1. +13
    -5
      frontend/src/components/PostEmbed.tsx
  2. +2
    -3
      frontend/src/components/PostFormTagsArea.tsx
  3. +24
    -11
      frontend/src/components/users/InheritDialogue.tsx
  4. +34
    -16
      frontend/src/components/users/UserCodeDialogue.tsx
  5. +8
    -2
      frontend/src/pages/posts/PostHistoryPage.tsx

+ 13
- 5
frontend/src/components/PostEmbed.tsx View File

@@ -3,6 +3,7 @@ import YoutubeEmbed from 'react-youtube'


import NicoViewer from '@/components/NicoViewer' import NicoViewer from '@/components/NicoViewer'
import TwitterEmbed from '@/components/TwitterEmbed' import TwitterEmbed from '@/components/TwitterEmbed'
import { useDialogue } from '@/components/dialogues/DialogueProvider'


import type { FC, RefObject } from 'react' import type { FC, RefObject } from 'react'


@@ -16,6 +17,8 @@ type Props = {




export default (({ ref, post, onLoadComplete, onMetadataChange }: Props) => { export default (({ ref, post, onLoadComplete, onMetadataChange }: Props) => {
const dialogue = useDialogue ()

const url = new URL (post.url) const url = new URL (post.url)


switch (url.hostname.split ('.').slice (-2).join ('.')) switch (url.hostname.split ('.').slice (-2).join ('.'))
@@ -82,12 +85,17 @@ export default (({ ref, post, onLoadComplete, onMetadataChange }: Props) => {
height={360}/>) height={360}/>)
: ( : (
<div> <div>
<a href="#" onClick={e => {
<a href="#" onClick={async e => {
e.preventDefault () e.preventDefault ()
setFramed (confirm ('未確認の外部ページを表示します。\n'
+ '悪意のあるスクリプトが実行される可能性があります。\n'
+ '表示しますか?'))
return

setFramed (await dialogue.confirm ({
title: '未確認の外部ページを表示します。',
description: (
<div>
<p>悪意のあるスクリプトが実行される可能性があります。</p>
<p>表示しますか?</p>
</div>),
confirmText: '表示' }))
}}> }}>
外部ページを表示 外部ページを表示
</a> </a>


+ 2
- 3
frontend/src/components/PostFormTagsArea.tsx View File

@@ -36,7 +36,7 @@ type Props = Omit<ComponentPropsWithoutRef<'textarea'>, 'value' | 'onChange' | '
setTags: (tags: string) => void } setTags: (tags: string) => void }




export default (({ tags, setTags, onBlur, ...rest }: Props) => {
export default (({ tags, setTags, ...rest }: Props) => {
const ref = useRef<HTMLTextAreaElement> (null) const ref = useRef<HTMLTextAreaElement> (null)


const [bounds, setBounds] = useState<{ start: number; end: number }> ({ start: 0, end: 0 }) const [bounds, setBounds] = useState<{ start: number; end: number }> ({ start: 0, end: 0 })
@@ -85,10 +85,9 @@ export default (({ tags, setTags, onBlur, ...rest }: Props) => {
await recompute (pos) await recompute (pos)
}} }}
onFocus={() => setFocused (true)} onFocus={() => setFocused (true)}
onBlur={ev => {
onBlur={() => {
setFocused (false) setFocused (false)
setSuggestionsVsbl (false) setSuggestionsVsbl (false)
onBlur?.(ev)
}}/> }}/>
{focused && ( {focused && (
<TagSearchBox <TagSearchBox


+ 24
- 11
frontend/src/components/users/InheritDialogue.tsx View File

@@ -1,9 +1,12 @@
import { useState } from 'react' import { useState } from 'react'


import { useDialogue } from '@/components/dialogues/DialogueProvider'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Dialog, import { Dialog,
DialogContent,
DialogTitle } from '@/components/ui/dialog'
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { toast } from '@/components/ui/use-toast' import { toast } from '@/components/ui/use-toast'
import { apiPost } from '@/lib/api' import { apiPost } from '@/lib/api'
@@ -16,10 +19,16 @@ type Props = { visible: boolean




export default ({ visible, onVisibleChange, setUser }: Props) => { export default ({ visible, onVisibleChange, setUser }: Props) => {
const dialogue = useDialogue ()

const [inputCode, setInputCode] = useState ('') const [inputCode, setInputCode] = useState ('')


const handleTransfer = async () => { const handleTransfer = async () => {
if (!(confirm ('引継ぎを行ってもよろしいですか?\n現在のアカウントからはログアウトされます.')))
if (!(await dialogue.confirm ({
title: '引継ぎを行ってもよろしいですか?',
description: '現在のアカウントからはログアウトされます.',
confirmText: '引継ぐ',
variant: 'danger' })))
return return


try try
@@ -44,14 +53,18 @@ export default ({ visible, onVisibleChange, setUser }: Props) => {


return ( return (
<Dialog open={visible} onOpenChange={onVisibleChange}> <Dialog open={visible} onOpenChange={onVisibleChange}>
<DialogContent>
<DialogTitle>ほかのブラウザから引継ぐ</DialogTitle>
<div className="flex gap-2">
<Input placeholder="引継ぎコードを入力"
value={inputCode}
onChange={ev => setInputCode (ev.target.value)}/>
<Button onClick={handleTransfer}>引継ぐ</Button>
</div>
<DialogContent className="px-6 pp-6 pt-7">
<DialogHeader className="pl-8">
<DialogTitle>ほかのブラウザから引継ぐ</DialogTitle>
<DialogDescription asChild>
<div className="flex gap-2">
<Input placeholder="引継ぎコードを入力"
value={inputCode}
onChange={ev => setInputCode (ev.target.value)}/>
<Button onClick={handleTransfer}>引継ぐ</Button>
</div>
</DialogDescription>
</DialogHeader>
</DialogContent> </DialogContent>
</Dialog>) </Dialog>)
} }

+ 34
- 16
frontend/src/components/users/UserCodeDialogue.tsx View File

@@ -1,6 +1,10 @@
import { useDialogue } from '@/components/dialogues/DialogueProvider'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Dialog, import { Dialog,
DialogContent, DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle } from '@/components/ui/dialog' DialogTitle } from '@/components/ui/dialog'
import { toast } from '@/components/ui/use-toast' import { toast } from '@/components/ui/use-toast'
import { apiPost } from '@/lib/api' import { apiPost } from '@/lib/api'
@@ -14,11 +18,20 @@ type Props = { visible: boolean




export default ({ visible, onVisibleChange, user, setUser }: Props) => { export default ({ visible, onVisibleChange, user, setUser }: Props) => {
const dialogue = useDialogue ()

const handleChange = async () => { const handleChange = async () => {
if (!(user)) if (!(user))
return return


if (!(confirm ('引継ぎコードを再発行しますか?\n再発行するとほかのブラウザからはログアウトされます.')))
if (!(await dialogue.confirm ({
title: '引継ぎコードを再発行しますか?',
description: (
<div>
<p>再発行するとほかのブラウザからはログアウトされます.</p>
</div>),
confirmText: '再発行',
variant: 'danger' })))
return return


const data = await apiPost<{ code: string }> ('/users/code/renew', { }, const data = await apiPost<{ code: string }> ('/users/code/renew', { },
@@ -33,21 +46,26 @@ export default ({ visible, onVisibleChange, user, setUser }: Props) => {


return ( return (
<Dialog open={visible} onOpenChange={onVisibleChange}> <Dialog open={visible} onOpenChange={onVisibleChange}>
<DialogContent>
<DialogTitle>引継ぎコード</DialogTitle>
<div>
<p>あなたの引継ぎコードはこちらです:</p>
<div className="m-2">{user?.inheritanceCode}</div>
<p className="mt-1 text-sm text-red-500">
このコードはほかの人には教えないでください!
</p>
<div className="my-4">
<Button onClick={handleChange}
className="px-4 py-2 bg-red-600 text-white rounded disabled:bg-gray-400">
引継ぎコード再発行
</Button>
</div>
</div>
<DialogContent className="px-6 pb-6 pt-7">
<DialogHeader className="pl-8">
<DialogTitle>引継ぎコード</DialogTitle>

<DialogDescription asChild>
<div>
<p>あなたの引継ぎコードはこちらです:</p>
<div className="m-2">{user?.inheritanceCode}</div>
<p className="mt-1 text-sm text-destructive">
このコードはほかの人には教えないでください!
</p>
</div>
</DialogDescription>
</DialogHeader>

<DialogFooter>
<Button onClick={handleChange} variant="destructive">
引継ぎコード再発行
</Button>
</DialogFooter>
</DialogContent> </DialogContent>
</Dialog>) </Dialog>)
} }

+ 8
- 2
frontend/src/pages/posts/PostHistoryPage.tsx View File

@@ -8,6 +8,7 @@ import TagLink from '@/components/TagLink'
import PrefetchLink from '@/components/PrefetchLink' import PrefetchLink from '@/components/PrefetchLink'
import PageTitle from '@/components/common/PageTitle' import PageTitle from '@/components/common/PageTitle'
import Pagination from '@/components/common/Pagination' import Pagination from '@/components/common/Pagination'
import { useDialogue } from '@/components/dialogues/DialogueProvider'
import MainArea from '@/components/layout/MainArea' import MainArea from '@/components/layout/MainArea'
import { toast } from '@/components/ui/use-toast' import { toast } from '@/components/ui/use-toast'
import { SITE_TITLE } from '@/config' import { SITE_TITLE } from '@/config'
@@ -35,6 +36,8 @@ const renderDiff = (diff: { current: string | null; prev: string | null }) => (




export default (() => { export default (() => {
const dialogue = useDialogue ()

const location = useLocation () const location = useLocation ()
const query = new URLSearchParams (location.search) const query = new URLSearchParams (location.search)
const id = query.get ('id') const id = query.get ('id')
@@ -66,8 +69,11 @@ export default (() => {
const handleRevert = async (e: MouseEvent<HTMLAnchorElement>, change: PostVersion) => { const handleRevert = async (e: MouseEvent<HTMLAnchorElement>, change: PostVersion) => {
e.preventDefault () e.preventDefault ()


if (!(confirm (`『${ change.title.current || change.url.current }』を版 ${
change.versionNo } に差戻します.\nよろしいですか?`)))
if (!(await dialogue.confirm ({
title: '差戻の確認',
description: `『${ change.title.current || change.url.current }』を版 ${
change.versionNo } に差戻します.\nよろしいですか?`,
confirmText: '差戻' })))
return return


try try


Loading…
Cancel
Save