| @@ -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> | ||||
| @@ -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 | ||||
| @@ -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>) | ||||
| } | } | ||||
| @@ -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,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 | ||||