Merge branch 'pr/Innei/290' into dev

This commit is contained in:
DIYgod 2023-04-09 23:29:53 +01:00
commit 445cd11b3f
No known key found for this signature in database
5 changed files with 209 additions and 148 deletions

View File

@ -13,6 +13,7 @@
"Site Duration": "サイトの運営期間",
"days": "日",
"Deleted!": "削除されました!",
"Fail to Deleted." : "削除できませんでした。",
"Converted!": "変換されました!",
"Failed to convert.": "変換に失敗しました。",
"All Posts": "すべての記事",
@ -24,7 +25,7 @@
"Scheduled": "予約済み",
"scheduled": "予約済み",
"published and local modified": "公開済みで、ローカルでの編集あり",
"Others on Crossbell": "Crossbellに関するその他の情報",
"Others on Crossbell": "Crossbell に関するその他の情報",
"link post-vs-page": "https://wordpress.com/ja/support/post-vs-page/",
"posts description": "記事は、時間順に表示されます。読者に新しいコンテンツを提供し続けることで、ブログのフォローワが増えるでしょう。<2>記事</2>",
"pages description": "ページは日付に影響を受けることがありません。「私たちについて」「お問い合わせ」などのようにページを活用しましょう。<2>ページ</2>",
@ -54,7 +55,7 @@
"Link": "リンク",
"Image": "画像",
"Upload Image": "画像をアップロード",
"Tip: xLog Flavored Markdown": "ヒントxLog風のマークダウン",
"Tip: xLog Flavored Markdown": "ヒントxLog 風のマークダウン",
"Preview": "プレビュー",
"Publish": "公開",
"Update": "更新",
@ -73,7 +74,7 @@
"Social Platforms": "ソーシャルプラットフォーム",
"Navigation": "ナビゲーション",
"Domains": "ドメイン",
"Custom CSS": "カスタムCSS",
"Custom CSS": "カスタム CSS",
"Operators": "オペレーター",
"Export data": "データのエクスポート",
"Site Settings": "サイト設定",
@ -82,37 +83,37 @@
"Supports both pictures and videos.": "画像と動画の両方に対応しています。",
"Name": "名前",
"Description": "説明",
"Integrate Google Analytics": "Google Analyticsと紐付けします。Measurement IDの検索方法については、<2>こちらの文章</2>をご覧ください。",
"Integrate Google Analytics": "Google Analytics と紐付けします。Measurement ID の検索方法については、<2>こちらの文章</2>をご覧ください。",
"Save": "保存",
"Tips": "ヒント",
"social tips": {
"p1": "紐付けしたプラットフォームの標識は、画面の右下隅に表示されます。",
"p2": "xLogは<2>こちらのプラットフォーム</2>と紐付け可能です。紐付けに成功すると自動的にそのロゴとリンクが表示されます。その他のプラットフォームの場合、デフォルトのロゴが表示されます。もしサポートして欲しいプラットフォームがありましたら、是非IssueあるいはPRを作成して、xLogをより完成度の高いプラットフォームにさせましょう。",
"p3": "<2>xSync</2>を使用して、Twitter、Telegram、Medium、Substackなどのプラットフォームに紐付けし、コンテンツを自動的に同期することもできます。同期設定を行った場合、ここにも表示されます。"
"p2": "xLog は<2>こちらのプラットフォーム</2>と紐付け可能です。(紐付けに成功すると自動的にそのロゴとリンクが表示されます。)その他のプラットフォームの場合、デフォルトのロゴが表示されます。もしサポートして欲しいプラットフォームがありましたら、是非 Issue あるいは PR を作成して、xLog をより完成度の高いプラットフォームにさせましょう。",
"p3": "<2>xSync</2>を使用して、Twitter、Telegram、Medium、Substack などのプラットフォームに紐付けし、コンテンツを自動的に同期することもできます。同期設定を行った場合、ここにも表示されます。"
},
"Platform": "プラットフォーム",
"Identity": "アイデンティティ",
"Remove": "削除",
"New Item": "新しいアイテム",
"xLog provides some out-of-the-box built-in pages": "xLogにはいくつすぐ使えるページテンプレートがあります。",
"xLog provides some out-of-the-box built-in pages": "xLog にはいくつすぐ使えるページテンプレートがあります。",
"Home page": "ホームページ",
"Archives page": "アーカイブページ",
"Tag page": "タグページ",
"NFT Showcase page": "NFTショーケースページ",
"NFT Showcase page": "NFT ショーケースページ",
"Label": "ラベル",
"URL": "URL",
"subdomain": "サブドメイン",
"Custom Domain": "カスタムドメイン",
"Set the following record on your DNS provider to active your custom domain": "カスタムドメインを有効にするには、DNSプロバイダーに次のレコードを設定してください。",
"Set the following record on your DNS provider to active your custom domain": "カスタムドメインを有効にするには、DNS プロバイダーに次のレコードを設定してください。",
"Scope: These styles will be applied to your entire blog, including this dashboard.": "適用範囲:これらのスタイルは、このダッシュボードを含むブログ全体に適用されます。",
"Support": "サポート",
"CSS variables: xLog provides some built-in CSS variables": "CSS変数xLogはいくつかのビルトインCSS変数を提供しています。",
"CSS variables: xLog provides some built-in CSS variables": "CSS 変数xLog はいくつかのビルトイン CSS 変数を提供しています。",
"Address": "住所",
"Character": "キャラクター",
"New operator": "新しいオペレーター",
"Operator Address": "オペレーターアドレス",
"Operator Character Check": "オペレーターキャラクターチェック",
"Warning": "警告",
"Operators have permissions to enter your dashboard, change your settings(excluding xLog subdomain) and post, modify, delete contents on your site.": "シャドー認証では、ダッシュボードへのアクセス、設定の変更xLogサブドメインを除く)、サイトのコンテンツの公開・修正・削除が可能です。",
"Operators have permissions to enter your dashboard, change your settings(excluding xLog subdomain) and post, modify, delete contents on your site.": "シャドー認証では、ダッシュボードへのアクセス、設定の変更xLog サブドメインを除く)、サイトのコンテンツの公開・修正・削除が可能です。",
"Add": "新規"
}

View File

@ -15,6 +15,7 @@
"Site Duration": "網站運作時間",
"days": "天",
"Deleted!": "刪掉了哦!",
"Fail to Deleted." : "删除失败",
"Converted!": "轉換成功!",
"Failed to convert.": "轉換失敗。",
"All Posts": "所有文章",

View File

@ -15,6 +15,7 @@
"Site Duration": "站点运行时间",
"days": "天",
"Deleted!" : "已删除!",
"Fail to Deleted." : "删除失败",
"Converted!" : "已转换!",
"Failed to convert." : "转换失败。",
"All Posts": "所有文章",

View File

@ -1,4 +1,4 @@
import { Fragment, useEffect, useMemo, useState } from "react"
import { FC, Fragment, useEffect, useMemo, useState } from "react"
import { getPageVisibility } from "~/lib/page-helpers"
import { useDate } from "~/hooks/useDate"
import { TabItem, Tabs } from "../ui/Tabs"
@ -25,6 +25,7 @@ import { Tooltip } from "../ui/Tooltip"
import { APP_NAME } from "~/lib/env"
import { Trans, useTranslation } from "next-i18next"
import { readFiles } from "~/lib/read-files"
import { useGetState } from "~/hooks/useGetState"
export const PagesManager: React.FC<{
isPost: boolean
@ -40,38 +41,9 @@ export const PagesManager: React.FC<{
[router.query.visibility],
)
const deletePage = useDeletePage()
const createOrUpdatePage = useCreateOrUpdatePage()
const [convertToastId, setConvertToastId] = useState("")
const [deleteToastId, setDeleteToastId] = useState("")
const { t } = useTranslation(["dashboard", "site"])
const date = useDate()
useEffect(() => {
if (deletePage.isSuccess) {
toast.success(t("Deleted!"), {
id: deleteToastId,
})
}
}, [deletePage.isSuccess, deleteToastId, t])
useEffect(() => {
if (createOrUpdatePage.isSuccess) {
toast.success(t("Converted!"), {
id: convertToastId,
})
} else if (createOrUpdatePage.isError) {
toast.error(t("Failed to convert."), {
id: convertToastId,
})
}
}, [
createOrUpdatePage.isSuccess,
createOrUpdatePage.isError,
convertToastId,
t,
])
const pages = useGetPagesBySite({
type: isPost ? "post" : "page",
site: subdomain!,
@ -125,91 +97,6 @@ export const PagesManager: React.FC<{
}
const queryClient = useQueryClient()
const getPageMenuItems = (page: Note) => {
const isCrossbell = !page.applications?.includes("xlog")
return [
{
text: "Edit",
icon: <span className="i-mingcute:edit-line inline-block"></span>,
onClick() {
router.push(getPageEditLink(page))
},
},
{
text:
"Convert to " +
(isCrossbell
? `${APP_NAME} ${isPost ? "Post" : "Page"}`
: isPost
? "Page"
: "Post"),
icon: <span className="i-mingcute:transfer-3-line inline-block"></span>,
onClick() {
const toastId = toast.loading("Converting...")
if (isCrossbell) {
setConvertToastId(toastId)
createOrUpdatePage.mutate({
published: true,
pageId: page.id,
siteId: subdomain,
tags: page.tags
?.filter((tag) => tag !== "post" && tag !== "page")
?.join(", "),
isPost: isPost,
applications: page.applications,
})
} else {
if (!page.metadata) {
const data = getStorage(`draft-${subdomain}-${page.id}`)
data.isPost = !isPost
setStorage(`draft-${subdomain}-${page.id}`, data)
queryClient.invalidateQueries(["getPagesBySite", subdomain])
queryClient.invalidateQueries(["getPage", page.id])
toast.success("Converted!", {
id: toastId,
})
} else {
setConvertToastId(toastId)
createOrUpdatePage.mutate({
published: true,
pageId: page.id,
siteId: subdomain,
tags: page.tags
?.filter((tag) => tag !== "post" && tag !== "page")
?.join(", "),
isPost: !isPost,
applications: page.applications,
})
}
}
},
},
{
text: "Delete",
icon: <span className="i-mingcute:delete-2-line inline-block"></span>,
onClick() {
if (!page.metadata) {
const toastId = toast.loading("Deleting...")
delStorage(`draft-${subdomain}-${page.id}`)
Promise.all([
queryClient.refetchQueries(["getPagesBySite", subdomain]),
queryClient.refetchQueries(["getPage", page.id]),
]).then(() => {
toast.success("Deleted!", {
id: toastId,
})
})
} else {
setDeleteToastId(toast.loading("Deleting..."))
deletePage.mutate({
site: subdomain,
id: page.id,
})
}
},
},
]
}
const importFile = () => {
const input = document.createElement("input")
@ -365,7 +252,7 @@ export const PagesManager: React.FC<{
</div>
<div className="w-10 flex-shrink-0">
<Menu>
{({ open }: { open: boolean }) => (
{({ open, close }) => (
<>
<Menu.Button as={Fragment}>
<button
@ -380,27 +267,12 @@ export const PagesManager: React.FC<{
<i className="i-mingcute:more-1-line text-2xl" />
</button>
</Menu.Button>
<Menu.Items className="text-sm absolute z-20 right-0 bg-white shadow-modal rounded-lg overflow-hidden py-2 w-64">
{getPageMenuItems(page).map((item) => {
return (
<Menu.Item key={item.text}>
<button
type="button"
className="h-10 flex w-full space-x-2 items-center px-3 hover:bg-gray-100"
onClick={(e) => {
e.preventDefault()
item.onClick()
}}
>
<span className="inline-flex">
{item.icon}
</span>
<span>{t(item.text)}</span>
</button>
</Menu.Item>
)
})}
</Menu.Items>
<MenuItems
isPost={isPost}
page={page}
onClick={close}
/>
</>
)}
</Menu>
@ -435,3 +307,181 @@ export const PagesManager: React.FC<{
</DashboardMain>
)
}
const usePageEditLink = (page: { id: string }, isPost: boolean) => {
const router = useRouter()
const subdomain = router.query.subdomain as string
return `/dashboard/${subdomain}/editor?id=${page.id}&type=${
isPost ? "post" : "page"
}`
}
const usePageMenuItems = ({
page,
isPost,
}: {
page: Note
isPost: boolean
}) => {
const isCrossbell = !page.applications?.includes("xlog")
const router = useRouter()
const createOrUpdatePage = useCreateOrUpdatePage()
const editLink = usePageEditLink(page, isPost)
const subdomain = router.query.subdomain as string
const queryClient = useQueryClient()
const deletePage = useDeletePage()
const [convertToastId, setConvertToastId] = useState("")
const [deleteToastId, setDeleteToastId] = useState("")
const getDeleteToastId = useGetState(deleteToastId)
const getCurrentToastId = useGetState(convertToastId)
const { t } = useTranslation(["dashboard", "site"])
useEffect(() => {
if (deletePage.isSuccess) {
toast.success(t("Deleted!"), {
id: getDeleteToastId(),
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [deletePage.isSuccess])
useEffect(() => {
if (deletePage.isError) {
toast.error(t("Fail to Deleted."), {
id: getDeleteToastId(),
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [deletePage.isError])
useEffect(() => {
if (createOrUpdatePage.isSuccess) {
toast.success(t("Converted!"), {
id: getCurrentToastId(),
})
} else if (createOrUpdatePage.isError) {
toast.error(t("Failed to convert."), {
id: getCurrentToastId(),
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [createOrUpdatePage.isSuccess, createOrUpdatePage.isError])
return [
{
text: "Edit",
icon: <span className="i-mingcute:edit-line inline-block"></span>,
onClick() {
router.push(editLink)
},
},
{
text:
"Convert to " +
(isCrossbell
? `${APP_NAME} ${isPost ? "Post" : "Page"}`
: isPost
? "Page"
: "Post"),
icon: <span className="i-mingcute:transfer-3-line inline-block"></span>,
onClick() {
const toastId = toast.loading("Converting...")
if (isCrossbell) {
setConvertToastId(toastId)
createOrUpdatePage.mutate({
published: true,
pageId: page.id,
siteId: subdomain,
tags: page.tags
?.filter((tag) => tag !== "post" && tag !== "page")
?.join(", "),
isPost: isPost,
applications: page.applications,
})
} else {
if (!page.metadata) {
const data = getStorage(`draft-${subdomain}-${page.id}`)
data.isPost = !isPost
setStorage(`draft-${subdomain}-${page.id}`, data)
queryClient.invalidateQueries(["getPagesBySite", subdomain])
queryClient.invalidateQueries(["getPage", page.id])
toast.success("Converted!", {
id: toastId,
})
} else {
setConvertToastId(toastId)
createOrUpdatePage.mutate({
published: true,
pageId: page.id,
siteId: subdomain,
tags: page.tags
?.filter((tag) => tag !== "post" && tag !== "page")
?.join(", "),
isPost: !isPost,
applications: page.applications,
})
}
}
},
},
{
text: "Delete",
icon: <span className="i-mingcute:delete-2-line inline-block"></span>,
onClick() {
if (!page.metadata) {
const toastId = toast.loading("Deleting...")
delStorage(`draft-${subdomain}-${page.id}`)
Promise.all([
queryClient.refetchQueries(["getPagesBySite", subdomain]),
queryClient.refetchQueries(["getPage", page.id]),
]).then(() => {
toast.success("Deleted!", {
id: toastId,
})
})
} else {
setDeleteToastId(toast.loading("Deleting..."))
deletePage.mutate({
site: subdomain,
id: page.id,
})
}
},
},
]
}
const MenuItems: FC<{
isPost: boolean
page: Note
onClick: () => void
}> = ({ isPost, page, onClick: onClose }) => {
const { t } = useTranslation(["dashboard", "site"])
return (
<Menu.Items className="text-sm absolute z-20 right-0 bg-white shadow-modal rounded-lg overflow-hidden py-2 w-64">
{usePageMenuItems({ page, isPost }).map((item) => {
return (
<Menu.Item key={item.text}>
<button
type="button"
className="h-10 flex w-full space-x-2 items-center px-3 hover:bg-gray-100"
onClick={(e) => {
e.preventDefault()
item.onClick()
onClose()
}}
>
<span className="inline-flex">{item.icon}</span>
<span>{t(item.text)}</span>
</button>
</Menu.Item>
)
})}
</Menu.Items>
)
}

8
src/hooks/useGetState.ts Normal file
View File

@ -0,0 +1,8 @@
import { useEffect, useRef } from "react"
export const useGetState = (state: any) => {
const ref = useRef(state)
useEffect(() => void (ref.current = state), [state])
return () => ref.current
}