Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: information page of specific club & autoclean HTML <script> <style> elements #494

Merged
merged 38 commits into from
Mar 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e42ac12
feat: detail information of specific club
at-wr Mar 2, 2024
d399d27
style: make the code fit Lint style
at-wr Mar 2, 2024
10ca1f4
style: make the code fit Lint style (again)
at-wr Mar 2, 2024
d18f441
style: add trailing comma
at-wr Mar 2, 2024
68ff9a1
style: merge `filteredClubs` into 2 lines
at-wr Mar 2, 2024
9e32ccf
style: remove extra semicolons and fixed the missing trailing comma i…
at-wr Mar 2, 2024
307910a
style: remove useless comment
at-wr Mar 2, 2024
ba2fe84
docs: Clubs page retitled
at-wr Mar 2, 2024
87d2f5e
style: use `NuxtLink` instead of `nuxt-link`
at-wr Mar 2, 2024
b05e7fe
fix: make the layout responsive by adjusting the custom mobile layout
at-wr Mar 2, 2024
83a1be1
feat: show `暂无简介` while there's no description for certain clubs
at-wr Mar 2, 2024
8382bc3
feat: new function `cleanHTML()`
at-wr Mar 2, 2024
0e0181c
refactor: use `cleanHTML` in order to clean <script> and <style> tags
at-wr Mar 2, 2024
b42c072
style: remove extra semicolon and spaces
at-wr Mar 2, 2024
5ae51e8
style: remove extra semicolon
at-wr Mar 2, 2024
1eeb48f
style: remove extra semicolon and add self-closing on <p>
at-wr Mar 2, 2024
6e7f20f
feat: add hint to go back if the club does not exist
at-wr Mar 2, 2024
6106e4f
fix: return `''` if `!content`
at-wr Mar 2, 2024
7e332da
style: merge into single line
at-wr Mar 2, 2024
b76ea25
style: fix Linter issues
at-wr Mar 2, 2024
b3d2659
style: remove v-bind
at-wr Mar 2, 2024
c1c1f56
fix: altered to the correct link
at-wr Mar 2, 2024
9aa9188
feat: add JetBrains Run/Debug Configuration
at-wr Mar 2, 2024
65c3e81
refactor: filter club info with a brand-new method
at-wr Mar 2, 2024
b2ce433
refactor: move `cleanHTML` to `/utils`
at-wr Mar 2, 2024
bb87ec6
refactor: move `cleanHTML` to `/utils`
at-wr Mar 2, 2024
4ee8c34
fix: add several styles to certain objects
at-wr Mar 2, 2024
b8d1b53
refactor: merge into `/cas/clubs`
at-wr Mar 2, 2024
f39ecc6
refactor: update URL of `/cas/clubs/*`
at-wr Mar 2, 2024
fcb7b69
fix: use `v-if` instead of `v-html`
at-wr Mar 2, 2024
8a67185
style: remove some nobody
at-wr Mar 2, 2024
0133094
refactor: merge `cleanHTML()` into `utils.ts`
at-wr Mar 2, 2024
9799043
refactor: merged into `utils.ts`
at-wr Mar 2, 2024
25a69f3
refactor: merged into single line
at-wr Mar 2, 2024
7a6f98c
refactor: unify `~/` into `@/`
at-wr Mar 2, 2024
ae8dc8b
fix: minor typescript fixes
at-wr Mar 2, 2024
f2a02a2
fix: minor fixes
qwerzl Mar 2, 2024
b8e3227
fix: minor fixes
qwerzl Mar 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .run/Run Nuxt via pnpm.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Nuxt via pnpm" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="pnpm run dev" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>
20 changes: 12 additions & 8 deletions components/custom/club-card.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
<script setup lang="ts">
import type { PropType } from 'vue'
import type { Club } from '~/content/clubs'
import type { Club } from '@/content/clubs'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
import { cn } from '@/lib/utils'
import Badge from '~/components/ui/badge/Badge.vue'
import { cleanHTML, cn } from '@/lib/utils'
import Badge from '@/components/ui/badge/Badge.vue'

const props = defineProps({
club: {
type: Object as PropType<Club>,
required: true,
},
})

const Description_C = cleanHTML(props.club.groups[0].C_DescriptionC)
</script>

