Compare commits
No commits in common. "V1.2" and "V1.1" have entirely different histories.
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -15,7 +15,6 @@
|
|||
"Chuudoku",
|
||||
"Codepen",
|
||||
"commonmark",
|
||||
"cooldown",
|
||||
"cout",
|
||||
"dompurify",
|
||||
"fontawesome",
|
||||
|
|
143
LICENSE
143
LICENSE
|
@ -1,5 +1,5 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
|
@ -7,15 +7,17 @@
|
|||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
|
@ -24,34 +26,44 @@ them if you wish), that you receive source code or can get it if you
|
|||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
@ -60,7 +72,7 @@ modification follow.
|
|||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
@ -537,45 +549,35 @@ to collect a royalty for further conveying from those to whom you convey
|
|||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
|
@ -633,29 +635,40 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
![Logo](https://github.com/KUN1007/kun-galgame-vue/blob/unfixed-loli/src/assets/images/favicon.png)
|
||||
![Logo](https://github.com/KUN1007/kun-galgame-vue/blob/layout/src/assets/images/favicon.png)Logo
|
||||
|
||||
The image is sourced from the game [Ark Order](https://apps.qoo-app.com/en/app/9593), featuring the character '鲲' (Kun).
|
||||
|
||||
|
|
89
index.html
89
index.html
|
@ -7,96 +7,13 @@
|
|||
type="image/svg+xml"
|
||||
href="/src/assets/images/favicon.webp"
|
||||
/>
|
||||
<link rel="stylesheet" href="/kungalgame-loading.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="KUN Visual Novel Forum. 鲲 Galgame 论坛。 The CUTEST Visual Novel Forum!世界上最萌的 Galgame 论坛. Topic, Technique. NO ADs Forever. Free Forever"
|
||||
content="The CUTEST Visual Novel Forum!世界上最萌的 Galgame 论坛. Topic, Technique. NO ADs Forever. Free Forever"
|
||||
/>
|
||||
<title>KUN Visual Novel Forum| 鲲 Galgame 论坛</title>
|
||||
<style>
|
||||
:root {
|
||||
--kungalgame-blue-1: #b6e3ff;
|
||||
--kungalgame-blue-4: #218bff;
|
||||
|
||||
--kungalgame-trans-blue-1: #b6e3ff77;
|
||||
|
||||
--kungalgame-shadow-0: 0px 0px 17px 5px var(--kungalgame-blue-1);
|
||||
}
|
||||
|
||||
#kungalgame-loading-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#kungalgame-loading {
|
||||
height: 4.8px;
|
||||
background: var(--kungalgame-blue-4);
|
||||
box-shadow: var(--kungalgame-shadow-0);
|
||||
box-sizing: border-box;
|
||||
animation: kungalgame-loading 7.7s linear infinite;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#kungalgame-loading::after,
|
||||
#kungalgame-loading::before {
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 1px;
|
||||
background: var(--kungalgame-blue-4);
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
right: -2px;
|
||||
opacity: 0;
|
||||
transform: rotate(-45deg) translateX(0px);
|
||||
box-sizing: border-box;
|
||||
animation: coli1 0.3s linear infinite;
|
||||
}
|
||||
|
||||
#kungalgame-loading::before {
|
||||
top: -4px;
|
||||
transform: rotate(45deg);
|
||||
animation: coli2 0.3s linear infinite;
|
||||
}
|
||||
|
||||
#kungalgame-loading h2 {
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
color: var(--kungalgame-blue-4);
|
||||
}
|
||||
|
||||
@keyframes kungalgame-loading {
|
||||
0% {
|
||||
width: 0;
|
||||
}
|
||||
100% {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes coli1 {
|
||||
0% {
|
||||
transform: rotate(-45deg) translateX(0px);
|
||||
opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
transform: rotate(-45deg) translateX(-45px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes coli2 {
|
||||
0% {
|
||||
transform: rotate(45deg) translateX(0px);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: rotate(45deg) translateX(-45px);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<title>KUNGalgame</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
|
|
63
package.json
63
package.json
|
@ -18,43 +18,46 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@milkdown/core": "7.3.6",
|
||||
"@milkdown/ctx": "7.3.6",
|
||||
"@milkdown/plugin-clipboard": "7.3.6",
|
||||
"@milkdown/plugin-history": "7.3.6",
|
||||
"@milkdown/plugin-indent": "7.3.6",
|
||||
"@milkdown/plugin-listener": "7.3.6",
|
||||
"@milkdown/plugin-prism": "7.3.6",
|
||||
"@milkdown/plugin-tooltip": "7.3.6",
|
||||
"@milkdown/plugin-trailing": "7.3.6",
|
||||
"@milkdown/preset-commonmark": "7.3.6",
|
||||
"@milkdown/preset-gfm": "7.3.6",
|
||||
"@milkdown/prose": "7.3.6",
|
||||
"@milkdown/transformer": "7.3.6",
|
||||
"@milkdown/utils": "7.3.6",
|
||||
"@milkdown/vue": "7.3.6",
|
||||
"@prosemirror-adapter/vue": "0.2.6",
|
||||
"@milkdown/core": "^7.3.1",
|
||||
"@milkdown/ctx": "^7.3.1",
|
||||
"@milkdown/plugin-clipboard": "^7.3.1",
|
||||
"@milkdown/plugin-history": "^7.3.1",
|
||||
"@milkdown/plugin-indent": "^7.3.1",
|
||||
"@milkdown/plugin-listener": "^7.3.1",
|
||||
"@milkdown/plugin-prism": "^7.3.1",
|
||||
"@milkdown/plugin-tooltip": "^7.3.1",
|
||||
"@milkdown/plugin-trailing": "^7.3.1",
|
||||
"@milkdown/preset-commonmark": "^7.3.1",
|
||||
"@milkdown/preset-gfm": "^7.3.1",
|
||||
"@milkdown/prose": "^7.3.1",
|
||||
"@milkdown/transformer": "^7.3.1",
|
||||
"@milkdown/utils": "^7.3.1",
|
||||
"@milkdown/vue": "^7.3.1",
|
||||
"@prosemirror-adapter/vue": "^0.2.6",
|
||||
"animate.css": "^4.1.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"dompurify": "^3.0.6",
|
||||
"localforage": "^1.10.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"refractor": "^4.8.1",
|
||||
"vue": "^3.4.24",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "^4.3.2"
|
||||
"vue": "^3.3.7",
|
||||
"vue-i18n": "^9.6.2",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/vue": "^4.1.2",
|
||||
"@types/node": "^20.12.7",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.75.0",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.10",
|
||||
"vue-tsc": "^2.0.14"
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@types/dompurify": "^3.0.4",
|
||||
"@types/js-cookie": "^3.0.5",
|
||||
"@types/node": "^20.8.10",
|
||||
"@types/nprogress": "^0.2.2",
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"sass": "^1.69.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.5.0",
|
||||
"vue-tsc": "^1.8.22"
|
||||
},
|
||||
"keywords": [
|
||||
"kun",
|
||||
|
@ -68,4 +71,4 @@
|
|||
"visual novel"
|
||||
],
|
||||
"license": "LGPL-3.0-or-later"
|
||||
}
|
||||
}
|
||||
|
|
3470
pnpm-lock.yaml
3470
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
84
public/kungalgame-loading.css
Normal file
84
public/kungalgame-loading.css
Normal file
|
@ -0,0 +1,84 @@
|
|||
/* When page blank, loading */
|
||||
|
||||
:root {
|
||||
--kungalgame-blue-1: #b6e3ff;
|
||||
--kungalgame-blue-4: #218bff;
|
||||
|
||||
--kungalgame-trans-blue-1: #b6e3ff77;
|
||||
|
||||
--kungalgame-shadow-0: 0px 0px 17px 5px var(--kungalgame-blue-1);
|
||||
}
|
||||
|
||||
#kungalgame-loading-container {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#kungalgame-loading {
|
||||
height: 4.8px;
|
||||
background: var(--kungalgame-blue-4);
|
||||
box-shadow: var(--kungalgame-shadow-0);
|
||||
box-sizing: border-box;
|
||||
animation: loading 7.7s linear infinite;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#kungalgame-loading::after,
|
||||
#kungalgame-loading::before {
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 1px;
|
||||
background: var(--kungalgame-blue-4);
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
right: -2px;
|
||||
opacity: 0;
|
||||
transform: rotate(-45deg) translateX(0px);
|
||||
box-sizing: border-box;
|
||||
animation: coli1 0.3s linear infinite;
|
||||
}
|
||||
|
||||
#kungalgame-loading::before {
|
||||
top: -4px;
|
||||
transform: rotate(45deg);
|
||||
animation: coli2 0.3s linear infinite;
|
||||
}
|
||||
|
||||
#kungalgame-loading h2 {
|
||||
position: absolute;
|
||||
top: 17px;
|
||||
color: var(--kungalgame-blue-4);
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
width: 0;
|
||||
}
|
||||
100% {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes coli1 {
|
||||
0% {
|
||||
transform: rotate(-45deg) translateX(0px);
|
||||
opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
transform: rotate(-45deg) translateX(-45px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes coli2 {
|
||||
0% {
|
||||
transform: rotate(45deg) translateX(0px);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: rotate(45deg) translateX(-45px);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
|
@ -9,9 +9,6 @@ import Info from '@/components/alert/Info.vue'
|
|||
const Capture = defineAsyncComponent(
|
||||
() => import('@/components/capture/Capture.vue')
|
||||
)
|
||||
const KUNGalgameSearchBox = defineAsyncComponent(
|
||||
() => import('@/components/search/KUNGalgameSearchBox.vue')
|
||||
)
|
||||
|
||||
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
@ -47,9 +44,6 @@ onBeforeMount(() => {
|
|||
<!-- Global capture component -->
|
||||
<Capture />
|
||||
|
||||
<!-- Global search component -->
|
||||
<KUNGalgameSearchBox />
|
||||
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ export interface EditUpdateTopicRequestData {
|
|||
content: string
|
||||
tags: string[]
|
||||
category: string[]
|
||||
edited: number
|
||||
}
|
||||
|
||||
// Request data format for getting hot tags
|
||||
|
|
|
@ -5,25 +5,11 @@ import type * as Home from './types/home'
|
|||
|
||||
// URLs to be requested
|
||||
const homeURLs = {
|
||||
search: `/home/search`,
|
||||
home: `/home/topic`,
|
||||
navHot: `/home/nav/hot`,
|
||||
navNew: `/home/nav/new`,
|
||||
}
|
||||
|
||||
// Home page topic list
|
||||
export async function getHomeSearchTopicApi(
|
||||
requestData: Home.HomeSearchTopicRequestData
|
||||
): Promise<Home.HomeSearchTopicResponseData> {
|
||||
const queryParams = objectToQueryParams(requestData)
|
||||
|
||||
const response = await fetchGet<Home.HomeSearchTopicResponseData>(
|
||||
`${homeURLs.search}?${queryParams}`
|
||||
)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// Home page topic list
|
||||
export async function getHomeTopicApi(
|
||||
requestData: Home.HomeTopicRequestData
|
||||
|
|
|
@ -1,19 +1,3 @@
|
|||
export interface HomeSearchTopic {
|
||||
tid: number
|
||||
title: string
|
||||
content: string
|
||||
category: string[]
|
||||
}
|
||||
|
||||
export interface HomeSearchTopicRequestData {
|
||||
keywords: string
|
||||
category: string
|
||||
page: number
|
||||
limit: number
|
||||
sortField: string
|
||||
sortOrder: string
|
||||
}
|
||||
|
||||
interface HomeUserInfo {
|
||||
uid: number
|
||||
avatar: string
|
||||
|
@ -33,6 +17,7 @@ export interface HomeNewTopic {
|
|||
}
|
||||
|
||||
export interface HomeTopicRequestData {
|
||||
keywords: string
|
||||
category: string
|
||||
page: number
|
||||
limit: number
|
||||
|
@ -59,12 +44,11 @@ export interface HomeTopic {
|
|||
upvote_time: number
|
||||
}
|
||||
|
||||
export type HomeSearchTopicResponseData = KUNGalgameResponseData<
|
||||
HomeSearchTopic[]
|
||||
>
|
||||
|
||||
// 10 hot topics on the left side
|
||||
export type HomeHotTopicResponseData = KUNGalgameResponseData<HomeHotTopic[]>
|
||||
|
||||
// 10 latest topics on the left side
|
||||
export type HomeNewTopicResponseData = KUNGalgameResponseData<HomeNewTopic[]>
|
||||
|
||||
// Topics displayed in the middle
|
||||
export type HomeTopicResponseData = KUNGalgameResponseData<HomeTopic[]>
|
||||
|
|
|
@ -16,8 +16,6 @@ export * from './non-moe/types/nonMoe'
|
|||
export * from './ranking/types/ranking'
|
||||
export * from './topic/types'
|
||||
export * from './update-log/types/updateLog'
|
||||
export * from './pool/types/pool'
|
||||
export * from './technique/types/technique'
|
||||
|
||||
// Expose all APIs
|
||||
export * from './balance'
|
||||
|
@ -29,5 +27,3 @@ export * from './non-moe'
|
|||
export * from './ranking'
|
||||
export * from './topic'
|
||||
export * from './update-log'
|
||||
export * from './pool'
|
||||
export * from './technique'
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import { fetchGet } from '@/utils/request'
|
||||
import objectToQueryParams from '@/utils/objectToQueryParams'
|
||||
import type * as Pool from './types/pool'
|
||||
|
||||
const poolURLs = {
|
||||
topic: `/pool/topic`,
|
||||
}
|
||||
|
||||
export async function getPoolTopicApi(
|
||||
requestData: Pool.PoolTopicsRequestData
|
||||
): Promise<Pool.PoolTopicResponseData> {
|
||||
const queryParams = objectToQueryParams(requestData)
|
||||
|
||||
const response = await fetchGet<Pool.PoolTopicResponseData>(
|
||||
`${poolURLs.topic}?${queryParams}`
|
||||
)
|
||||
|
||||
return response
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
export interface PoolTopic {
|
||||
tid: number
|
||||
title: string
|
||||
views: number
|
||||
likesCount: number
|
||||
time: number
|
||||
content: string
|
||||
}
|
||||
|
||||
export interface PoolTopicsRequestData {
|
||||
page: number
|
||||
limit: number
|
||||
sortField: string
|
||||
sortOrder: string
|
||||
}
|
||||
|
||||
export type PoolTopicResponseData = KUNGalgameResponseData<PoolTopic[]>
|
|
@ -1,19 +0,0 @@
|
|||
import { fetchGet } from '@/utils/request'
|
||||
import objectToQueryParams from '@/utils/objectToQueryParams'
|
||||
import type * as Technique from './types/technique'
|
||||
|
||||
const techniqueURLs = {
|
||||
topic: `/technique/topic`,
|
||||
}
|
||||
|
||||
export async function getTechniqueTopicApi(
|
||||
requestData: Technique.TechniqueTopicsRequestData
|
||||
): Promise<Technique.TechniqueTopicResponseData> {
|
||||
const queryParams = objectToQueryParams(requestData)
|
||||
|
||||
const response = await fetchGet<Technique.TechniqueTopicResponseData>(
|
||||
`${techniqueURLs.topic}?${queryParams}`
|
||||
)
|
||||
|
||||
return response
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
export interface TechniqueTopic {
|
||||
tid: number
|
||||
title: string
|
||||
views: number
|
||||
likesCount: number
|
||||
replyCount: number
|
||||
content: string
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
export interface TechniqueTopicsRequestData {
|
||||
page: number
|
||||
limit: number
|
||||
sortField: string
|
||||
sortOrder: string
|
||||
}
|
||||
|
||||
export type TechniqueTopicResponseData = KUNGalgameResponseData<
|
||||
TechniqueTopic[]
|
||||
>
|
|
@ -4,7 +4,7 @@ import objectToQueryParams from '@/utils/objectToQueryParams'
|
|||
import * as Reply from './types/reply'
|
||||
|
||||
// Get topic replies by tid
|
||||
export async function getRepliesByTidApi(
|
||||
export async function getRepliesByPidApi(
|
||||
request: Reply.TopicReplyRequestData
|
||||
): Promise<Reply.TopicReplyResponseData> {
|
||||
const queryParams = objectToQueryParams(request, 'tid')
|
||||
|
@ -16,7 +16,7 @@ export async function getRepliesByTidApi(
|
|||
}
|
||||
|
||||
// Create a reply by tid
|
||||
export async function postReplyByTidApi(
|
||||
export async function postReplyByPidApi(
|
||||
request: Reply.TopicCreateReplyRequestData
|
||||
): Promise<Reply.TopicCreateReplyResponseData> {
|
||||
const url = `/topics/${request.tid}/reply`
|
||||
|
|
|
@ -37,7 +37,6 @@ export interface TopicCreateReplyRequestData {
|
|||
to_floor: number
|
||||
tags: string[]
|
||||
content: string
|
||||
time: number
|
||||
}
|
||||
|
||||
// Upvote reply, the upvote operation is irreversible
|
||||
|
@ -45,7 +44,6 @@ export interface TopicUpvoteReplyRequestData {
|
|||
tid: number
|
||||
to_uid: number
|
||||
rid: number
|
||||
time: number
|
||||
}
|
||||
|
||||
// Like reply
|
||||
|
@ -70,7 +68,6 @@ export interface TopicUpdateReplyRequestData {
|
|||
rid: number
|
||||
content: string
|
||||
tags: string[]
|
||||
edited: number
|
||||
}
|
||||
|
||||
// Response data format for a single topic reply, returning multiple reply data in an array
|
||||
|
|
|
@ -37,7 +37,6 @@ export interface TopicDetail {
|
|||
export interface TopicUpvoteTopicRequestData {
|
||||
tid: number
|
||||
to_uid: number
|
||||
time: number
|
||||
}
|
||||
|
||||
// Request data for liking a topic
|
||||
|
|
|
@ -1,802 +0,0 @@
|
|||
[
|
||||
{
|
||||
"name": "汗",
|
||||
"left": 287,
|
||||
"top": 425,
|
||||
"width": 12,
|
||||
"height": 14,
|
||||
"visible": 1,
|
||||
"layer_id": 2961,
|
||||
"group_layer_id": 2931
|
||||
},
|
||||
{
|
||||
"name": "追真剣",
|
||||
"left": 234,
|
||||
"top": 383,
|
||||
"width": 76,
|
||||
"height": 27,
|
||||
"visible": 1,
|
||||
"layer_id": 3022,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "笑1",
|
||||
"left": 232,
|
||||
"top": 380,
|
||||
"width": 78,
|
||||
"height": 29,
|
||||
"visible": 1,
|
||||
"layer_id": 2842,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "笑2",
|
||||
"left": 235,
|
||||
"top": 385,
|
||||
"width": 76,
|
||||
"height": 32,
|
||||
"visible": 1,
|
||||
"layer_id": 2845,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "怒1",
|
||||
"left": 239,
|
||||
"top": 382,
|
||||
"width": 63,
|
||||
"height": 28,
|
||||
"visible": 1,
|
||||
"layer_id": 2848,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "怒2",
|
||||
"left": 240,
|
||||
"top": 382,
|
||||
"width": 60,
|
||||
"height": 26,
|
||||
"visible": 1,
|
||||
"layer_id": 2864,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "悲1",
|
||||
"left": 237,
|
||||
"top": 386,
|
||||
"width": 70,
|
||||
"height": 28,
|
||||
"visible": 1,
|
||||
"layer_id": 2855,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "悲2",
|
||||
"left": 238,
|
||||
"top": 391,
|
||||
"width": 66,
|
||||
"height": 28,
|
||||
"visible": 1,
|
||||
"layer_id": 2859,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "泣1",
|
||||
"left": 243,
|
||||
"top": 388,
|
||||
"width": 58,
|
||||
"height": 30,
|
||||
"visible": 1,
|
||||
"layer_id": 2865,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "泣2",
|
||||
"left": 240,
|
||||
"top": 386,
|
||||
"width": 65,
|
||||
"height": 31,
|
||||
"visible": 1,
|
||||
"layer_id": 2870,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "驚1",
|
||||
"left": 232,
|
||||
"top": 379,
|
||||
"width": 78,
|
||||
"height": 31,
|
||||
"visible": 1,
|
||||
"layer_id": 2877,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "驚2",
|
||||
"left": 232,
|
||||
"top": 379,
|
||||
"width": 78,
|
||||
"height": 33,
|
||||
"visible": 1,
|
||||
"layer_id": 2882,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "照1",
|
||||
"left": 234,
|
||||
"top": 382,
|
||||
"width": 77,
|
||||
"height": 31,
|
||||
"visible": 1,
|
||||
"layer_id": 2887,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "照2",
|
||||
"left": 239,
|
||||
"top": 385,
|
||||
"width": 70,
|
||||
"height": 31,
|
||||
"visible": 1,
|
||||
"layer_id": 2893,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "えっへん",
|
||||
"left": 235,
|
||||
"top": 384,
|
||||
"width": 72,
|
||||
"height": 25,
|
||||
"visible": 1,
|
||||
"layer_id": 2899,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "ギャグ笑",
|
||||
"left": 234,
|
||||
"top": 383,
|
||||
"width": 76,
|
||||
"height": 33,
|
||||
"visible": 1,
|
||||
"layer_id": 2905,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "ギャグ怒",
|
||||
"left": 239,
|
||||
"top": 384,
|
||||
"width": 61,
|
||||
"height": 29,
|
||||
"visible": 1,
|
||||
"layer_id": 2910,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "ギャグ泣",
|
||||
"left": 239,
|
||||
"top": 389,
|
||||
"width": 63,
|
||||
"height": 26,
|
||||
"visible": 1,
|
||||
"layer_id": 2915,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "ギャグ驚",
|
||||
"left": 235,
|
||||
"top": 383,
|
||||
"width": 66,
|
||||
"height": 23,
|
||||
"visible": 1,
|
||||
"layer_id": 2918,
|
||||
"group_layer_id": 2923
|
||||
},
|
||||
{
|
||||
"name": "追真剣",
|
||||
"left": 238,
|
||||
"top": 392,
|
||||
"width": 78,
|
||||
"height": 41,
|
||||
"visible": 0,
|
||||
"layer_id": 3020,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "笑1",
|
||||
"left": 238,
|
||||
"top": 390,
|
||||
"width": 78,
|
||||
"height": 43,
|
||||
"visible": 0,
|
||||
"layer_id": 2843,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "笑2",
|
||||
"left": 244,
|
||||
"top": 395,
|
||||
"width": 69,
|
||||
"height": 37,
|
||||
"visible": 0,
|
||||
"layer_id": 2846,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "怒1",
|
||||
"left": 238,
|
||||
"top": 391,
|
||||
"width": 78,
|
||||
"height": 42,
|
||||
"visible": 0,
|
||||
"layer_id": 2850,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "怒2",
|
||||
"left": 238,
|
||||
"top": 391,
|
||||
"width": 78,
|
||||
"height": 42,
|
||||
"visible": 0,
|
||||
"layer_id": 2853,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "悲1",
|
||||
"left": 238,
|
||||
"top": 392,
|
||||
"width": 78,
|
||||
"height": 41,
|
||||
"visible": 0,
|
||||
"layer_id": 2856,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "悲2",
|
||||
"left": 240,
|
||||
"top": 396,
|
||||
"width": 75,
|
||||
"height": 37,
|
||||
"visible": 0,
|
||||
"layer_id": 2861,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "泣1",
|
||||
"left": 243,
|
||||
"top": 396,
|
||||
"width": 71,
|
||||
"height": 37,
|
||||
"visible": 1,
|
||||
"layer_id": 3025,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "泣2",
|
||||
"left": 238,
|
||||
"top": 394,
|
||||
"width": 78,
|
||||
"height": 60,
|
||||
"visible": 0,
|
||||
"layer_id": 3024,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "驚1",
|
||||
"left": 238,
|
||||
"top": 390,
|
||||
"width": 78,
|
||||
"height": 43,
|
||||
"visible": 0,
|
||||
"layer_id": 2880,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "驚2",
|
||||
"left": 238,
|
||||
"top": 390,
|
||||
"width": 78,
|
||||
"height": 43,
|
||||
"visible": 0,
|
||||
"layer_id": 2885,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "照1",
|
||||
"left": 238,
|
||||
"top": 392,
|
||||
"width": 78,
|
||||
"height": 41,
|
||||
"visible": 0,
|
||||
"layer_id": 2891,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "照2",
|
||||
"left": 244,
|
||||
"top": 395,
|
||||
"width": 69,
|
||||
"height": 37,
|
||||
"visible": 0,
|
||||
"layer_id": 2896,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "えっへん",
|
||||
"left": 244,
|
||||
"top": 396,
|
||||
"width": 68,
|
||||
"height": 38,
|
||||
"visible": 0,
|
||||
"layer_id": 2902,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "ギャグ笑",
|
||||
"left": 244,
|
||||
"top": 395,
|
||||
"width": 69,
|
||||
"height": 37,
|
||||
"visible": 0,
|
||||
"layer_id": 2908,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "ギャグ怒",
|
||||
"left": 244,
|
||||
"top": 401,
|
||||
"width": 66,
|
||||
"height": 33,
|
||||
"visible": 0,
|
||||
"layer_id": 2913,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "ギャグ泣",
|
||||
"left": 244,
|
||||
"top": 405,
|
||||
"width": 66,
|
||||
"height": 47,
|
||||
"visible": 0,
|
||||
"layer_id": 2916,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "ギャグ驚",
|
||||
"left": 248,
|
||||
"top": 395,
|
||||
"width": 57,
|
||||
"height": 37,
|
||||
"visible": 0,
|
||||
"layer_id": 2920,
|
||||
"group_layer_id": 2925
|
||||
},
|
||||
{
|
||||
"name": "追開け",
|
||||
"left": 270,
|
||||
"top": 430,
|
||||
"width": 19,
|
||||
"height": 16,
|
||||
"visible": 1,
|
||||
"layer_id": 2980,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "追閉じ",
|
||||
"left": 271,
|
||||
"top": 430,
|
||||
"width": 18,
|
||||
"height": 14,
|
||||
"visible": 0,
|
||||
"layer_id": 2969,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "追真剣",
|
||||
"left": 273,
|
||||
"top": 433,
|
||||
"width": 12,
|
||||
"height": 13,
|
||||
"visible": 0,
|
||||
"layer_id": 3023,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "笑1",
|
||||
"left": 269,
|
||||
"top": 429,
|
||||
"width": 21,
|
||||
"height": 18,
|
||||
"visible": 0,
|
||||
"layer_id": 2844,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "笑2",
|
||||
"left": 268,
|
||||
"top": 428,
|
||||
"width": 24,
|
||||
"height": 22,
|
||||
"visible": 0,
|
||||
"layer_id": 2847,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "怒1",
|
||||
"left": 272,
|
||||
"top": 433,
|
||||
"width": 15,
|
||||
"height": 12,
|
||||
"visible": 0,
|
||||
"layer_id": 2851,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "怒2",
|
||||
"left": 268,
|
||||
"top": 428,
|
||||
"width": 25,
|
||||
"height": 23,
|
||||
"visible": 0,
|
||||
"layer_id": 2854,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "悲1",
|
||||
"left": 272,
|
||||
"top": 434,
|
||||
"width": 14,
|
||||
"height": 11,
|
||||
"visible": 0,
|
||||
"layer_id": 2857,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "悲2",
|
||||
"left": 272,
|
||||
"top": 435,
|
||||
"width": 15,
|
||||
"height": 12,
|
||||
"visible": 0,
|
||||
"layer_id": 2862,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "泣1",
|
||||
"left": 270,
|
||||
"top": 431,
|
||||
"width": 21,
|
||||
"height": 17,
|
||||
"visible": 0,
|
||||
"layer_id": 2869,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "泣2",
|
||||
"left": 270,
|
||||
"top": 433,
|
||||
"width": 22,
|
||||
"height": 13,
|
||||
"visible": 0,
|
||||
"layer_id": 2876,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "驚1",
|
||||
"left": 272,
|
||||
"top": 432,
|
||||
"width": 14,
|
||||
"height": 16,
|
||||
"visible": 0,
|
||||
"layer_id": 2881,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "驚2",
|
||||
"left": 268,
|
||||
"top": 427,
|
||||
"width": 26,
|
||||
"height": 25,
|
||||
"visible": 0,
|
||||
"layer_id": 2886,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "照1",
|
||||
"left": 269,
|
||||
"top": 431,
|
||||
"width": 20,
|
||||
"height": 15,
|
||||
"visible": 0,
|
||||
"layer_id": 2892,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "照2",
|
||||
"left": 268,
|
||||
"top": 430,
|
||||
"width": 23,
|
||||
"height": 19,
|
||||
"visible": 0,
|
||||
"layer_id": 2897,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "えっへん",
|
||||
"left": 269,
|
||||
"top": 429,
|
||||
"width": 22,
|
||||
"height": 20,
|
||||
"visible": 0,
|
||||
"layer_id": 2903,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "ギャグ笑",
|
||||
"left": 267,
|
||||
"top": 427,
|
||||
"width": 26,
|
||||
"height": 24,
|
||||
"visible": 0,
|
||||
"layer_id": 2909,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "ギャグ怒",
|
||||
"left": 270,
|
||||
"top": 430,
|
||||
"width": 20,
|
||||
"height": 16,
|
||||
"visible": 0,
|
||||
"layer_id": 2914,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "ギャグ泣",
|
||||
"left": 270,
|
||||
"top": 434,
|
||||
"width": 19,
|
||||
"height": 11,
|
||||
"visible": 0,
|
||||
"layer_id": 2917,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "ギャグ驚",
|
||||
"left": 267,
|
||||
"top": 427,
|
||||
"width": 25,
|
||||
"height": 24,
|
||||
"visible": 0,
|
||||
"layer_id": 2921,
|
||||
"group_layer_id": 2927
|
||||
},
|
||||
{
|
||||
"name": "G頬",
|
||||
"left": 249,
|
||||
"top": 415,
|
||||
"width": 59,
|
||||
"height": 28,
|
||||
"visible": 0,
|
||||
"layer_id": 813,
|
||||
"group_layer_id": 2314
|
||||
},
|
||||
{
|
||||
"name": "頬1",
|
||||
"left": 246,
|
||||
"top": 410,
|
||||
"width": 65,
|
||||
"height": 35,
|
||||
"visible": 0,
|
||||
"layer_id": 2369,
|
||||
"group_layer_id": 2314
|
||||
},
|
||||
{
|
||||
"name": "頬2",
|
||||
"left": 245,
|
||||
"top": 410,
|
||||
"width": 66,
|
||||
"height": 35,
|
||||
"visible": 0,
|
||||
"layer_id": 2364,
|
||||
"group_layer_id": 2314
|
||||
},
|
||||
{
|
||||
"name": "頬3",
|
||||
"left": 245,
|
||||
"top": 408,
|
||||
"width": 66,
|
||||
"height": 38,
|
||||
"visible": 0,
|
||||
"layer_id": 2360,
|
||||
"group_layer_id": 2314
|
||||
},
|
||||
{
|
||||
"name": "頬4",
|
||||
"left": 242,
|
||||
"top": 406,
|
||||
"width": 69,
|
||||
"height": 44,
|
||||
"visible": 1,
|
||||
"layer_id": 2356,
|
||||
"group_layer_id": 2314
|
||||
},
|
||||
{
|
||||
"name": "頬5",
|
||||
"left": 245,
|
||||
"top": 410,
|
||||
"width": 66,
|
||||
"height": 36,
|
||||
"visible": 0,
|
||||
"layer_id": 2352,
|
||||
"group_layer_id": 2314
|
||||
},
|
||||
{
|
||||
"name": "斜め1",
|
||||
"left": 148,
|
||||
"top": 323,
|
||||
"width": 309,
|
||||
"height": 600,
|
||||
"visible": 1,
|
||||
"layer_id": 2346,
|
||||
"group_layer_id": 2086
|
||||
},
|
||||
{
|
||||
"name": "斜め2",
|
||||
"left": 148,
|
||||
"top": 323,
|
||||
"width": 309,
|
||||
"height": 600,
|
||||
"visible": 0,
|
||||
"layer_id": 2345,
|
||||
"group_layer_id": 2086
|
||||
},
|
||||
{
|
||||
"name": "斜め1",
|
||||
"left": 147,
|
||||
"top": 323,
|
||||
"width": 313,
|
||||
"height": 600,
|
||||
"visible": 1,
|
||||
"layer_id": 2934,
|
||||
"group_layer_id": 2090
|
||||
},
|
||||
{
|
||||
"name": "斜め2",
|
||||
"left": 147,
|
||||
"top": 323,
|
||||
"width": 313,
|
||||
"height": 600,
|
||||
"visible": 1,
|
||||
"layer_id": 2933,
|
||||
"group_layer_id": 2090
|
||||
},
|
||||
{
|
||||
"name": "斜め1",
|
||||
"left": 137,
|
||||
"top": 323,
|
||||
"width": 367,
|
||||
"height": 602,
|
||||
"visible": 1,
|
||||
"layer_id": 2340,
|
||||
"group_layer_id": 2094
|
||||
},
|
||||
{
|
||||
"name": "斜め2",
|
||||
"left": 137,
|
||||
"top": 323,
|
||||
"width": 367,
|
||||
"height": 602,
|
||||
"visible": 1,
|
||||
"layer_id": 2339,
|
||||
"group_layer_id": 2094
|
||||
},
|
||||
{
|
||||
"name": "斜め1",
|
||||
"left": 148,
|
||||
"top": 323,
|
||||
"width": 294,
|
||||
"height": 599,
|
||||
"visible": 0,
|
||||
"layer_id": 2338,
|
||||
"group_layer_id": 2098
|
||||
},
|
||||
{
|
||||
"name": "斜め2",
|
||||
"left": 148,
|
||||
"top": 323,
|
||||
"width": 290,
|
||||
"height": 599,
|
||||
"visible": 1,
|
||||
"layer_id": 2337,
|
||||
"group_layer_id": 2098
|
||||
},
|
||||
{
|
||||
"name": "汗",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"visible": 0,
|
||||
"layer_id": 2931,
|
||||
"group_layer_id": 974
|
||||
},
|
||||
{
|
||||
"name": "眉",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"visible": 0,
|
||||
"layer_id": 2923,
|
||||
"group_layer_id": 974
|
||||
},
|
||||
{
|
||||
"name": "目",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"visible": 1,
|
||||
"layer_id": 2925,
|
||||
"group_layer_id": 974
|
||||
},
|
||||
{
|
||||
"name": "口",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"visible": 0,
|
||||
"layer_id": 2927,
|
||||
"group_layer_id": 974
|
||||
},
|
||||
{
|
||||
"name": "頬",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"visible": 0,
|
||||
"layer_id": 2314,
|
||||
"group_layer_id": 975
|
||||
},
|
||||
{
|
||||
"name": "喫茶店服",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"visible": 0,
|
||||
"layer_id": 2086,
|
||||
"group_layer_id": 975
|
||||
},
|
||||
{
|
||||
"name": "パジャマ",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"visible": 0,
|
||||
"layer_id": 2090,
|
||||
"group_layer_id": 975
|
||||
},
|
||||
{
|
||||
"name": "私服2",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"visible": 0,
|
||||
"layer_id": 2094,
|
||||
"group_layer_id": 975
|
||||
},
|
||||
{
|
||||
"name": "私服1",
|
||||
"left": 0,
|
||||
"top": 0,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"visible": 1,
|
||||
"layer_id": 2098,
|
||||
"group_layer_id": 975
|
||||
}
|
||||
]
|
|
@ -1,32 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RouterLink to="/kun" class="return">
|
||||
<Icon class="icon" icon="line-md:home-md-twotone" />
|
||||
<span>{{ $tm('back.home') }}</span>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.return {
|
||||
position: absolute;
|
||||
bottom: 1%;
|
||||
right: 2%;
|
||||
color: var(--kungalgame-font-color-0);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.return:hover {
|
||||
color: var(--kungalgame-blue-4);
|
||||
}
|
||||
</style>
|
|
@ -5,6 +5,7 @@ const router = useRouter()
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Back to homepage -->
|
||||
<div class="return" @click="router.back()">
|
||||
<span>{{ `< ${$tm('back.back')}` }}</span>
|
||||
</div>
|
||||
|
|
|
@ -1,27 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="footer">
|
||||
<div>{{ $tm('footer.copyright') }}</div>
|
||||
|
||||
<div>
|
||||
<span>{{ $tm('footer.openSource') }}</span>
|
||||
|
||||
<a
|
||||
href="http://github.com/KUN1007/kun-galgame-vue"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="KUN Visual Novel Open Source GitHub Repository. 鲲 Galgame 开源 GitHub 仓库."
|
||||
>
|
||||
<Icon icon="line-md:github-loop" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>{{ $tm('footer.reserved') }} 1.3.0</span>
|
||||
</div>
|
||||
<span>Copyright © 2023 KUNGalgame</span>
|
||||
<span>All rights reserved | Version 1.2.0</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -34,25 +16,5 @@ import { Icon } from '@iconify/vue'
|
|||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
justify-self: center;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
justify-self: center;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
color: var(--kungalgame-blue-5);
|
||||
|
||||
&:hover {
|
||||
transition: all 0.2s;
|
||||
transform: scale(1.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
309
src/components/KUNGalgameSearchBox.vue
Normal file
309
src/components/KUNGalgameSearchBox.vue
Normal file
|
@ -0,0 +1,309 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
// Import debounce function
|
||||
import { debounce } from '@/utils/debounce'
|
||||
import { ref, onBeforeMount } from 'vue'
|
||||
// Import user store
|
||||
import { useKUNGalgameHomeStore } from '@/store/modules/home'
|
||||
import { storeToRefs } from 'pinia'
|
||||
const { keywords, searchHistory, category } = storeToRefs(
|
||||
useKUNGalgameHomeStore()
|
||||
)
|
||||
|
||||
// Value of the input field
|
||||
const inputValue = ref('')
|
||||
// Whether to show search history
|
||||
const isShowSearchHistory = ref(false)
|
||||
// Style when the input field is active
|
||||
const inputActiveClass = ref({})
|
||||
|
||||
// Define props to tell the input field in which category to search for topics
|
||||
const props = defineProps(['category'])
|
||||
|
||||
// Initialize the content of the search box to prevent content from persisting after page refresh
|
||||
// Assign the topic category to be searched because the search box will be rendered on three pages
|
||||
// , corresponding to three categories
|
||||
onBeforeMount(() => {
|
||||
keywords.value = ''
|
||||
category.value = props.category
|
||||
})
|
||||
|
||||
// Define the debounce handling function
|
||||
const debouncedSearch = debounce((inputValue: string) => {
|
||||
// Reset page status and loading state before searching
|
||||
useKUNGalgameHomeStore().resetPageStatus()
|
||||
keywords.value = inputValue
|
||||
}, 300) // 300 milliseconds debounce delay
|
||||
|
||||
// When the search box is focused
|
||||
const handleInputFocus = () => {
|
||||
if (searchHistory.value.length !== 0) {
|
||||
isShowSearchHistory.value = true
|
||||
}
|
||||
inputActiveClass.value = {
|
||||
backgroundColor: 'var(--kungalgame-white)',
|
||||
}
|
||||
}
|
||||
|
||||
// When the search box is blurred
|
||||
const handleInputBlur = () => {
|
||||
// Delay hiding the search history so that clicking on the search history can trigger the fill event
|
||||
setTimeout(() => {
|
||||
isShowSearchHistory.value = false
|
||||
inputActiveClass.value = {}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
// Search function logic
|
||||
const search = () => {
|
||||
debouncedSearch(inputValue.value)
|
||||
if (!searchHistory.value.includes(inputValue.value)) {
|
||||
// Push the element into the array only when there are no identical elements in the array
|
||||
searchHistory.value.push(inputValue.value)
|
||||
}
|
||||
}
|
||||
|
||||
// When the user presses Enter
|
||||
const handleClickEnter = (event: KeyboardEvent) => {
|
||||
event.preventDefault()
|
||||
search()
|
||||
}
|
||||
|
||||
// Clicking the search button triggers the search logic
|
||||
const handleClickSearch = () => {
|
||||
if (inputValue.value.trim()) {
|
||||
search()
|
||||
}
|
||||
}
|
||||
|
||||
// Clicking on search history
|
||||
const handleClickHistory = (index: number) => {
|
||||
inputValue.value = searchHistory.value[index]
|
||||
}
|
||||
|
||||
// Clear search history
|
||||
const clearSearchHistory = () => {
|
||||
searchHistory.value = []
|
||||
}
|
||||
|
||||
// Delete search history
|
||||
const handleDeleteHistory = (historyIndex: number) => {
|
||||
searchHistory.value.splice(historyIndex, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Interactive area search box -->
|
||||
<div class="container">
|
||||
<!-- Search box form -->
|
||||
<form class="search-form">
|
||||
<!-- Search box content -->
|
||||
<div class="content">
|
||||
<!-- Input field -->
|
||||
<input
|
||||
v-model="inputValue"
|
||||
type="search"
|
||||
class="input"
|
||||
:style="inputActiveClass"
|
||||
:placeholder="`${$tm('mainPage.header.search')}`"
|
||||
@focus="handleInputFocus"
|
||||
@blur="handleInputBlur"
|
||||
@input="debouncedSearch(inputValue)"
|
||||
@keydown.enter="handleClickEnter"
|
||||
/>
|
||||
</div>
|
||||
<!-- Search box icon -->
|
||||
<div class="search-btn" @click="handleClickSearch">
|
||||
<Icon icon="line-md:search" />
|
||||
</div>
|
||||
</form>
|
||||
<!-- Search history container -->
|
||||
<div v-if="isShowSearchHistory" class="history">
|
||||
<!-- Search history title -->
|
||||
<div class="title">
|
||||
<span>{{ $tm('mainPage.header.history') }}</span>
|
||||
<span @click="clearSearchHistory">
|
||||
{{ $tm('mainPage.header.clear') }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Search history -->
|
||||
<div class="history-container">
|
||||
<div
|
||||
class="single-history"
|
||||
v-for="(history, index) in searchHistory"
|
||||
:key="index"
|
||||
@click="handleClickHistory(index)"
|
||||
>
|
||||
<span>{{ history }} </span>
|
||||
<span @click="handleDeleteHistory(index)">
|
||||
<Icon class="delete" icon="line-md:close-circle" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* Search topics */
|
||||
.container {
|
||||
height: 39px;
|
||||
width: 1px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
/* Prevent line breaks when the page is resized */
|
||||
white-space: nowrap;
|
||||
background-color: var(--kungalgame-trans-blue-2);
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
flex-grow: 2;
|
||||
/* Position relative to the secondary menu */
|
||||
position: relative;
|
||||
display: flex;
|
||||
color: var(--kungalgame-font-color-3);
|
||||
}
|
||||
|
||||
/* Search box form */
|
||||
.search-form {
|
||||
display: flex;
|
||||
height: 39px;
|
||||
/* Grows with the page */
|
||||
width: 1px;
|
||||
flex-grow: 1;
|
||||
/* Centered */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Search content area */
|
||||
.content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Input field */
|
||||
.input {
|
||||
padding: 0 15px;
|
||||
height: 39px;
|
||||
width: 100%;
|
||||
/* Font size for input during search */
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
background-color: var(--kungalgame-trans-white-5);
|
||||
color: var(--kungalgame-font-color-3);
|
||||
transition: all 0.2s;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--kungalgame-font-color-1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Search button */
|
||||
.search-btn {
|
||||
/* Square shape, does not shrink */
|
||||
height: 39px;
|
||||
width: 39px;
|
||||
flex-shrink: 0;
|
||||
border-left: 1px solid var(--kungalgame-trans-blue-4);
|
||||
/* Center the search icon */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--kungalgame-red-1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--kungalgame-red-2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Search history container */
|
||||
.history {
|
||||
width: 100%;
|
||||
/* Absolute positioning relative to the search area in the nav */
|
||||
position: absolute;
|
||||
/* Tight positioning under the topic search area */
|
||||
top: 39px;
|
||||
left: 0;
|
||||
flex-direction: column;
|
||||
background-color: var(--kungalgame-white);
|
||||
color: var(--kungalgame-font-color-3);
|
||||
border: 1px solid var(--kungalgame-red-1);
|
||||
border-radius: 7px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
/* Text for the search history title */
|
||||
.title {
|
||||
display: flex;
|
||||
margin: 10px;
|
||||
/* Distribute two hint texts left and right */
|
||||
justify-content: space-between;
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
&:nth-child(2) {
|
||||
cursor: pointer;
|
||||
border-bottom: 1.5px solid var(--kungalgame-trans-white-5);
|
||||
|
||||
&:hover {
|
||||
border-bottom: 1.5px solid var(--kungalgame-blue-4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Container for storing search history tags */
|
||||
.history-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Font for individual search records */
|
||||
font-size: 13px;
|
||||
/* Blank space on the left and right sides of the search record */
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.single-history {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space between;
|
||||
padding: 7px 3px;
|
||||
margin: 2px 0;
|
||||
|
||||
&:hover {
|
||||
color: var(--kungalgame-blue-4);
|
||||
|
||||
.delete {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
span:nth-child(1) {
|
||||
cursor: default;
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
span:nth-child(2) {
|
||||
width: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete button */
|
||||
.delete {
|
||||
width: 30px;
|
||||
right: 5px;
|
||||
font-size: 17px;
|
||||
position: absolute;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: var(--kungalgame-font-color-0);
|
||||
background-color: var(--kungalgame-white);
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -1,17 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import { useTempMessageStore } from '@/store/temp/message'
|
||||
import { useKUNGalgameMessageStore } from '@/store/modules/message'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const { showAlert, alertMsg, isShowCancel } = storeToRefs(useTempMessageStore())
|
||||
const { showAlert, alertMsg, isShowCancel } = storeToRefs(
|
||||
useKUNGalgameMessageStore()
|
||||
)
|
||||
|
||||
const handleClose = () => {
|
||||
showAlert.value = false
|
||||
useTempMessageStore().handleClose()
|
||||
useKUNGalgameMessageStore().handleClose()
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
showAlert.value = false
|
||||
useTempMessageStore().handleConfirm()
|
||||
useKUNGalgameMessageStore().handleConfirm()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useTempMessageStore } from '@/store/temp/message'
|
||||
import { useKUNGalgameMessageStore } from '@/store/modules/message'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import img from './loli'
|
||||
import 'animate.css'
|
||||
|
||||
const { showInfo, infoMsg } = storeToRefs(useTempMessageStore())
|
||||
const { showInfo, infoMsg } = storeToRefs(useKUNGalgameMessageStore())
|
||||
|
||||
const { loli, name } = img
|
||||
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
// Import questions
|
||||
import { questionsEN, Question } from './questionsEN'
|
||||
import { questionsCN } from './questionsCN'
|
||||
|
||||
import Message from '@/components/alert/Message'
|
||||
|
||||
import { useTempMessageStore } from '@/store/temp/message'
|
||||
import { useKUNGalgameMessageStore } from '@/store/modules/message'
|
||||
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const { showKUNGalgameLanguage } = storeToRefs(useKUNGalgameSettingsStore())
|
||||
const { isShowCapture, isCaptureSuccessful } = storeToRefs(
|
||||
useTempMessageStore()
|
||||
useKUNGalgameMessageStore()
|
||||
)
|
||||
// Current language
|
||||
const questions = ref<Question[]>([])
|
||||
|
|
|
@ -27,21 +27,15 @@ import { Plugin } from '@milkdown/prose/state'
|
|||
import '@/styles/editor/index.scss'
|
||||
|
||||
// Syntax highlight
|
||||
import c from 'refractor/lang/c'
|
||||
import cpp from 'refractor/lang/cpp'
|
||||
import csharp from 'refractor/lang/csharp'
|
||||
import css from 'refractor/lang/css'
|
||||
import go from 'refractor/lang/go'
|
||||
import haskell from 'refractor/lang/haskell'
|
||||
import python from 'refractor/lang/python'
|
||||
import java from 'refractor/lang/java'
|
||||
import javascript from 'refractor/lang/javascript'
|
||||
import typescript from 'refractor/lang/typescript'
|
||||
import jsx from 'refractor/lang/jsx'
|
||||
import kotlin from 'refractor/lang/kotlin'
|
||||
import r from 'refractor/lang/r'
|
||||
import rust from 'refractor/lang/rust'
|
||||
import scala from 'refractor/lang/scala'
|
||||
import sql from 'refractor/lang/sql'
|
||||
import tsx from 'refractor/lang/tsx'
|
||||
import markdown from 'refractor/lang/markdown'
|
||||
|
@ -92,22 +86,16 @@ const editorInfo = useEditor((root) =>
|
|||
|
||||
ctx.set(prismConfig.key, {
|
||||
configureRefractor: (refractor) => {
|
||||
refractor.register(c)
|
||||
refractor.register(cpp)
|
||||
refractor.register(csharp)
|
||||
refractor.register(css)
|
||||
refractor.register(go)
|
||||
refractor.register(haskell)
|
||||
refractor.register(python)
|
||||
refractor.register(markdown)
|
||||
refractor.register(java)
|
||||
refractor.register(javascript)
|
||||
refractor.register(typescript)
|
||||
refractor.register(jsx)
|
||||
refractor.register(kotlin)
|
||||
refractor.register(r)
|
||||
refractor.register(rust)
|
||||
refractor.register(scala)
|
||||
refractor.register(sql)
|
||||
refractor.register(tsx)
|
||||
},
|
||||
|
@ -197,10 +185,6 @@ const editorInfo = useEditor((root) =>
|
|||
scrollbar-color: var(--kungalgame-blue-4) var(--kungalgame-blue-1); /* Firefox 64+ */
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
|
|
@ -8,25 +8,22 @@ import { ProsemirrorAdapterProvider } from '@prosemirror-adapter/vue'
|
|||
import MilkdownEditor from './MilkdownEditor.vue'
|
||||
|
||||
// KUN Visual Novel store
|
||||
import { useTempEditStore } from '@/store/temp/edit'
|
||||
import { useKUNGalgameEditStore } from '@/store/modules/edit'
|
||||
import { useTempReplyStore } from '@/store/temp/topic/reply'
|
||||
import { usePersistKUNGalgameReplyStore } from '@/store/modules/topic/reply'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const { content: rewriteContent, isTopicRewriting } = storeToRefs(
|
||||
useTempEditStore()
|
||||
)
|
||||
const {
|
||||
editorHeight: editEditorHeight,
|
||||
isSaveTopic,
|
||||
content: editContent,
|
||||
content,
|
||||
topicRewrite,
|
||||
} = storeToRefs(useKUNGalgameEditStore())
|
||||
const { isReplyRewriting, replyRewrite } = storeToRefs(useTempReplyStore())
|
||||
const {
|
||||
editorHeight: replyEditorHeight,
|
||||
isSaveReply,
|
||||
isReplyRewriting,
|
||||
replyDraft,
|
||||
replyRewrite,
|
||||
} = storeToRefs(usePersistKUNGalgameReplyStore())
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -50,14 +47,14 @@ onBeforeMount(() => {
|
|||
*/
|
||||
// Load topic data before mounting if not saved (and must be on the Edit page)
|
||||
if (isSaveTopic.value && routeName.value === 'Edit') {
|
||||
valueMarkdown.value = editContent.value
|
||||
valueMarkdown.value = content.value
|
||||
}
|
||||
/**
|
||||
* Editor is in the re-editing edit mode
|
||||
*/
|
||||
// Load data for re-editing a topic before mounting
|
||||
if (isTopicRewriting.value && routeName.value === 'Edit') {
|
||||
valueMarkdown.value = rewriteContent.value
|
||||
if (topicRewrite.value.isTopicRewriting && routeName.value === 'Edit') {
|
||||
valueMarkdown.value = topicRewrite.value.content
|
||||
}
|
||||
/**
|
||||
* Editor is in the reply mode
|
||||
|
@ -74,21 +71,22 @@ onBeforeMount(() => {
|
|||
}
|
||||
})
|
||||
|
||||
const saveMarkdown = (editorMarkdown: string) =>
|
||||
debounce(() => {
|
||||
const saveMarkdown = (editorMarkdown: string) => {
|
||||
// Create a debounce function
|
||||
const debouncedUpdateContent = debounce(() => {
|
||||
/**
|
||||
* Editor is in edit mode
|
||||
*/
|
||||
// Save to the edit store if not in topic re-edit mode
|
||||
if (!isTopicRewriting.value && routeName.value === 'Edit') {
|
||||
editContent.value = editorMarkdown
|
||||
if (!topicRewrite.value.isTopicRewriting && routeName.value === 'Edit') {
|
||||
content.value = editorMarkdown
|
||||
}
|
||||
/**
|
||||
* Editor is in re-editing edit mode
|
||||
*/
|
||||
// Load data for re-editing a topic before mounting
|
||||
if (isTopicRewriting.value && routeName.value === 'Edit') {
|
||||
rewriteContent.value = editorMarkdown
|
||||
if (topicRewrite.value.isTopicRewriting && routeName.value === 'Edit') {
|
||||
topicRewrite.value.content = editorMarkdown
|
||||
}
|
||||
/**
|
||||
* Editor is in reply mode
|
||||
|
@ -104,6 +102,9 @@ const saveMarkdown = (editorMarkdown: string) =>
|
|||
replyRewrite.value.content = editorMarkdown
|
||||
}
|
||||
}, 1007)
|
||||
|
||||
debouncedUpdateContent()
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- MilkdownEditorWrapper.vue -->
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
|
||||
import { computed } from 'vue'
|
||||
// Milkdown core
|
||||
import {
|
||||
Editor,
|
||||
rootCtx,
|
||||
|
@ -11,28 +11,22 @@ import {
|
|||
import { Milkdown, useEditor } from '@milkdown/vue'
|
||||
import { commonmark } from '@milkdown/preset-commonmark'
|
||||
import { gfm } from '@milkdown/preset-gfm'
|
||||
|
||||
// Milkdown Plugins
|
||||
import { prism, prismConfig } from '@milkdown/plugin-prism'
|
||||
import { replaceAll } from '@milkdown/utils'
|
||||
|
||||
// KUN Visual Novel style
|
||||
import '@/styles/editor/index.scss'
|
||||
|
||||
// Syntax highlight
|
||||
import c from 'refractor/lang/c'
|
||||
import cpp from 'refractor/lang/cpp'
|
||||
import csharp from 'refractor/lang/csharp'
|
||||
import css from 'refractor/lang/css'
|
||||
import go from 'refractor/lang/go'
|
||||
import haskell from 'refractor/lang/haskell'
|
||||
import python from 'refractor/lang/python'
|
||||
import java from 'refractor/lang/java'
|
||||
import javascript from 'refractor/lang/javascript'
|
||||
import typescript from 'refractor/lang/typescript'
|
||||
import jsx from 'refractor/lang/jsx'
|
||||
import kotlin from 'refractor/lang/kotlin'
|
||||
import r from 'refractor/lang/r'
|
||||
import rust from 'refractor/lang/rust'
|
||||
import scala from 'refractor/lang/scala'
|
||||
import sql from 'refractor/lang/sql'
|
||||
import tsx from 'refractor/lang/tsx'
|
||||
import markdown from 'refractor/lang/markdown'
|
||||
|
@ -46,7 +40,7 @@ const valueMarkdown = computed(() => props.valueMarkdown)
|
|||
|
||||
const editable = () => !props.isReadonly
|
||||
|
||||
const editor = useEditor((root) =>
|
||||
useEditor((root) =>
|
||||
Editor.make()
|
||||
.config((ctx) => {
|
||||
ctx.set(rootCtx, root)
|
||||
|
@ -63,22 +57,16 @@ const editor = useEditor((root) =>
|
|||
|
||||
ctx.set(prismConfig.key, {
|
||||
configureRefractor: (refractor) => {
|
||||
refractor.register(c)
|
||||
refractor.register(cpp)
|
||||
refractor.register(csharp)
|
||||
refractor.register(css)
|
||||
refractor.register(go)
|
||||
refractor.register(haskell)
|
||||
refractor.register(python)
|
||||
refractor.register(markdown)
|
||||
refractor.register(java)
|
||||
refractor.register(javascript)
|
||||
refractor.register(typescript)
|
||||
refractor.register(jsx)
|
||||
refractor.register(kotlin)
|
||||
refractor.register(r)
|
||||
refractor.register(rust)
|
||||
refractor.register(scala)
|
||||
refractor.register(sql)
|
||||
refractor.register(tsx)
|
||||
},
|
||||
|
@ -88,13 +76,6 @@ const editor = useEditor((root) =>
|
|||
.use(gfm)
|
||||
.use(prism)
|
||||
)
|
||||
|
||||
watch(
|
||||
() => valueMarkdown.value,
|
||||
() => {
|
||||
editor.get()?.action(replaceAll(valueMarkdown.value))
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<!-- MilkdownEditor.vue -->
|
||||
|
@ -135,10 +116,6 @@ watch(
|
|||
scrollbar-color: var(--kungalgame-blue-4) var(--kungalgame-blue-1);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
import { onBeforeMount, ref } from 'vue'
|
||||
|
||||
import { useTempEditStore } from '@/store/temp/edit'
|
||||
import { useKUNGalgameEditStore } from '@/store/modules/edit'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import { debounce } from '@/utils/debounce'
|
||||
|
||||
const { title: rewriteTitle, isTopicRewriting } = storeToRefs(
|
||||
useTempEditStore()
|
||||
const { isSaveTopic, title, topicRewrite } = storeToRefs(
|
||||
useKUNGalgameEditStore()
|
||||
)
|
||||
const { isSaveTopic, title: editTitle } = storeToRefs(useKUNGalgameEditStore())
|
||||
|
||||
// Topic title text
|
||||
const topicTitle = ref('')
|
||||
|
@ -22,14 +20,14 @@ onBeforeMount(() => {
|
|||
* Editor is in edit mode
|
||||
*/
|
||||
if (isSaveTopic.value) {
|
||||
topicTitle.value = editTitle.value
|
||||
topicTitle.value = title.value
|
||||
}
|
||||
/**
|
||||
* Editor is in re-editing edit mode
|
||||
*/
|
||||
// Load data for re-editing a topic before mounting
|
||||
if (isTopicRewriting.value) {
|
||||
topicTitle.value = rewriteTitle.value
|
||||
if (topicRewrite.value.isTopicRewriting) {
|
||||
topicTitle.value = topicRewrite.value.title
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -41,27 +39,30 @@ const handleInput = () => {
|
|||
}
|
||||
|
||||
if (topicTitle.value.trim() === '') {
|
||||
rewriteTitle.value = ''
|
||||
editTitle.value = ''
|
||||
title.value = ''
|
||||
topicRewrite.value.title = ''
|
||||
return
|
||||
}
|
||||
|
||||
return debounce(() => {
|
||||
// Create a debounce handling function
|
||||
const debouncedInput = debounce(() => {
|
||||
/**
|
||||
* Editor is in reply mode
|
||||
*/
|
||||
// Save to the edit store if not in topic re-edit mode
|
||||
if (!isTopicRewriting.value) {
|
||||
editTitle.value = topicTitle.value
|
||||
if (!topicRewrite.value.isTopicRewriting) {
|
||||
title.value = topicTitle.value
|
||||
}
|
||||
/**
|
||||
* Editor is in re-editing edit mode
|
||||
*/
|
||||
// Save to the re-editing page's store if in re-edit mode
|
||||
if (isTopicRewriting.value) {
|
||||
rewriteTitle.value = topicTitle.value
|
||||
if (topicRewrite.value.isTopicRewriting) {
|
||||
topicRewrite.value.title = topicTitle.value
|
||||
}
|
||||
}, 300)
|
||||
|
||||
debouncedInput()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,27 +1,17 @@
|
|||
<!-- Custom plugins, calculate text size -->
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
import { computed, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import Settings from '../components/Settings.vue'
|
||||
import { usePluginViewContext } from '@prosemirror-adapter/vue'
|
||||
|
||||
import { useTempEditStore } from '@/store/temp/edit'
|
||||
import { useKUNGalgameEditStore } from '@/store/modules/edit'
|
||||
import { useTempReplyStore } from '@/store/temp/topic/reply'
|
||||
import { usePersistKUNGalgameReplyStore } from '@/store/modules/topic/reply'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const { textCount: textCountEditRewrite, isTopicRewriting } = storeToRefs(
|
||||
useTempEditStore()
|
||||
)
|
||||
const { textCount: textCountEdit } = storeToRefs(useKUNGalgameEditStore())
|
||||
const { textCount: textCountReplyRewrite, isReplyRewriting } = storeToRefs(
|
||||
useTempReplyStore()
|
||||
)
|
||||
const { textCount: textCountReply } = storeToRefs(
|
||||
usePersistKUNGalgameReplyStore()
|
||||
)
|
||||
const { textCount: textCountReply } = storeToRefs(useTempReplyStore())
|
||||
|
||||
const { view } = usePluginViewContext()
|
||||
|
||||
|
@ -35,14 +25,6 @@ const size = computed(() => {
|
|||
watch(
|
||||
() => size.value,
|
||||
() => {
|
||||
if (routeName.value === 'Edit' && isTopicRewriting.value) {
|
||||
textCountEditRewrite.value = size.value
|
||||
return
|
||||
}
|
||||
if (routeName.value === 'Topic' && isReplyRewriting.value) {
|
||||
textCountReplyRewrite.value = size.value
|
||||
return
|
||||
}
|
||||
if (routeName.value === 'Edit') {
|
||||
textCountEdit.value = size.value
|
||||
}
|
||||
|
@ -51,16 +33,6 @@ watch(
|
|||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (routeName.value === 'Edit' && isTopicRewriting.value) {
|
||||
textCountEditRewrite.value = size.value
|
||||
return
|
||||
}
|
||||
if (routeName.value === 'Topic' && isReplyRewriting.value) {
|
||||
textCountReplyRewrite.value = size.value
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, ref, watch } from 'vue'
|
||||
import SearchBox from './SearchBox.vue'
|
||||
import SearchHistory from './SearchHistory.vue'
|
||||
import SearchResult from './SearchResult.vue'
|
||||
|
||||
import { useTempHomeStore } from '@/store/temp/home'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import { HomeSearchTopic } from '@/api'
|
||||
|
||||
const { search, isShowSearch } = storeToRefs(useTempHomeStore())
|
||||
|
||||
const topics = ref<HomeSearchTopic[]>([])
|
||||
const container = ref<HTMLElement>()
|
||||
|
||||
const searchTopics = async () => {
|
||||
return (await useTempHomeStore().searchTopic()).data
|
||||
}
|
||||
|
||||
watch(
|
||||
() => search.value.keywords,
|
||||
async () => {
|
||||
if (search.value.keywords) {
|
||||
topics.value = await searchTopics()
|
||||
} else {
|
||||
topics.value = []
|
||||
useTempHomeStore().resetSearchStatus()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => container.value,
|
||||
() => {
|
||||
const element = container.value
|
||||
if (element) {
|
||||
element.addEventListener('scroll', scrollHandler)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const scrollHandler = async () => {
|
||||
if (isScrollAtBottom() && search.value.isLoading && search.value.keywords) {
|
||||
search.value.page++
|
||||
|
||||
const lazyLoadTopics = await searchTopics()
|
||||
|
||||
if (!lazyLoadTopics.length) {
|
||||
search.value.isLoading = false
|
||||
}
|
||||
|
||||
topics.value = [...topics.value, ...lazyLoadTopics]
|
||||
}
|
||||
}
|
||||
|
||||
const isScrollAtBottom = () => {
|
||||
if (container.value) {
|
||||
const scrollHeight = container.value.scrollHeight
|
||||
const scrollTop = container.value.scrollTop
|
||||
const clientHeight = container.value.clientHeight
|
||||
|
||||
const errorMargin = 1.007
|
||||
return Math.abs(scrollHeight - scrollTop - clientHeight) < errorMargin
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
const element = container.value
|
||||
if (element) {
|
||||
element.removeEventListener('scroll', scrollHandler)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body" :disabled="isShowSearch">
|
||||
<Transition name="search">
|
||||
<div class="mask" v-if="isShowSearch" @click="isShowSearch = false">
|
||||
<div ref="container" class="container" @click.stop>
|
||||
<SearchBox />
|
||||
|
||||
<SearchHistory v-if="!search.keywords" />
|
||||
|
||||
<SearchResult :topics="topics" v-if="topics.length" />
|
||||
|
||||
<span class="empty" v-if="!topics.length && search.keywords">
|
||||
{{ $tm('mainPage.header.emptyResult') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mask {
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--kungalgame-mask-color-0);
|
||||
display: flex;
|
||||
transition: opacity 0.3s ease;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--kungalgame-font-color-3);
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
color: var(--kungalgame-font-color-3);
|
||||
background-color: var(--kungalgame-trans-white-2);
|
||||
box-shadow: var(--kungalgame-shadow-0);
|
||||
border-radius: 17px;
|
||||
padding: 10px;
|
||||
width: 40vw;
|
||||
max-width: 500px;
|
||||
min-height: 200px;
|
||||
max-height: 600px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: var(--kungalgame-blue-2);
|
||||
font-style: oblique;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.search-enter-from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.search-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.search-enter-from .container,
|
||||
.search-leave-to .container {
|
||||
transition: all 0.3s ease;
|
||||
-webkit-transform: scale(1.1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.container {
|
||||
width: 60vw;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.container {
|
||||
width: 90vw;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,90 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onBeforeMount, onMounted, watch } from 'vue'
|
||||
|
||||
import { debounce } from '@/utils/debounce'
|
||||
|
||||
import { useTempHomeStore } from '@/store/temp/home'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const { search } = storeToRefs(useTempHomeStore())
|
||||
|
||||
const input = ref<HTMLElement | null>(null)
|
||||
const inputValue = ref('')
|
||||
|
||||
onBeforeMount(() => {
|
||||
search.value.keywords = ''
|
||||
})
|
||||
|
||||
const debouncedSearch = debounce((inputValue: string) => {
|
||||
if (inputValue.trim()) {
|
||||
search.value.keywords = inputValue
|
||||
} else {
|
||||
search.value.keywords = ''
|
||||
}
|
||||
}, 300)
|
||||
|
||||
watch(
|
||||
() => search.value.keywords,
|
||||
() => {
|
||||
if (!inputValue.value) {
|
||||
inputValue.value = search.value.keywords
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
if (input) {
|
||||
input.value?.focus()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-form">
|
||||
<input
|
||||
ref="input"
|
||||
v-model="inputValue"
|
||||
type="search"
|
||||
class="input"
|
||||
:placeholder="`${$tm('mainPage.header.search')}`"
|
||||
@input="debouncedSearch(inputValue)"
|
||||
@keydown.enter="debouncedSearch(inputValue)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search-form {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
max-width: 777px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 17px;
|
||||
background-color: var(--kungalgame-trans-white-2);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.input {
|
||||
padding: 0 15px;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
background-color: var(--kungalgame-trans-white-9);
|
||||
border: 2px solid var(--kungalgame-blue-4);
|
||||
border-radius: 17px;
|
||||
color: var(--kungalgame-font-color-3);
|
||||
transition: all 0.2s;
|
||||
|
||||
&:focus {
|
||||
border: 2px solid var(--kungalgame-pink-4);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--kungalgame-font-color-1);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,136 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
import { usePersistKUNGalgameHomeStore } from '@/store/modules/home'
|
||||
import { useTempHomeStore } from '@/store/temp/home'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const { searchHistory } = storeToRefs(usePersistKUNGalgameHomeStore())
|
||||
const { search } = storeToRefs(useTempHomeStore())
|
||||
|
||||
const handleClickHistory = (index: number) => {
|
||||
search.value.keywords = searchHistory.value[index]
|
||||
}
|
||||
|
||||
const clearSearchHistory = () => {
|
||||
searchHistory.value = []
|
||||
}
|
||||
|
||||
const handleDeleteHistory = (historyIndex: number) => {
|
||||
searchHistory.value.splice(historyIndex, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="history">
|
||||
<div class="title">
|
||||
<span>{{ $tm('mainPage.header.history') }}</span>
|
||||
<span @click="clearSearchHistory">
|
||||
{{ $tm('mainPage.header.clear') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="history-container" v-if="searchHistory.length">
|
||||
<div
|
||||
class="single-history"
|
||||
v-for="(history, index) in searchHistory"
|
||||
:key="index"
|
||||
@click="handleClickHistory(index)"
|
||||
>
|
||||
<span>{{ history }} </span>
|
||||
<span>
|
||||
<Icon
|
||||
@click="handleDeleteHistory(index)"
|
||||
class="delete"
|
||||
icon="line-md:close-circle"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="empty" v-if="!searchHistory.length">
|
||||
{{ $tm('mainPage.header.emptyHistory') }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.history {
|
||||
width: 100%;
|
||||
top: 70px;
|
||||
left: 0;
|
||||
flex-direction: column;
|
||||
color: var(--kungalgame-font-color-3);
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
margin: 10px;
|
||||
justify-content: space-between;
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
&:nth-child(2) {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: var(--kungalgame-blue-4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.history-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 13px;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.single-history {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space between;
|
||||
padding: 7px 3px;
|
||||
margin: 2px 0;
|
||||
|
||||
&:hover {
|
||||
color: var(--kungalgame-blue-4);
|
||||
|
||||
.delete {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
span:nth-child(1) {
|
||||
cursor: default;
|
||||
position: relative;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
span:nth-child(2) {
|
||||
width: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
.delete {
|
||||
width: 30px;
|
||||
right: 5px;
|
||||
font-size: 17px;
|
||||
position: absolute;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: var(--kungalgame-font-color-0);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: var(--kungalgame-blue-2);
|
||||
font-style: oblique;
|
||||
}
|
||||
</style>
|
|
@ -1,94 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { HomeSearchTopic } from '@/api'
|
||||
|
||||
import { usePersistKUNGalgameHomeStore } from '@/store/modules/home'
|
||||
|
||||
const { searchHistory } = storeToRefs(usePersistKUNGalgameHomeStore())
|
||||
import { useTempHomeStore } from '@/store/temp/home'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const router = useRouter()
|
||||
const { search, isShowSearch } = storeToRefs(useTempHomeStore())
|
||||
|
||||
const props = defineProps<{
|
||||
topics: HomeSearchTopic[]
|
||||
}>()
|
||||
|
||||
const topics = computed(() => props.topics)
|
||||
|
||||
const handleClickTopic = (tid: number) => {
|
||||
router.push(`/topic/${tid}`)
|
||||
if (!searchHistory.value.includes(search.value.keywords)) {
|
||||
searchHistory.value.push(search.value.keywords)
|
||||
}
|
||||
isShowSearch.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="result">
|
||||
<div
|
||||
v-for="(topic, index) in topics"
|
||||
:key="index"
|
||||
:to="`/topic/${topic.tid}`"
|
||||
class="topic"
|
||||
@click="handleClickTopic(topic.tid)"
|
||||
>
|
||||
<span class="title">{{ topic.title }}</span>
|
||||
<span class="content">{{ topic.content }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.result {
|
||||
width: 100%;
|
||||
top: 70px;
|
||||
left: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.topic {
|
||||
color: var(--kungalgame-font-color-3);
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
background-color: var(--kungalgame-trans-blue-0);
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 17px;
|
||||
cursor: pointer;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition: all 0.2s;
|
||||
background-color: var(--kungalgame-white);
|
||||
box-shadow: var(--kungalgame-shadow-1);
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
&:nth-child(1) {
|
||||
white-space: wrap;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
color: var(--kungalgame-blue-5);
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
white-space: wrap;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,3 +1,4 @@
|
|||
<!-- Settings panel component, displaying the entire forum's settings panel -->
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
|
@ -18,16 +19,19 @@ const emits = defineEmits<{
|
|||
close: [showKUNGalgamePanel: boolean]
|
||||
}>()
|
||||
|
||||
// Restore all settings to default
|
||||
const handleRecover = () => {
|
||||
settingsStore.setKUNGalgameSettingsRecover()
|
||||
}
|
||||
|
||||
// Close the settings panel
|
||||
const handelCloseSettingsPanel = () => {
|
||||
emits('close', false)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Root element -->
|
||||
<div class="root">
|
||||
<div class="container">
|
||||
<div class="title">
|
||||
|
@ -35,8 +39,10 @@ const handelCloseSettingsPanel = () => {
|
|||
<span><Icon class="settings-icon" icon="uiw:setting-o" /></span>
|
||||
</div>
|
||||
|
||||
<!-- Mode switch component -->
|
||||
<Mode />
|
||||
|
||||
<!-- Language switch component -->
|
||||
<SwitchLanguage />
|
||||
|
||||
<div class="switch">
|
||||
|
@ -57,15 +63,18 @@ const handelCloseSettingsPanel = () => {
|
|||
|
||||
<TransitionGroup name="item" tag="div">
|
||||
<div class="item" v-if="isShowPageWidth">
|
||||
<!-- Page width adjustment component -->
|
||||
<PageWidth />
|
||||
</div>
|
||||
|
||||
<div class="item" v-else-if="!isShowPageWidth">
|
||||
<!-- Set the page font -->
|
||||
<Font />
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
<!-- Background settings component -->
|
||||
<Background />
|
||||
|
||||
<button class="reset" @click="handleRecover">
|
||||
|
@ -73,15 +82,19 @@ const handelCloseSettingsPanel = () => {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Mascot component -->
|
||||
<Loli class="loli" />
|
||||
|
||||
<!-- Close panel -->
|
||||
<div class="close">
|
||||
<!-- showKUNGalgamePanel exists in the settings, false to close the settings panel -->
|
||||
<Icon @click="handelCloseSettingsPanel" icon="line-md:close" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* Root container */
|
||||
.root {
|
||||
top: 65px;
|
||||
right: 0;
|
||||
|
@ -113,6 +126,7 @@ const handelCloseSettingsPanel = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// Keep the settings button rotating
|
||||
.settings-icon {
|
||||
animation: settings 3s linear infinite;
|
||||
}
|
||||
|
@ -126,6 +140,7 @@ const handelCloseSettingsPanel = () => {
|
|||
}
|
||||
}
|
||||
|
||||
/* Menu for switching settings options */
|
||||
.switch {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -189,7 +204,7 @@ const handelCloseSettingsPanel = () => {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-move,
|
||||
.item-move, /* Transition applied to moving elements */
|
||||
.item-enter-active,
|
||||
.item-leave-active {
|
||||
transition: all 0.5s ease;
|
||||
|
@ -201,6 +216,8 @@ const handelCloseSettingsPanel = () => {
|
|||
transform: translateY(77px);
|
||||
}
|
||||
|
||||
/* Ensure the leaving element is removed from the layout flow
|
||||
to correctly calculate the animated movement. */
|
||||
.item-leave-active {
|
||||
position: absolute;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref } from 'vue'
|
||||
import CustomBackground from './CustomBackground.vue'
|
||||
import Message from '@/components/alert/Message'
|
||||
import BackgroundImageSkeleton from '@/components/skeleton/settings-panel/BackgroundImageSkeleton.vue'
|
||||
|
||||
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||
|
@ -11,16 +11,35 @@ import { getBackgroundURL } from '@/hooks/useBackgroundPicture'
|
|||
import { restoreBackground } from '@/hooks/useBackgroundPicture'
|
||||
|
||||
const imageArray = ref<string[]>([])
|
||||
const { showKUNGalgameBackground } = storeToRefs(useKUNGalgameSettingsStore())
|
||||
// Use the settings panel store
|
||||
const { showKUNGalgameBackground, showKUNGalgameCustomBackground } =
|
||||
storeToRefs(useKUNGalgameSettingsStore())
|
||||
|
||||
// Get background image thumbnails
|
||||
const getBackground = async (imageNumber: number) => {
|
||||
return await getBackgroundURL(`bg${imageNumber}-m`)
|
||||
}
|
||||
|
||||
// Change the background image
|
||||
const handleChangeImage = (index: number) => {
|
||||
showKUNGalgameBackground.value = `bg${index}`
|
||||
}
|
||||
|
||||
// Custom background
|
||||
const url = ref('')
|
||||
|
||||
const handleCustomBackground = () => {
|
||||
if (url.value) {
|
||||
showKUNGalgameCustomBackground.value = url.value
|
||||
showKUNGalgameBackground.value = 'bg1007'
|
||||
url.value = ''
|
||||
} else {
|
||||
Message('Please input a valid image URL', '请输入合法的图片链接', 'warn')
|
||||
}
|
||||
}
|
||||
|
||||
const handleHoverBackgroundImage = () => {}
|
||||
|
||||
onMounted(async () => {
|
||||
for (const background of backgroundImages) {
|
||||
const backgroundURL = await getBackground(background.index)
|
||||
|
@ -37,6 +56,7 @@ onMounted(async () => {
|
|||
<ul class="kungalgame-background-container">
|
||||
<li>
|
||||
<span>{{ $tm('header.settings.preset') }}</span>
|
||||
<!-- Preset background collection -->
|
||||
<ul class="kungalgame-restore-bg">
|
||||
<li
|
||||
v-for="kun in backgroundImages"
|
||||
|
@ -47,6 +67,7 @@ onMounted(async () => {
|
|||
v-if="kun"
|
||||
:src="imageArray[kun.index - 1]"
|
||||
@click="handleChangeImage(kun.index)"
|
||||
@hover="handleHoverBackgroundImage"
|
||||
/>
|
||||
|
||||
<BackgroundImageSkeleton v-if="!imageArray[kun.index - 1]" />
|
||||
|
@ -54,9 +75,29 @@ onMounted(async () => {
|
|||
</ul>
|
||||
</li>
|
||||
|
||||
<!-- User-customized background -->
|
||||
<li>
|
||||
<CustomBackground />
|
||||
<!-- Title -->
|
||||
<span>{{ $tm('header.settings.custom') }}</span>
|
||||
|
||||
<!-- Input field -->
|
||||
<div class="kungalgamer-bg">
|
||||
<div class="bg-url-input">
|
||||
<input
|
||||
:placeholder="`${$tm('header.settings.url')}`"
|
||||
type="text"
|
||||
v-model="url"
|
||||
required
|
||||
/>
|
||||
|
||||
<!-- Confirm background URL -->
|
||||
<button @click="handleCustomBackground">
|
||||
{{ $tm('header.settings.confirm') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reset blank background -->
|
||||
<button class="restore-bg" @click="restoreBackground">
|
||||
{{ $tm('header.settings.restore') }}
|
||||
</button>
|
||||
|
@ -66,19 +107,23 @@ onMounted(async () => {
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* Background settings */
|
||||
.kungalgame-background-container {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
/* Height of the background menu */
|
||||
height: 100%;
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
color: var(--kungalgame-font-color-3);
|
||||
|
||||
/* Font for the title of the background container */
|
||||
span {
|
||||
height: 30px;
|
||||
/* Centered */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
@ -89,6 +134,7 @@ onMounted(async () => {
|
|||
margin: 10px 0;
|
||||
}
|
||||
|
||||
/* Grid of background image thumbnails, three rows and three columns */
|
||||
.kungalgame-restore-bg {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -99,18 +145,22 @@ onMounted(async () => {
|
|||
grid-template-columns: repeat(3, 80px);
|
||||
grid-template-rows: repeat(3, 50px);
|
||||
position: relative;
|
||||
/* Distance from the lower area */
|
||||
margin-bottom: 10px;
|
||||
|
||||
/* Center individual images */
|
||||
li {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
/* Spacing for individual images */
|
||||
img {
|
||||
cursor: pointer;
|
||||
width: 70px;
|
||||
position: relative;
|
||||
|
||||
/* Image hover effect */
|
||||
&:hover {
|
||||
transform: scale(3);
|
||||
transition: 0.2s;
|
||||
|
@ -123,6 +173,54 @@ onMounted(async () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
.kungalgamer-bg {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
/* URL input box */
|
||||
.bg-url-input {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--kungalgame-font-color-3);
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding-left: 5px;
|
||||
height: 25px;
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
background-color: var(--kungalgame-trans-white-9);
|
||||
color: var(--kungalgame-font-color-3);
|
||||
|
||||
/* Focus on the input box */
|
||||
&:focus {
|
||||
outline: none;
|
||||
background-color: var(--kungalgame-trans-blue-0);
|
||||
}
|
||||
}
|
||||
/* Confirm button */
|
||||
button {
|
||||
flex-shrink: 0;
|
||||
padding: 0 10px;
|
||||
height: 25px;
|
||||
width: 70px;
|
||||
color: var(--kungalgame-font-color-3);
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
border-left: none;
|
||||
background-color: var(--kungalgame-trans-white-5);
|
||||
cursor: pointer;
|
||||
|
||||
/* Confirm button hover effect */
|
||||
&:hover {
|
||||
background-color: var(--kungalgame-trans-red-1);
|
||||
|
||||
/* Confirm button active effect */
|
||||
&:active {
|
||||
background-color: var(--kungalgame-trans-red-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.restore-bg {
|
||||
font-size: 15px;
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import Message from '@/components/alert/Message'
|
||||
|
||||
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import { saveImage, getImage } from '@/hooks/useLocalforage'
|
||||
|
||||
const { showKUNGalgameBackground, showKUNGalgameCustomBackground } =
|
||||
storeToRefs(useKUNGalgameSettingsStore())
|
||||
|
||||
const props = defineProps<{
|
||||
isMobile?: boolean
|
||||
}>()
|
||||
const input = ref<HTMLElement>()
|
||||
|
||||
const handleCustomBackground = () => {
|
||||
input.value?.click()
|
||||
}
|
||||
|
||||
const handleFileChange = async (event: Event) => {
|
||||
const input = event.target as HTMLInputElement
|
||||
|
||||
if (!input.files || !input.files[0]) {
|
||||
return
|
||||
}
|
||||
|
||||
const file = input.files[0]
|
||||
await saveImage(file, 'kun-galgame-custom-bg')
|
||||
const backgroundImageBlobData = await getImage('kun-galgame-custom-bg')
|
||||
|
||||
if (backgroundImageBlobData) {
|
||||
showKUNGalgameBackground.value = 'bg1007'
|
||||
showKUNGalgameCustomBackground.value = URL.createObjectURL(
|
||||
backgroundImageBlobData
|
||||
)
|
||||
} else {
|
||||
Message('Upload image failed!', '上传图片错误!', 'error')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="kungalgamer-bg">
|
||||
<input
|
||||
ref="input"
|
||||
hidden
|
||||
type="file"
|
||||
accept=".jpg, .jpeg, .png"
|
||||
@change="handleFileChange($event)"
|
||||
/>
|
||||
<button
|
||||
:class="props.isMobile ? 'mobile' : ''"
|
||||
@click="handleCustomBackground"
|
||||
>
|
||||
{{ $tm('header.settings.custom') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.kungalgamer-bg {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
button {
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
color: var(--kungalgame-font-color-3);
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
background-color: var(--kungalgame-trans-white-9);
|
||||
transition: all 0.2s;
|
||||
color: var(--kungalgame-blue-4);
|
||||
|
||||
&:hover {
|
||||
color: var(--kungalgame-white);
|
||||
background-color: var(--kungalgame-blue-4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
border-radius: 14px;
|
||||
}
|
||||
</style>
|
|
@ -1,5 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// Global message component (top)
|
||||
import Message from '@/components/alert/Message'
|
||||
|
||||
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||
|
@ -21,6 +23,7 @@ const setFont = () => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Set the width of certain pages -->
|
||||
<div class="font">
|
||||
<div class="title">
|
||||
<span>{{ $tm('header.settings.font') }}</span>
|
||||
|
@ -31,7 +34,6 @@ const setFont = () => {
|
|||
{{ showKUNGalgameFontStyle }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="font-input">
|
||||
<input
|
||||
:placeholder="`${$tm('header.settings.fontInput')}`"
|
||||
|
@ -53,6 +55,7 @@ const setFont = () => {
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* URL input box */
|
||||
.font-input {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -68,12 +71,13 @@ const setFont = () => {
|
|||
background-color: var(--kungalgame-trans-white-9);
|
||||
color: var(--kungalgame-font-color-3);
|
||||
|
||||
/* Focus on the input box */
|
||||
&:focus {
|
||||
outline: none;
|
||||
background-color: var(--kungalgame-trans-blue-0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Confirm button */
|
||||
button {
|
||||
flex-shrink: 0;
|
||||
padding: 0 10px;
|
||||
|
@ -85,9 +89,11 @@ const setFont = () => {
|
|||
background-color: var(--kungalgame-trans-white-5);
|
||||
cursor: pointer;
|
||||
|
||||
/* Confirm button hover effect */
|
||||
&:hover {
|
||||
background-color: var(--kungalgame-trans-red-1);
|
||||
|
||||
/* Confirm button active effect */
|
||||
&:active {
|
||||
background-color: var(--kungalgame-trans-red-3);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { useLoliDataURL } from '@/hooks/useLoli'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { getLoli } from './loli'
|
||||
|
||||
import LoliSkeleton from '@/components/skeleton/settings-panel/LoliSkeleton.vue'
|
||||
import KUNGalgameLoading from '@/components/loading/KUNGalgameLoading.vue'
|
||||
|
@ -26,12 +26,12 @@ const isShowLoading = ref(false)
|
|||
|
||||
const reGetLoli = async () => {
|
||||
isShowLoading.value = true
|
||||
loliData.value = await getLoli()
|
||||
loliData.value = await useLoliDataURL()
|
||||
isShowLoading.value = false
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await reGetLoli()
|
||||
loliData.value = await useLoliDataURL()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,25 +1,30 @@
|
|||
<script setup lang="ts">
|
||||
// Import the icon font
|
||||
import { Icon } from '@iconify/vue'
|
||||
// Import the settings store
|
||||
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
// Use the settings store
|
||||
const settingsStore = useKUNGalgameSettingsStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mode">
|
||||
<!-- Day / Night mode toggle -->
|
||||
<span>{{ $tm('header.settings.mode') }}</span>
|
||||
<div class="mode-container">
|
||||
<span>
|
||||
<Icon
|
||||
class="sun"
|
||||
icon="line-md:moon-filled-alt-to-sunny-filled-loop-transition"
|
||||
@click="useKUNGalgameSettingsStore().setKUNGalgameTheme('')"
|
||||
@click="settingsStore.setKUNGalgameTheme('')"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<Icon
|
||||
class="moon"
|
||||
icon="line-md:sunny-outline-to-moon-loop-transition"
|
||||
@click="useKUNGalgameSettingsStore().setKUNGalgameTheme('dark')"
|
||||
@click="settingsStore.setKUNGalgameTheme('dark')"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -33,7 +38,6 @@ import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.mode-container {
|
||||
font-size: 25px;
|
||||
width: 60%;
|
||||
|
|
|
@ -9,10 +9,14 @@ const route = useRoute()
|
|||
const settingsStore = useKUNGalgameSettingsStore()
|
||||
const { showKUNGalgamePageWidth } = storeToRefs(settingsStore)
|
||||
|
||||
// Page width
|
||||
const pageWidth = ref(0)
|
||||
// Current route name
|
||||
const routeName = computed(() => route.name as string)
|
||||
// Whether page width adjustment is disabled
|
||||
const isDisabled = ref(false)
|
||||
|
||||
// Pages where width adjustment is allowed
|
||||
const pageNameArray = [
|
||||
'KUN',
|
||||
'Topic',
|
||||
|
@ -24,9 +28,12 @@ const pageNameArray = [
|
|||
'ThanksList',
|
||||
]
|
||||
|
||||
// Initialize page width
|
||||
const initPageWidth = () => {
|
||||
if (pageNameArray.includes(routeName.value)) {
|
||||
// Page width value equals store width value
|
||||
pageWidth.value = showKUNGalgamePageWidth.value[routeName.value]
|
||||
// Enable input
|
||||
isDisabled.value = false
|
||||
} else {
|
||||
isDisabled.value = true
|
||||
|
@ -35,6 +42,7 @@ const initPageWidth = () => {
|
|||
|
||||
watch(pageWidth, () => {
|
||||
if (pageNameArray.includes(routeName.value)) {
|
||||
// Store user-input width
|
||||
showKUNGalgamePageWidth.value[routeName.value] = pageWidth.value
|
||||
}
|
||||
})
|
||||
|
@ -49,6 +57,7 @@ onActivated(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Set the width for specific pages -->
|
||||
<div
|
||||
class="width"
|
||||
:class="isDisabled ? 'disabled' : ''"
|
||||
|
@ -58,7 +67,6 @@ onActivated(() => {
|
|||
<span>{{ $tm('header.settings.width') }}</span>
|
||||
<span>{{ pageWidth }}%</span>
|
||||
</div>
|
||||
|
||||
<div class="page-width">
|
||||
<span>50%</span>
|
||||
<input
|
||||
|
@ -85,7 +93,7 @@ onActivated(() => {
|
|||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Main page width slider */
|
||||
.main {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
|
@ -97,6 +105,7 @@ onActivated(() => {
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Styles when page width adjustment is disabled */
|
||||
.disabled {
|
||||
cursor: not-allowed;
|
||||
color: var(--kungalgame-font-color-0);
|
||||
|
|
|
@ -1,11 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import { watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
// Import the settings store
|
||||
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { watch } from 'vue'
|
||||
// Import i18n
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { showKUNGalgameLanguage } = storeToRefs(useKUNGalgameSettingsStore())
|
||||
// Use the settings store
|
||||
const settingsStore = useKUNGalgameSettingsStore()
|
||||
const { showKUNGalgameLanguage } = storeToRefs(settingsStore)
|
||||
|
||||
/*
|
||||
* Website language settings
|
||||
*/
|
||||
const { locale } = useI18n({ useScope: 'global' })
|
||||
|
||||
watch(showKUNGalgameLanguage, () => {
|
||||
|
@ -24,12 +31,13 @@ watch(showKUNGalgameLanguage, () => {
|
|||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// Language settings
|
||||
.set-lang {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
// Language selection box
|
||||
.select {
|
||||
width: 100px;
|
||||
font-size: 16px;
|
||||
|
|
|
@ -1,72 +0,0 @@
|
|||
import loliData from '@/assets/ren/ren.json'
|
||||
import { randomNum } from '@/utils/random'
|
||||
|
||||
export const getLoli = async () => {
|
||||
const linkPreset = `https://cdn.jsdelivr.net/gh/kun-moe/kun-image@main/ren/`
|
||||
const getAssetsFile = (name: number) => `${linkPreset}${name}.webp`
|
||||
|
||||
const randomBrow = randomNum(1, 18)
|
||||
const randomEye = randomNum(19, 36)
|
||||
const randomMouth = randomNum(37, 56)
|
||||
const randomFace = randomNum(57, 62)
|
||||
const randomSkirt = randomNum(63, 70)
|
||||
|
||||
const loli = {
|
||||
lass: loliData[randomSkirt],
|
||||
eye: loliData[randomEye],
|
||||
brow: loliData[randomBrow],
|
||||
mouth: loliData[randomMouth],
|
||||
face: loliData[randomFace],
|
||||
}
|
||||
|
||||
const loliBodyLeft = `${loli.lass.left}px`
|
||||
const loliBodyTop = `${loli.lass.top}px`
|
||||
|
||||
const loliEyeLeft = `${loli.eye.left}px`
|
||||
const loliEyeTop = `${loli.eye.top}px`
|
||||
|
||||
const loliBrowLeft = `${loli.brow.left}px`
|
||||
const loliBrowTop = `${loli.brow.top}px`
|
||||
|
||||
const loliMouthLeft = `${loli.mouth.left}px`
|
||||
const loliMouthTop = `${loli.mouth.top}px`
|
||||
|
||||
const loliFaceLeft = `${loli.face.left}px`
|
||||
const loliFaceTop = `${loli.face.top}px`
|
||||
|
||||
const promises = [
|
||||
getAssetsFile(loli.lass.layer_id),
|
||||
getAssetsFile(loli.eye.layer_id),
|
||||
getAssetsFile(loli.brow.layer_id),
|
||||
getAssetsFile(loli.mouth.layer_id),
|
||||
getAssetsFile(loli.face.layer_id),
|
||||
].map((url) => fetch(url))
|
||||
|
||||
const responses = await Promise.all(promises)
|
||||
const results = await Promise.all(
|
||||
responses.map((response) => response.blob())
|
||||
)
|
||||
|
||||
const [body, eye, brow, mouth, face] = results.map((blob) =>
|
||||
URL.createObjectURL(blob)
|
||||
)
|
||||
|
||||
return {
|
||||
loliBodyLeft,
|
||||
loliBodyTop,
|
||||
loliEyeLeft,
|
||||
loliEyeTop,
|
||||
loliBrowLeft,
|
||||
loliBrowTop,
|
||||
loliMouthLeft,
|
||||
loliMouthTop,
|
||||
loliFaceLeft,
|
||||
loliFaceTop,
|
||||
|
||||
body,
|
||||
eye,
|
||||
brow,
|
||||
mouth,
|
||||
face,
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
const count = 10
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-for="(_, index) in count" :key="index" class="skeleton">
|
||||
<ul>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.skeleton {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: var(--kungalgame-trans-white-5);
|
||||
border-radius: 3px;
|
||||
margin: 0 auto;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
||||
li {
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
var(--kungalgame-trans-blue-2) 25%,
|
||||
var(--kungalgame-pink-0) 37%,
|
||||
var(--kungalgame-trans-blue-2) 63%
|
||||
);
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
list-style: none;
|
||||
background-size: 400% 100%;
|
||||
margin-top: 10px;
|
||||
background-position: 100% 50%;
|
||||
animation: skeleton 1.7s ease infinite;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
width: 50%;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
width: 77%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes skeleton {
|
||||
0% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 0 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,85 +0,0 @@
|
|||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div class="skeleton">
|
||||
<div class="container">
|
||||
<span></span>
|
||||
<ul>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.skeleton {
|
||||
margin-top: 30px;
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: var(--kungalgame-trans-white-5);
|
||||
border-radius: 3px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
span {
|
||||
flex-shrink: 0;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: var(--kungalgame-trans-blue-2);
|
||||
margin-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
||||
li {
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
var(--kungalgame-trans-blue-2) 25%,
|
||||
var(--kungalgame-pink-0) 37%,
|
||||
var(--kungalgame-trans-blue-2) 63%
|
||||
);
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
height: 17px;
|
||||
list-style: none;
|
||||
background-size: 400% 100%;
|
||||
margin-top: 10px;
|
||||
background-position: 100% 50%;
|
||||
animation: skeleton 1.7s ease infinite;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
height: 30px;
|
||||
width: 23%;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
width: 77%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes skeleton {
|
||||
0% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 0 50%;
|
||||
}
|
||||
}
|
||||
</style>
|
94
src/components/tooltip/KUNGalgameTooltip.vue
Normal file
94
src/components/tooltip/KUNGalgameTooltip.vue
Normal file
|
@ -0,0 +1,94 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, defineProps } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
content: String,
|
||||
position: String, // 'top', 'bottom', 'left', 'right'
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tooltip">
|
||||
<div class="tooltip-content">
|
||||
{{ content }}
|
||||
</div>
|
||||
<div class="tooltip-arrow"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.tooltip-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tooltip-arrow {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-width: 6px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.tooltip.top .tooltip-content {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.tooltip.top .tooltip-arrow {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
border-color: transparent transparent #333 transparent;
|
||||
}
|
||||
|
||||
.tooltip.bottom .tooltip-content {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.tooltip.bottom .tooltip-arrow {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
border-color: #333 transparent transparent transparent;
|
||||
}
|
||||
|
||||
.tooltip.left .tooltip-content {
|
||||
top: 50%;
|
||||
right: 100%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.tooltip.left .tooltip-arrow {
|
||||
top: 50%;
|
||||
right: 100%;
|
||||
margin-top: -6px;
|
||||
border-color: transparent #333 transparent transparent;
|
||||
}
|
||||
|
||||
.tooltip.right .tooltip-content {
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.tooltip.right .tooltip-arrow {
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
margin-top: -6px;
|
||||
border-color: transparent transparent transparent #333;
|
||||
}
|
||||
</style>
|
|
@ -1,8 +1,8 @@
|
|||
<!-- This file is for adapting the top navigation bar for mobile devices -->
|
||||
<script setup lang="ts">
|
||||
import Mode from '../setting-panel/components/Mode.vue'
|
||||
import SwitchLanguage from '../setting-panel/components/SwitchLanguage.vue'
|
||||
import CustomBackground from '../setting-panel/components/CustomBackground.vue'
|
||||
import { hamburgerItem } from './hamburgerItem'
|
||||
import { topBarItem } from './topBarItem'
|
||||
|
||||
defineEmits(['showKUNGalgameHamburger'])
|
||||
</script>
|
||||
|
@ -20,22 +20,18 @@ defineEmits(['showKUNGalgameHamburger'])
|
|||
</div>
|
||||
<!-- Interactive items -->
|
||||
<div class="item" style="font-size: 17px">
|
||||
<span v-for="kun in hamburgerItem" :key="kun.index">
|
||||
<span v-for="kun in topBarItem" :key="kun.index">
|
||||
<RouterLink :to="kun.router">
|
||||
{{ $tm(`header.hamburger.${kun.name}`) }}
|
||||
{{ $tm(`header['${kun.name}']`) }}
|
||||
</RouterLink>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<Mode style="font-size: 15px" />
|
||||
<!-- Day and night mode switch component -->
|
||||
<Mode style="font-size: 20px" />
|
||||
|
||||
<SwitchLanguage style="font-size: 15px" />
|
||||
|
||||
<CustomBackground :is-mobile="true" />
|
||||
|
||||
<div class="home">
|
||||
<RouterLink to="/kun">{{ $tm('header.hamburger.home') }}</RouterLink>
|
||||
</div>
|
||||
<!-- Language switch component -->
|
||||
<SwitchLanguage style="font-size: 20px" />
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
@ -58,9 +54,8 @@ defineEmits(['showKUNGalgameHamburger'])
|
|||
}
|
||||
|
||||
.container {
|
||||
height: 100vh;
|
||||
position: absolute;
|
||||
width: 247px;
|
||||
width: 277px;
|
||||
padding: 10px;
|
||||
background-color: var(--kungalgame-trans-white-2);
|
||||
border: 1px solid var(--kungalgame-blue-1);
|
||||
|
@ -93,21 +88,4 @@ defineEmits(['showKUNGalgameHamburger'])
|
|||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.home {
|
||||
width: 100%;
|
||||
margin-top: 50px;
|
||||
|
||||
a {
|
||||
padding: 5px 10px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 17px;
|
||||
font-size: 17px;
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
color: var(--kungalgame-blue-4);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,29 +4,33 @@ import { Icon } from '@iconify/vue'
|
|||
import 'animate.css'
|
||||
import { topBarItem } from './topBarItem'
|
||||
import { onBeforeRouteLeave } from 'vue-router'
|
||||
|
||||
// Mobile version hamburger
|
||||
const Hamburger = defineAsyncComponent(() => import('./Hamburger.vue'))
|
||||
// Settings panel
|
||||
const KUNGalgameSettingsPanel = defineAsyncComponent(
|
||||
() => import('../setting-panel/KUNGalgameSettingPanel.vue')
|
||||
)
|
||||
// Panel when clicking on the user's avatar
|
||||
const KUNGalgameUserInfo = defineAsyncComponent(
|
||||
() => import('./KUNGalgameUserInfo.vue')
|
||||
)
|
||||
|
||||
import { useTempHomeStore } from '@/store/temp/home'
|
||||
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const { isShowSearch } = storeToRefs(useTempHomeStore())
|
||||
const { name, avatarMin } = storeToRefs(useKUNGalgameUserStore())
|
||||
|
||||
const showKUNGalgameHamburger = ref(false)
|
||||
// Show settings panel state
|
||||
const showKUNGalgamePanel = ref(false)
|
||||
// Show mobile mode hamburger state
|
||||
const showKUNGalgameHamburger = ref(false)
|
||||
// Show user panel when clicking on the user's avatar
|
||||
const showKUNGalgameUserPanel = ref(false)
|
||||
|
||||
// Set the navigation bar width based on the number of navigation items
|
||||
const navItemNum = topBarItem.length
|
||||
const navItemLength = `${navItemNum}00px`
|
||||
|
||||
// Destroy the SettingsPanel and Hamburger before leaving the route
|
||||
onBeforeRouteLeave(() => {
|
||||
showKUNGalgamePanel.value = false
|
||||
showKUNGalgameHamburger.value = false
|
||||
|
@ -35,6 +39,7 @@ onBeforeRouteLeave(() => {
|
|||
|
||||
<template>
|
||||
<div class="header">
|
||||
<!-- Top left interactive bar -->
|
||||
<div class="nav-top">
|
||||
<div class="hamburger">
|
||||
<Icon
|
||||
|
@ -50,32 +55,32 @@ onBeforeRouteLeave(() => {
|
|||
</Transition>
|
||||
</div>
|
||||
|
||||
<!-- Website name and logo -->
|
||||
<div class="kungalgame">
|
||||
<RouterLink to="/kun">
|
||||
<img
|
||||
src="@/assets/images/favicon.webp"
|
||||
alt="KUN Visual Novel | 鲲 Galgame"
|
||||
alt="KUN Visual Novel 鲲 Galgame"
|
||||
/>
|
||||
<span>{{ $tm('header.name') }}</span>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<!-- Navigation bar -->
|
||||
<div class="top-bar">
|
||||
<!-- Top individual sections -->
|
||||
<span v-for="kun in topBarItem" :key="kun.index">
|
||||
<RouterLink :to="{ path: kun.router }">
|
||||
{{ $tm(`header.${kun.name}`) }}
|
||||
{{ $tm(`header['${kun.name}']`) }}
|
||||
</RouterLink>
|
||||
</span>
|
||||
|
||||
<!-- Hover effect under the top section -->
|
||||
<div class="box"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kungalgamer-info">
|
||||
<span class="search" @click="isShowSearch = true">
|
||||
<Icon icon="line-md:search" />
|
||||
</span>
|
||||
|
||||
<!-- showKUNGalgamePanel is a boolean value in the store, true/false controls the display and close of the settings panel -->
|
||||
<span
|
||||
class="settings"
|
||||
@click="showKUNGalgamePanel = !showKUNGalgamePanel"
|
||||
|
@ -125,6 +130,7 @@ onBeforeRouteLeave(() => {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
/* 相对于设置面板定位 */
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-bottom: 7px;
|
||||
|
@ -236,16 +242,6 @@ $navNumber: v-bind(navItemNum);
|
|||
align-items: center;
|
||||
margin-right: 50px;
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--kungalgame-font-color-2);
|
||||
font-size: 25px;
|
||||
cursor: pointer;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.settings {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -311,13 +307,12 @@ $navNumber: v-bind(navItemNum);
|
|||
margin-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.settings {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.settings {
|
||||
display: none !important;
|
||||
}
|
||||
.top-bar {
|
||||
display: none;
|
||||
}
|
||||
|
@ -332,11 +327,5 @@ $navNumber: v-bind(navItemNum);
|
|||
.kungalgamer-info {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
img {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useRouter } from 'vue-router'
|
|||
// Global message component (top)
|
||||
import Message from '@/components/alert/Message'
|
||||
// Global message component (bottom)
|
||||
import { useTempMessageStore } from '@/store/temp/message'
|
||||
import { useKUNGalgameMessageStore } from '@/store/modules/message'
|
||||
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||
import { storeToRefs } from 'pinia'
|
||||
// Reset store
|
||||
|
@ -33,7 +33,10 @@ const handlePanelBlur = async () => {
|
|||
// Log out - for simplicity, the code here does not communicate with the backend to remove the token from Redis.
|
||||
const logOut = async () => {
|
||||
// Get the user's response
|
||||
const res = await useTempMessageStore().alert('AlertInfo.edit.logout', true)
|
||||
const res = await useKUNGalgameMessageStore().alert(
|
||||
'AlertInfo.edit.logout',
|
||||
true
|
||||
)
|
||||
if (res) {
|
||||
kungalgameStoreReset()
|
||||
router.push('/login')
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
// Interface for individual items in the top navigation bar
|
||||
interface Hamburger {
|
||||
index: number
|
||||
name: string
|
||||
router: string
|
||||
}
|
||||
|
||||
// Items in the top navigation bar
|
||||
// (be sure to include '/' here, or child routes may have issues!!!)
|
||||
export const hamburgerItem: Hamburger[] = [
|
||||
{ index: 1, name: 'pool', router: '/pool' },
|
||||
{ index: 2, name: 'create', router: '/edit' },
|
||||
{ index: 3, name: 'technique', router: '/technique' },
|
||||
{ index: 4, name: 'about', router: '/kungalgame' },
|
||||
{ index: 5, name: 'ranking', router: '/ranking' },
|
||||
{ index: 6, name: 'update', router: '/update-log' },
|
||||
{ index: 7, name: 'bylaw', router: '/bylaw' },
|
||||
{ index: 8, name: 'balance', router: '/balance' },
|
||||
{ index: 9, name: 'nonMoe', router: '/non-moe' },
|
||||
{ index: 10, name: 'thanks', router: '/thanks-list' },
|
||||
{ index: 11, name: 'join', router: '/contact' },
|
||||
]
|
|
@ -2,23 +2,21 @@
|
|||
import { ref, watch } from 'vue'
|
||||
|
||||
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||
import { useTempMessageStore } from '@/store/temp/message'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useKUNGalgameMessageStore } from '@/store/modules/message'
|
||||
const info = useKUNGalgameMessageStore()
|
||||
|
||||
// The parent component instructs it to send the verification code, and it will do so.
|
||||
const props = defineProps<{
|
||||
email: string
|
||||
isSendCode: boolean
|
||||
}>()
|
||||
|
||||
const { isCaptureSuccessful } = storeToRefs(useTempMessageStore())
|
||||
const info = useTempMessageStore()
|
||||
|
||||
const isSendCode = ref(false)
|
||||
const isSending = ref(false)
|
||||
|
||||
const countdown = ref(0)
|
||||
|
||||
watch(
|
||||
() => isSendCode.value,
|
||||
() => props.isSendCode,
|
||||
async () => {
|
||||
if (!isSending.value) {
|
||||
isSending.value = true
|
||||
|
@ -39,16 +37,10 @@ watch(
|
|||
}
|
||||
}
|
||||
)
|
||||
|
||||
const handleSendCode = () => {
|
||||
if (isCaptureSuccessful.value) {
|
||||
isSendCode.value = !isSendCode.value
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button @click="handleSendCode" :disabled="isSending">
|
||||
<button :disabled="isSending">
|
||||
{{ isSending ? countdown : $tm('login.register.send') }}
|
||||
</button>
|
||||
</template>
|
||||
|
|
|
@ -1,86 +1,29 @@
|
|||
/* -B means backend, message error code is defined by backend */
|
||||
|
||||
const errorMessagesEN: Record<number, string> = {
|
||||
// User Part
|
||||
10101: `User not found (-B)`,
|
||||
10102: `User password error (-B)`,
|
||||
10103: `Email verification code error (-B)`,
|
||||
10104: `Email is already registered, please change it (-B)`,
|
||||
10105: `Username is already registered, please change it (-B)`,
|
||||
10106: `User bio is too long (-B)`,
|
||||
10107: `Invalid Email, Name, Password, or Verification Code Format (-B)`,
|
||||
10108: `Invalid password format (-B)`,
|
||||
10109: `Invalid Email or Verification Code Format (-B)`,
|
||||
10110: `Avatar image upload error. The image is an array. (-B)`,
|
||||
10111: `Avatar image upload error. The final compressed size of the image exceeds 50KB. (-B)`,
|
||||
10112: `In cooldown for login, two identical login attempts should have a one-minute interval. (-B)`,
|
||||
10113: `In cooldown for register, two identical register attempts should have a one-minute interval. (-B)`,
|
||||
10101: 'User not found',
|
||||
10102: 'User password error',
|
||||
10103: 'Email verification code error',
|
||||
10104: 'Email is already registered, please change it',
|
||||
10105: 'Username is already registered, please change it',
|
||||
|
||||
// Topic Part
|
||||
10201: `Your daily topic limit has been reached for today. (-B)`,
|
||||
10202: `Your moemoepoints are less than 1100, so you can't use the topic suggestion feature (-B)`,
|
||||
10204: `Topic title length exceed 40 characters. Or empty. (-B)`,
|
||||
10205: `Topic content length exceed 100007 characters. Or empty. (-B)`,
|
||||
10206: `Topic with a maximum of 7 tags. Minimum one tag. (-B)`,
|
||||
10207: `Topic with a maximum of 2 categories. Minimum one category. (-B)`,
|
||||
10208: `Invalid topics timestamp. (-B)`,
|
||||
|
||||
// Auth Part
|
||||
10301: `Sending emails too frequently, please waiting 30s (-B)`,
|
||||
10302: `Invalid Email Format (-B)`,
|
||||
10303: `Invalid Email, Password, or Verification Code Format (-B)`,
|
||||
|
||||
// Comment Part
|
||||
10401: `Comment length exceed 1007 characters. Or empty. (-B)`,
|
||||
|
||||
// Reply Part
|
||||
10501: `Reply with a maximum of 7 tags. (-B)`,
|
||||
10502: `Single tag maximum length is 17 characters (-B)`,
|
||||
10503: `Reply content is empty (-B)`,
|
||||
10504: `Reply maximum length is 10007 characters (-B)`,
|
||||
10505: `Invalid reply timestamp. (-B)`,
|
||||
10201: 'Your daily topic limit has been reached for today.',
|
||||
10202: `Your moemoepoints are less than 1100, so you can't use the topic suggestion feature`,
|
||||
}
|
||||
|
||||
const errorMessagesCN: Record<number, string> = {
|
||||
10101: `用户未找到 (-B)`,
|
||||
10102: `用户密码错误 (-B)`,
|
||||
10103: `邮箱验证码错误 (-B)`,
|
||||
10104: `邮箱已被注册,请更改 (-B)`,
|
||||
10105: `用户名已被注册,请修改 (-B)`,
|
||||
10106: `用户签名过长 (-B)`,
|
||||
10107: `非法的邮箱, 用户名, 密码, 或验证码 (-B)`,
|
||||
10108: `非法的密码格式 (-B)`,
|
||||
10109: `非法的邮箱或验证码格式 (-B)`,
|
||||
10110: `头像上传错误. 图片为数组 (-B)`,
|
||||
10111: `头像上传错误. 图片最终压缩大小超过 50kb (-B)`,
|
||||
10112: `登陆冷却中,两次相同登陆时间间隔一分钟 (-B)`,
|
||||
10113: `注册冷却中,两次相同注册时间间隔一分钟 (-B)`,
|
||||
10101: '用户未找到',
|
||||
10102: '用户密码错误',
|
||||
10103: '邮箱验证码错误',
|
||||
10104: '邮箱已被注册,请更改',
|
||||
10105: '用户名已被注册,请修改',
|
||||
|
||||
10201: `您今日可以发表的话题数已达上限 (-B)`,
|
||||
10202: `您的萌萌点不足 1100, 无法使用推话题功能 (-B)`,
|
||||
10204: `话题标题长度超过 40 个字符, 或为空 (-B)`,
|
||||
10205: `话题内容长度超过 100007 个字符, 或为空 (-B)`,
|
||||
10206: `话题最多 7 个标签, 最少一个标签 (-B)`,
|
||||
10207: `话题最多 2 个分类, 最少一个分类 (-B)`,
|
||||
10208: `非法的话题时间戳. (-B)`,
|
||||
|
||||
10301: `发送邮件频率过快, 请等待 30 秒 (-B)`,
|
||||
10302: `非法的邮箱格式 (-B)`,
|
||||
10303: `非法的邮箱, 密码, 或验证码 (-B)`,
|
||||
|
||||
10401: `评论内容长度超过 1007 个字符, 或为空 (-B)`,
|
||||
|
||||
10501: `回复最多 7 个标签 (-B)`,
|
||||
10502: `单个标签最长 17 个字符 (-B)`,
|
||||
10503: `回复内容不可为空 (-B)`,
|
||||
10504: `回复内容最大长度为 10007 个字符 (-B)`,
|
||||
10505: `非法的回复时间戳 (-B)`,
|
||||
10201: '您今日可以发表的话题数已达上限',
|
||||
10202: '您的萌萌点不足 1100, 无法使用推话题功能',
|
||||
}
|
||||
|
||||
export const getErrorMessageEN = (errorCode: number) => {
|
||||
return errorMessagesEN[errorCode] || `Unknown server error (-B)`
|
||||
return errorMessagesEN[errorCode] || 'Unknown server error'
|
||||
}
|
||||
|
||||
export const getErrorMessageCN = (errorCode: number) => {
|
||||
return errorMessagesCN[errorCode] || `未知的服务器错误 (-B)`
|
||||
return errorMessagesCN[errorCode] || '未知的服务器错误'
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
// Global message component (top)
|
||||
import Message from '@/components/alert/Message'
|
||||
import { generateTokenByRefreshTokenApi } from '@/api'
|
||||
// Use the user store
|
||||
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||
// Import the router
|
||||
import router from '@/router'
|
||||
// Import known error handling functions
|
||||
import { kungalgameErrorHandler } from './errorHandler'
|
||||
|
||||
interface ErrorResponseData {
|
||||
|
@ -13,15 +18,27 @@ interface ErrorResponseData {
|
|||
* Then identifies errors based on custom backend status codes
|
||||
* If unable to recognize, it throws an error.
|
||||
*/
|
||||
export const onRequestError = async (response: Response) => {
|
||||
export async function onRequestError(response: Response) {
|
||||
// Identify errors based on status codes
|
||||
if (response.status === 401) {
|
||||
Message(
|
||||
'Login expired, please log in again.',
|
||||
'登陆过期,请重新登陆',
|
||||
'error'
|
||||
)
|
||||
useKUNGalgameUserStore().removeToken()
|
||||
router.push('/login')
|
||||
// Attempt to obtain a new token using the refresh token
|
||||
const accessTokenResponse = await generateTokenByRefreshTokenApi()
|
||||
|
||||
// If a new token is successfully obtained, set the token
|
||||
if (accessTokenResponse.code === 200 && accessTokenResponse.data.token) {
|
||||
useKUNGalgameUserStore().setToken(accessTokenResponse.data.token)
|
||||
// Set the page to reload with the new token applied
|
||||
location.reload()
|
||||
} else {
|
||||
// Otherwise, prompt the user to log in again
|
||||
Message(
|
||||
'Login expired, please log in again.',
|
||||
'登陆过期,请重新登陆',
|
||||
'error'
|
||||
)
|
||||
useKUNGalgameUserStore().removeToken()
|
||||
router.push('/login')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -34,6 +51,8 @@ export const onRequestError = async (response: Response) => {
|
|||
return
|
||||
}
|
||||
|
||||
// Get the error response data
|
||||
const data: ErrorResponseData = await response.json()
|
||||
// Handle known errors
|
||||
kungalgameErrorHandler(data.code)
|
||||
}
|
||||
|
|
81
src/hooks/useLoli.ts
Normal file
81
src/hooks/useLoli.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||
|
||||
// It's a bit strange here; there's a 'data' property on the buffer
|
||||
// , not sure where it comes from
|
||||
interface LoliBuffer {
|
||||
data: Uint8Array
|
||||
}
|
||||
|
||||
export interface LoliData {
|
||||
loliBodyLeft: string
|
||||
loliBodyTop: string
|
||||
loliEyeLeft: string
|
||||
loliEyeTop: string
|
||||
loliBrowLeft: string
|
||||
loliBrowTop: string
|
||||
loliMouthLeft: string
|
||||
loliMouthTop: string
|
||||
loliFaceLeft: string
|
||||
loliFaceTop: string
|
||||
|
||||
body: LoliBuffer
|
||||
eye: LoliBuffer
|
||||
brow: LoliBuffer
|
||||
mouth: LoliBuffer
|
||||
face: LoliBuffer
|
||||
}
|
||||
|
||||
export type LoliDataResponseData = KUNGalgameResponseData<LoliData>
|
||||
|
||||
const fetchGetLoliData = async (): Promise<
|
||||
KUNGalgameResponseData<LoliData>
|
||||
> => {
|
||||
const baseUrl = import.meta.env.VITE_API_BASE_URL
|
||||
const url = `/loli/image`
|
||||
const fullUrl = `${baseUrl}${url}`
|
||||
const response = await fetch(fullUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${useKUNGalgameUserStore().getToken()}`,
|
||||
},
|
||||
})
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
// Not caching loli data here, as it would clutter the indexdb with too much data
|
||||
const createImageUrl = (imageBuffer: LoliBuffer) => {
|
||||
const uint8Array = new Uint8Array(imageBuffer.data)
|
||||
const blob = new Blob([uint8Array], { type: 'image/webp' })
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
|
||||
export const useLoliDataURL = async () => {
|
||||
const loliData = (await fetchGetLoliData()).data
|
||||
|
||||
const imageUrls = [
|
||||
createImageUrl(loliData.body),
|
||||
createImageUrl(loliData.eye),
|
||||
createImageUrl(loliData.brow),
|
||||
createImageUrl(loliData.mouth),
|
||||
createImageUrl(loliData.face),
|
||||
]
|
||||
|
||||
return {
|
||||
loliBodyLeft: loliData.loliBodyLeft,
|
||||
loliBodyTop: loliData.loliBodyTop,
|
||||
loliEyeLeft: loliData.loliEyeLeft,
|
||||
loliEyeTop: loliData.loliEyeTop,
|
||||
loliBrowLeft: loliData.loliBrowLeft,
|
||||
loliBrowTop: loliData.loliBrowTop,
|
||||
loliMouthLeft: loliData.loliMouthLeft,
|
||||
loliMouthTop: loliData.loliMouthTop,
|
||||
loliFaceLeft: loliData.loliFaceLeft,
|
||||
loliFaceTop: loliData.loliFaceTop,
|
||||
body: imageUrls[0],
|
||||
eye: imageUrls[1],
|
||||
brow: imageUrls[2],
|
||||
mouth: imageUrls[3],
|
||||
face: imageUrls[4],
|
||||
}
|
||||
}
|
|
@ -6,21 +6,6 @@ export default {
|
|||
technique: 'TECHNIQUE',
|
||||
about: 'ABOUT',
|
||||
return: 'HOME',
|
||||
hamburger: {
|
||||
name: 'KUN Visual Novel',
|
||||
pool: 'Pool',
|
||||
create: 'Create Topic',
|
||||
technique: 'Technique',
|
||||
about: 'About Us',
|
||||
ranking: 'Ranking',
|
||||
update: 'Update Log',
|
||||
bylaw: 'Regulations',
|
||||
balance: 'P & L',
|
||||
nonMoe: 'Non-moe',
|
||||
thanks: 'Thanks List',
|
||||
join: 'Join / Contacts',
|
||||
home: 'Back Home',
|
||||
},
|
||||
settings: {
|
||||
name: 'Settings',
|
||||
mode: 'Mode',
|
||||
|
@ -33,7 +18,8 @@ export default {
|
|||
background: 'Background Setting',
|
||||
preset: 'Use our preset background',
|
||||
custom: 'Custom Background',
|
||||
confirm: 'Confirm',
|
||||
url: 'Paste the picture url here',
|
||||
confirm: 'confirm',
|
||||
restore: 'Restore blank background',
|
||||
recover: 'Recover all settings to default',
|
||||
},
|
||||
|
@ -44,16 +30,14 @@ export default {
|
|||
},
|
||||
back: {
|
||||
back: 'Back',
|
||||
home: 'Home',
|
||||
},
|
||||
mainPage: {
|
||||
header: {
|
||||
filter: 'Filter',
|
||||
category: 'Category',
|
||||
galgame: 'Visual Novel',
|
||||
technique: 'Technique',
|
||||
others: 'Others',
|
||||
search: 'Search Topics',
|
||||
all: 'All Topics',
|
||||
history: 'Search History',
|
||||
clear: 'Clear all history',
|
||||
updated: 'Default',
|
||||
time: 'Time',
|
||||
popularity: 'Popularity',
|
||||
|
@ -61,15 +45,10 @@ export default {
|
|||
likes: 'Likes',
|
||||
replies: 'Replies',
|
||||
comments: 'Comments',
|
||||
search: 'Input to Auto Search',
|
||||
history: 'Search History',
|
||||
clear: 'Clear all history',
|
||||
emptyHistory: `This loli hasn't searched for anything`,
|
||||
emptyResult: 'No results found...',
|
||||
},
|
||||
asideActive: {
|
||||
fold: 'Fold Aside',
|
||||
create: 'CREATE TOPIC',
|
||||
create: 'CREATE Topic!',
|
||||
update: 'Update',
|
||||
balance: 'P & L',
|
||||
ranking: 'Ranking',
|
||||
|
@ -98,7 +77,6 @@ export default {
|
|||
acgngame: 'ACGNGAME',
|
||||
shinnku: `Shinnku's Visual Novel`,
|
||||
ymgal: 'YM galgame',
|
||||
kun: `KUN's Blog`,
|
||||
},
|
||||
describe: {
|
||||
title: 'KUN Visual Novel',
|
||||
|
@ -107,13 +85,17 @@ export default {
|
|||
kun3: 'NO ADs Forever',
|
||||
kun4: 'Free Forever',
|
||||
},
|
||||
contact: 'Contact Us',
|
||||
},
|
||||
},
|
||||
topic: {
|
||||
aside: {
|
||||
floor: 'Sort by Floor Number',
|
||||
like: 'Sort by Likes Count',
|
||||
comment: 'Sort by Comment Count',
|
||||
top: 'Back Top',
|
||||
floorSort: 'Floor Sort',
|
||||
timeSort: 'Time Sort',
|
||||
likeSort: 'Like Sort',
|
||||
commentSort: 'Reply Sort',
|
||||
updatedSort: 'Update Sort',
|
||||
tags: 'Topics Under the Same Tags',
|
||||
tagsEmpty: 'The tags currently has no other topics',
|
||||
master: 'Other Topics of The Master',
|
||||
|
@ -365,18 +347,6 @@ export default {
|
|||
donate: 'Donate Us',
|
||||
home: 'Back Home',
|
||||
},
|
||||
pool: {
|
||||
load: 'Click to Load More Topics',
|
||||
complete: `Already there's nothing left...`,
|
||||
view: 'Sort by Views',
|
||||
like: 'Sort by Likes',
|
||||
time: 'Sort by Time',
|
||||
},
|
||||
technique: {
|
||||
prev: 'Prev',
|
||||
next: 'Next',
|
||||
KKKKK: `We're not sure how this page should be written. If you have any suggestions, please contact us.`,
|
||||
},
|
||||
donate: {
|
||||
donate: 'Donate Us',
|
||||
no: 'comes with no Moemoepoint rewards',
|
||||
|
@ -397,11 +367,6 @@ export default {
|
|||
success: 'Login successful',
|
||||
home: 'You will be redirected to the home page in 3 seconds',
|
||||
},
|
||||
footer: {
|
||||
copyright: 'Copyright © 2023 KUN Visual Novel (except for images)',
|
||||
openSource: 'GitHub Open Source',
|
||||
reserved: 'All rights reserved | Version',
|
||||
},
|
||||
// 非页面组件这里统一用大驼峰
|
||||
ComponentAlert: {
|
||||
confirm: 'OK',
|
||||
|
@ -411,12 +376,14 @@ export default {
|
|||
edit: {
|
||||
publish: 'Confirm to publish?',
|
||||
publishSuccess: 'Publish Successfully',
|
||||
publishCancel: 'Cancel Publish',
|
||||
upvoteTopic:
|
||||
'Are you sure you want to upvote this topic? This will cost you 17 Moe Moe Points',
|
||||
upvoteReply:
|
||||
'Are you sure you want to upvote this reply? This will cost you 3 Moe Moe Points',
|
||||
rewrite: 'Confirm to Rewrite?',
|
||||
rewriteSuccess: 'Rewrite Successfully',
|
||||
rewriteCancel: 'Cancel Rewrite',
|
||||
closePanel: 'Confirm closing the panel? Your changes will not be saved.',
|
||||
draft: 'The draft has been saved successfully!',
|
||||
leave: 'Confirm leaving the page? Your changes will not be saved.',
|
||||
|
|
|
@ -6,21 +6,6 @@ export default {
|
|||
technique: '技术交流',
|
||||
about: '关于我们',
|
||||
return: '返回主页',
|
||||
hamburger: {
|
||||
name: '鲲 Galgame',
|
||||
pool: '所有话题',
|
||||
create: '发布话题',
|
||||
technique: '技术交流',
|
||||
about: '关于我们',
|
||||
ranking: '排行榜单',
|
||||
update: '更新日志',
|
||||
bylaw: '执行条例',
|
||||
balance: '收支公示',
|
||||
nonMoe: '不萌记录',
|
||||
thanks: '感谢名单',
|
||||
join: '加入 / 联系',
|
||||
home: '返回主页',
|
||||
},
|
||||
settings: {
|
||||
name: '设置面板',
|
||||
mode: '模式切换',
|
||||
|
@ -33,6 +18,7 @@ export default {
|
|||
background: '背景设置',
|
||||
preset: '点击使用我们预设的背景',
|
||||
custom: '自定义背景',
|
||||
url: '请在这里粘贴图片的URL',
|
||||
confirm: '确定',
|
||||
restore: '恢复空白背景',
|
||||
recover: '恢复所有设置为默认',
|
||||
|
@ -44,16 +30,14 @@ export default {
|
|||
},
|
||||
back: {
|
||||
back: '返回',
|
||||
home: '主页',
|
||||
},
|
||||
mainPage: {
|
||||
header: {
|
||||
filter: '筛选',
|
||||
category: '分类',
|
||||
galgame: 'Galgame',
|
||||
technique: '技术交流',
|
||||
others: '其它',
|
||||
search: '搜索话题',
|
||||
all: '全部话题',
|
||||
history: '搜索历史',
|
||||
clear: '清除所有历史',
|
||||
updated: '恢复默认排序',
|
||||
time: '按照时间排序',
|
||||
popularity: '按热度值排序',
|
||||
|
@ -61,11 +45,6 @@ export default {
|
|||
likes: '按点赞数排序',
|
||||
replies: '按回复数排序',
|
||||
comments: '按评论数排序',
|
||||
search: '输入内容以自动搜索',
|
||||
history: '搜索历史',
|
||||
clear: '清除所有历史',
|
||||
emptyHistory: '这只萝莉什么也没搜索过',
|
||||
emptyResult: '什么也没有搜索到。。。',
|
||||
},
|
||||
asideActive: {
|
||||
fold: '折叠左侧区域',
|
||||
|
@ -98,7 +77,6 @@ export default {
|
|||
acgngame: 'ACGNGAME',
|
||||
shinnku: '失落的小站',
|
||||
ymgal: '月幕 galgame',
|
||||
kun: '鲲的博客',
|
||||
},
|
||||
describe: {
|
||||
title: '鲲 Galgame',
|
||||
|
@ -107,13 +85,17 @@ export default {
|
|||
kun3: '鲲 Galgame 永远不会有广告',
|
||||
kun4: '鲲 Galgame 永远不会收费',
|
||||
},
|
||||
contact: '联系我们',
|
||||
},
|
||||
},
|
||||
topic: {
|
||||
aside: {
|
||||
floor: '按照楼层数排序',
|
||||
like: '按照点赞数排序',
|
||||
comment: '按照评论数排序',
|
||||
top: '返回到顶端',
|
||||
floorSort: '按楼层排序',
|
||||
timeSort: '按时间排序',
|
||||
likeSort: '按点赞排序',
|
||||
commentSort: '按评论排序',
|
||||
updatedSort: '按更新排序',
|
||||
tags: '相同标签下的其它话题',
|
||||
tagsEmpty: '该标签下暂无其它话题',
|
||||
master: '楼主的其它话题',
|
||||
|
@ -364,18 +346,6 @@ export default {
|
|||
donate: '赞助我们',
|
||||
home: '返回主页',
|
||||
},
|
||||
pool: {
|
||||
load: '点击继续加载话题',
|
||||
complete: '已经。。。一滴也不剩了',
|
||||
view: '按浏览数排序',
|
||||
like: '按点赞数排序',
|
||||
time: '按照时间排序',
|
||||
},
|
||||
technique: {
|
||||
prev: '上一页',
|
||||
next: '下一页',
|
||||
KKKKK: `我们不知道这个页面怎么写了,如果有建议,请联系我们`,
|
||||
},
|
||||
donate: {
|
||||
donate: '赞助我们',
|
||||
no: '没有任何的萌萌点奖励',
|
||||
|
@ -395,11 +365,6 @@ export default {
|
|||
success: '登陆成功',
|
||||
home: '3 秒后你将会进入主页',
|
||||
},
|
||||
footer: {
|
||||
copyright: '版权所有 © 2023 鲲 Galgame (图片除外)',
|
||||
openSource: 'GitHub 开源',
|
||||
reserved: '保留所有权利 | 版本',
|
||||
},
|
||||
// 非页面组件这里统一用大驼峰
|
||||
ComponentAlert: {
|
||||
confirm: '确定',
|
||||
|
@ -409,10 +374,12 @@ export default {
|
|||
edit: {
|
||||
publish: '确认发布吗?',
|
||||
publishSuccess: '发布成功',
|
||||
publishCancel: '取消发布',
|
||||
upvoteTopic: '您确定推这个话题吗,这将会消耗您 17 萌萌点',
|
||||
upvoteReply: '您确定推这个回复吗,这将会消耗您 3 萌萌点',
|
||||
rewrite: '确认 Rewrite 吗?',
|
||||
rewriteSuccess: 'Rewrite 成功',
|
||||
rewriteCancel: '取消 Rewrite',
|
||||
closePanel: '确认关闭面板吗?您的更改将不会被保存',
|
||||
draft: '草稿已经保存成功!',
|
||||
leave: '确认离开界面吗?您的更改将不会保存',
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, watch, ref } from 'vue'
|
||||
// Import animations
|
||||
import 'animate.css'
|
||||
import { getCurrentBackground } from '@/hooks/useBackgroundPicture'
|
||||
import KUNGalgameTopBar from '@/components/top-bar/KUNGalgameTopBar.vue'
|
||||
|
||||
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { getImage } from '@/hooks/useLocalforage'
|
||||
|
||||
const { showKUNGalgameBackground, showKUNGalgameCustomBackground } =
|
||||
storeToRefs(useKUNGalgameSettingsStore())
|
||||
|
@ -14,12 +14,6 @@ const { showKUNGalgameBackground, showKUNGalgameCustomBackground } =
|
|||
const imageURL = ref('')
|
||||
|
||||
onMounted(async () => {
|
||||
const backgroundImageBlobData = await getImage('kun-galgame-custom-bg')
|
||||
if (showKUNGalgameBackground.value === 'bg1007' && backgroundImageBlobData) {
|
||||
showKUNGalgameCustomBackground.value = URL.createObjectURL(
|
||||
backgroundImageBlobData
|
||||
)
|
||||
}
|
||||
imageURL.value = await getCurrentBackground()
|
||||
})
|
||||
|
||||
|
@ -37,7 +31,7 @@ watch(
|
|||
<div class="top-bar">
|
||||
<KUNGalgameTopBar />
|
||||
</div>
|
||||
|
||||
<!-- <RouterView /> -->
|
||||
<RouterView #default="{ route, Component }">
|
||||
<Transition
|
||||
:enter-active-class="`animate__animated ${route.meta.transition}`"
|
||||
|
|
|
@ -6,31 +6,62 @@ import { createPinia } from 'pinia'
|
|||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import type { App } from 'vue'
|
||||
|
||||
// Import store for the income and expense public disclosure page
|
||||
import { useKUNGalgameBalanceStore } from './modules/balance'
|
||||
|
||||
// Import store for the editing interface
|
||||
import { useKUNGalgameEditStore } from './modules/edit'
|
||||
import { usePersistKUNGalgameHomeStore } from './modules/home'
|
||||
|
||||
// Import home store
|
||||
import { useKUNGalgameHomeStore } from './modules/home'
|
||||
|
||||
// Import user store
|
||||
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||
|
||||
// Import message store
|
||||
import { useKUNGalgameMessageStore } from './modules/message'
|
||||
|
||||
// Import non-moe records store
|
||||
import { useKUNGalgameNonMoeStore } from './modules/nonMoe'
|
||||
|
||||
// Import ranking store
|
||||
import { useKUNGalgameRankingStore } from './modules/ranking'
|
||||
|
||||
// Import website settings panel store
|
||||
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||
|
||||
// Import store for the topic detail page
|
||||
import { usePersistKUNGalgameTopicStore } from '@/store/modules/topic/topic'
|
||||
import { usePersistKUNGalgameReplyStore } from '@/store/modules/topic/reply'
|
||||
|
||||
const store = createPinia()
|
||||
|
||||
// Function to set up Pinia, to be called in main.ts
|
||||
export function setupKUNGalgamePinia(app: App<Element>) {
|
||||
store.use(piniaPluginPersistedstate)
|
||||
app.use(store)
|
||||
}
|
||||
|
||||
// Reset all stores, used for logging out
|
||||
export function kungalgameStoreReset() {
|
||||
const balanceStore = useKUNGalgameBalanceStore()
|
||||
const editStore = useKUNGalgameEditStore()
|
||||
const homeStore = usePersistKUNGalgameHomeStore()
|
||||
const homeStore = useKUNGalgameHomeStore()
|
||||
const userStore = useKUNGalgameUserStore()
|
||||
const messageStore = useKUNGalgameMessageStore()
|
||||
const nonMoeStore = useKUNGalgameNonMoeStore()
|
||||
const rankingStore = useKUNGalgameRankingStore()
|
||||
const settingsStore = useKUNGalgameSettingsStore()
|
||||
const topicStore = usePersistKUNGalgameTopicStore()
|
||||
const replyStore = usePersistKUNGalgameReplyStore()
|
||||
|
||||
balanceStore.$reset()
|
||||
editStore.$reset()
|
||||
homeStore.$reset()
|
||||
userStore.$reset()
|
||||
messageStore.$reset()
|
||||
nonMoeStore.$reset()
|
||||
rankingStore.$reset()
|
||||
settingsStore.$reset()
|
||||
topicStore.$reset()
|
||||
replyStore.$reset()
|
||||
|
|
|
@ -15,8 +15,8 @@ interface BalanceStore {
|
|||
expenditure: BalanceExpenditureRequestData
|
||||
}
|
||||
|
||||
export const useTempBalanceStore = defineStore({
|
||||
id: 'tempBalance',
|
||||
export const useKUNGalgameBalanceStore = defineStore({
|
||||
id: 'KUNGalgameBalance',
|
||||
persist: false,
|
||||
state: (): BalanceStore => ({
|
||||
income: {
|
|
@ -1,19 +1,20 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { postNewTopicApi, getTopTagsApi } from '@/api'
|
||||
import { postNewTopicApi, updateNewTopicApi, getTopTagsApi } from '@/api'
|
||||
import {
|
||||
EditCreateTopicRequestData,
|
||||
EditCreateTopicResponseData,
|
||||
EditUpdateTopicRequestData,
|
||||
EditUpdateTopicResponseData,
|
||||
EditGetHotTagsRequestData,
|
||||
EditGetHotTagsResponseData,
|
||||
} from '@/api'
|
||||
|
||||
import { EditStore } from '../types/edit'
|
||||
import { checkTopicPublish } from '../utils/checkTopicPublish'
|
||||
import type { EditStorePersist } from '../types/edit'
|
||||
|
||||
export const useKUNGalgameEditStore = defineStore({
|
||||
id: 'KUNGalgameEdit',
|
||||
persist: true,
|
||||
state: (): EditStorePersist => ({
|
||||
state: (): EditStore => ({
|
||||
editorHeight: 300,
|
||||
textCount: 0,
|
||||
|
||||
|
@ -23,6 +24,16 @@ export const useKUNGalgameEditStore = defineStore({
|
|||
category: [],
|
||||
isShowHotKeywords: true,
|
||||
isSaveTopic: false,
|
||||
|
||||
topicRewrite: {
|
||||
tid: 0,
|
||||
title: '',
|
||||
content: '',
|
||||
tags: [],
|
||||
category: [],
|
||||
|
||||
isTopicRewriting: false,
|
||||
},
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
|
@ -44,6 +55,24 @@ export const useKUNGalgameEditStore = defineStore({
|
|||
return await postNewTopicApi(requestData)
|
||||
},
|
||||
|
||||
// Update a topic
|
||||
async rewriteTopic(): Promise<EditUpdateTopicResponseData | undefined> {
|
||||
const requestData: EditUpdateTopicRequestData = {
|
||||
tid: this.topicRewrite.tid,
|
||||
title: this.topicRewrite.title,
|
||||
content: this.topicRewrite.content,
|
||||
tags: this.topicRewrite.tags,
|
||||
category: this.topicRewrite.category,
|
||||
}
|
||||
|
||||
// If the topic data is invalid, return directly
|
||||
if (!checkTopicPublish(this.textCount, requestData)) {
|
||||
return
|
||||
}
|
||||
|
||||
return await updateNewTopicApi(requestData)
|
||||
},
|
||||
|
||||
// Get popular tags
|
||||
async getHotTags(limit: number): Promise<EditGetHotTagsResponseData> {
|
||||
const requestData: EditGetHotTagsRequestData = { limit }
|
||||
|
@ -53,7 +82,6 @@ export const useKUNGalgameEditStore = defineStore({
|
|||
// Reset topic draft data for publishing
|
||||
resetTopicData() {
|
||||
this.textCount = 0
|
||||
|
||||
this.title = ''
|
||||
this.content = ''
|
||||
this.tags = []
|
||||
|
@ -61,5 +89,16 @@ export const useKUNGalgameEditStore = defineStore({
|
|||
|
||||
this.isSaveTopic = false
|
||||
},
|
||||
|
||||
// Reset data for re-editing a topic
|
||||
resetRewriteTopicData() {
|
||||
this.textCount = 0
|
||||
this.topicRewrite.title = ''
|
||||
this.topicRewrite.content = ''
|
||||
this.topicRewrite.tags = []
|
||||
this.topicRewrite.category = []
|
||||
|
||||
this.topicRewrite.isTopicRewriting = false
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,12 +1,61 @@
|
|||
/* Home Page Store */
|
||||
import { defineStore } from 'pinia'
|
||||
import type { HomeStorePersist } from '../types/home'
|
||||
// API
|
||||
import { getHomeTopicApi } from '@/api/index'
|
||||
// Data interface types
|
||||
import { HomeTopicRequestData, HomeTopicResponseData } from '@/api/index'
|
||||
|
||||
export const usePersistKUNGalgameHomeStore = defineStore({
|
||||
// Home store type
|
||||
import { HomeStore } from '../types/home'
|
||||
|
||||
export const useKUNGalgameHomeStore = defineStore({
|
||||
id: 'KUNGalgameHome',
|
||||
persist: true,
|
||||
state: (): HomeStorePersist => ({
|
||||
state: (): HomeStore => ({
|
||||
// Search box store
|
||||
/**
|
||||
* @param {String} keywords - Search keywords, default to all if not provided
|
||||
* @param {Array} category - Topic categories, currently there are three: Galgame, Technique, Others
|
||||
* @param {Number} page - Page number for pagination
|
||||
* @param {Number} limit - Number of data per page
|
||||
* @param {String} sortField - Field to sort by
|
||||
* @param {String} sortOrder - Sorting order, can be 'asc' or 'desc'
|
||||
* @returns {HomeTopicResponseData} topicData
|
||||
*/
|
||||
keywords: '',
|
||||
category: '',
|
||||
page: 1,
|
||||
limit: 17,
|
||||
sortField: 'updated',
|
||||
sortOrder: 'desc',
|
||||
isLoading: true,
|
||||
|
||||
// Other store
|
||||
// Whether to activate the left interaction panel on the main page
|
||||
isActiveMainPageAside: true,
|
||||
|
||||
// Search history storage
|
||||
searchHistory: [],
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
// Get home topics
|
||||
async getHomeTopic(): Promise<HomeTopicResponseData> {
|
||||
// The values here are used for initialization
|
||||
const requestData: HomeTopicRequestData = {
|
||||
keywords: this.keywords,
|
||||
category: this.category,
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
sortField: this.sortField,
|
||||
sortOrder: this.sortOrder,
|
||||
}
|
||||
return await getHomeTopicApi(requestData)
|
||||
},
|
||||
// Reset page number and loading status for sorting to take effect
|
||||
resetPageStatus() {
|
||||
this.page = 1
|
||||
this.isLoading = true
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -51,8 +51,10 @@ import {
|
|||
updateUserPasswordByEmailApi,
|
||||
} from '@/api'
|
||||
|
||||
import type { KUNGalgamerStore } from '../types/kungalgamer'
|
||||
// KUNGalgamer store type
|
||||
import { KUNGalgamerStore } from '../types/kungalgamer'
|
||||
|
||||
// Here, pinia-plugin-persistedstate is used, so storage is automatic
|
||||
export const useKUNGalgameUserStore = defineStore({
|
||||
id: 'KUNGalgameUser',
|
||||
persist: true,
|
||||
|
@ -107,6 +109,10 @@ export const useKUNGalgameUserStore = defineStore({
|
|||
res.data.roles
|
||||
)
|
||||
this.setToken(res.data.token)
|
||||
} else if (res.code === 500) {
|
||||
console.log(res.message)
|
||||
} else {
|
||||
throw new Error('500 Server ERROR')
|
||||
}
|
||||
return res
|
||||
},
|
||||
|
|
|
@ -3,8 +3,8 @@ import { defineStore } from 'pinia'
|
|||
// Type of message store
|
||||
import { MessageStore } from '../types/message'
|
||||
|
||||
export const useTempMessageStore = defineStore({
|
||||
id: 'tempMessage',
|
||||
export const useKUNGalgameMessageStore = defineStore({
|
||||
id: 'KUNGalgameMessage',
|
||||
// No need to persist any message components
|
||||
persist: false,
|
||||
state: (): MessageStore => ({
|
|
@ -4,8 +4,8 @@ import type { NonMoeLogRequestData, NonMoeGetLogsResponseData } from '@/api'
|
|||
|
||||
import { getNonMoeLogsApi } from '@/api'
|
||||
|
||||
export const useTempNonMoeStore = defineStore({
|
||||
id: 'tempNonMoe',
|
||||
export const useKUNGalgameNonMoeStore = defineStore({
|
||||
id: 'KUNGalgameNonMoe',
|
||||
persist: false,
|
||||
state: (): NonMoeLogRequestData => ({
|
||||
page: 1,
|
|
@ -14,8 +14,8 @@ interface RankingStore {
|
|||
user: RankingGetUserRequestData
|
||||
}
|
||||
|
||||
export const useTempRankingStore = defineStore({
|
||||
id: 'tempRanking',
|
||||
export const useKUNGalgameRankingStore = defineStore({
|
||||
id: 'KUNGalgameRanking',
|
||||
persist: false,
|
||||
state: (): RankingStore => ({
|
||||
topic: {
|
|
@ -1,14 +1,16 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
import { postReplyByTidApi } from '@/api'
|
||||
// Replies
|
||||
import { postReplyByPidApi, updateReplyApi } from '@/api'
|
||||
|
||||
import type {
|
||||
TopicCreateReplyRequestData,
|
||||
TopicCreateReplyResponseData,
|
||||
TopicUpdateReplyRequestData,
|
||||
TopicUpdateReplyResponseData,
|
||||
} from '@/api'
|
||||
|
||||
import { checkReplyPublish } from '@/store/utils/checkReplyPublish'
|
||||
import type { ReplyStorePersist } from '@/store/types/topic/reply'
|
||||
import { ReplyStorePersist } from '@/store/types/topic/reply'
|
||||
|
||||
export const usePersistKUNGalgameReplyStore = defineStore({
|
||||
id: 'KUNGalgameReply',
|
||||
|
@ -18,9 +20,10 @@ export const usePersistKUNGalgameReplyStore = defineStore({
|
|||
|
||||
isSaveReply: false,
|
||||
isShowHotKeywords: true,
|
||||
|
||||
editorHeight: 200,
|
||||
textCount: 0,
|
||||
|
||||
// Whether the reply is being rewritten
|
||||
isReplyRewriting: false,
|
||||
|
||||
replyDraft: {
|
||||
tid: 0,
|
||||
|
@ -30,10 +33,17 @@ export const usePersistKUNGalgameReplyStore = defineStore({
|
|||
tags: [],
|
||||
toFloor: 0,
|
||||
},
|
||||
replyRewrite: {
|
||||
tid: 0,
|
||||
rid: 0,
|
||||
content: '',
|
||||
tags: [],
|
||||
edited: 0,
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
// Create a new reply
|
||||
async postNewReply(): Promise<TopicCreateReplyResponseData | undefined> {
|
||||
async postNewReply(): Promise<TopicCreateReplyResponseData> {
|
||||
// The values here are used to initialize the reply
|
||||
const requestData: TopicCreateReplyRequestData = {
|
||||
tid: this.replyDraft.tid,
|
||||
|
@ -41,20 +51,23 @@ export const usePersistKUNGalgameReplyStore = defineStore({
|
|||
to_floor: this.replyDraft.toFloor,
|
||||
tags: this.replyDraft.tags,
|
||||
content: this.replyDraft.content,
|
||||
time: Date.now(),
|
||||
}
|
||||
return await postReplyByPidApi(requestData)
|
||||
},
|
||||
|
||||
if (!checkReplyPublish(requestData.tags, requestData.content)) {
|
||||
return
|
||||
// Update a reply
|
||||
async updateReply(): Promise<TopicUpdateReplyResponseData> {
|
||||
const requestData: TopicUpdateReplyRequestData = {
|
||||
tid: this.replyRewrite.tid,
|
||||
rid: this.replyRewrite.rid,
|
||||
content: this.replyRewrite.content,
|
||||
tags: this.replyRewrite.tags,
|
||||
}
|
||||
|
||||
return await postReplyByTidApi(requestData)
|
||||
return await updateReplyApi(requestData)
|
||||
},
|
||||
|
||||
// Reset reply draft to its original value, used for the reply publish button
|
||||
resetReplyDraft() {
|
||||
this.textCount = 0
|
||||
|
||||
this.replyDraft.tid = 0
|
||||
this.replyDraft.toUserName = ''
|
||||
this.replyDraft.toUid = 0
|
||||
|
@ -64,5 +77,15 @@ export const usePersistKUNGalgameReplyStore = defineStore({
|
|||
|
||||
this.isSaveReply = false
|
||||
},
|
||||
|
||||
// Reset data for re-editing a reply
|
||||
resetRewriteReplyData() {
|
||||
this.replyRewrite.tid = 0
|
||||
this.replyRewrite.rid = 0
|
||||
this.replyRewrite.content = ''
|
||||
this.replyRewrite.tags = []
|
||||
|
||||
this.isReplyRewriting = false
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
import { updateNewTopicApi } from '@/api'
|
||||
|
||||
import type {
|
||||
EditUpdateTopicRequestData,
|
||||
EditUpdateTopicResponseData,
|
||||
} from '@/api'
|
||||
|
||||
import { checkTopicPublish } from '@/store/utils/checkTopicPublish'
|
||||
import { EditStoreTemp } from '@/store/types/edit'
|
||||
|
||||
export const useTempEditStore = defineStore({
|
||||
id: 'tempEdit',
|
||||
persist: false,
|
||||
state: (): EditStoreTemp => ({
|
||||
tid: 0,
|
||||
title: '',
|
||||
content: '',
|
||||
tags: [],
|
||||
category: [],
|
||||
|
||||
textCount: 0,
|
||||
isTopicRewriting: false,
|
||||
}),
|
||||
actions: {
|
||||
// Update a topic
|
||||
async rewriteTopic(): Promise<EditUpdateTopicResponseData | undefined> {
|
||||
const requestData: EditUpdateTopicRequestData = {
|
||||
tid: this.tid,
|
||||
title: this.title,
|
||||
content: this.content,
|
||||
tags: this.tags,
|
||||
category: this.category,
|
||||
edited: Date.now(),
|
||||
}
|
||||
|
||||
// If the topic data is invalid, return directly
|
||||
if (!checkTopicPublish(this.textCount, requestData)) {
|
||||
return
|
||||
}
|
||||
|
||||
return await updateNewTopicApi(requestData)
|
||||
},
|
||||
|
||||
// Reset data for re-editing a topic
|
||||
resetRewriteTopicData() {
|
||||
this.textCount = 0
|
||||
this.title = ''
|
||||
this.content = ''
|
||||
this.tags = []
|
||||
this.category = []
|
||||
|
||||
this.isTopicRewriting = false
|
||||
},
|
||||
},
|
||||
})
|
|
@ -1,72 +0,0 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { getHomeSearchTopicApi, getHomeTopicApi } from '@/api/index'
|
||||
import type {
|
||||
HomeSearchTopicRequestData,
|
||||
HomeSearchTopicResponseData,
|
||||
HomeTopicRequestData,
|
||||
HomeTopicResponseData,
|
||||
} from '@/api/index'
|
||||
import type { HomeStoreTemp } from '../types/home'
|
||||
|
||||
export const useTempHomeStore = defineStore({
|
||||
id: 'tempHome',
|
||||
persist: false,
|
||||
state: (): HomeStoreTemp => ({
|
||||
search: {
|
||||
keywords: '',
|
||||
category: ['Galgame', 'Technique', 'Others'],
|
||||
page: 1,
|
||||
limit: 7,
|
||||
sortField: 'updated',
|
||||
sortOrder: 'desc',
|
||||
isLoading: true,
|
||||
},
|
||||
topic: {
|
||||
category: ['Galgame'],
|
||||
page: 1,
|
||||
limit: 17,
|
||||
sortField: 'updated',
|
||||
sortOrder: 'desc',
|
||||
isLoading: true,
|
||||
},
|
||||
|
||||
isShowSearch: false,
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
async searchTopic(): Promise<HomeSearchTopicResponseData> {
|
||||
const requestData: HomeSearchTopicRequestData = {
|
||||
keywords: this.search.keywords.trim().slice(0, 40),
|
||||
category: JSON.stringify(this.search.category),
|
||||
page: this.search.page,
|
||||
limit: this.search.limit,
|
||||
sortField: this.search.sortField,
|
||||
sortOrder: this.search.sortOrder,
|
||||
}
|
||||
return await getHomeSearchTopicApi(requestData)
|
||||
},
|
||||
|
||||
async getHomeTopic(): Promise<HomeTopicResponseData> {
|
||||
const requestData: HomeTopicRequestData = {
|
||||
category: JSON.stringify(this.topic.category),
|
||||
page: this.topic.page,
|
||||
limit: this.topic.limit,
|
||||
sortField: this.topic.sortField,
|
||||
sortOrder: this.topic.sortOrder,
|
||||
}
|
||||
return await getHomeTopicApi(requestData)
|
||||
},
|
||||
|
||||
resetSearchStatus() {
|
||||
this.search.page = 1
|
||||
this.search.limit = 7
|
||||
this.search.isLoading = true
|
||||
},
|
||||
|
||||
resetHomePageStatus() {
|
||||
this.topic.page = 1
|
||||
this.topic.limit = 17
|
||||
this.topic.isLoading = true
|
||||
},
|
||||
},
|
||||
})
|
|
@ -1,36 +0,0 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
import { getPoolTopicApi } from '@/api'
|
||||
import type { PoolTopicsRequestData, PoolTopicResponseData } from '@/api'
|
||||
|
||||
import type { PoolStoreTemp } from '@/store/types/pool'
|
||||
|
||||
export const useTempPoolStore = defineStore({
|
||||
id: 'tempPool',
|
||||
persist: false,
|
||||
state: (): PoolStoreTemp => ({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
sortField: 'time',
|
||||
sortOrder: 'desc',
|
||||
|
||||
isScrollToTop: false,
|
||||
}),
|
||||
actions: {
|
||||
async getTopics(): Promise<PoolTopicResponseData> {
|
||||
const requestData: PoolTopicsRequestData = {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
sortField: this.sortField,
|
||||
sortOrder: this.sortOrder,
|
||||
}
|
||||
|
||||
return await getPoolTopicApi(requestData)
|
||||
},
|
||||
|
||||
resetPageStatus() {
|
||||
this.page = 1
|
||||
this.limit = 10
|
||||
},
|
||||
},
|
||||
})
|
|
@ -1,37 +0,0 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
import { getTechniqueTopicApi } from '@/api'
|
||||
import type {
|
||||
TechniqueTopicsRequestData,
|
||||
TechniqueTopicResponseData,
|
||||
} from '@/api'
|
||||
|
||||
import type { TechniqueStoreTemp } from '@/store/types/technique'
|
||||
|
||||
export const useTempTechniqueStore = defineStore({
|
||||
id: 'tempTechnique',
|
||||
persist: false,
|
||||
state: (): TechniqueStoreTemp => ({
|
||||
page: 1,
|
||||
limit: 10,
|
||||
sortField: 'time',
|
||||
sortOrder: 'desc',
|
||||
}),
|
||||
actions: {
|
||||
async getTopics(): Promise<TechniqueTopicResponseData> {
|
||||
const requestData: TechniqueTopicsRequestData = {
|
||||
page: this.page,
|
||||
limit: this.limit,
|
||||
sortField: this.sortField,
|
||||
sortOrder: this.sortOrder,
|
||||
}
|
||||
|
||||
return await getTechniqueTopicApi(requestData)
|
||||
},
|
||||
|
||||
resetPageStatus() {
|
||||
this.page = 1
|
||||
this.limit = 10
|
||||
},
|
||||
},
|
||||
})
|
|
@ -1,11 +1,10 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
// Replies
|
||||
import {
|
||||
getRepliesByTidApi,
|
||||
getRepliesByPidApi,
|
||||
updateReplyUpvoteApi,
|
||||
updateReplyLikeApi,
|
||||
updateReplyDislikeApi,
|
||||
updateReplyApi,
|
||||
} from '@/api'
|
||||
|
||||
import type {
|
||||
|
@ -17,11 +16,8 @@ import type {
|
|||
TopicLikeReplyResponseData,
|
||||
TopicDislikeReplyRequestData,
|
||||
TopicDislikeReplyResponseData,
|
||||
TopicUpdateReplyRequestData,
|
||||
TopicUpdateReplyResponseData,
|
||||
} from '@/api'
|
||||
|
||||
import { checkReplyPublish } from '@/store/utils/checkReplyPublish'
|
||||
import type { ReplyStoreTemp } from '@/store/types/topic/reply'
|
||||
|
||||
export const useTempReplyStore = defineStore({
|
||||
|
@ -32,9 +28,8 @@ export const useTempReplyStore = defineStore({
|
|||
isEdit: false,
|
||||
isScrollToTop: false,
|
||||
isLoading: true,
|
||||
|
||||
// Reply ID starts from 0, -1 is just for monitoring data changes
|
||||
scrollToReplyId: -1,
|
||||
isReplyRewriting: false,
|
||||
|
||||
replyRequest: {
|
||||
page: 1,
|
||||
|
@ -43,20 +38,13 @@ export const useTempReplyStore = defineStore({
|
|||
sortOrder: 'asc',
|
||||
},
|
||||
|
||||
replyRewrite: {
|
||||
tid: 0,
|
||||
rid: 0,
|
||||
content: '',
|
||||
tags: [],
|
||||
edited: 0,
|
||||
},
|
||||
|
||||
tempReply: {
|
||||
rid: 0,
|
||||
tid: 0,
|
||||
// Floor where the reply is located
|
||||
floor: 0,
|
||||
// Floor where the replied reply is located
|
||||
to_floor: 0,
|
||||
|
||||
r_user: {
|
||||
uid: 0,
|
||||
name: '',
|
||||
|
@ -91,7 +79,7 @@ export const useTempReplyStore = defineStore({
|
|||
sortField: this.replyRequest.sortField || 'floor',
|
||||
sortOrder: this.replyRequest.sortOrder || 'desc',
|
||||
}
|
||||
return await getRepliesByTidApi(requestData)
|
||||
return await getRepliesByPidApi(requestData)
|
||||
},
|
||||
|
||||
// Upvote a reply
|
||||
|
@ -104,7 +92,6 @@ export const useTempReplyStore = defineStore({
|
|||
tid: tid,
|
||||
to_uid: toUid,
|
||||
rid: rid,
|
||||
time: Date.now(),
|
||||
}
|
||||
return await updateReplyUpvoteApi(requestData)
|
||||
},
|
||||
|
@ -140,37 +127,5 @@ export const useTempReplyStore = defineStore({
|
|||
}
|
||||
return await updateReplyDislikeApi(requestData)
|
||||
},
|
||||
|
||||
// Update a reply
|
||||
async updateReply(): Promise<TopicUpdateReplyResponseData | undefined> {
|
||||
const requestData: TopicUpdateReplyRequestData = {
|
||||
tid: this.replyRewrite.tid,
|
||||
rid: this.replyRewrite.rid,
|
||||
content: this.replyRewrite.content,
|
||||
tags: this.replyRewrite.tags,
|
||||
edited: Date.now(),
|
||||
}
|
||||
|
||||
if (!checkReplyPublish(requestData.tags, requestData.content)) {
|
||||
return
|
||||
}
|
||||
|
||||
return await updateReplyApi(requestData)
|
||||
},
|
||||
|
||||
// Reset data for re-editing a reply
|
||||
resetRewriteReplyData() {
|
||||
this.replyRewrite.tid = 0
|
||||
this.replyRewrite.rid = 0
|
||||
this.replyRewrite.content = ''
|
||||
this.replyRewrite.tags = []
|
||||
|
||||
this.isReplyRewriting = false
|
||||
},
|
||||
|
||||
resetPageStatus() {
|
||||
this.replyRequest.page = 1
|
||||
this.isLoading = true
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { defineStore } from 'pinia'
|
||||
|
||||
// Topics
|
||||
import {
|
||||
getTopicByTidApi,
|
||||
getRelatedTopicsByTagsApi,
|
||||
|
@ -53,7 +54,6 @@ export const useTempTopicStore = defineStore({
|
|||
const requestData: TopicUpvoteTopicRequestData = {
|
||||
tid: tid,
|
||||
to_uid: toUid,
|
||||
time: Date.now(),
|
||||
}
|
||||
return await updateTopicUpvoteApi(requestData)
|
||||
},
|
||||
|
|
45
src/store/types/edit.d.ts
vendored
45
src/store/types/edit.d.ts
vendored
|
@ -1,26 +1,45 @@
|
|||
export interface EditStorePersist {
|
||||
// Ahahaha, I just want to use "rewrite" in the name to tease you
|
||||
interface TopicRewrite {
|
||||
// Topic ID
|
||||
tid: number
|
||||
// Topic title
|
||||
title: string
|
||||
// Topic content
|
||||
content: string
|
||||
// Topic tags
|
||||
tags: Array<string>
|
||||
// Topic category
|
||||
category: Array<string>
|
||||
|
||||
// Whether the topic is being rewritten
|
||||
isTopicRewriting: boolean
|
||||
}
|
||||
|
||||
export interface EditStore {
|
||||
editorHeight: number
|
||||
textCount: number
|
||||
|
||||
/**
|
||||
* Topic related
|
||||
* @param {string} title - Topic title
|
||||
* @param {string} content - Topic content (rich text)
|
||||
* @param {Array<string>} tags - Topic tags
|
||||
* @param {Array<string>} category - Topic category
|
||||
* @param {boolean} isSave - Whether to save the topic draft
|
||||
*/
|
||||
// Topic title
|
||||
title: string
|
||||
// Topic content
|
||||
content: string
|
||||
// Topic tags
|
||||
tags: Array<string>
|
||||
// Topic category
|
||||
category: Array<string>
|
||||
|
||||
// Whether to display hot keywords
|
||||
isShowHotKeywords: boolean
|
||||
// Whether to save the topic
|
||||
isSaveTopic: boolean
|
||||
}
|
||||
|
||||
export interface EditStoreTemp {
|
||||
tid: number
|
||||
title: string
|
||||
content: string
|
||||
tags: Array<string>
|
||||
category: Array<string>
|
||||
|
||||
textCount: number
|
||||
// Whether the topic is being rewritten
|
||||
isTopicRewriting: boolean
|
||||
/* Rewrite a topic */
|
||||
topicRewrite: TopicRewrite
|
||||
}
|
||||
|
|
25
src/store/types/home.d.ts
vendored
25
src/store/types/home.d.ts
vendored
|
@ -1,34 +1,13 @@
|
|||
interface HomeSearchTemp {
|
||||
export interface HomeStore {
|
||||
keywords: string
|
||||
category: string[]
|
||||
category: string
|
||||
page: number
|
||||
limit: number
|
||||
sortField: string
|
||||
sortOrder: string
|
||||
|
||||
// Whether to continue loading after it's done
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export interface HomeTopicTemp {
|
||||
category: string[]
|
||||
page: number
|
||||
limit: number
|
||||
sortField: string
|
||||
sortOrder: string
|
||||
|
||||
// Whether to continue loading after it's done
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export interface HomeStoreTemp {
|
||||
search: HomeSearchTemp
|
||||
topic: HomeTopicTemp
|
||||
|
||||
isShowSearch: boolean
|
||||
}
|
||||
|
||||
export interface HomeStorePersist {
|
||||
// Other stores
|
||||
// Whether to activate the left interactive panel of the main page
|
||||
isActiveMainPageAside: boolean
|
||||
|
|
8
src/store/types/pool.d.ts
vendored
8
src/store/types/pool.d.ts
vendored
|
@ -1,8 +0,0 @@
|
|||
export interface PoolStoreTemp {
|
||||
page: number
|
||||
limit: number
|
||||
sortField: string
|
||||
sortOrder: string
|
||||
|
||||
isScrollToTop: boolean
|
||||
}
|
6
src/store/types/technique.d.ts
vendored
6
src/store/types/technique.d.ts
vendored
|
@ -1,6 +0,0 @@
|
|||
export interface TechniqueStoreTemp {
|
||||
page: number
|
||||
limit: number
|
||||
sortField: string
|
||||
sortOrder: string
|
||||
}
|
8
src/store/types/topic/reply.d.ts
vendored
8
src/store/types/topic/reply.d.ts
vendored
|
@ -42,10 +42,7 @@ export interface ReplyStoreTemp {
|
|||
// Reply ID to scroll to
|
||||
scrollToReplyId: number
|
||||
|
||||
isReplyRewriting: boolean
|
||||
|
||||
replyRequest: ReplyRequest
|
||||
replyRewrite: ReplyRewrite
|
||||
tempReply: TopicReply
|
||||
tempReplyRewrite: ReplyRewriteTemp
|
||||
}
|
||||
|
@ -55,9 +52,10 @@ export interface ReplyStorePersist {
|
|||
|
||||
isSaveReply: boolean
|
||||
isShowHotKeywords: boolean
|
||||
|
||||
editorHeight: number
|
||||
textCount: number
|
||||
|
||||
isReplyRewriting: boolean
|
||||
|
||||
replyDraft: ReplyDraft
|
||||
replyRewrite: ReplyRewrite
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import Message from '@/components/alert/Message'
|
||||
|
||||
export const checkReplyPublish = (tags: string[], content: string) => {
|
||||
if (tags.length > 7) {
|
||||
Message('Reply with a maximum of 7 tags', '回复最多 7 个标签', 'warn')
|
||||
return false
|
||||
}
|
||||
|
||||
for (const tag of tags) {
|
||||
if (tag.length > 17) {
|
||||
Message(
|
||||
'Single tag maximum length is 17 characters',
|
||||
'单个标签最长 17 个字符',
|
||||
'warn'
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (!content.trim()) {
|
||||
Message('Reply content cannot be empty', '回复内容不可为空', 'warn')
|
||||
return false
|
||||
}
|
||||
|
||||
if (content.length > 10007) {
|
||||
Message(
|
||||
'Reply maximum length is 10007 characters',
|
||||
'回复内容最大长度为 10007 个字符',
|
||||
'warn'
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
|
@ -29,29 +29,14 @@ export const checkTopicPublish = (
|
|||
return false
|
||||
}
|
||||
|
||||
if (topicData.title.trim().length > 40) {
|
||||
// If the title is empty, show a warning
|
||||
Message(
|
||||
'Title maximum length is 40 characters!',
|
||||
'标题最大长度为 40 个字符!',
|
||||
'warn'
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
// Check content character count
|
||||
if (!textCount) {
|
||||
// If the content is empty, show a warning
|
||||
Message('Content cannot be empty!', '内容不可为空!', 'warn')
|
||||
return false
|
||||
}
|
||||
|
||||
if (textCount > 100007) {
|
||||
Message(
|
||||
'Content maximum length is 100007!',
|
||||
'内容最大长度为100007!',
|
||||
'warn'
|
||||
)
|
||||
Message('Content max length is 100007!', '内容最大长度为100007!', 'warn')
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -61,22 +46,6 @@ export const checkTopicPublish = (
|
|||
return false
|
||||
}
|
||||
|
||||
if (topicData.tags.length > 7) {
|
||||
Message('Reply with a maximum of 7 tags', '回复最多 7 个标签', 'warn')
|
||||
return false
|
||||
}
|
||||
|
||||
for (const tag of topicData.tags) {
|
||||
if (tag.length > 17) {
|
||||
Message(
|
||||
'Single tag maximum length is 17 characters',
|
||||
'单个标签最长 17 个字符',
|
||||
'warn'
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check category
|
||||
if (!topicData.category.length) {
|
||||
Message(
|
||||
|
@ -87,15 +56,6 @@ export const checkTopicPublish = (
|
|||
return false
|
||||
}
|
||||
|
||||
if (topicData.category.length > 2) {
|
||||
Message(
|
||||
'Topic with a maximum of 2 categories!',
|
||||
'最多选择两个分类!',
|
||||
'warn'
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
// If all checks pass, return true
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -62,11 +62,3 @@ button {
|
|||
); /* Background color of selected text */
|
||||
color: var(--kungalgame-blue-4); /* Text color of selected text */
|
||||
}
|
||||
|
||||
/* Milkdown */
|
||||
p code {
|
||||
background-color: var(--kungalgame-trans-blue-2);
|
||||
padding: 2px 7px;
|
||||
font-size: 13px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
|
49
src/types/pool/tags.ts
Normal file
49
src/types/pool/tags.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
// Temp data
|
||||
|
||||
interface tag {
|
||||
index: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export const tags: tag[] = [
|
||||
{
|
||||
index: 1,
|
||||
name: '啊这可海星',
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
name: '啊这可海星',
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
name: '啊这可海星',
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
name: '啊这可海星啊这可海星',
|
||||
},
|
||||
{
|
||||
index: 5,
|
||||
name: '啊这可海星',
|
||||
},
|
||||
{
|
||||
index: 6,
|
||||
name: '啊这可海星啊这可海星',
|
||||
},
|
||||
{
|
||||
index: 7,
|
||||
name: '啊这可海星',
|
||||
},
|
||||
{
|
||||
index: 8,
|
||||
name: '啊这可海星啊这可',
|
||||
},
|
||||
{
|
||||
index: 9,
|
||||
name: '啊这可海星',
|
||||
},
|
||||
{
|
||||
index: 10,
|
||||
name: '啊这可海星',
|
||||
},
|
||||
]
|
13
src/utils/cookie.ts
Normal file
13
src/utils/cookie.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import Cookies from 'js-cookie'
|
||||
|
||||
export const getToken = () => {
|
||||
return Cookies.get('kungalgame-moemoe-access-token')
|
||||
}
|
||||
|
||||
export const setToken = (token: string) => {
|
||||
Cookies.set('kungalgame-moemoe-access-token', token)
|
||||
}
|
||||
|
||||
export const removeToken = () => {
|
||||
Cookies.remove('kungalgame-moemoe-access-token')
|
||||
}
|
|
@ -1,16 +1,20 @@
|
|||
export const debounce = <F extends (...args: any[]) => any>(
|
||||
fn: F,
|
||||
time: number
|
||||
): ((...args: Parameters<F>) => void) => {
|
||||
let timeoutID: NodeJS.Timeout | null = null
|
||||
/*
|
||||
* Debounce function that takes a function and a delay time as parameters
|
||||
*/
|
||||
export type DebouncedFunction<T extends (...args: any[]) => any> = (
|
||||
...args: Parameters<T>
|
||||
) => void
|
||||
|
||||
return function (...args: Parameters<F>) {
|
||||
if (timeoutID !== null) {
|
||||
clearTimeout(timeoutID)
|
||||
}
|
||||
export function debounce<T extends (...args: any[]) => any>(
|
||||
func: T,
|
||||
delay: number
|
||||
): DebouncedFunction<T> {
|
||||
let timeoutId: ReturnType<typeof setTimeout>
|
||||
|
||||
timeoutID = setTimeout(() => {
|
||||
fn(...args)
|
||||
}, time)
|
||||
return (...args) => {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => {
|
||||
func(...args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ const languageOptions = {
|
|||
},
|
||||
}
|
||||
|
||||
const replaceTimeUnits = (input: string, language: string) => {
|
||||
function replaceTimeUnits(input: string, language: string) {
|
||||
const languageOption =
|
||||
(languageOptions as Record<string, any>)[language] || languageOptions.en
|
||||
|
||||
|
@ -66,7 +66,7 @@ const replaceTimeUnits = (input: string, language: string) => {
|
|||
}
|
||||
|
||||
// Format time difference
|
||||
export const formatTimeDifference = (pastTime: number, language: string) => {
|
||||
export function formatTimeDifference(pastTime: number, language: string) {
|
||||
const now = dayjs()
|
||||
const diffInSeconds = now.diff(pastTime, 'second')
|
||||
const hint = language === 'en' ? ' ago' : '前'
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
import dayjs from 'dayjs'
|
||||
import 'dayjs/locale/en'
|
||||
|
||||
dayjs.locale('en')
|
||||
|
||||
export const formatTimeI18n = (time: number) => {
|
||||
const formattedENDate = dayjs(time).format('MMMM D, YYYY - h:mm:ss A')
|
||||
const formattedCNDate = dayjs(time).format('YYYY年MM月DD日-HH:mm:ss 发布')
|
||||
return { formattedENDate, formattedCNDate }
|
||||
}
|
12
src/utils/getPlainText.ts
Normal file
12
src/utils/getPlainText.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export function getPlainText(html: string): string {
|
||||
// Remove HTML tags
|
||||
const plainText = html.replace(/<[^>]*>/g, '')
|
||||
|
||||
// Decode HTML entities
|
||||
const textWithEntitiesDecoded = new DOMParser().parseFromString(
|
||||
plainText,
|
||||
'text/html'
|
||||
).body.textContent
|
||||
|
||||
return textWithEntitiesDecoded ? textWithEntitiesDecoded : ''
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
export const markdownToText = (markdown: string) => {
|
||||
return markdown
|
||||
.replace(/(\*\*|__)(.*?)\1/gs, '$2')
|
||||
.replace(/(\*|_)(.*?)\1/gs, '$2')
|
||||
.replace(/#+\s*(.*?)\n/g, '$1\n')
|
||||
.replace(/!?\[(.*?)\]\(.*?\)/gs, '$1')
|
||||
.replace(/`([^`]+)`/g, '$1')
|
||||
.replace(/```[\s\S]+?```/gs, '')
|
||||
.replace(/[<>~\\]/g, '')
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
// Using the user store
|
||||
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||
import { requestRefresh } from './requestRefresh'
|
||||
// Error handling function
|
||||
import { onRequestError } from '@/error/onRequestError'
|
||||
|
||||
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||
|
||||
const successResponseArray = [200, 201, 202, 204, 205, 206]
|
||||
|
||||
export type FetchOptions = {
|
||||
method: HttpMethod
|
||||
credentials: 'include'
|
||||
|
@ -11,6 +14,7 @@ export type FetchOptions = {
|
|||
body?: BodyInit
|
||||
}
|
||||
|
||||
// Fetch request function
|
||||
const kunFetchRequest = async <T>(
|
||||
url: string,
|
||||
options: FetchOptions
|
||||
|
@ -18,6 +22,7 @@ const kunFetchRequest = async <T>(
|
|||
const baseUrl = import.meta.env.VITE_API_BASE_URL
|
||||
const fullUrl = `${baseUrl}${url}`
|
||||
|
||||
// Add the token to the request headers
|
||||
const headers = {
|
||||
...options.headers,
|
||||
Authorization: `Bearer ${useKUNGalgameUserStore().getToken()}`,
|
||||
|
@ -25,18 +30,15 @@ const kunFetchRequest = async <T>(
|
|||
|
||||
const response = await fetch(fullUrl, { ...options, headers })
|
||||
|
||||
if (response.status === 205) {
|
||||
const newResponseData = await requestRefresh(fullUrl, options)
|
||||
const data: T = await newResponseData.json()
|
||||
return data
|
||||
} else if (response.status === 233 || !response.ok) {
|
||||
// Handle some known backend error
|
||||
// If not 20X, then throw an error
|
||||
if (!successResponseArray.includes(response.status)) {
|
||||
// Handle errors, such as token expiration
|
||||
await onRequestError(response)
|
||||
return {} as T
|
||||
} else {
|
||||
const data: T = await response.json()
|
||||
return data
|
||||
throw new Error('KUNGalgame Fetch Error occurred, but no problem')
|
||||
}
|
||||
|
||||
const data: T = await response.json()
|
||||
return data
|
||||
}
|
||||
|
||||
const fetchGet = async <T>(
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import Message from '@/components/alert/Message'
|
||||
import router from '@/router'
|
||||
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||
import { generateTokenByRefreshTokenApi } from '@/api'
|
||||
import type { FetchOptions } from './request'
|
||||
|
||||
export const requestRefresh = async (
|
||||
fullUrl: string,
|
||||
options: FetchOptions
|
||||
) => {
|
||||
const accessTokenResponse = await generateTokenByRefreshTokenApi()
|
||||
|
||||
useKUNGalgameUserStore().setToken(accessTokenResponse.data.token)
|
||||
|
||||
const headers = {
|
||||
...options.headers,
|
||||
Authorization: `Bearer ${useKUNGalgameUserStore().getToken()}`,
|
||||
}
|
||||
|
||||
const response = await fetch(fullUrl, { ...options, headers })
|
||||
|
||||
return response
|
||||
}
|
|
@ -1,30 +1,29 @@
|
|||
export const isValidTimestamp = (timestamp: number) => {
|
||||
return (
|
||||
timestamp.toString().length === 10 || timestamp.toString().length === 13
|
||||
)
|
||||
}
|
||||
|
||||
// Regular expression to match a valid URL
|
||||
export const isValidURL = (url: string) => {
|
||||
const regex =
|
||||
/^(https?|http):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|net|org|biz|moe|info|name|pro|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?'\\+&%$#=~_-]+))*$/
|
||||
return regex.test(url)
|
||||
}
|
||||
|
||||
// Regular expression to match a valid email
|
||||
export const isValidEmail = (email: string) => {
|
||||
const regex = /^[^\s@]{1,64}@[^\s@]{1,255}\.[^\s@]{1,24}$/
|
||||
return regex.test(email)
|
||||
}
|
||||
|
||||
// Match a username of 1 to 17 characters containing Chinese, English, Japanese, numbers, underscores, or tildes
|
||||
export const isValidName = (name: string) => {
|
||||
const regex = /^[\p{L}\p{N}~_]{1,17}$/u
|
||||
return regex.test(name)
|
||||
}
|
||||
|
||||
// Regular expression to match a password of 6 to 107 characters, containing at least one letter and one number, and optionally including special characters \w!@#$%^&*()-+=
|
||||
export const isValidPassword = (pwd: string) => {
|
||||
const regex = /^(?=.*[a-zA-Z])(?=.*[0-9])[\w!@#$%^&*()-+=]{6,107}$/
|
||||
return regex.test(pwd)
|
||||
}
|
||||
|
||||
// Regular expression to match a 7-character alphanumeric email confirmation code
|
||||
export const isValidMailConfirmCode = (code: string) => {
|
||||
const regex = /^[a-zA-Z0-9]{7}$/
|
||||
return regex.test(code)
|
||||
|
|
|
@ -16,10 +16,4 @@ import MainPageFooter from './footer/MainPageFooter.vue'
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.root {
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -38,7 +38,7 @@ const mainPageWidth = computed(() => {
|
|||
|
||||
.content-container {
|
||||
width: v-bind(mainPageWidth);
|
||||
transition: width 0.2s;
|
||||
transition: all 0.2s;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
|
@ -58,6 +58,7 @@ const mainPageWidth = computed(() => {
|
|||
.content-container {
|
||||
width: 80%;
|
||||
border: none;
|
||||
background-color: var(--kungalgame-trans-white-9);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,6 +70,7 @@ const mainPageWidth = computed(() => {
|
|||
.content-container {
|
||||
width: 100%;
|
||||
border: none;
|
||||
background-color: var(--kungalgame-trans-white-9);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -22,6 +22,7 @@ import ArticleContent from './components/ArticleContent.vue'
|
|||
.article-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 1px solid var(--kungalgame-trans-blue-4);
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -6,66 +6,86 @@ import { hourDiff } from '@/utils/time'
|
|||
|
||||
import { HomeTopic } from '@/api'
|
||||
|
||||
import { useTempHomeStore } from '@/store/temp/home'
|
||||
// Import the homepage store
|
||||
import { useKUNGalgameHomeStore } from '@/store/modules/home'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const { topic } = storeToRefs(useTempHomeStore())
|
||||
|
||||
const homeTopics = ref<HomeTopic[]>([])
|
||||
const content = ref<HTMLElement>()
|
||||
|
||||
const getTopics = async (): Promise<HomeTopic[]> => {
|
||||
return (await useTempHomeStore().getHomeTopic()).data
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [topic.value.category, topic.value.sortField, topic.value.sortOrder],
|
||||
async () => {
|
||||
homeTopics.value = await getTopics()
|
||||
}
|
||||
const { page, keywords, sortField, sortOrder, isLoading } = storeToRefs(
|
||||
useKUNGalgameHomeStore()
|
||||
)
|
||||
|
||||
const scrollHandler = async () => {
|
||||
if (isScrollAtBottom() && topic.value.isLoading) {
|
||||
topic.value.page++
|
||||
// Define reactive topic data in the component
|
||||
const topics = ref<HomeTopic[]>([])
|
||||
// Page container for calculating whether it has reached the bottom
|
||||
const content = ref<HTMLElement>()
|
||||
|
||||
// Function to get page topics
|
||||
const getTopics = async (): Promise<HomeTopic[]> => {
|
||||
return (await useKUNGalgameHomeStore().getHomeTopic()).data
|
||||
}
|
||||
|
||||
// Call fetchTopics to get topic data (watch is great!)
|
||||
watch([keywords, sortField, sortOrder], async () => {
|
||||
topics.value = await getTopics()
|
||||
})
|
||||
|
||||
// Scroll event handler
|
||||
const scrollHandler = async () => {
|
||||
// Handling logic when scrolling to the bottom
|
||||
if (isScrollAtBottom() && isLoading.value) {
|
||||
// Automatically increment the page number
|
||||
page.value++
|
||||
|
||||
// Get the topics for the next page
|
||||
const lazyLoadTopics = await getTopics()
|
||||
|
||||
// Check if data has already been loaded, if so, no need to load more
|
||||
if (!lazyLoadTopics.length) {
|
||||
topic.value.isLoading = false
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
homeTopics.value = [...homeTopics.value, ...lazyLoadTopics]
|
||||
// Append the newly loaded reply data to the existing reply data
|
||||
topics.value = [...topics.value, ...lazyLoadTopics]
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it has scrolled to the bottom
|
||||
const isScrollAtBottom = () => {
|
||||
if (content.value) {
|
||||
const scrollHeight = content.value.scrollHeight
|
||||
const scrollTop = content.value.scrollTop
|
||||
const clientHeight = content.value.clientHeight
|
||||
|
||||
// Compare with a margin of error, as JavaScript floating-point numbers are not precise
|
||||
// Why 1007? Because I got KUN san on October 7th, ahahaha
|
||||
const errorMargin = 1.007
|
||||
return Math.abs(scrollHeight - scrollTop - clientHeight) < errorMargin
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
useTempHomeStore().resetHomePageStatus()
|
||||
// Reset page number, loading status, etc. before mounting
|
||||
useKUNGalgameHomeStore().resetPageStatus()
|
||||
})
|
||||
|
||||
// Add a scroll event listener after the component is mounted
|
||||
onMounted(async () => {
|
||||
// Get a reference to the scrolling element
|
||||
const element = content.value
|
||||
|
||||
// If the element is found, start the listener to track scroll behavior
|
||||
if (element) {
|
||||
element.addEventListener('scroll', scrollHandler)
|
||||
}
|
||||
|
||||
homeTopics.value = await getTopics()
|
||||
// Load topics for the first time
|
||||
topics.value = await getTopics()
|
||||
})
|
||||
|
||||
// Remove the scroll event listener before the component is unmounted
|
||||
onBeforeUnmount(() => {
|
||||
const element = content.value
|
||||
// If the page element is found, remove the listener
|
||||
if (element) {
|
||||
element.removeEventListener('scroll', scrollHandler)
|
||||
}
|
||||
|
@ -74,9 +94,10 @@ onBeforeUnmount(() => {
|
|||
|
||||
<template>
|
||||
<div class="topic-container" ref="content">
|
||||
<TransitionGroup name="list" tag="div" v-if="homeTopics.length">
|
||||
<TransitionGroup name="list" tag="div" v-if="topics.length">
|
||||
<!-- Posted within 10 hours -->
|
||||
<div
|
||||
v-for="topic in homeTopics"
|
||||
v-for="topic in topics"
|
||||
:key="topic.tid"
|
||||
:class="
|
||||
hourDiff(topic.upvote_time, 10) ? 'kungalgame-comet-surround' : ''
|
||||
|
@ -92,9 +113,9 @@ onBeforeUnmount(() => {
|
|||
</TransitionGroup>
|
||||
|
||||
<!-- Skeleton -->
|
||||
<HomeTopicSkeleton :count="7" v-if="!homeTopics.length" />
|
||||
<HomeTopicSkeleton :count="7" v-if="!topics.length" />
|
||||
|
||||
<HomeTopicSkeleton v-if="topic.isLoading && homeTopics.length >= 16" />
|
||||
<HomeTopicSkeleton v-if="isLoading && topics.length >= 16" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import UserPart from './UserPart.vue'
|
||||
import TopicPart from './TopicPart.vue'
|
||||
|
||||
|
@ -8,16 +7,14 @@ import { HomeTopic } from '@/api'
|
|||
const props = defineProps<{
|
||||
topic: HomeTopic
|
||||
}>()
|
||||
|
||||
const topic = computed(() => props.topic)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="topic">
|
||||
<UserPart :user="topic.user" />
|
||||
<UserPart :user="props.topic.user" />
|
||||
|
||||
<RouterLink :to="`/topic/${topic.tid}`">
|
||||
<TopicPart :topic="topic" />
|
||||
<RouterLink :to="`/topic/${props.topic.tid}`">
|
||||
<TopicPart :topic="props.topic" />
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -25,7 +22,7 @@ const topic = computed(() => props.topic)
|
|||
<style lang="scss" scoped>
|
||||
.topic {
|
||||
width: 100%;
|
||||
height: 77px;
|
||||
height: 74px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 3px;
|
||||
background-color: var(--kungalgame-trans-blue-0);
|
||||
|
|
|
@ -3,12 +3,13 @@ import { computed } from 'vue'
|
|||
import { Icon } from '@iconify/vue'
|
||||
|
||||
import { formatTimeDifference } from '@/utils/formatTime'
|
||||
import { markdownToText } from '@/utils/markdownToText'
|
||||
|
||||
import { useKUNGalgameSettingsStore } from '@/store/modules/settings'
|
||||
import { storeToRefs } from 'pinia'
|
||||
const settingsStore = storeToRefs(useKUNGalgameSettingsStore())
|
||||
|
||||
import { getPlainText } from '@/utils/getPlainText'
|
||||
|
||||
import { HomeTopic } from '@/api'
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -30,6 +31,8 @@ const {
|
|||
popularity,
|
||||
} = props.topic
|
||||
|
||||
const plainText = getPlainText(content)
|
||||
|
||||
const getRepliesCount = computed(() => {
|
||||
return repliesCount + comments
|
||||
})
|
||||
|
@ -72,7 +75,7 @@ const getRepliesCount = computed(() => {
|
|||
|
||||
<div class="introduction">
|
||||
<p>
|
||||
{{ markdownToText(content) }}
|
||||
{{ plainText }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -120,7 +123,7 @@ const getRepliesCount = computed(() => {
|
|||
font-size: smaller;
|
||||
|
||||
li {
|
||||
margin-right: 5px;
|
||||
margin-left: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
@ -147,6 +150,7 @@ const getRepliesCount = computed(() => {
|
|||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
|
@ -160,10 +164,4 @@ const getRepliesCount = computed(() => {
|
|||
color: var(--kungalgame-font-color-2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.time {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,58 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import KUNGalgameSearchBox from '@/components/KUNGalgameSearchBox.vue'
|
||||
import SortTopic from './SortTopic.vue'
|
||||
|
||||
import { useTempHomeStore } from '@/store/temp/home'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import { categoryItem } from './navItem'
|
||||
|
||||
const { topic } = storeToRefs(useTempHomeStore())
|
||||
const categoryIcon = ref('galgame')
|
||||
|
||||
const handleSortByCategory = (name: string) => {
|
||||
useTempHomeStore().resetHomePageStatus()
|
||||
topic.value.category = []
|
||||
categoryIcon.value = name
|
||||
|
||||
// Because category is [Galgame, Technique, Others], need to capitalize first letter
|
||||
const capitalizeFirstLetter = name.charAt(0).toUpperCase() + name.slice(1)
|
||||
topic.value.category.push(capitalizeFirstLetter)
|
||||
}
|
||||
|
||||
const iconMap: Record<string, string> = {
|
||||
galgame: 'icon-park-outline:game',
|
||||
technique: 'mingcute:tool-line',
|
||||
others: 'basil:other-1-outline',
|
||||
}
|
||||
const category = ['Galgame']
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Top interactive area of the article section -->
|
||||
<div class="nav-article">
|
||||
<div class="category">
|
||||
<span>{{ $tm('mainPage.header.category') }}</span>
|
||||
<span><Icon :icon="iconMap[categoryIcon]" /></span>
|
||||
|
||||
<div class="category-container">
|
||||
<div class="category-submenu">
|
||||
<div
|
||||
class="item"
|
||||
v-for="(kun, _) in categoryItem"
|
||||
:key="kun.index"
|
||||
@click="handleSortByCategory(kun.name)"
|
||||
>
|
||||
<span><Icon class="icon-item" :icon="kun.icon" /></span>
|
||||
<span>
|
||||
{{ $tm(`mainPage.header.${kun.name}`) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sorting area container -->
|
||||
<SortTopic />
|
||||
|
||||
<!-- Search box, only Galgame on the homepage -->
|
||||
<KUNGalgameSearchBox :category="category" style="border: none" />
|
||||
<!-- Enter all topics in the interactive area -->
|
||||
<RouterLink to="/pool" class="more">
|
||||
<span>{{ $tm('mainPage.header.all') }}</span>
|
||||
<Icon class="all-topic" icon="line-md:chevron-triple-right" />
|
||||
|
@ -66,94 +28,11 @@ const iconMap: Record<string, string> = {
|
|||
height: 40px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid var(--kungalgame-trans-blue-4);
|
||||
color: var(--kungalgame-font-color-3);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.category {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 1px;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
background-color: var(--kungalgame-trans-blue-0);
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
|
||||
& > span:nth-child(2) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 18px;
|
||||
margin-left: 7px;
|
||||
color: var(--kungalgame-blue-4);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition: all 0.2s;
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
background-color: var(--kungalgame-blue-4);
|
||||
color: var(--kungalgame-white);
|
||||
|
||||
& > span:nth-child(2) {
|
||||
color: var(--kungalgame-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-container {
|
||||
width: 100%;
|
||||
top: 40px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.category-submenu {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
background-color: var(--kungalgame-trans-white-2);
|
||||
box-shadow: var(--shadow);
|
||||
border-radius: 5px;
|
||||
|
||||
.item {
|
||||
padding: 10px 0;
|
||||
font-size: 14px;
|
||||
color: var(--kungalgame-font-color-3);
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--kungalgame-trans-blue-1);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--kungalgame-trans-blue-2);
|
||||
}
|
||||
|
||||
.icon-item {
|
||||
color: var(--kungalgame-blue-4);
|
||||
padding-right: 3px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category:hover .category-submenu {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.more {
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
|
@ -161,23 +40,19 @@ const iconMap: Record<string, string> = {
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
background-color: var(--kungalgame-trans-blue-0);
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
background-color: var(--kungalgame-trans-blue-3);
|
||||
flex-grow: 1;
|
||||
border-radius: 5px;
|
||||
border-radius: 0 5px 0 0;
|
||||
cursor: pointer;
|
||||
border-left: 1px solid var(--kungalgame-trans-blue-4);
|
||||
color: var(--kungalgame-font-color-3);
|
||||
margin-left: 7px;
|
||||
|
||||
&:hover {
|
||||
transition: all 0.2s;
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
background-color: var(--kungalgame-blue-4);
|
||||
color: var(--kungalgame-white);
|
||||
background-color: var(--kungalgame-trans-blue-2);
|
||||
}
|
||||
|
||||
& > span:nth-child(2) {
|
||||
color: var(--kungalgame-white);
|
||||
}
|
||||
&:active {
|
||||
background-color: var(--kungalgame-trans-blue-4);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +1,33 @@
|
|||
<script setup lang="ts">
|
||||
// Import icons
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useTempHomeStore } from '@/store/temp/home'
|
||||
import { useKUNGalgameHomeStore } from '@/store/modules/home'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import { sortItem } from './navItem'
|
||||
// Import sorting list fields
|
||||
import { navSortItem } from './navSortItem'
|
||||
import { ref } from 'vue'
|
||||
|
||||
// Styles for ascending and descending orders
|
||||
const ascClass = ref('')
|
||||
|
||||
const { topic } = storeToRefs(useTempHomeStore())
|
||||
const { sortField, sortOrder } = storeToRefs(useKUNGalgameHomeStore())
|
||||
|
||||
const handleSortByField = (field: string) => {
|
||||
useTempHomeStore().resetHomePageStatus()
|
||||
topic.value.sortField = field
|
||||
useKUNGalgameHomeStore().resetPageStatus()
|
||||
sortField.value = field
|
||||
}
|
||||
|
||||
const orderAscending = () => {
|
||||
useTempHomeStore().resetHomePageStatus()
|
||||
topic.value.sortOrder = 'asc'
|
||||
useKUNGalgameHomeStore().resetPageStatus()
|
||||
sortOrder.value = 'asc'
|
||||
// Change style
|
||||
ascClass.value = 'active'
|
||||
}
|
||||
|
||||
const orderDescending = () => {
|
||||
useTempHomeStore().resetHomePageStatus()
|
||||
topic.value.sortOrder = 'desc'
|
||||
useKUNGalgameHomeStore().resetPageStatus()
|
||||
sortOrder.value = 'desc'
|
||||
ascClass.value = ''
|
||||
}
|
||||
|
||||
|
@ -34,24 +36,29 @@ const iconMap: Record<string, string> = {
|
|||
time: 'eos-icons:hourglass',
|
||||
popularity: 'bi:fire',
|
||||
views: 'ic:outline-remove-red-eye',
|
||||
likes_count: 'line-md:thumbs-up-twotone',
|
||||
replies_count: 'ri:reply-line',
|
||||
likes: 'line-md:thumbs-up-twotone',
|
||||
replies: 'ri:reply-line',
|
||||
comments: 'fa-regular:comment-dots',
|
||||
}
|
||||
|
||||
const isSortField = () => {
|
||||
return Object.keys(iconMap).includes(sortField.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container" :class="ascClass">
|
||||
<span>{{ $tm('mainPage.header.filter') }}</span>
|
||||
<span class="filter">
|
||||
<Icon :icon="iconMap[topic.sortField]" />
|
||||
<Icon v-if="isSortField()" :icon="iconMap[sortField]" />
|
||||
</span>
|
||||
|
||||
<!-- Secondary menu for sorting -->
|
||||
<div class="sort-container">
|
||||
<div class="sort-submenu">
|
||||
<div
|
||||
class="sort-item"
|
||||
v-for="kun in sortItem"
|
||||
v-for="kun in navSortItem"
|
||||
:key="kun.index"
|
||||
@click="handleSortByField(kun.sortField)"
|
||||
>
|
||||
|
@ -83,27 +90,15 @@ const iconMap: Record<string, string> = {
|
|||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 1px;
|
||||
background-color: var(--kungalgame-trans-blue-3);
|
||||
flex-grow: 1;
|
||||
border-radius: 5px 0 0 0;
|
||||
position: relative;
|
||||
background-color: var(--kungalgame-trans-blue-0);
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
border-radius: 5px;
|
||||
border-right: 1px solid var(--kungalgame-trans-blue-4);
|
||||
cursor: pointer;
|
||||
margin-left: 7px;
|
||||
|
||||
&:hover {
|
||||
transition: all 0.2s;
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
background-color: var(--kungalgame-blue-4);
|
||||
color: var(--kungalgame-white);
|
||||
|
||||
& > span:nth-child(2) {
|
||||
color: var(--kungalgame-white);
|
||||
}
|
||||
background-color: var(--kungalgame-trans-white-5);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,9 +120,7 @@ const iconMap: Record<string, string> = {
|
|||
.sort-submenu {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
background-color: var(--kungalgame-trans-white-2);
|
||||
box-shadow: var(--shadow);
|
||||
border-radius: 5px;
|
||||
box-shadow: 1px 2px 1px 1px var(--kungalgame-trans-blue-4);
|
||||
}
|
||||
|
||||
.container:hover .sort-submenu {
|
||||
|
@ -136,6 +129,7 @@ const iconMap: Record<string, string> = {
|
|||
|
||||
.sort-item {
|
||||
padding: 10px 0;
|
||||
background-color: var(--kungalgame-trans-white-2);
|
||||
font-size: 14px;
|
||||
color: var(--kungalgame-font-color-3);
|
||||
text-decoration: none;
|
||||
|
@ -150,10 +144,6 @@ const iconMap: Record<string, string> = {
|
|||
&:active {
|
||||
background-color: var(--kungalgame-trans-blue-2);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-item {
|
||||
|
@ -164,10 +154,10 @@ const iconMap: Record<string, string> = {
|
|||
|
||||
.sort-order {
|
||||
width: 100%;
|
||||
padding: 10px 0;
|
||||
display: flex;
|
||||
cursor: default;
|
||||
background-color: var(--kungalgame-trans-white-2);
|
||||
border-radius: 0 0 5px 5px;
|
||||
|
||||
span {
|
||||
color: var(--kungalgame-blue-4);
|
||||
|
@ -176,8 +166,6 @@ const iconMap: Record<string, string> = {
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 17px;
|
||||
padding: 10px 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
transition: all 0.2s;
|
||||
|
@ -189,26 +177,25 @@ const iconMap: Record<string, string> = {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
border: 1px solid var(--kungalgame-pink-4);
|
||||
background-color: var(--kungalgame-pink-4);
|
||||
color: var(--kungalgame-white);
|
||||
|
||||
& > span:nth-child(2) {
|
||||
color: var(--kungalgame-white);
|
||||
}
|
||||
background-color: var(--kungalgame-trans-red-3);
|
||||
|
||||
.filter {
|
||||
color: var(--kungalgame-pink-4);
|
||||
color: var(--kungalgame-red-4);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.sort-item {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
&:nth-child(1) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue