This is another component
3 | 4 | -------------------------------------------------------------------------------- /packages/documentation/docs/guide/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Combo819/social-media-archiver/HEAD/packages/documentation/docs/guide/debug.png -------------------------------------------------------------------------------- /packages/frontend/src/Component/RepostCommentList/index.ts: -------------------------------------------------------------------------------- 1 | import RepostCommentList from './RepostCommentList'; 2 | export { RepostCommentList }; 3 | -------------------------------------------------------------------------------- /packages/backend/src/Components/Post/DAL/postMigration.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | const postMigration = {}; 4 | 5 | export { postMigration }; 6 | -------------------------------------------------------------------------------- /packages/documentation/docs/guide/headbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Combo819/social-media-archiver/HEAD/packages/documentation/docs/guide/headbook.png -------------------------------------------------------------------------------- /packages/documentation/docs/zh/guide/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Combo819/social-media-archiver/HEAD/packages/documentation/docs/zh/guide/debug.png -------------------------------------------------------------------------------- /packages/documentation/docs/zh/guide/disclaimer.md: -------------------------------------------------------------------------------- 1 | # 免责声明 2 | 本项目采用MIT协议。自行承担使用本项目所引起的任何风险。 3 | 请遵守各平台API的条款和条件。如有必要,请及时删除下载的内容。 4 | 本项目用于学习与交流,请勿公开部署。 -------------------------------------------------------------------------------- /packages/extension-chrome/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Combo819/social-media-archiver/HEAD/packages/extension-chrome/public/favicon.ico -------------------------------------------------------------------------------- /packages/extension-chrome/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Combo819/social-media-archiver/HEAD/packages/extension-chrome/public/logo192.png -------------------------------------------------------------------------------- /packages/extension-chrome/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Combo819/social-media-archiver/HEAD/packages/extension-chrome/public/logo512.png -------------------------------------------------------------------------------- /packages/frontend/src/Utility/route/index.ts: -------------------------------------------------------------------------------- 1 | export * from './generateQueryString'; 2 | export * from './getRouteState'; 3 | export * from './useQuery'; 4 | -------------------------------------------------------------------------------- /packages/backend/src/Components/Comment/DAL/index.ts: -------------------------------------------------------------------------------- 1 | export * from './commentDAL'; 2 | export * from './commentSchema'; 3 | export * from './commentMigration'; 4 | -------------------------------------------------------------------------------- /packages/backend/src/Config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './constants'; 2 | export * from './axios'; 3 | export * from './httpCode'; 4 | export * from './paths'; 5 | -------------------------------------------------------------------------------- /packages/documentation/docs/zh/guide/headbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Combo819/social-media-archiver/HEAD/packages/documentation/docs/zh/guide/headbook.png -------------------------------------------------------------------------------- /packages/documentation/docs/zh/guide/build.md: -------------------------------------------------------------------------------- 1 | # 打包 2 | 完成代码之后,就可以为Linux、Mac和Windows构建二进制可执行文件了。 3 | 在项目根目录下,运行: 4 | ```bash 5 | npm run dist 6 | ``` 7 | 打包后的文件将会在 `dist` 目录中。 -------------------------------------------------------------------------------- /packages/backend/src/Components/Comment/DAL/commentMigration.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | const commentMigration = { 4 | }; 5 | 6 | export { commentMigration }; 7 | -------------------------------------------------------------------------------- /packages/backend/src/Components/SubComment/DAL/index.ts: -------------------------------------------------------------------------------- 1 | export * from './subCommentDAL'; 2 | export * from './subCommentMigration'; 3 | export * from './subCommentSchema'; 4 | -------------------------------------------------------------------------------- /packages/backend/src/Components/Video/Types/videoSymbols.ts: -------------------------------------------------------------------------------- 1 | const VIDEO_IOC_SYMBOLS = { 2 | IVideoService: Symbol('IVideoService'), 3 | }; 4 | export { VIDEO_IOC_SYMBOLS }; 5 | -------------------------------------------------------------------------------- /packages/frontend/src/Component/CommentList/CommentList.module.scss: -------------------------------------------------------------------------------- 1 | .comment-list-pagination { 2 | margin-top: 16px; 3 | margin-bottom: 16px; 4 | float: right; 5 | } 6 | -------------------------------------------------------------------------------- /packages/frontend/src/Store/index.ts: -------------------------------------------------------------------------------- 1 | import { RootState as RootStateType } from './reducers'; 2 | export * from './actions'; 3 | 4 | export type RootState = RootStateType; 5 | -------------------------------------------------------------------------------- /packages/backend/src/Components/SubComment/DAL/subCommentMigration.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | const subCommentMigration = {}; 4 | 5 | export { subCommentMigration }; 6 | -------------------------------------------------------------------------------- /packages/backend/src/Components/RepostComment/DAL/index.ts: -------------------------------------------------------------------------------- 1 | export * from './repostCommentDAL'; 2 | export * from './repostCommentSchema'; 3 | export * from './repostCommentMigration'; 4 | -------------------------------------------------------------------------------- /packages/frontend/src/Api/config.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const BASE_URL = ''; 4 | 5 | axios.defaults.baseURL = BASE_URL + '/api'; 6 | 7 | export { axios, BASE_URL }; 8 | -------------------------------------------------------------------------------- /packages/frontend/src/Component/SubCommentList/SubCommentList.module.scss: -------------------------------------------------------------------------------- 1 | .sub-comment-list-pagination { 2 | margin-top: 16px; 3 | margin-bottom: 16px; 4 | float: right; 5 | } 6 | -------------------------------------------------------------------------------- /packages/frontend/src/Utility/parseUrl/index.ts: -------------------------------------------------------------------------------- 1 | import { getImageUrl } from './getImageUrl'; 2 | import { getVideoUrl } from './getVideoUrl'; 3 | export { getImageUrl, getVideoUrl }; 4 | -------------------------------------------------------------------------------- /packages/extension-chrome/src/App.css: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.css'; 2 | .App { 3 | text-align: center; 4 | width: 400px; 5 | height: 300px; 6 | padding: 20px 30px; 7 | } 8 | -------------------------------------------------------------------------------- /packages/backend/src/Components/Account/Types/accountSymbols.ts: -------------------------------------------------------------------------------- 1 | const ACCOUNT_IOC_SYMBOLS = { 2 | IAccountService: Symbol('IAccountService'), 3 | }; 4 | 5 | export { ACCOUNT_IOC_SYMBOLS }; 6 | -------------------------------------------------------------------------------- /packages/backend/src/Components/Monitor/Types/monitorSymbols.ts: -------------------------------------------------------------------------------- 1 | const MONITOR_IOC_SYMBOLS = { 2 | IMonitorService: Symbol.for('IMonitorService'), 3 | }; 4 | 5 | export { MONITOR_IOC_SYMBOLS }; -------------------------------------------------------------------------------- /packages/backend/src/Components/RepostComment/DAL/repostCommentMigration.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | const repostCommentMigration = {}; 4 | 5 | export { repostCommentMigration }; 6 | -------------------------------------------------------------------------------- /packages/backend/src/Components/Image/Types/imageSymbols.ts: -------------------------------------------------------------------------------- 1 | const IMAGE_IOC_SYMBOLS = { 2 | ImageServiceInterface: Symbol('ImageServiceInterface'), 3 | }; 4 | 5 | export { IMAGE_IOC_SYMBOLS }; 6 | -------------------------------------------------------------------------------- /packages/frontend/src/Pages/Home/Home.module.scss: -------------------------------------------------------------------------------- 1 | .home-card{ 2 | margin-top: 16px; 3 | } 4 | 5 | .home-pagination{ 6 | margin-top: 16px; 7 | margin-bottom: 16px; 8 | float: right; 9 | } -------------------------------------------------------------------------------- /packages/frontend/src/Api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './post'; 2 | export * from './comment'; 3 | export * from './monitor'; 4 | export * from './user'; 5 | export * from './account'; 6 | export * from './import' -------------------------------------------------------------------------------- /packages/backend/src/Components/User/Types/userSymbols.ts: -------------------------------------------------------------------------------- 1 | const USER_IOC_SYMBOLS = { 2 | IUserService: Symbol('IUserService'), 3 | IUserDAL: Symbol('IUserDAL'), 4 | }; 5 | 6 | export { USER_IOC_SYMBOLS }; 7 | -------------------------------------------------------------------------------- /packages/documentation/.npmignore: -------------------------------------------------------------------------------- 1 | pids 2 | logs 3 | node_modules 4 | npm-debug.log 5 | coverage/ 6 | run 7 | dist 8 | .DS_Store 9 | .nyc_output 10 | .basement 11 | config.local.js 12 | basement_dist 13 | -------------------------------------------------------------------------------- /packages/backend/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": ".ts,.js", 4 | "ignore": [], 5 | "exec": "ts-node ./src/index.ts", 6 | "env": { 7 | "NODE_ENV": "development" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/frontend/src/Utility/route/useQuery.ts: -------------------------------------------------------------------------------- 1 | import { useLocation } from 'react-router-dom'; 2 | 3 | function useQuery() { 4 | return new URLSearchParams(useLocation().search); 5 | } 6 | 7 | export { useQuery }; 8 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.vscode 3 | credential.json 4 | /static 5 | /storage 6 | /*-rxdb-* 7 | /credential* 8 | **/web 9 | /dist 10 | /copyScript.js 11 | /rxdb 12 | /src/images 13 | **/node_modules 14 | **/.vscode 15 | -------------------------------------------------------------------------------- /packages/backend/src/Config/httpCode.ts: -------------------------------------------------------------------------------- 1 | const HTTP_STATUS_CODE = { 2 | OK: 200, 3 | BAD_REQUEST: 400, 4 | NOT_FOUND: 404, 5 | INTERNAL_SERVER: 500, 6 | CONFLICT: 409, 7 | }; 8 | 9 | export { HTTP_STATUS_CODE }; 10 | -------------------------------------------------------------------------------- /packages/frontend/src/Component/MonitorPopover/monitorPopover.module.scss: -------------------------------------------------------------------------------- 1 | .card { 2 | .remove-icon { 3 | color: #ff4d4f; 4 | cursor: pointer; 5 | } 6 | .validate-icon{ 7 | cursor: pointer; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/documentation/docs/zh/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /logo.svg 4 | tagline: 一个Node.js模板,可存档任何社交媒体的帖子 5 | actionText: 快速开始 → 6 | actionLink: /zh/guide/ 7 | features: null 8 | footer: Made by with ❤️ 9 | --- 10 | -------------------------------------------------------------------------------- /packages/frontend/src/Utility/parseUrl/getImageUrl.ts: -------------------------------------------------------------------------------- 1 | import { BASE_URL } from '../../Api/config'; 2 | const getImageUrl = (fileName: string): string => { 3 | return `${BASE_URL}/images/${fileName}`; 4 | }; 5 | 6 | export { getImageUrl }; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /static 3 | **/build 4 | **/credential* 5 | **/dist 6 | /rxdb 7 | **/log 8 | **/node_modules 9 | **/storage 10 | 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | lerna-debug.log* 15 | .next 16 | out -------------------------------------------------------------------------------- /packages/backend/src/Components/Post/Types/postSymbols.ts: -------------------------------------------------------------------------------- 1 | const POST_IOC_SYMBOLS = { 2 | IPostService: Symbol('IPostService'), 3 | IPostDAL: Symbol('IPostDAL'), 4 | IPostCrawler: Symbol('IPostCrawler'), 5 | }; 6 | 7 | export { POST_IOC_SYMBOLS }; 8 | -------------------------------------------------------------------------------- /packages/frontend/src/Utility/parseUrl/getVideoUrl.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { BASE_URL } from '../../Api/config'; 3 | const getVideoUrl = (fileName:string): string => { 4 | return `${BASE_URL}/videos/${fileName}`; 5 | }; 6 | 7 | export { getVideoUrl }; 8 | -------------------------------------------------------------------------------- /packages/backend/src/Components/Video/Types/videoTypes.ts: -------------------------------------------------------------------------------- 1 | type ParamsQueue = { 2 | url: string; 3 | staticPath: string; 4 | }; 5 | 6 | interface IVideoService { 7 | downloadVideo: (videoUrl: string) => void; 8 | } 9 | 10 | export { ParamsQueue, IVideoService }; 11 | -------------------------------------------------------------------------------- /packages/documentation/docs/.vuepress/styles/index.styl: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom Styles here. 3 | * 4 | * ref:https://v1.vuepress.vuejs.org/config/#index-styl 5 | */ 6 | 7 | .home .hero img 8 | max-width 450px!important 9 | 10 | p>img 11 | display block 12 | margin auto -------------------------------------------------------------------------------- /packages/frontend/src/Component/AccountModal/accountModal.module.scss: -------------------------------------------------------------------------------- 1 | .profile{ 2 | .avatar{ 3 | display: inline-block; 4 | } 5 | .username{ 6 | margin-left: 20px; 7 | display: inline-block; 8 | } 9 | margin-bottom: 20px; 10 | } -------------------------------------------------------------------------------- /packages/frontend/src/Component/PostCard/index.module.scss: -------------------------------------------------------------------------------- 1 | .deleteButton{ 2 | float: right; 3 | cursor: pointer; 4 | color: rgba(0, 0, 0, 0.45); 5 | } 6 | 7 | .deleteButton:hover{ 8 | color:#ff4d4f 9 | } 10 | 11 | .time{ 12 | color: #00000073; 13 | } -------------------------------------------------------------------------------- /packages/backend/src/Components/Comment/Types/commentSymbols.ts: -------------------------------------------------------------------------------- 1 | const COMMENT_IOC_SYMBOLS = { 2 | ICommentService: Symbol('ICommentService'), 3 | ICommentDAL: Symbol('ICommentDAL'), 4 | ICommentCrawler: Symbol('ICommentCrawler'), 5 | }; 6 | 7 | export { COMMENT_IOC_SYMBOLS }; 8 | -------------------------------------------------------------------------------- /packages/documentation/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /logo.svg 4 | tagline: A Node.js template to be implemented to archive post from any social media. 5 | actionText: Quick Start → 6 | actionLink: /guide/ 7 | features: null 8 | footer: Made by with ❤️ 9 | --- 10 | -------------------------------------------------------------------------------- /packages/documentation/docs/.vuepress/styles/palette.styl: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom palette here. 3 | * 4 | * ref:https://v1.vuepress.vuejs.org/zh/config/#palette-styl 5 | */ 6 | 7 | $accentColor = #3eaf7c 8 | $textColor = #2c3e50 9 | $borderColor = #eaecef 10 | $codeBgColor = #282c34 11 | -------------------------------------------------------------------------------- /packages/documentation/docs/guide/build.md: -------------------------------------------------------------------------------- 1 | # Build 2 | After you have completed the code, you can build binary executable files for Linux, Mac, and Windows. 3 | In project root directory, run: 4 | ```bash 5 | npm run dist 6 | ``` 7 | Then, archived files for each platform will be created in `dist` directory. -------------------------------------------------------------------------------- /packages/extension-chrome/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /packages/backend/src/Error/ErrorClass/index.ts: -------------------------------------------------------------------------------- 1 | export * from './BadRequestError'; 2 | export * from './DatabaseError'; 3 | export * from './NotFoundError'; 4 | export * from './ResourceError'; 5 | export * from './ServerError'; 6 | export * from './ConflictError'; 7 | export * from './NotImplementedError'; 8 | -------------------------------------------------------------------------------- /packages/frontend/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /packages/backend/src/Components/SubComment/Types/subCommentSymbols.ts: -------------------------------------------------------------------------------- 1 | const SUB_COMMENT_IOC_SYMBOLS = { 2 | ISubCommentDAL: Symbol('ISubCommentDAL'), 3 | ISubCommentService: Symbol('ISubCommentService'), 4 | ISubCommentCrawler: Symbol('ISubCommentCrawler'), 5 | }; 6 | 7 | export { SUB_COMMENT_IOC_SYMBOLS }; 8 | -------------------------------------------------------------------------------- /packages/documentation/docs/.vuepress/components/Foo/Bar.vue: -------------------------------------------------------------------------------- 1 | 2 |3 | {{ msg }} 4 |
5 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /packages/frontend/src/Api/user.ts: -------------------------------------------------------------------------------- 1 | import { axios } from './config'; 2 | import { AxiosPromise } from 'axios'; 3 | 4 | function getUsersByNameApi(username: string): AxiosPromise3 | {{ msg }} 4 |
5 | 6 | 7 | 16 | -------------------------------------------------------------------------------- /packages/frontend/src/Store/actions/account.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from '../../types'; 2 | import { ACCOUNT } from '../actionTypes'; 3 | 4 | const accountCreators = { 5 | setAccount: (payload: IUser | null) => ({ 6 | type: ACCOUNT.SET_ACCOUNT, 7 | payload, 8 | }), 9 | }; 10 | 11 | export { accountCreators }; 12 | -------------------------------------------------------------------------------- /packages/extension-chrome/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "social-media-archiver", 4 | "description": "archive a post", 5 | "version": "1.0.0", 6 | "browser_action": { 7 | "default_popup": "index.html" 8 | }, 9 | "permissions": ["http://*/","https://*/","storage"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/backend/src/Components/Image/Types/imageTypes.ts: -------------------------------------------------------------------------------- 1 | type ParamsQueue = { 2 | url: string; 3 | staticPath: string; 4 | }; 5 | 6 | interface ImageServiceInterface { 7 | downloadImage: ( 8 | imageUrl: string, 9 | priority?: number, 10 | ) => void; 11 | } 12 | 13 | export { ParamsQueue, ImageServiceInterface }; 14 | -------------------------------------------------------------------------------- /packages/backend/src/Components/RepostComment/Types/repostCommentSymbols.ts: -------------------------------------------------------------------------------- 1 | const REPOST_COMMENT_IOC_SYMBOLS = { 2 | IRepostCommentDAL: Symbol('IRepostCommentDAL'), 3 | IRepostCommentService: Symbol('IRepostCommentService'), 4 | IRepostCommentCrawler: Symbol('IRepostCommentCrawler'), 5 | }; 6 | 7 | export { REPOST_COMMENT_IOC_SYMBOLS }; 8 | -------------------------------------------------------------------------------- /packages/backend/src/Components/Image/Service/imageApi.ts: -------------------------------------------------------------------------------- 1 | import { downloadAxios } from '../../../Config'; 2 | import { AxiosPromise } from 'axios'; 3 | 4 | function downloadImageApi(url: string): AxiosPromise { 5 | return downloadAxios({ 6 | url, 7 | responseType: 'stream', 8 | }); 9 | } 10 | 11 | export { downloadImageApi }; 12 | -------------------------------------------------------------------------------- /packages/backend/src/Components/Video/Service/videoApi.ts: -------------------------------------------------------------------------------- 1 | import { downloadAxios } from '../../../Config'; 2 | import { AxiosPromise } from 'axios'; 3 | 4 | function downloadVideoApi(url: string): AxiosPromise { 5 | return downloadAxios({ 6 | url, 7 | responseType: 'stream', 8 | }); 9 | } 10 | 11 | export { downloadVideoApi }; 12 | -------------------------------------------------------------------------------- /packages/extension-chrome/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(
3 |
4 |
5 |
Social Media Archiver
6 |
7 |
4 |
5 |
6 |
Social Media Archiver
7 |
8 |
12 | Monitor can monitor the collections. You can add the api url of a 13 | collection, like a upvote, watch later, favorite, and so on, as long 14 | as it's supported. Once you add the api url, the monitor module will 15 | request the api periodically, and catch the new added post in the 16 | collection, and then backup that post. 17 |
18 |19 | First, select the collection type, and then copy the api url from 20 | the Chrome debug tool to the collection url input box. 21 |
22 |26 | Monitor可以监控集合。你可以添加集合(比如点赞,收藏,稍后观看,喜爱等等,只要程序支持这种集合)的api 27 | url。添加后,monitor模块会定时请求api,当监测到有新的贴文被添加进这个集合后,本程序就会把这个新的贴文备份下来。 28 |
29 |30 | 首先,选择collection 31 | type,然后复制Chrome调试工具中的api的url到collection url输入框中。 32 |
33 |
38 | ::: tip
39 | [head-book](https://head-book.ml) 是一个假社交媒体网站,你可以在 headbook 上看这个结构的例子。
40 | :::
41 |
42 | ## 运作原理
43 |
44 | 
45 | 用户从网页或扩展提交一个帖子得 url。后端获取到帖子得 url 并解析出帖子 id。然后将帖子 id 传递给爬虫模块,爬虫模块获取帖子数据并从平台上下载图片和视频。
46 | 还有一个模块负责控制请求速度,避免 429 错误。你可以在配置文件中配置请求速度。
47 | 当用户浏览网页时,后端会从数据库中获取爬下来得数据,并将其返回给浏览器。然后网页将显示已被保存的帖子。
48 |
--------------------------------------------------------------------------------
/packages/frontend/src/Component/AppSider/AppSider.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Button, Layout, Modal } from 'antd';
3 | import { PostQuery } from '../PostQuery/PostQuery';
4 | import styles from './AppSider.module.scss';
5 | import { Collapser } from './Collapser';
6 | import { useMedia } from 'react-use';
7 | import { SearchOutlined } from '@ant-design/icons';
8 |
9 | const { Sider } = Layout;
10 | function AppSider() {
11 | const [collapsed, setCollapsed] = useState