<template>
Expand Down Expand Up @@ -44,8 +46,8 @@ const props = defineProps({
<CardContent class="grid gap-4">
<div class=" flex items-center space-x-4 rounded-md">
<div class="flex-1 space-y-1">
<ScrollArea v-if="props.club.groups[0].C_DescriptionC" class="text-sm h-32">
{{ props.club.groups[0].C_DescriptionC }}
<ScrollArea v-if="Description_C" class="text-sm h-32">
{{ Description_C }}
</ScrollArea>
<!-- idk why some clubs have no description -->
<div v-else class="h-32 flex items-center">
Expand All @@ -57,9 +59,11 @@ const props = defineProps({
</div>
</CardContent>
<CardFooter>
<Button class="w-full">
详细信息
</Button>
<NuxtLink :to="`/cas/clubs/${props.club.groups[0].C_GroupsID}`" class="w-full">
<Button class="w-full">
详细信息
</Button>
</NuxtLink>
</CardFooter>
</Card>
</template>
2 changes: 1 addition & 1 deletion components/custom/sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { Button } from '@/components/ui/button'
<NuxtLink to="/cas/clubs">
<Button :variant="$route.name === 'cas-clubs' ? 'secondary' : 'ghost'" class="w-full justify-start">
<Icon class="mr-2 h-4 w-4" name="material-symbols:grid-view-outline-rounded" />
我们的社团
社团列表
</Button>
</NuxtLink>
<NuxtLink to="/cas/checkin">
Expand Down
5 changes: 3 additions & 2 deletions content/clubs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface Clubs {
Academic: (Club)[]
Personal?: (Club)[] | null
}
type ClubCategoryKey = Exclude<keyof Clubs, 'Personal'>
export interface Groups {
C_GroupsID: string
C_GroupNo: string
Expand Down Expand Up @@ -44,8 +45,8 @@ export interface Clubrecord {

export interface Club {
groups: (Groups)[]
supervisor?: (Supervisor | null)[] | null
supervisor?: (Supervisor)[]
gmember: (Clubmember)[]
grecord?: (Clubrecord | null)[] | null
grecord?: (Clubrecord)[]
projectyes: number
}
8 changes: 8 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ export function valueUpdater<T extends Updater<any>>(updaterOrValue: T, ref: Ref
? updaterOrValue(ref.value)
: updaterOrValue
}

export function cleanHTML(content: string): string {
if (!content)
return ''

return content.replace(/<script[^>]*>([\s\S]*?)<\/script>/gmi, '')
.replace(/<style[^>]*>([\s\S]*?)<\/style>/gmi, '')
}
147 changes: 147 additions & 0 deletions pages/cas/clubs/[id].vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<script lang="ts" setup>
import { useRoute } from 'vue-router'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { cleanHTML } from '@/lib/utils'
import json from '@/content/clubs.json'
import type { Club, Clubs } from '@/content/clubs'

const clubs: Clubs = json as Clubs
const route = useRoute()
const id = route.params.id // Fetch current Club ID via route params

// Filter clubs based on C_GroupsID and include information at the same level as groups
const filteredClubs = Object.values(clubs).flatMap(clubCategory =>
clubCategory.filter((club: Club) =>
club.groups.some(group => group.C_GroupsID === id),
).map((club: Club) => ({
...club, // Spread to include all same-level information
groups: club.groups.filter(group => group.C_GroupsID === id), // Filter groups to only include those that match the ID
})),
) as Club[]

// Get the number of members in each group
const groupMemberCounts = filteredClubs.length > 0 ? filteredClubs[0].gmember.length : 0

// Get the Chinese Description of the club
let hasDescriptionC = false
let Description_C = ''

if (filteredClubs[0] && filteredClubs[0].groups[0].C_DescriptionC) {
Description_C = cleanHTML(filteredClubs[0].groups[0].C_DescriptionC)
hasDescriptionC = true
}

// This page requires login
definePageMeta({
middleware: ['auth'],
})
</script>

<template>
<div v-if="filteredClubs.length > 0">
<div v-for="club in filteredClubs" :key="club.groups[0].C_GroupNo">
<div v-for="group in club.groups" :key="group.C_GroupsID">
<div class="flex flex-col-reverse xl:flex-row">
<Card class="xl:w-3/4 w-full mt-2 xl:mt-0">
<CardHeader>
<CardTitle class="flex justify-between items-center">
<div>
{{ group.C_NameC }}
</div>
<Badge v-if="club.gmember.length === 0" variant="destructive">
已解散
</badge>
</CardTitle>

<CardDescription class="flex items-center">
<Icon name="material-symbols:language" />
<div class="ml-1">
{{ group.C_NameE }}
</div>
</CardDescription>
</CardHeader>
<CardContent>
<div v-if="hasDescriptionC" v-text="Description_C" />
<div v-else class="text-sm italic text-muted-foreground text-center w-full">
暂无简介 ;-(
</div>
<!-- Don't show the English Description until i18n is completed -->
</CardContent>
</Card>
<Card class="xl:w-1/4 w-full xl:ml-2">
<CardHeader>
<CardTitle class="flex items-center gap-x-1">
社团属性
</CardTitle>
<CardDescription class="flex items-center">
<Icon name="material-symbols:info-outline" />
<div class="ml-1">
Club Information
</div>
</CardDescription>
</CardHeader>
<CardContent>
<div>
<span class="font-bold">社团类型</span>: {{ group.C_Category }}
</div>
<div>
<span class="font-bold">社团人数</span>: {{ groupMemberCounts }} 人
</div>
<div class="flex">
<span class="font-bold">指导老师:</span>
<span v-for="supervisor in club.supervisor" :key="supervisor.TeacherID" class="ml-2">
{{ supervisor.T_Name }} ({{ supervisor.T_Nickname }})
</span>
</div>
</CardContent>
</Card>
</div>
<!-- <div style="display:none"> -->
<!-- <Card v-if="club.grecord.length > 0" class="w-full"> -->
<!-- <CardHeader> -->
<!-- <CardTitle class="flex justify-between items-center"> -->
<!-- <div> -->
<!-- 近期活动 -->
<!-- </div> -->
<!-- </CardTitle> -->

<!-- <CardDescription class="flex items-center"> -->
<!-- <Icon name="material-symbols:draw-outline" /> -->
<!-- <div class="ml-1"> -->
<!-- Recent Activities -->
<!-- </div> -->
<!-- </CardDescription> -->
<!-- </CardHeader> -->
<!-- <CardContent> -->
<!-- <div v-for="grecord in club.grecord" :key="grecord.C_Theme"> -->
<!-- <div class="font-bold"> -->
<!-- {{ grecord.C_Theme }} -->
<!-- </div> -->
<!-- <div class="text-sm text-muted-foreground mb-1"> -->
<!-- <Icon name="material-symbols:schedule-outline" /> -->
<!-- {{ grecord.C_Date }} -->
<!-- </div> -->
<!-- <div class="text-sm"> -->
<!-- {{ grecord.C_Reflection }} -->
<!-- </div> -->
<!-- <br> -->
<!-- </div> -->
<!-- </CardContent> -->
<!-- </Card> -->
<!-- </div> -->
</div>
</div>
</div>
<div v-else>
<div class="flex flex-col justify-center h-1/2 text-center">
<h3 class="font-bold text-xl">
你当前访问的页面不存在,也许你应该考虑...
</h3>
<br>
<NuxtLink class="w-full" to="/">
<Button>回到主页</Button>
</NuxtLink>
</div>
</div>
</template>
18 changes: 10 additions & 8 deletions pages/cas/clubs.vue → pages/cas/clubs/index.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
<script setup lang="ts">
import TabsList from '~/components/ui/tabs/TabsList.vue'
import Tabs from '~/components/ui/tabs/Tabs.vue'
import TabsContent from '~/components/ui/tabs/TabsContent.vue'
import TabsTrigger from '~/components/ui/tabs/TabsTrigger.vue'
import json from '~/content/clubs.json'
import TabsList from '@/components/ui/tabs/TabsList.vue'
import Tabs from '@/components/ui/tabs/Tabs.vue'
import TabsContent from '@/components/ui/tabs/TabsContent.vue'
import TabsTrigger from '@/components/ui/tabs/TabsTrigger.vue'
import json from '@/content/clubs.json'

import type { Clubs } from '~/content/clubs'
import ClubCard from '~/components/custom/club-card.vue'
import type { ClubCategoryKey, Clubs } from '@/content/clubs'
import ClubCard from '@/components/custom/club-card.vue'

const clubs: Clubs = json as Clubs

// This page requires login
definePageMeta({
middleware: ['auth'],
})

const categories = (['Sports', 'Service', 'Arts', 'Life', 'Academic'] as const).map(c => c as ClubCategoryKey)
</script>

<template>
Expand All @@ -39,7 +41,7 @@ definePageMeta({
</TabsList>
</div>
<TabsContent
v-for="i in ['Sports', 'Service', 'Arts', 'Life', 'Academic']"
v-for="i in categories"
:key="i"
:value="i"
class="border-none p-0 outline-none"
Expand Down