├── blog_dv ├── media │ └── .gitkeep ├── article │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── tests.py │ ├── apps.py │ ├── admin.py │ ├── filters.py │ ├── permissions.py │ ├── views.py │ ├── models.py │ └── serializers.py ├── blog_dv │ ├── __init__.py │ ├── asgi.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py ├── comment │ ├── __init__.py │ ├── tests.py │ ├── admin.py │ ├── apps.py │ ├── views.py │ ├── permissions.py │ ├── models.py │ └── serializers.py ├── jwt_token │ ├── __init__.py │ ├── views.py │ └── serializers.py ├── user_info │ ├── __init__.py │ ├── models.py │ ├── tests.py │ ├── admin.py │ ├── apps.py │ ├── permissions.py │ ├── serializers.py │ └── views.py └── manage.py ├── frontend ├── README.md ├── src │ ├── stores │ │ ├── index.js │ │ └── user.js │ ├── http │ │ ├── login.js │ │ ├── article.js │ │ └── index.js │ ├── views │ │ ├── Home.Vue │ │ ├── ArticleDetail.vue │ │ ├── UserCenter.vue │ │ ├── Login.vue │ │ ├── ArticleCreate.vue │ │ └── ArticleEdit.vue │ ├── main.js │ ├── components │ │ ├── BlogFooter.vue │ │ ├── SearchBox.vue │ │ ├── BlogHeader.vue │ │ ├── Comments.vue │ │ └── ArticleList.vue │ ├── assets │ │ └── vue.svg │ ├── App.vue │ ├── router │ │ └── index.js │ └── utils │ │ └── authorization.js ├── .gitignore ├── index.html ├── vite.config.js ├── package.json ├── public │ └── vite.svg └── package-lock.json ├── images └── home.jpg ├── .gitignore ├── README.md ├── requirements.txt └── LICENSE /blog_dv/media/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog_dv/article/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog_dv/blog_dv/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog_dv/comment/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog_dv/jwt_token/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog_dv/user_info/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog_dv/article/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /blog_dv/article/tests.py: -------------------------------------------------------------------------------- 1 | # from django.test import TestCase 2 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # 安装依赖 2 | 3 | - npm install axios 4 | - npm install vue-router@4 5 | -------------------------------------------------------------------------------- /images/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sonder-MX/Blogs-Django-Vue/HEAD/images/home.jpg -------------------------------------------------------------------------------- /blog_dv/user_info/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /blog_dv/user_info/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /blog_dv/comment/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /blog_dv/user_info/admin.py: -------------------------------------------------------------------------------- 1 | # from django.contrib import admin 2 | 3 | # Register your models here. 4 | # user: suer1 5 | # pwd: 12345678 6 | -------------------------------------------------------------------------------- /frontend/src/stores/index.js: -------------------------------------------------------------------------------- 1 | import { createPinia } from "pinia" 2 | 3 | const pinia = createPinia() 4 | 5 | // 主仓库 6 | export default pinia 7 | -------------------------------------------------------------------------------- /blog_dv/comment/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Comment 4 | 5 | # Register your models here. 6 | admin.site.register(Comment) 7 | -------------------------------------------------------------------------------- /blog_dv/article/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ArticleConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'article' 7 | -------------------------------------------------------------------------------- /blog_dv/comment/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CommentConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'comment' 7 | -------------------------------------------------------------------------------- /blog_dv/user_info/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UserInfoConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'user_info' 7 | -------------------------------------------------------------------------------- /frontend/src/http/login.js: -------------------------------------------------------------------------------- 1 | import request from "." 2 | 3 | // 用户登录 4 | export const reqLogin = (data) => request.post("/token/", data) 5 | 6 | // 用户注册 7 | export const reqRegister = (data) => request.post("/user/", data) 8 | -------------------------------------------------------------------------------- /blog_dv/user_info/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import SAFE_METHODS, BasePermission 2 | 3 | 4 | class IsSelfOrReadOnly(BasePermission): 5 | def has_object_permission(self, request, view, obj): 6 | if request.method in SAFE_METHODS: 7 | return True 8 | return obj == request.user 9 | -------------------------------------------------------------------------------- /blog_dv/article/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Article, Avatar, Category, Tag 4 | 5 | # Register your models here. 6 | # user name: admin 7 | # pwd: drf-vue 8 | admin.site.register(Article) 9 | admin.site.register(Category) 10 | admin.site.register(Tag) 11 | admin.site.register(Avatar) 12 | -------------------------------------------------------------------------------- /frontend/src/http/article.js: -------------------------------------------------------------------------------- 1 | import request from "." 2 | 3 | // 获取文章列表 4 | export const getArticleList = (page) => { 5 | if (page <= 1) { 6 | page = 1 7 | } 8 | return request.get("/article/?page=" + page) 9 | } 10 | 11 | // 根据id获取文章 12 | export const getArticleById = (articleId) => request.post(`/article/${articleId}/`) 13 | -------------------------------------------------------------------------------- /frontend/src/views/Home.Vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue" 2 | import App from "./App.vue" 3 | import router from "./router" 4 | import pinia from "./stores" 5 | 6 | URLSearchParams.prototype.appendIfExists = function (key, value) { 7 | if (value !== null && value !== undefined) { 8 | this.append(key, value) 9 | } 10 | } 11 | 12 | createApp(App).use(router).use(pinia).mount("#app") 13 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Blog 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import vue from "@vitejs/plugin-vue" 2 | import { resolve } from "path" 3 | import { defineConfig } from "vite" 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [vue()], 8 | server: { 9 | port: 9000, 10 | }, 11 | resolve: { 12 | alias: { 13 | "@": resolve(__dirname, "src"), // 路径别名 14 | }, 15 | extensions: [".js", ".json"], // 使用路径别名时想要省略的后缀名 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /blog_dv/article/filters.py: -------------------------------------------------------------------------------- 1 | from django_filters import rest_framework as filters 2 | 3 | from .models import Article 4 | 5 | 6 | class ArticleFilter(filters.FilterSet): 7 | """ 8 | 自定义文章标题过滤器类,实现对文章标题进行模糊搜索(不区分大小写) 9 | """ 10 | 11 | title = filters.CharFilter( 12 | field_name="title", lookup_expr="icontains", label="文章标题(模糊搜索且不区分大小写)" 13 | ) 14 | 15 | class Meta: 16 | model = Article 17 | fields = ["title"] 18 | -------------------------------------------------------------------------------- /blog_dv/article/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | 4 | class IsAdminUserOrReadOnly(permissions.BasePermission): 5 | """ 6 | 仅管理员可以修改 7 | 其他用户只可以查看 8 | """ 9 | 10 | def has_permission(self, request, view): 11 | # 对所有人允许 GET, HEAD, OPTIONS 请求 12 | if request.method in permissions.SAFE_METHODS: 13 | return True 14 | # 仅管理员可进行其他操作 15 | return request.user.is_superuser 16 | -------------------------------------------------------------------------------- /blog_dv/blog_dv/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for blog_dv project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog_dv.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /blog_dv/blog_dv/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for blog_dv project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog_dv.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /frontend/src/components/BlogFooter.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 23 | -------------------------------------------------------------------------------- /blog_dv/jwt_token/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView 2 | 3 | from .serializers import RefreshTokenSerializer, TokenSerializer 4 | 5 | 6 | class CustomTokenObtainPairView(TokenObtainPairView): 7 | """自定义 token 视图""" 8 | 9 | serializer_class = TokenSerializer 10 | 11 | 12 | class CustomTokenRefreshView(TokenRefreshView): 13 | """自定义 refresh 视图""" 14 | 15 | serializer_class = RefreshTokenSerializer 16 | -------------------------------------------------------------------------------- /blog_dv/comment/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets 2 | 3 | from .models import Comment 4 | from .permissions import IsOwnerOrReader 5 | from .serializers import CommentSeriallizer 6 | 7 | 8 | class CommentViewSet(viewsets.ModelViewSet): 9 | queryset = Comment.objects.all() 10 | serializer_class = CommentSeriallizer 11 | permission_classes = [IsOwnerOrReader] 12 | 13 | def perform_create(self, serializer): 14 | serializer.save(author=self.request.user) 15 | -------------------------------------------------------------------------------- /frontend/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/stores/user.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia" 2 | 3 | const useUserStore = defineStore("userStore", { 4 | state: () => ({ 5 | token: localStorage.getItem("blog.token") || null, 6 | refreshToken: localStorage.getItem("blog.refresh") || null, 7 | username: localStorage.getItem("blog.username") || null, 8 | tokenExp: localStorage.getItem("blog.tokenExp") || "", // token过期时间 9 | }), 10 | getters: {}, 11 | actions: {}, 12 | }) 13 | 14 | export default useUserStore 15 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@jridgewell/sourcemap-codec": "^1.4.14", 13 | "axios": "^1.3.4", 14 | "pinia": "^2.1.6", 15 | "vue": "^3.2.45", 16 | "vue-router": "^4.1.6" 17 | }, 18 | "devDependencies": { 19 | "@vitejs/plugin-vue": "^4.0.0", 20 | "vite": "^4.1.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 36 | -------------------------------------------------------------------------------- /blog_dv/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'blog_dv.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /blog_dv/comment/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import SAFE_METHODS, BasePermission 2 | 3 | 4 | class IsOwnerOrReader(BasePermission): 5 | message = "你必须是发布者才可以修改!" 6 | 7 | @staticmethod 8 | def safe_methods_or_owner(request, func): 9 | if request.method in SAFE_METHODS: 10 | return True 11 | return func() 12 | 13 | def has_permission(self, request, view): 14 | """登录验证""" 15 | return self.safe_methods_or_owner( 16 | request, lambda: request.user.is_authenticated 17 | ) 18 | 19 | def has_object_permission(self, request, view, obj): 20 | """obj为评论模型的实例,执行晚于视图集中perform_create()方法""" 21 | return self.safe_methods_or_owner(request, lambda: obj.author == request.user) 22 | -------------------------------------------------------------------------------- /blog_dv/comment/models.py: -------------------------------------------------------------------------------- 1 | from article.models import Article 2 | from django.contrib.auth.models import User 3 | from django.db import models 4 | from django.utils import timezone 5 | 6 | 7 | class Comment(models.Model): 8 | author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="comments") 9 | article = models.ForeignKey( 10 | Article, on_delete=models.CASCADE, related_name="comments" 11 | ) 12 | parent = models.ForeignKey( 13 | "self", 14 | null=True, 15 | blank=True, 16 | on_delete=models.SET_NULL, 17 | related_name="children", 18 | ) 19 | content = models.TextField(verbose_name="评论") 20 | created = models.DateTimeField(default=timezone.now) 21 | 22 | def __str__(self): 23 | return self.content[:20] 24 | 25 | class Meta: 26 | db_table = "comment_db" 27 | ordering = ["-created"] 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Installer logs 7 | pip-log.txt 8 | pip-delete-this-directory.txt 9 | 10 | # Django stuff: 11 | *.log 12 | local_settings.py 13 | db.sqlite3 14 | db.sqlite3-journal 15 | 16 | 17 | # Sphinx documentation 18 | docs/_build/ 19 | 20 | # PyBuilder 21 | target/ 22 | 23 | # Jupyter Notebook 24 | .ipynb_checkpoints 25 | 26 | # IPython 27 | profile_default/ 28 | ipython_config.py 29 | 30 | # pyenv 31 | .python-version 32 | 33 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 34 | __pypackages__/ 35 | 36 | # SageMath parsed files 37 | *.sage.py 38 | 39 | # Environments 40 | .env 41 | .venv 42 | env/ 43 | venv/ 44 | ENV/ 45 | env.bak/ 46 | venv.bak/ 47 | 48 | # setting file 49 | .idea 50 | .vscode 51 | 52 | # database migrations file 53 | migrations 54 | 55 | # media 56 | *.png 57 | *.jpg 58 | *.gif 59 | -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createWebHistory, createRouter } from "vue-router" 2 | 3 | const routes = [ 4 | { 5 | path: "/", 6 | name: "Home", 7 | component: () => import("@/views/Home.vue"), 8 | }, 9 | { 10 | path: "/article/:id", 11 | name: "ArticleDetail", 12 | component: () => import("@/views/ArticleDetail.vue"), 13 | }, 14 | { 15 | path: "/login", 16 | name: "Login", 17 | component: () => import("@/views/Login.vue"), 18 | }, 19 | { 20 | path: "/user/:username", 21 | name: "UserCenter", 22 | component: () => import("@/views/UserCenter.vue"), 23 | }, 24 | { 25 | path: "/article/create", 26 | name: "ArticleCreate", 27 | component: () => import("@/views/ArticleCreate.vue"), 28 | }, 29 | { 30 | path: "/article/edit/:id", 31 | name: "ArticleEdit", 32 | component: () => import("@/views/ArticleEdit.vue"), 33 | }, 34 | ] 35 | 36 | const router = createRouter({ 37 | history: createWebHistory(), 38 | routes, 39 | }) 40 | 41 | export default router 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 个人博客(Django4+Vue3) 2 | 3 | 👉[原教程传送](https://www.dusaiphoto.com/article/103/). 4 | 5 | ## 预览 6 | 7 | ![home](./images/home.jpg) 8 | 9 | ## 环境配置 10 | 11 | 1. [安装 Pyhton](https://www.python.org/) (version >= 3.10) 12 | 13 | - 安装依赖 14 | - `pip install -r requirements.txt` 15 | - 或者 `pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple` 16 | 17 | 2. [安装 nodejs](https://nodejs.org/en/) (version >= 18.14.0) 18 | 19 | - npm install -g @vue/cli 20 | 21 | ## 快速开始 22 | 23 | ### 准备 24 | 25 | 1. 进入 [blog_dv](/blog_dv) 文件夹下,依次执行以下命令,完成数据库的迁移(使用 sqlite 数据库): 26 | 27 | ```shell 28 | python manage.py makemigrations 29 | python manage.py migrate 30 | ``` 31 | 32 | 2. 创建超级用户: 33 | 34 | ```shell 35 | python manage.py createsuperuser # 按提示创建超级用户 36 | ``` 37 | 38 | 3. 进入 [frontend](/frontend) 文件夹下,执行以下命令: 39 | 40 | ```shell 41 | npm install 42 | ``` 43 | 44 | ### 启动 45 | 46 | 1. 进入 blog_dv 目录下,打开终端输入:`python manage.py runserver` 47 | 2. 进入 frontend 目录下,打开一个新的终端:`npm run dev` 48 | 3. 打开浏览器,输入地址:http://localhost:8000/admin/ 进入后台管理 49 | 4. http://localhost:9000 进入客户端 50 | 5. 完成! 51 | -------------------------------------------------------------------------------- /blog_dv/jwt_token/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework_simplejwt.serializers import ( 2 | TokenObtainPairSerializer, 3 | TokenRefreshSerializer, 4 | ) 5 | from rest_framework_simplejwt.state import token_backend 6 | 7 | 8 | class TokenSerializer(TokenObtainPairSerializer): 9 | """自定义获取token序列化器""" 10 | 11 | def validate(self, attrs): 12 | data = super().validate(attrs) 13 | refresh = self.get_token(self.user) # 获取Token对象 14 | # data["refresh"] = str(refresh) # 不添加也会默认返回 15 | # data["access"] = str(refresh.access_token) 16 | data["username"] = self.user.username 17 | data["token_exp"] = refresh.access_token.payload["exp"] # access_token过期时间 18 | return data 19 | 20 | @classmethod 21 | def get_token(cls, user): 22 | token = super().get_token(user) 23 | return token 24 | 25 | 26 | class RefreshTokenSerializer(TokenRefreshSerializer): 27 | """自定义刷新token序列化器""" 28 | 29 | def validate(self, attrs): 30 | data = super(RefreshTokenSerializer, self).validate(attrs) 31 | decoded_payload = token_backend.decode(data["access"], verify=True) 32 | data["token_exp"] = decoded_payload["exp"] # access_token过期时间 33 | return data 34 | -------------------------------------------------------------------------------- /frontend/src/http/index.js: -------------------------------------------------------------------------------- 1 | import axios from "axios" 2 | 3 | const tagUrl = "http://127.0.0.1:8000/api" 4 | 5 | export const sendGetReq = async (resUrl) => { 6 | const response = await axios.get(tagUrl + resUrl) 7 | return response.data 8 | } 9 | 10 | export const sendPostReq = async (surl, payload, config_obj = "") => { 11 | return await axios.post(tagUrl + surl, payload, config_obj) 12 | } 13 | 14 | export const sendPutReq = async (surl, payload, config_obj) => { 15 | return await axios.put(tagUrl + surl, payload, config_obj) 16 | } 17 | 18 | export const sendPatchReq = async (surl, payload, config_obj) => { 19 | return await axios.patch(tagUrl + surl, payload, config_obj) 20 | } 21 | 22 | export const sendDeleteReq = async (surl, config_obj) => { 23 | return await axios.delete(tagUrl + surl, config_obj) 24 | } 25 | 26 | const baseUrl = "http://127.0.0.1:8000/api" 27 | 28 | // 对axios二次封装 29 | const request = axios.create({ 30 | baseURL: baseUrl, 31 | timeout: 10000, 32 | }) 33 | 34 | // 请求拦截器 35 | request.interceptors.request.use((config) => { 36 | return config 37 | }) 38 | 39 | // 响应拦截器 40 | request.interceptors.response.use( 41 | (response) => { 42 | return Promise.resolve(response.data) 43 | }, 44 | (error) => { 45 | const status = error.response.status // 错误状态码 46 | // todo 错误处理 47 | return Promise.reject(error) 48 | } 49 | ) 50 | 51 | export default request 52 | -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/utils/authorization.js: -------------------------------------------------------------------------------- 1 | import { sendPostReq, sendGetReq } from "@/http" 2 | 3 | async function authorization() { 4 | const storage = localStorage 5 | 6 | let hasLogin = false 7 | let userName = storage.getItem("username.myblog") 8 | 9 | const expiredTime = Number(storage.getItem("expiredTime.myblog")) 10 | const refreshToken = storage.getItem("refresh.myblog") 11 | 12 | // token 未过期 13 | if (expiredTime > Date.now()) { 14 | hasLogin = true 15 | } 16 | // token 过期 申请刷新 token 17 | else if (refreshToken !== null) { 18 | try { 19 | let response = sendPostReq("/token/refresh/", { refresh: refreshToken }) 20 | 21 | const nextExpiredTime = Date.now() + 60000 * 60 22 | storage.setItem("access.myblog", response.data.access) 23 | storage.setItem("expiredTime.myblog", nextExpiredTime) 24 | sendGetReq("/user/" + userName + "/").then((resp) => { 25 | storage.setItem("isSuperuser.myblog", resp.data.is_superuser) 26 | }) 27 | storage.removeItem("refresh.myblog") 28 | 29 | hasLogin = true 30 | } catch (err) { 31 | storage.clear() 32 | hasLogin = false 33 | console.log("authorization err") 34 | } 35 | } 36 | // 无任何有效 token 37 | else { 38 | storage.clear() 39 | hasLogin = false 40 | console.log("authorization exp") 41 | } 42 | 43 | return [hasLogin, userName] 44 | } 45 | 46 | export default authorization 47 | -------------------------------------------------------------------------------- /blog_dv/comment/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from user_info.serializers import UserDescSerializer 3 | 4 | from .models import Comment 5 | 6 | 7 | class CommentChildrenSerializer(serializers.ModelSerializer): 8 | url = serializers.HyperlinkedIdentityField(view_name="comment-detail") 9 | author = UserDescSerializer(read_only=True) 10 | 11 | class Meta: 12 | model = Comment 13 | exclude = ["parent", "article"] 14 | 15 | 16 | class CommentSeriallizer(serializers.ModelSerializer): 17 | """ 18 | url 超链接字段让接口的跳转更方便 19 | author 嵌套序列化器让显示的内容更丰富 20 | """ 21 | 22 | url = serializers.HyperlinkedIdentityField(view_name="comment-detail") 23 | author = UserDescSerializer(read_only=True) 24 | 25 | article = serializers.HyperlinkedRelatedField( 26 | view_name="article-detail", read_only=True 27 | ) 28 | article_id = serializers.IntegerField( 29 | write_only=True, allow_null=False, required=True 30 | ) 31 | 32 | parent = CommentChildrenSerializer(read_only=True) 33 | parent_id = serializers.IntegerField( 34 | write_only=True, allow_null=True, required=False 35 | ) 36 | 37 | def update(self, instance, validated_data): 38 | validated_data.pop("parent_id", None) 39 | return super().update(instance, validated_data) 40 | 41 | class Meta: 42 | model = Comment 43 | fields = "__all__" 44 | extra_kwargs = {"created": {"read_only": True}} 45 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | anyio==3.6.2 2 | asgiref==3.6.0 3 | async-timeout==4.0.3 4 | blinker==1.6.2 5 | certifi==2022.12.7 6 | charset-normalizer==3.1.0 7 | click==8.1.3 8 | colorama==0.4.6 9 | coreapi==2.3.3 10 | coreschema==0.0.4 11 | defusedxml==0.7.1 12 | diff-match-patch==20230430 13 | Django==4.1.7 14 | django-cors-headers==3.14.0 15 | django-extensions==3.2.1 16 | django-filter==22.1 17 | django-import-export==3.2.0 18 | django-redis==5.3.0 19 | django-simpleui==2023.3.1 20 | djangorestframework==3.14.0 21 | djangorestframework-simplejwt==5.2.2 22 | drf-extensions==0.7.1 23 | et-xmlfile==1.1.0 24 | fastapi==0.95.1 25 | Flask==2.3.2 26 | h11==0.14.0 27 | httptools==0.5.0 28 | idna==3.4 29 | itsdangerous==2.1.2 30 | itypes==1.2.0 31 | Jinja2==3.1.2 32 | lxml==4.9.2 33 | Markdown==3.4.1 34 | MarkupPy==1.14 35 | MarkupSafe==2.1.2 36 | mccabe==0.7.0 37 | mysqlclient==2.1.1 38 | odfpy==1.4.1 39 | openpyxl==3.1.2 40 | Pillow==9.4.0 41 | pycodestyle==2.10.0 42 | pydantic==1.10.7 43 | pyflakes==3.0.1 44 | Pygments==2.15.0 45 | PyJWT==2.6.0 46 | PyMySQL==1.0.3 47 | pypng==0.20220715.0 48 | python-dotenv==1.0.0 49 | pytz==2022.7.1 50 | PyYAML==6.0 51 | qrcode==7.4.2 52 | redis==5.0.0 53 | requests==2.28.2 54 | sniffio==1.3.0 55 | sqlparse==0.4.3 56 | starlette==0.26.1 57 | tablib==3.5.0 58 | typing_extensions==4.5.0 59 | tzdata==2022.7 60 | uritemplate==4.1.1 61 | urllib3==1.26.15 62 | uvicorn==0.22.0 63 | watchfiles==0.19.0 64 | websockets==11.0.2 65 | Werkzeug==2.3.6 66 | xlrd==2.0.1 67 | xlwt==1.3.0 68 | -------------------------------------------------------------------------------- /blog_dv/user_info/serializers.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from rest_framework import serializers 3 | 4 | 5 | class UserDescSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = User 8 | fields = ["id", "username", "last_login", "date_joined"] 9 | 10 | 11 | class UserRegisterSerializer(serializers.ModelSerializer): 12 | url = serializers.HyperlinkedIdentityField( 13 | view_name="user-detail", lookup_field="username" 14 | ) 15 | 16 | class Meta: 17 | model = User 18 | fields = ["url", "id", "username", "password", "is_superuser"] 19 | extra_kwargs = { 20 | "password": {"write_only": True}, 21 | "is_superuser": {"read_only": True}, 22 | } 23 | 24 | def create(self, validated_data): 25 | user = User.objects.create_user(**validated_data) 26 | return user 27 | 28 | def update(self, instance, validated_data): 29 | if "password" in validated_data: 30 | password = validated_data.pop("password") 31 | instance.set_password(password) 32 | return super().update(instance, validated_data) 33 | 34 | 35 | class UserDetailSerializer(serializers.ModelSerializer): 36 | class Meta: 37 | model = User 38 | fields = [ 39 | "id", 40 | "username", 41 | "last_name", 42 | "first_name", 43 | "email", 44 | "last_login", 45 | "date_joined", 46 | ] 47 | -------------------------------------------------------------------------------- /blog_dv/user_info/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from rest_framework import viewsets 3 | from rest_framework.decorators import action 4 | from rest_framework.permissions import AllowAny, IsAuthenticatedOrReadOnly 5 | from rest_framework.response import Response 6 | 7 | from .permissions import IsSelfOrReadOnly 8 | from .serializers import UserDetailSerializer, UserRegisterSerializer 9 | 10 | 11 | class UserViewSet(viewsets.ModelViewSet): 12 | queryset = User.objects.all() 13 | serializer_class = UserRegisterSerializer 14 | lookup_field = "username" 15 | 16 | def get_permissions(self): 17 | if self.request.method == "POST": 18 | self.permission_classes = [AllowAny] 19 | else: 20 | self.permission_classes = [IsAuthenticatedOrReadOnly, IsSelfOrReadOnly] 21 | 22 | return super().get_permissions() 23 | 24 | @action(detail=True, methods=["get"]) 25 | def info(self, request, username=None): 26 | queryset = User.objects.get(username=username) 27 | serializer = UserDetailSerializer(queryset, many=False) 28 | return Response(serializer.data) 29 | 30 | @action(detail=False) 31 | def sorted(self, request): 32 | users = User.objects.all().order_by("-username") 33 | 34 | page = self.paginate_queryset(users) 35 | if page is not None: 36 | serializer = self.get_serializer(page, many=True) 37 | return self.get_paginated_response(serializer.data) 38 | 39 | serializer = self.get_serializer(users, many=True) 40 | return Response(serializer.data) 41 | -------------------------------------------------------------------------------- /frontend/src/components/SearchBox.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 25 | 26 | 83 | -------------------------------------------------------------------------------- /blog_dv/article/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework import viewsets 2 | 3 | from .filters import ArticleFilter 4 | from .models import Article, Avatar, Category, Tag 5 | from .permissions import IsAdminUserOrReadOnly 6 | from .serializers import ( 7 | ArticleDetailSerializer, 8 | ArticleSerializer, 9 | AvatarSerializer, 10 | CategoryDetailSerializer, 11 | CategorySerializer, 12 | TagSerializer, 13 | ) 14 | 15 | 16 | # 视图集类把列表、详情等逻辑都集成到一起,并且提供了默认的增删改查的实现 17 | class ArticleViewSet(viewsets.ModelViewSet): 18 | queryset = Article.objects.all() 19 | serializer_class = ArticleSerializer 20 | permission_classes = [IsAdminUserOrReadOnly] 21 | filterset_class = ArticleFilter 22 | 23 | def perform_create(self, serializer): 24 | serializer.save(author=self.request.user) 25 | 26 | def get_serializer_class(self): 27 | if self.action == "list": 28 | return ArticleSerializer 29 | return ArticleDetailSerializer 30 | 31 | 32 | class CategoryViewSet(viewsets.ModelViewSet): 33 | """分类视图集""" 34 | 35 | queryset = Category.objects.all() 36 | serializer_class = CategorySerializer 37 | permission_classes = [IsAdminUserOrReadOnly] 38 | pagination_class = None 39 | 40 | def get_serializer_class(self): 41 | if self.action == "list": 42 | return CategorySerializer 43 | return CategoryDetailSerializer 44 | 45 | 46 | class TagViewSet(viewsets.ModelViewSet): 47 | queryset = Tag.objects.all() 48 | serializer_class = TagSerializer 49 | permission_classes = [IsAdminUserOrReadOnly] 50 | pagination_class = None 51 | 52 | 53 | class AvatarViewSet(viewsets.ModelViewSet): 54 | queryset = Avatar.objects.all() 55 | serializer_class = AvatarSerializer 56 | permission_classes = [IsAdminUserOrReadOnly] 57 | -------------------------------------------------------------------------------- /blog_dv/blog_dv/urls.py: -------------------------------------------------------------------------------- 1 | """blog_dv URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/4.1/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from article import views 17 | from comment.views import CommentViewSet 18 | from django.conf import settings 19 | from django.conf.urls.static import static 20 | from django.contrib import admin 21 | from django.urls import include, path 22 | from jwt_token.views import CustomTokenObtainPairView, CustomTokenRefreshView 23 | from rest_framework.routers import DefaultRouter 24 | from user_info.views import UserViewSet 25 | 26 | router = DefaultRouter() 27 | router.register(r"article", views.ArticleViewSet) 28 | router.register(r"category", views.CategoryViewSet) 29 | router.register(r"tag", views.TagViewSet) 30 | router.register(r"avatar", views.AvatarViewSet) 31 | router.register(r"comment", CommentViewSet) 32 | router.register(r"user", UserViewSet) 33 | 34 | urlpatterns = [ 35 | path("admin/", admin.site.urls), 36 | path("api-auth/", include("rest_framework.urls")), 37 | path("api/", include(router.urls)), 38 | path("api/token/", CustomTokenObtainPairView.as_view(), name="token_obtain_pair"), 39 | path("api/token/refresh/", CustomTokenRefreshView.as_view(), name="token_refresh"), 40 | ] 41 | 42 | if settings.DEBUG: 43 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 44 | -------------------------------------------------------------------------------- /frontend/src/views/ArticleDetail.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 58 | 59 | 76 | 77 | 92 | -------------------------------------------------------------------------------- /blog_dv/article/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import User 2 | from django.db import models 3 | from django.utils import timezone 4 | from markdown import Markdown 5 | 6 | 7 | class Category(models.Model): 8 | """文章分类""" 9 | 10 | title = models.CharField(max_length=100) 11 | created = models.DateTimeField(default=timezone.now) 12 | 13 | def __str__(self): 14 | return self.title 15 | 16 | class Meta: 17 | db_table = "category" 18 | ordering = ["-created"] 19 | 20 | 21 | class Tag(models.Model): 22 | """文章标签""" 23 | 24 | text = models.CharField(verbose_name="文章标签", max_length=30) 25 | 26 | def __str__(self): 27 | return self.text 28 | 29 | class Meta: 30 | db_table = "artag" 31 | ordering = ["-id"] 32 | 33 | 34 | class Avatar(models.Model): 35 | """标题图片""" 36 | 37 | content = models.ImageField(upload_to="avatar/%Y%m%d") 38 | 39 | class Meta: 40 | db_table = "avatar" 41 | 42 | 43 | class Article(models.Model): 44 | """博客 model""" 45 | 46 | title = models.CharField(verbose_name="标题", max_length=100) 47 | body = models.TextField(verbose_name="正文") 48 | created = models.DateTimeField(verbose_name="创建时间", default=timezone.now) 49 | updated = models.DateTimeField(verbose_name="更新时间", auto_now=True) 50 | tags = models.ManyToManyField(Tag, blank=True, related_name="articles") 51 | category = models.ForeignKey( 52 | Category, 53 | null=True, 54 | blank=True, 55 | on_delete=models.SET_NULL, 56 | related_name="articles", 57 | ) 58 | author = models.ForeignKey( 59 | User, null=True, on_delete=models.CASCADE, related_name="articles" 60 | ) 61 | avatar = models.ForeignKey( 62 | Avatar, null=True, blank=True, on_delete=models.SET_NULL, related_name="article" 63 | ) 64 | 65 | def __str__(self): 66 | return self.title 67 | 68 | def get_md(self): 69 | md = Markdown( 70 | extensions=[ 71 | "markdown.extensions.extra", 72 | "markdown.extensions.codehilite", 73 | "markdown.extensions.toc", 74 | ] 75 | ) 76 | md_body = md.convert(self.body) 77 | return md_body, md.toc 78 | 79 | class Meta: 80 | db_table = "article" 81 | ordering = ["-created"] 82 | -------------------------------------------------------------------------------- /frontend/src/components/BlogHeader.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 56 | 57 | 104 | 105 | 130 | -------------------------------------------------------------------------------- /frontend/src/components/Comments.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 110 | 111 | 156 | -------------------------------------------------------------------------------- /frontend/src/views/UserCenter.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 111 | 112 | 142 | -------------------------------------------------------------------------------- /blog_dv/blog_dv/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for blog_dv project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.1.5. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.1/ref/settings/ 11 | """ 12 | 13 | import os 14 | from datetime import timedelta 15 | 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | # Quick-start development settings - unsuitable for production 19 | # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ 20 | # SECURITY WARNING: keep the secret key used in production secret! 21 | SECRET_KEY = "django-insecure--84wnua&74*d%&%$d4-3p66izzby*e(!29w)p0%lc7_$&g7pl2" 22 | 23 | # SECURITY WARNING: don't run with debug turned on in production! 24 | DEBUG = True 25 | 26 | ALLOWED_HOSTS = ["*"] 27 | 28 | # Application definition 29 | INSTALLED_APPS = [ 30 | "django.contrib.admin", 31 | "django.contrib.auth", 32 | "django.contrib.contenttypes", 33 | "django.contrib.sessions", 34 | "django.contrib.messages", 35 | "django.contrib.staticfiles", 36 | "rest_framework", 37 | "django_filters", 38 | "corsheaders", 39 | "article", 40 | "user_info", 41 | "comment", 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | "django.middleware.security.SecurityMiddleware", 46 | "django.contrib.sessions.middleware.SessionMiddleware", 47 | "corsheaders.middleware.CorsMiddleware", # 跨域 48 | "django.middleware.common.CommonMiddleware", 49 | "django.middleware.csrf.CsrfViewMiddleware", 50 | "django.contrib.auth.middleware.AuthenticationMiddleware", 51 | "django.contrib.messages.middleware.MessageMiddleware", 52 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 53 | ] 54 | 55 | ROOT_URLCONF = "blog_dv.urls" 56 | 57 | TEMPLATES = [ 58 | { 59 | "BACKEND": "django.template.backends.django.DjangoTemplates", 60 | "DIRS": [], 61 | "APP_DIRS": True, 62 | "OPTIONS": { 63 | "context_processors": [ 64 | "django.template.context_processors.debug", 65 | "django.template.context_processors.request", 66 | "django.contrib.auth.context_processors.auth", 67 | "django.contrib.messages.context_processors.messages", 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = "blog_dv.wsgi.application" 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 77 | DATABASES = { 78 | "default": { 79 | "ENGINE": "django.db.backends.sqlite3", 80 | "NAME": os.path.join(BASE_DIR, "db.sqlite3"), 81 | } 82 | } 83 | 84 | # Password validation 85 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 86 | AUTH_PASSWORD_VALIDATORS = [ 87 | { 88 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 89 | }, 90 | { 91 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 92 | }, 93 | { 94 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 95 | }, 96 | { 97 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 98 | }, 99 | ] 100 | 101 | # Internationalization 102 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 103 | LANGUAGE_CODE = "zh-hans" 104 | TIME_ZONE = "Asia/Shanghai" 105 | USE_I18N = True 106 | USE_TZ = False # 不使用 UTC 时间 107 | 108 | # Static files (CSS, JavaScript, Images) 109 | # https://docs.djangoproject.com/en/4.1/howto/static-files/ 110 | STATIC_URL = "static/" 111 | 112 | # Default primary key field type 113 | # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field 114 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 115 | 116 | MEDIA_URL = "/media/" 117 | MEDIA_ROOT = os.path.join(BASE_DIR, "media") 118 | 119 | CORS_ORIGIN_ALLOW_ALL = True # 跨域 120 | 121 | REST_FRAMEWORK = { 122 | "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"], 123 | "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", # 全局分页器 124 | "PAGE_SIZE": 5, # 全局分页 page size 125 | # 使用 Json Web Token 认证机制 126 | "DEFAULT_AUTHENTICATION_CLASSES": ( 127 | "rest_framework_simplejwt.authentication.JWTAuthentication", # 使用 simplejwt 认证类 128 | ), 129 | } 130 | 131 | # Token 有效期 132 | SIMPLE_JWT = { 133 | "ACCESS_TOKEN_LIFETIME": timedelta(hours=1), # token过期时间 1小时 134 | "REFRESH_TOKEN_LIFETIME": timedelta(days=10), # refresh token 过期时间 10 天 135 | } 136 | -------------------------------------------------------------------------------- /frontend/src/views/Login.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 121 | 122 | 160 | -------------------------------------------------------------------------------- /frontend/src/views/ArticleCreate.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 140 | 141 | 183 | -------------------------------------------------------------------------------- /frontend/src/views/ArticleEdit.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 141 | 142 | 177 | -------------------------------------------------------------------------------- /blog_dv/article/serializers.py: -------------------------------------------------------------------------------- 1 | from comment.serializers import CommentSeriallizer 2 | from rest_framework import serializers 3 | from user_info.serializers import UserDescSerializer 4 | 5 | from .models import Article, Avatar, Category, Tag 6 | 7 | # class ArticleListSerializer(serializers.ModelSerializer): 8 | # # 实现超链接可以用 DRF 框架提供的 HyperlinkedIdentityField 9 | # url = serializers.HyperlinkedIdentityField(view_name='article:detail') 10 | # author = UserDescSerializer(read_only=True) # 只读 11 | # 12 | # class Meta: 13 | # model = Article 14 | # fields = ['url', 'title', 'body', 'author'] 15 | # # read_only_fields = ['author'] 16 | # 17 | # 18 | # class ArticleDetailSerializer(serializers.ModelSerializer): 19 | # class Meta: 20 | # model = Article 21 | # fields = '__all__' 22 | 23 | 24 | class TagSerializer(serializers.HyperlinkedModelSerializer): 25 | # 检查tag text是否已存在 26 | @staticmethod 27 | def check_tag_obj_exists(validated_data): 28 | text = validated_data.get("text") 29 | if Tag.objects.filter(text=text).exists(): 30 | raise serializers.ValidationError(f"Tag with text {text} exists.") 31 | 32 | def create(self, validated_data): 33 | self.check_tag_obj_exists(validated_data) 34 | return super().create(validated_data) 35 | 36 | def update(self, instance, validated_data): 37 | self.check_tag_obj_exists(validated_data) 38 | return super().update(instance, validated_data) 39 | 40 | class Meta: 41 | model = Tag 42 | fields = "__all__" 43 | 44 | 45 | class AvatarSerializer(serializers.ModelSerializer): 46 | url = serializers.HyperlinkedIdentityField(view_name="avatar-detail") 47 | 48 | class Meta: 49 | model = Avatar 50 | fields = "__all__" 51 | 52 | 53 | class CategorySerializer(serializers.ModelSerializer): 54 | """分类的序列化器""" 55 | 56 | url = serializers.HyperlinkedIdentityField(view_name="category-detail") 57 | 58 | class Meta: 59 | model = Category 60 | fields = "__all__" 61 | read_only_fields = ["created"] 62 | 63 | 64 | class ArticleBaseSerializer(serializers.HyperlinkedModelSerializer): 65 | id = serializers.IntegerField(read_only=True) 66 | author = UserDescSerializer(read_only=True) 67 | # category 的嵌套序列化字段 68 | category = CategorySerializer(read_only=True) 69 | tags = serializers.SlugRelatedField( 70 | queryset=Tag.objects.all(), many=True, required=False, slug_field="text" 71 | ) 72 | 73 | # 覆写方法,如果输入的标签不存在则创建它 74 | def to_internal_value(self, data): 75 | tags_data = data.get("tags") 76 | 77 | if isinstance(tags_data, list): 78 | for text in tags_data: 79 | if not Tag.objects.filter(text=text).exists(): 80 | Tag.objects.create(text=text) 81 | 82 | return super().to_internal_value(data) 83 | 84 | # category 的 id 字段,用于创建/更新 category 外键 85 | category_id = serializers.IntegerField( 86 | write_only=True, allow_null=True, required=False 87 | ) 88 | 89 | # category_id 字段的验证器 400 error 90 | @staticmethod 91 | def validate_category_id(value): 92 | if not Category.objects.filter(id=value).exists() and value is not None: 93 | raise serializers.ValidationError(f"Category with id {value} not exists.") 94 | return value 95 | 96 | avatar = AvatarSerializer(read_only=True) 97 | avatar_id = serializers.IntegerField( 98 | write_only=True, allow_null=True, required=False 99 | ) 100 | 101 | @staticmethod 102 | def validate_avatar_id(value): 103 | if not Avatar.objects.filter(id=value).exists() and value is not None: 104 | raise serializers.ValidationError(f"Avatar with id {value} not exists!") 105 | return value 106 | 107 | 108 | class ArticleSerializer(ArticleBaseSerializer): 109 | """博文序列化器""" 110 | 111 | class Meta: 112 | model = Article 113 | fields = "__all__" 114 | extra_kwargs = {"body": {"write_only": True}} 115 | 116 | 117 | class ArticleDetailSerializer(ArticleBaseSerializer): 118 | id = serializers.IntegerField(read_only=True) 119 | comment = CommentSeriallizer(many=True, read_only=True) 120 | # 渲染后的正文 121 | body_html = serializers.SerializerMethodField() 122 | # 渲染后的目录 123 | toc_html = serializers.SerializerMethodField() 124 | 125 | @staticmethod 126 | def get_body_html(obj): 127 | return obj.get_md()[0] 128 | 129 | @staticmethod 130 | def get_toc_html(obj): 131 | return obj.get_md()[1] 132 | 133 | class Meta: 134 | model = Article 135 | fields = "__all__" 136 | 137 | 138 | class ArticleCategoryDetailSerializer(serializers.ModelSerializer): 139 | """给分类详情的嵌套序列化器""" 140 | 141 | url = serializers.HyperlinkedIdentityField(view_name="article-detail") 142 | 143 | class Meta: 144 | model = Article 145 | fields = [ 146 | "url", 147 | "title", 148 | ] 149 | 150 | 151 | class CategoryDetailSerializer(serializers.ModelSerializer): 152 | """分类详情""" 153 | 154 | articles = ArticleCategoryDetailSerializer(many=True, read_only=True) 155 | 156 | class Meta: 157 | model = Category 158 | fields = [ 159 | "id", 160 | "title", 161 | "created", 162 | "articles", 163 | ] 164 | -------------------------------------------------------------------------------- /frontend/src/components/ArticleList.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 146 | 147 | 214 | -------------------------------------------------------------------------------- /frontend/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "frontend", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "@jridgewell/sourcemap-codec": "^1.4.14", 12 | "axios": "^1.3.4", 13 | "pinia": "^2.1.6", 14 | "vue": "^3.2.45", 15 | "vue-router": "^4.1.6" 16 | }, 17 | "devDependencies": { 18 | "@vitejs/plugin-vue": "^4.0.0", 19 | "vite": "^4.1.0" 20 | } 21 | }, 22 | "node_modules/@babel/parser": { 23 | "version": "7.22.15", 24 | "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.22.15.tgz", 25 | "integrity": "sha512-RWmQ/sklUN9BvGGpCDgSubhHWfAx24XDTDObup4ffvxaYsptOg2P3KG0j+1eWKLxpkX0j0uHxmpq2Z1SP/VhxA==", 26 | "bin": { 27 | "parser": "bin/babel-parser.js" 28 | }, 29 | "engines": { 30 | "node": ">=6.0.0" 31 | } 32 | }, 33 | "node_modules/@esbuild/android-arm": { 34 | "version": "0.16.17", 35 | "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz", 36 | "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", 37 | "cpu": [ 38 | "arm" 39 | ], 40 | "dev": true, 41 | "optional": true, 42 | "os": [ 43 | "android" 44 | ], 45 | "engines": { 46 | "node": ">=12" 47 | } 48 | }, 49 | "node_modules/@esbuild/android-arm64": { 50 | "version": "0.16.17", 51 | "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", 52 | "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", 53 | "cpu": [ 54 | "arm64" 55 | ], 56 | "dev": true, 57 | "optional": true, 58 | "os": [ 59 | "android" 60 | ], 61 | "engines": { 62 | "node": ">=12" 63 | } 64 | }, 65 | "node_modules/@esbuild/android-x64": { 66 | "version": "0.16.17", 67 | "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz", 68 | "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", 69 | "cpu": [ 70 | "x64" 71 | ], 72 | "dev": true, 73 | "optional": true, 74 | "os": [ 75 | "android" 76 | ], 77 | "engines": { 78 | "node": ">=12" 79 | } 80 | }, 81 | "node_modules/@esbuild/darwin-arm64": { 82 | "version": "0.16.17", 83 | "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", 84 | "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", 85 | "cpu": [ 86 | "arm64" 87 | ], 88 | "dev": true, 89 | "optional": true, 90 | "os": [ 91 | "darwin" 92 | ], 93 | "engines": { 94 | "node": ">=12" 95 | } 96 | }, 97 | "node_modules/@esbuild/darwin-x64": { 98 | "version": "0.16.17", 99 | "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", 100 | "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", 101 | "cpu": [ 102 | "x64" 103 | ], 104 | "dev": true, 105 | "optional": true, 106 | "os": [ 107 | "darwin" 108 | ], 109 | "engines": { 110 | "node": ">=12" 111 | } 112 | }, 113 | "node_modules/@esbuild/freebsd-arm64": { 114 | "version": "0.16.17", 115 | "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", 116 | "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", 117 | "cpu": [ 118 | "arm64" 119 | ], 120 | "dev": true, 121 | "optional": true, 122 | "os": [ 123 | "freebsd" 124 | ], 125 | "engines": { 126 | "node": ">=12" 127 | } 128 | }, 129 | "node_modules/@esbuild/freebsd-x64": { 130 | "version": "0.16.17", 131 | "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", 132 | "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", 133 | "cpu": [ 134 | "x64" 135 | ], 136 | "dev": true, 137 | "optional": true, 138 | "os": [ 139 | "freebsd" 140 | ], 141 | "engines": { 142 | "node": ">=12" 143 | } 144 | }, 145 | "node_modules/@esbuild/linux-arm": { 146 | "version": "0.16.17", 147 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", 148 | "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", 149 | "cpu": [ 150 | "arm" 151 | ], 152 | "dev": true, 153 | "optional": true, 154 | "os": [ 155 | "linux" 156 | ], 157 | "engines": { 158 | "node": ">=12" 159 | } 160 | }, 161 | "node_modules/@esbuild/linux-arm64": { 162 | "version": "0.16.17", 163 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", 164 | "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", 165 | "cpu": [ 166 | "arm64" 167 | ], 168 | "dev": true, 169 | "optional": true, 170 | "os": [ 171 | "linux" 172 | ], 173 | "engines": { 174 | "node": ">=12" 175 | } 176 | }, 177 | "node_modules/@esbuild/linux-ia32": { 178 | "version": "0.16.17", 179 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", 180 | "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", 181 | "cpu": [ 182 | "ia32" 183 | ], 184 | "dev": true, 185 | "optional": true, 186 | "os": [ 187 | "linux" 188 | ], 189 | "engines": { 190 | "node": ">=12" 191 | } 192 | }, 193 | "node_modules/@esbuild/linux-loong64": { 194 | "version": "0.16.17", 195 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", 196 | "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", 197 | "cpu": [ 198 | "loong64" 199 | ], 200 | "dev": true, 201 | "optional": true, 202 | "os": [ 203 | "linux" 204 | ], 205 | "engines": { 206 | "node": ">=12" 207 | } 208 | }, 209 | "node_modules/@esbuild/linux-mips64el": { 210 | "version": "0.16.17", 211 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", 212 | "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", 213 | "cpu": [ 214 | "mips64el" 215 | ], 216 | "dev": true, 217 | "optional": true, 218 | "os": [ 219 | "linux" 220 | ], 221 | "engines": { 222 | "node": ">=12" 223 | } 224 | }, 225 | "node_modules/@esbuild/linux-ppc64": { 226 | "version": "0.16.17", 227 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", 228 | "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", 229 | "cpu": [ 230 | "ppc64" 231 | ], 232 | "dev": true, 233 | "optional": true, 234 | "os": [ 235 | "linux" 236 | ], 237 | "engines": { 238 | "node": ">=12" 239 | } 240 | }, 241 | "node_modules/@esbuild/linux-riscv64": { 242 | "version": "0.16.17", 243 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", 244 | "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", 245 | "cpu": [ 246 | "riscv64" 247 | ], 248 | "dev": true, 249 | "optional": true, 250 | "os": [ 251 | "linux" 252 | ], 253 | "engines": { 254 | "node": ">=12" 255 | } 256 | }, 257 | "node_modules/@esbuild/linux-s390x": { 258 | "version": "0.16.17", 259 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", 260 | "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", 261 | "cpu": [ 262 | "s390x" 263 | ], 264 | "dev": true, 265 | "optional": true, 266 | "os": [ 267 | "linux" 268 | ], 269 | "engines": { 270 | "node": ">=12" 271 | } 272 | }, 273 | "node_modules/@esbuild/linux-x64": { 274 | "version": "0.16.17", 275 | "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz", 276 | "integrity": "sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==", 277 | "cpu": [ 278 | "x64" 279 | ], 280 | "dev": true, 281 | "optional": true, 282 | "os": [ 283 | "linux" 284 | ], 285 | "engines": { 286 | "node": ">=12" 287 | } 288 | }, 289 | "node_modules/@esbuild/netbsd-x64": { 290 | "version": "0.16.17", 291 | "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", 292 | "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", 293 | "cpu": [ 294 | "x64" 295 | ], 296 | "dev": true, 297 | "optional": true, 298 | "os": [ 299 | "netbsd" 300 | ], 301 | "engines": { 302 | "node": ">=12" 303 | } 304 | }, 305 | "node_modules/@esbuild/openbsd-x64": { 306 | "version": "0.16.17", 307 | "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", 308 | "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", 309 | "cpu": [ 310 | "x64" 311 | ], 312 | "dev": true, 313 | "optional": true, 314 | "os": [ 315 | "openbsd" 316 | ], 317 | "engines": { 318 | "node": ">=12" 319 | } 320 | }, 321 | "node_modules/@esbuild/sunos-x64": { 322 | "version": "0.16.17", 323 | "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", 324 | "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", 325 | "cpu": [ 326 | "x64" 327 | ], 328 | "dev": true, 329 | "optional": true, 330 | "os": [ 331 | "sunos" 332 | ], 333 | "engines": { 334 | "node": ">=12" 335 | } 336 | }, 337 | "node_modules/@esbuild/win32-arm64": { 338 | "version": "0.16.17", 339 | "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", 340 | "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", 341 | "cpu": [ 342 | "arm64" 343 | ], 344 | "dev": true, 345 | "optional": true, 346 | "os": [ 347 | "win32" 348 | ], 349 | "engines": { 350 | "node": ">=12" 351 | } 352 | }, 353 | "node_modules/@esbuild/win32-ia32": { 354 | "version": "0.16.17", 355 | "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", 356 | "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", 357 | "cpu": [ 358 | "ia32" 359 | ], 360 | "dev": true, 361 | "optional": true, 362 | "os": [ 363 | "win32" 364 | ], 365 | "engines": { 366 | "node": ">=12" 367 | } 368 | }, 369 | "node_modules/@esbuild/win32-x64": { 370 | "version": "0.16.17", 371 | "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", 372 | "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", 373 | "cpu": [ 374 | "x64" 375 | ], 376 | "dev": true, 377 | "optional": true, 378 | "os": [ 379 | "win32" 380 | ], 381 | "engines": { 382 | "node": ">=12" 383 | } 384 | }, 385 | "node_modules/@jridgewell/sourcemap-codec": { 386 | "version": "1.4.15", 387 | "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 388 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" 389 | }, 390 | "node_modules/@vitejs/plugin-vue": { 391 | "version": "4.0.0", 392 | "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.0.0.tgz", 393 | "integrity": "sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==", 394 | "dev": true, 395 | "engines": { 396 | "node": "^14.18.0 || >=16.0.0" 397 | }, 398 | "peerDependencies": { 399 | "vite": "^4.0.0", 400 | "vue": "^3.2.25" 401 | } 402 | }, 403 | "node_modules/@vue/compiler-core": { 404 | "version": "3.3.4", 405 | "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.4.tgz", 406 | "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", 407 | "dependencies": { 408 | "@babel/parser": "^7.21.3", 409 | "@vue/shared": "3.3.4", 410 | "estree-walker": "^2.0.2", 411 | "source-map-js": "^1.0.2" 412 | } 413 | }, 414 | "node_modules/@vue/compiler-dom": { 415 | "version": "3.3.4", 416 | "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", 417 | "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", 418 | "dependencies": { 419 | "@vue/compiler-core": "3.3.4", 420 | "@vue/shared": "3.3.4" 421 | } 422 | }, 423 | "node_modules/@vue/compiler-sfc": { 424 | "version": "3.3.4", 425 | "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", 426 | "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", 427 | "dependencies": { 428 | "@babel/parser": "^7.20.15", 429 | "@vue/compiler-core": "3.3.4", 430 | "@vue/compiler-dom": "3.3.4", 431 | "@vue/compiler-ssr": "3.3.4", 432 | "@vue/reactivity-transform": "3.3.4", 433 | "@vue/shared": "3.3.4", 434 | "estree-walker": "^2.0.2", 435 | "magic-string": "^0.30.0", 436 | "postcss": "^8.1.10", 437 | "source-map-js": "^1.0.2" 438 | } 439 | }, 440 | "node_modules/@vue/compiler-ssr": { 441 | "version": "3.3.4", 442 | "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", 443 | "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", 444 | "dependencies": { 445 | "@vue/compiler-dom": "3.3.4", 446 | "@vue/shared": "3.3.4" 447 | } 448 | }, 449 | "node_modules/@vue/devtools-api": { 450 | "version": "6.5.0", 451 | "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz", 452 | "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" 453 | }, 454 | "node_modules/@vue/reactivity": { 455 | "version": "3.3.4", 456 | "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.3.4.tgz", 457 | "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", 458 | "dependencies": { 459 | "@vue/shared": "3.3.4" 460 | } 461 | }, 462 | "node_modules/@vue/reactivity-transform": { 463 | "version": "3.3.4", 464 | "resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", 465 | "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", 466 | "dependencies": { 467 | "@babel/parser": "^7.20.15", 468 | "@vue/compiler-core": "3.3.4", 469 | "@vue/shared": "3.3.4", 470 | "estree-walker": "^2.0.2", 471 | "magic-string": "^0.30.0" 472 | } 473 | }, 474 | "node_modules/@vue/runtime-core": { 475 | "version": "3.3.4", 476 | "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.3.4.tgz", 477 | "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", 478 | "dependencies": { 479 | "@vue/reactivity": "3.3.4", 480 | "@vue/shared": "3.3.4" 481 | } 482 | }, 483 | "node_modules/@vue/runtime-dom": { 484 | "version": "3.3.4", 485 | "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", 486 | "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", 487 | "dependencies": { 488 | "@vue/runtime-core": "3.3.4", 489 | "@vue/shared": "3.3.4", 490 | "csstype": "^3.1.1" 491 | } 492 | }, 493 | "node_modules/@vue/server-renderer": { 494 | "version": "3.3.4", 495 | "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.3.4.tgz", 496 | "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", 497 | "dependencies": { 498 | "@vue/compiler-ssr": "3.3.4", 499 | "@vue/shared": "3.3.4" 500 | }, 501 | "peerDependencies": { 502 | "vue": "3.3.4" 503 | } 504 | }, 505 | "node_modules/@vue/shared": { 506 | "version": "3.3.4", 507 | "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.4.tgz", 508 | "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" 509 | }, 510 | "node_modules/asynckit": { 511 | "version": "0.4.0", 512 | "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", 513 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 514 | }, 515 | "node_modules/axios": { 516 | "version": "1.3.4", 517 | "resolved": "https://registry.npmmirror.com/axios/-/axios-1.3.4.tgz", 518 | "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", 519 | "dependencies": { 520 | "follow-redirects": "^1.15.0", 521 | "form-data": "^4.0.0", 522 | "proxy-from-env": "^1.1.0" 523 | } 524 | }, 525 | "node_modules/combined-stream": { 526 | "version": "1.0.8", 527 | "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", 528 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 529 | "dependencies": { 530 | "delayed-stream": "~1.0.0" 531 | }, 532 | "engines": { 533 | "node": ">= 0.8" 534 | } 535 | }, 536 | "node_modules/csstype": { 537 | "version": "3.1.2", 538 | "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz", 539 | "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" 540 | }, 541 | "node_modules/delayed-stream": { 542 | "version": "1.0.0", 543 | "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", 544 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 545 | "engines": { 546 | "node": ">=0.4.0" 547 | } 548 | }, 549 | "node_modules/esbuild": { 550 | "version": "0.16.17", 551 | "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.16.17.tgz", 552 | "integrity": "sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==", 553 | "dev": true, 554 | "hasInstallScript": true, 555 | "bin": { 556 | "esbuild": "bin/esbuild" 557 | }, 558 | "engines": { 559 | "node": ">=12" 560 | }, 561 | "optionalDependencies": { 562 | "@esbuild/android-arm": "0.16.17", 563 | "@esbuild/android-arm64": "0.16.17", 564 | "@esbuild/android-x64": "0.16.17", 565 | "@esbuild/darwin-arm64": "0.16.17", 566 | "@esbuild/darwin-x64": "0.16.17", 567 | "@esbuild/freebsd-arm64": "0.16.17", 568 | "@esbuild/freebsd-x64": "0.16.17", 569 | "@esbuild/linux-arm": "0.16.17", 570 | "@esbuild/linux-arm64": "0.16.17", 571 | "@esbuild/linux-ia32": "0.16.17", 572 | "@esbuild/linux-loong64": "0.16.17", 573 | "@esbuild/linux-mips64el": "0.16.17", 574 | "@esbuild/linux-ppc64": "0.16.17", 575 | "@esbuild/linux-riscv64": "0.16.17", 576 | "@esbuild/linux-s390x": "0.16.17", 577 | "@esbuild/linux-x64": "0.16.17", 578 | "@esbuild/netbsd-x64": "0.16.17", 579 | "@esbuild/openbsd-x64": "0.16.17", 580 | "@esbuild/sunos-x64": "0.16.17", 581 | "@esbuild/win32-arm64": "0.16.17", 582 | "@esbuild/win32-ia32": "0.16.17", 583 | "@esbuild/win32-x64": "0.16.17" 584 | } 585 | }, 586 | "node_modules/estree-walker": { 587 | "version": "2.0.2", 588 | "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", 589 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 590 | }, 591 | "node_modules/follow-redirects": { 592 | "version": "1.15.2", 593 | "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz", 594 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", 595 | "engines": { 596 | "node": ">=4.0" 597 | }, 598 | "peerDependenciesMeta": { 599 | "debug": { 600 | "optional": true 601 | } 602 | } 603 | }, 604 | "node_modules/form-data": { 605 | "version": "4.0.0", 606 | "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz", 607 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 608 | "dependencies": { 609 | "asynckit": "^0.4.0", 610 | "combined-stream": "^1.0.8", 611 | "mime-types": "^2.1.12" 612 | }, 613 | "engines": { 614 | "node": ">= 6" 615 | } 616 | }, 617 | "node_modules/fsevents": { 618 | "version": "2.3.2", 619 | "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", 620 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", 621 | "dev": true, 622 | "hasInstallScript": true, 623 | "optional": true, 624 | "os": [ 625 | "darwin" 626 | ], 627 | "engines": { 628 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 629 | } 630 | }, 631 | "node_modules/function-bind": { 632 | "version": "1.1.1", 633 | "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.1.tgz", 634 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 635 | "dev": true 636 | }, 637 | "node_modules/has": { 638 | "version": "1.0.3", 639 | "resolved": "https://registry.npmmirror.com/has/-/has-1.0.3.tgz", 640 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 641 | "dev": true, 642 | "dependencies": { 643 | "function-bind": "^1.1.1" 644 | }, 645 | "engines": { 646 | "node": ">= 0.4.0" 647 | } 648 | }, 649 | "node_modules/is-core-module": { 650 | "version": "2.11.0", 651 | "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.11.0.tgz", 652 | "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", 653 | "dev": true, 654 | "dependencies": { 655 | "has": "^1.0.3" 656 | } 657 | }, 658 | "node_modules/magic-string": { 659 | "version": "0.30.3", 660 | "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.3.tgz", 661 | "integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==", 662 | "dependencies": { 663 | "@jridgewell/sourcemap-codec": "^1.4.15" 664 | }, 665 | "engines": { 666 | "node": ">=12" 667 | } 668 | }, 669 | "node_modules/mime-db": { 670 | "version": "1.52.0", 671 | "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", 672 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 673 | "engines": { 674 | "node": ">= 0.6" 675 | } 676 | }, 677 | "node_modules/mime-types": { 678 | "version": "2.1.35", 679 | "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", 680 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 681 | "dependencies": { 682 | "mime-db": "1.52.0" 683 | }, 684 | "engines": { 685 | "node": ">= 0.6" 686 | } 687 | }, 688 | "node_modules/nanoid": { 689 | "version": "3.3.4", 690 | "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.4.tgz", 691 | "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", 692 | "bin": { 693 | "nanoid": "bin/nanoid.cjs" 694 | }, 695 | "engines": { 696 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 697 | } 698 | }, 699 | "node_modules/path-parse": { 700 | "version": "1.0.7", 701 | "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz", 702 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 703 | "dev": true 704 | }, 705 | "node_modules/picocolors": { 706 | "version": "1.0.0", 707 | "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz", 708 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" 709 | }, 710 | "node_modules/pinia": { 711 | "version": "2.1.6", 712 | "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.1.6.tgz", 713 | "integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==", 714 | "dependencies": { 715 | "@vue/devtools-api": "^6.5.0", 716 | "vue-demi": ">=0.14.5" 717 | }, 718 | "peerDependencies": { 719 | "@vue/composition-api": "^1.4.0", 720 | "typescript": ">=4.4.4", 721 | "vue": "^2.6.14 || ^3.3.0" 722 | }, 723 | "peerDependenciesMeta": { 724 | "@vue/composition-api": { 725 | "optional": true 726 | }, 727 | "typescript": { 728 | "optional": true 729 | } 730 | } 731 | }, 732 | "node_modules/pinia/node_modules/vue-demi": { 733 | "version": "0.14.6", 734 | "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.6.tgz", 735 | "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==", 736 | "hasInstallScript": true, 737 | "bin": { 738 | "vue-demi-fix": "bin/vue-demi-fix.js", 739 | "vue-demi-switch": "bin/vue-demi-switch.js" 740 | }, 741 | "engines": { 742 | "node": ">=12" 743 | }, 744 | "peerDependencies": { 745 | "@vue/composition-api": "^1.0.0-rc.1", 746 | "vue": "^3.0.0-0 || ^2.6.0" 747 | }, 748 | "peerDependenciesMeta": { 749 | "@vue/composition-api": { 750 | "optional": true 751 | } 752 | } 753 | }, 754 | "node_modules/postcss": { 755 | "version": "8.4.21", 756 | "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.21.tgz", 757 | "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", 758 | "dependencies": { 759 | "nanoid": "^3.3.4", 760 | "picocolors": "^1.0.0", 761 | "source-map-js": "^1.0.2" 762 | }, 763 | "engines": { 764 | "node": "^10 || ^12 || >=14" 765 | } 766 | }, 767 | "node_modules/proxy-from-env": { 768 | "version": "1.1.0", 769 | "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 770 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 771 | }, 772 | "node_modules/resolve": { 773 | "version": "1.22.1", 774 | "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz", 775 | "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", 776 | "dev": true, 777 | "dependencies": { 778 | "is-core-module": "^2.9.0", 779 | "path-parse": "^1.0.7", 780 | "supports-preserve-symlinks-flag": "^1.0.0" 781 | }, 782 | "bin": { 783 | "resolve": "bin/resolve" 784 | } 785 | }, 786 | "node_modules/rollup": { 787 | "version": "3.17.2", 788 | "resolved": "https://registry.npmmirror.com/rollup/-/rollup-3.17.2.tgz", 789 | "integrity": "sha512-qMNZdlQPCkWodrAZ3qnJtvCAl4vpQ8q77uEujVCCbC/6CLB7Lcmvjq7HyiOSnf4fxTT9XgsE36oLHJBH49xjqA==", 790 | "dev": true, 791 | "bin": { 792 | "rollup": "dist/bin/rollup" 793 | }, 794 | "engines": { 795 | "node": ">=14.18.0", 796 | "npm": ">=8.0.0" 797 | }, 798 | "optionalDependencies": { 799 | "fsevents": "~2.3.2" 800 | } 801 | }, 802 | "node_modules/source-map-js": { 803 | "version": "1.0.2", 804 | "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz", 805 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 806 | "engines": { 807 | "node": ">=0.10.0" 808 | } 809 | }, 810 | "node_modules/supports-preserve-symlinks-flag": { 811 | "version": "1.0.0", 812 | "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 813 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 814 | "dev": true, 815 | "engines": { 816 | "node": ">= 0.4" 817 | } 818 | }, 819 | "node_modules/vite": { 820 | "version": "4.1.4", 821 | "resolved": "https://registry.npmmirror.com/vite/-/vite-4.1.4.tgz", 822 | "integrity": "sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==", 823 | "dev": true, 824 | "dependencies": { 825 | "esbuild": "^0.16.14", 826 | "postcss": "^8.4.21", 827 | "resolve": "^1.22.1", 828 | "rollup": "^3.10.0" 829 | }, 830 | "bin": { 831 | "vite": "bin/vite.js" 832 | }, 833 | "engines": { 834 | "node": "^14.18.0 || >=16.0.0" 835 | }, 836 | "optionalDependencies": { 837 | "fsevents": "~2.3.2" 838 | }, 839 | "peerDependencies": { 840 | "@types/node": ">= 14", 841 | "less": "*", 842 | "sass": "*", 843 | "stylus": "*", 844 | "sugarss": "*", 845 | "terser": "^5.4.0" 846 | }, 847 | "peerDependenciesMeta": { 848 | "@types/node": { 849 | "optional": true 850 | }, 851 | "less": { 852 | "optional": true 853 | }, 854 | "sass": { 855 | "optional": true 856 | }, 857 | "stylus": { 858 | "optional": true 859 | }, 860 | "sugarss": { 861 | "optional": true 862 | }, 863 | "terser": { 864 | "optional": true 865 | } 866 | } 867 | }, 868 | "node_modules/vue": { 869 | "version": "3.3.4", 870 | "resolved": "https://registry.npmmirror.com/vue/-/vue-3.3.4.tgz", 871 | "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", 872 | "dependencies": { 873 | "@vue/compiler-dom": "3.3.4", 874 | "@vue/compiler-sfc": "3.3.4", 875 | "@vue/runtime-dom": "3.3.4", 876 | "@vue/server-renderer": "3.3.4", 877 | "@vue/shared": "3.3.4" 878 | } 879 | }, 880 | "node_modules/vue-router": { 881 | "version": "4.1.6", 882 | "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.6.tgz", 883 | "integrity": "sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==", 884 | "dependencies": { 885 | "@vue/devtools-api": "^6.4.5" 886 | }, 887 | "peerDependencies": { 888 | "vue": "^3.2.0" 889 | } 890 | } 891 | } 892 | } 893 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------