(`https://api.github.com/repos/${VITE_OWNER}/${VITE_BLOGS_REPO}/milestones`)
42 | }
43 |
--------------------------------------------------------------------------------
/assets/styles/transition.css:
--------------------------------------------------------------------------------
1 | @keyframes slide-enter {
2 | 0% {
3 | opacity: 0;
4 | transform: translateY(10px)
5 | }
6 |
7 | 100% {
8 | opacity: 1;
9 | transform: none
10 | }
11 | }
12 |
13 | [slide-enter], .slide-enter, .slide-enter-content > * {
14 | --stagger: 0;
15 | --delay: 90ms;
16 | --start: 0ms;
17 |
18 | animation: slide-enter 1s both 1;
19 | animation-delay: calc(var(--start) + var(--stagger) * var(--delay));
20 | }
21 |
22 | .slide-enter-content > *:nth-child(1) { --stagger: 1; }
23 | .slide-enter-content > *:nth-child(2) { --stagger: 2; }
24 | .slide-enter-content > *:nth-child(3) { --stagger: 3; }
25 | .slide-enter-content > *:nth-child(4) { --stagger: 4; }
26 | .slide-enter-content > *:nth-child(5) { --stagger: 5; }
27 | .slide-enter-content > *:nth-child(6) { --stagger: 6; }
28 | .slide-enter-content > *:nth-child(7) { --stagger: 7; }
29 | .slide-enter-content > *:nth-child(8) { --stagger: 8; }
30 | .slide-enter-content > *:nth-child(9) { --stagger: 9; }
31 | .slide-enter-content > *:nth-child(10) { --stagger: 10; }
32 | .slide-enter-content > *:nth-child(11) { --stagger: 11; }
33 | .slide-enter-content > *:nth-child(12) { --stagger: 12; }
34 | .slide-enter-content > *:nth-child(13) { --stagger: 13; }
35 | .slide-enter-content > *:nth-child(14) { --stagger: 14; }
36 | .slide-enter-content > *:nth-child(15) { --stagger: 15; }
37 | .slide-enter-content > *:nth-child(16) { --stagger: 16; }
38 | .slide-enter-content > *:nth-child(17) { --stagger: 17; }
39 | .slide-enter-content > *:nth-child(18) { --stagger: 18; }
40 | .slide-enter-content > *:nth-child(19) { --stagger: 19; }
41 | .slide-enter-content > *:nth-child(20) { --stagger: 20; }
42 |
--------------------------------------------------------------------------------
/pages/blog/[[catelog]].vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | A total of {{ searchResult?.total_count || '?' }} articles
30 |
31 |
32 |
33 |
34 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## Issue-nuxt-blog
6 |
7 | 
8 |
9 |
10 |
11 |
12 | 简体中文 | [English](https://github.com/chansee97/issue-nuxt-blog/blob/main/README_en.md)
13 |
14 |
15 |
16 | Issue-nuxt-blog是基于Github issue实现的博客项目, 新增和编辑文章更加快速方便,而且部署简单,无需服务器,只需配置环境变量即可使用。
17 |
18 | 本项目基于另一个使用本地文件记录的[nuxt博客](https://github.com/chansee97/nuxt-blog),使用并不方便,所以有了使用issue的版本
19 |
20 | ## 💡 特点
21 | - Nuxt3技术栈,ssr渲染,对seo更友好
22 | - 适应移动的端布局
23 | - 页面简约,暗模式过渡动画优雅
24 | - 使用Github issue作为数据源,文章操作更加简单
25 | - 使用issue Label来为文章标记标签, Milestone作为文章分类
26 | - 文章搜索功能
27 | - 只需配置简单的环境变量即可使用,无需更改代码
28 | - 基于[utteranc](https://utteranc.es/)的评论功能,评论与issue绑定
29 | - 使用[rsshub](https://docs.rsshub.app/)生成RSS订阅
30 |
31 |
32 | ## 📖 使用方法
33 |
34 | ### 使用前准备
35 | 1. 准备一个新仓库,仓库名称随意,例如“my-blogs”,记下仓库的名字
36 | 2. 记下自己的github用户名,例如“chansee97”
37 | 3. 创建一个专门用来读取issue的[github token](https://github.com/settings/tokens/new)并保存,配置如下
38 | 
39 | 4. 准备一个[vecel](https://vercel.com/)账号(此处以vecel部署为例)
40 |
41 | ### 项目部署
42 | 1. [fork本项目](https://github.com/chansee97/issue-nuxt-blog/fork)到自己的仓库
43 | 2. vecel中[import](https://vercel.com/new)刚才fork的项目, 在部署前导入自己的变量
44 | 
45 | 你应当在部署的站点增加如下的环境变量
46 | - `VITE_OWNER` issue所在仓库的拥有者
47 | - `VITE_BLOGS_REPO` issue所在仓库名称
48 | - `VITE_GITHUB_TOKEN ` issue账号的验证token,如果不设置会导致速率下降,访问受限
49 | 3. 环境变量设置完毕后,点击部署Deploy,耐心等待部署成功
50 | 4. 至此,所有的设置完成,没有其他需要修改的地方,如果你有自定义的需求,可以进行个人开发修改
51 |
52 | ### 增加文章
53 |
54 | 从刚才新建的仓库进入,新建打开状态的issue即可,所有的issue会同步在博客站点上,如果issue关闭,则文章也会隐藏
55 |
56 | issue的标题即为文章的标题
57 | issue的正文即为文章的内容
58 | issue的Labels 为文章标签,你可以为一个文章打上多个标签
59 | issue的Milestone 为文章分类,你可以为一个文章设置一个分类
60 |
61 | 可以参考我的[博客issue](https://github.com/chansee97/my-blogs/issues)
62 |
63 | ### 修改个人介绍
64 | 博客中的个人介绍与issue所有者的reamde简介文件一致,请仔细编辑自己名下简介文件
65 | github上个人简介的仓库是https://github.com/用户名/用户名
66 | 简介则是此仓库下的`reamde.md`文件
67 |
68 | ## 🔎 本地开发
69 | 确保安装依赖项:
70 | ```shell
71 | pnpm install
72 | ```
73 |
74 | 启动开发服务
75 | ```shell
76 | pnpm dev
77 | ```
78 | ## 协议
79 | [MIT](LICENSE)
80 |
--------------------------------------------------------------------------------
/components/DarkMode.vue:
--------------------------------------------------------------------------------
1 |
55 |
56 |
57 |
58 |
59 |
60 |
84 |
--------------------------------------------------------------------------------
/api/type.d.ts:
--------------------------------------------------------------------------------
1 | export interface Readme {
2 | name: string;
3 | path: string;
4 | sha: string;
5 | size: number;
6 | url: string;
7 | html_url: string;
8 | git_url: string;
9 | download_url: string;
10 | type: string;
11 | content: string;
12 | encoding: string;
13 | _links: {
14 | self: string;
15 | git: string;
16 | html: string;
17 | };
18 | }
19 |
20 | export interface IssueResult {
21 | incomplete_results: boolean
22 | items: any[]
23 | total_count: number
24 | }
25 | export interface Issue {
26 | url: string;
27 | repository_url: string;
28 | labels_url: string;
29 | comments_url: string;
30 | events_url: string;
31 | html_url: string;
32 | id: number;
33 | node_id: string;
34 | number: number;
35 | title: string;
36 | user: Creator;
37 | labels: Label[];
38 | state: string;
39 | locked: boolean;
40 | assignee: string;
41 | assignees: any[];
42 | milestone: Milestone;
43 | comments: number;
44 | created_at: string;
45 | updated_at: string;
46 | closed_at: string;
47 | author_association: string;
48 | active_lock_reason: string;
49 | body: string;
50 | reactions: Reaction;
51 | timeline_url: string;
52 | performed_via_github_app: string;
53 | state_reason: string;
54 | }
55 |
56 |
57 | export interface Label {
58 | id: number;
59 | node_id: string;
60 | url: string;
61 | name: string;
62 | color: string;
63 | default?: boolean;
64 | description?: string;
65 | }
66 |
67 | interface Creator {
68 | login: string;
69 | id: number;
70 | node_id: string;
71 | avatar_url: string;
72 | gravatar_id: string;
73 | url: string;
74 | html_url: string;
75 | followers_url: string;
76 | following_url: string;
77 | gists_url: string;
78 | starred_url: string;
79 | subscriptions_url: string;
80 | organizations_url: string;
81 | repos_url: string;
82 | events_url: string;
83 | received_events_url: string;
84 | type: string;
85 | site_admin: boolean;
86 | }
87 |
88 | export interface Milestone {
89 | url: string;
90 | html_url: string;
91 | labels_url: string;
92 | id: number;
93 | node_id: string;
94 | number: number;
95 | title: string;
96 | description: string;
97 | creator: Creator;
98 | open_issues: number;
99 | closed_issues: number;
100 | state: string;
101 | created_at: string;
102 | updated_at: string;
103 | due_on: string;
104 | closed_at: string;
105 | }
106 |
107 | interface Reaction {
108 | url: string;
109 | total_count: number;
110 | '+1': number;
111 | '-1': number;
112 | laugh: number;
113 | hooray: number;
114 | confused: number;
115 | heart: number;
116 | rocket: number;
117 | eyes: number;
118 | }
119 |
--------------------------------------------------------------------------------
/README_en.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ## Issue-nuxt-blog
5 |
6 | 
7 |
8 |
9 |
10 |
11 | [Chinese](https://github.com/chansee97/issue-nuxt-blog/blob/main/README.md) | English
12 |
13 |
14 | Issue-nuxt-blog is a blog project based on the GitHub issue. It is faster and more convenient to add and edit articles, and it is easy to deploy. It does not require a server, and can be used by configuring environment variables.
15 |
16 | This project is based on another [nuxt blog](https://github.com/chansee97/nuxt-blog) that uses local file records, which is not convenient to use, so there is a version that uses issue
17 |
18 | ##💡 Features
19 | - Nuxt3 technology stack, ssr rendering, more SEO-friendly
20 | - Adaptable to mobile end layouts
21 | - The page is minimalist, and the dark mode transition animation is elegant
22 | - Using GitHub issue as a data source makes article manipulation easier
23 | - Use issue Label to tag articles, Milestone for article classification
24 | - Article search function
25 | - Simply configure simple environment variables to use without changing the code
26 | - Comment function based on [utteranc](https://utteranc.es/), comment and issue binding
27 | - Generate RSS feeds using [rsshub](https://docs.rsshub.app/)
28 |
29 |
30 | ## How to use 📖
31 |
32 | ### Prepare before use
33 | 1. Prepare a new warehouse with a random name, such as "my-blogs", and write down the name of the warehouse.
34 | 2. Write down your github username, such as "chansee97".
35 | 3. Create a [github token](https://github.com/settings/tokens/new) dedicated to reading the issue and save it, configured as follows
36 | 
37 | 4. Prepare a [vecel](https://vercel.com/) account (take vecel deployment as an example here)
38 |
39 | ### Project deployment
40 | 1. [Fork this item](https://github.com/chansee97/issue-nuxt-blog/fork) to your own repository
41 | 2. vecel [import](https://vercel.com/new) just fork the project, import your own variables before deployment
42 | 
43 | You should add the following environment variables to the deployment site
44 | - `VITE_OWNER` The owner of the warehouse where the issue is located
45 | - `VITE_BLOGS_REPO` the name of the warehouse where the issue is located
46 | - `VITE_GITHUB_TOKEN` issue account verification token, if not set, it will cause the rate to drop and access is limited
47 | 1. After setting the environment variables, click Deploy and wait patiently for the deployment to be successful.
48 | 2. At this point, all settings are completed, and there is no other place to modify. If you have custom requirements, you can make personal development modifications
49 |
50 | ### Add article
51 |
52 | Enter from the newly created warehouse and create a new open issue. All issues will be synchronized on the blog site. If the issue is closed, the article will also be hidden.
53 |
54 | The title of the issue is the title of the article
55 | The body of the issue is the content of the article
56 | The Labels of the issue are article tags, you can tag multiple tags for an article
57 | Issue Milestone for article classification, you can set a category for an article
58 |
59 | You can refer to my [blog issue](https://github.com/chansee97/my-blogs/issues)
60 |
61 | ### Modify the personal introduction
62 | The personal introduction in the blog is the same as the reamde profile file of the issue owner. Please edit the profile file in your name carefully.
63 | The repository for the profile on GitHub is https://github.com/username/username
64 | The profile is the `reamde.md` file under this repository
65 |
66 | ## 🔎 local development
67 | Make sure to install dependencies:
68 | ```shell
69 | pnpm install
70 | ```
71 |
72 | Start development services
73 | ```shell
74 | pnpm dev
75 | ```
76 | ## LICENSE
77 | [MIT](LICENSE)
78 |
--------------------------------------------------------------------------------
/assets/styles/markdown.scss:
--------------------------------------------------------------------------------
1 | .prose {
2 |
3 | /* common var */
4 | --common-transition: all 0.3s ease;
5 | --common-rd: 6px;
6 | --common-bg: #69a79429;
7 | --common-bd:#c9c7c2;
8 |
9 | max-width: 65em;
10 | margin: auto;
11 |
12 | /* 代码高亮 */
13 | .hljs {
14 | background-color: var(--common-bg) !important;
15 | }
16 |
17 | /* toc样式 */
18 | .table-of-contents > ul{
19 | list-style: none;
20 | }
21 |
22 | /* headings */
23 | h1,
24 | h2,
25 | h3,
26 | h4,
27 | h5,
28 | h6 {
29 | position: relative;
30 | margin-top: 1em;
31 | margin-bottom: 1em;
32 | font-weight: bold;
33 | line-height: 1.4;
34 | cursor: text;
35 | }
36 |
37 | h2,
38 | h3,
39 | h4 {
40 | margin-left: -1em;
41 | & > a {
42 | opacity: 0;
43 | transition: var(--common-transition);
44 | text-decoration: none;
45 | }
46 |
47 | &:hover > a {
48 | opacity: 0.7;
49 | }
50 | }
51 |
52 | h1 {
53 | font-size: 2.25em;
54 | line-height: 1.2;
55 | }
56 |
57 | h2 {
58 | font-size: 1.75em;
59 | line-height: 1.225;
60 | }
61 |
62 | h3 {
63 | font-size: 1.5em;
64 | line-height: 1.43;
65 | }
66 |
67 | h4 {
68 | font-size: 1.25em;
69 | }
70 |
71 | h5 {
72 | font-size: 1em;
73 | }
74 |
75 | h6 {
76 | font-size: 1em;
77 | }
78 |
79 | /* list */
80 | ul,
81 | ol {
82 | padding-left: 2em;
83 | }
84 |
85 | ul {
86 | list-style: disc;
87 | }
88 |
89 | ol {
90 | list-style-type: decimal;
91 | }
92 |
93 | .contains-task-list {
94 | padding-left: 0;
95 | list-style-type: none;
96 |
97 | input[type='checkbox'] {
98 | margin: 0 0.5em;
99 | }
100 | }
101 |
102 | /* p */
103 | p {
104 | margin-block: 0.8em;
105 | word-break: break-word;
106 | }
107 |
108 | /* anchor */
109 | a {
110 | text-decoration: underline var(--common-bd);
111 | text-decoration-thickness: 1px;
112 | text-underline-offset: 0.3em;
113 | transition: var(--common-transition);
114 |
115 | &:hover {
116 | text-decoration-color: currentcolor;
117 | }
118 | }
119 |
120 | /* code */
121 | pre,
122 | code {
123 | font-family: 'Fira Mono', ui-monospace, monospace;
124 | }
125 |
126 | pre {
127 | margin: 0.5em 0;
128 | overflow-x: auto;
129 | border-radius: var(--common-rd);
130 |
131 | .line {
132 | display: block;
133 | min-height: 1em;
134 | }
135 | }
136 |
137 | :not(pre) > code {
138 | padding-inline: 0.3em;
139 | font-weight: bold;
140 | background-color: var(--common-bg);
141 | border-radius: 3px;
142 | }
143 |
144 | /* blockquote */
145 | blockquote {
146 | padding: 5px 1em;
147 | font-style: italic;
148 | background-color: var(--common-bg);
149 | border-color: var(--common-bg);
150 | border-left-width: 0.4em;
151 | border-radius: var(--common-rd);
152 |
153 | > p {
154 | margin: 0.5em 0;
155 | }
156 | }
157 |
158 | /* hr */
159 | hr {
160 | width: 200px;
161 | margin: 2.5em auto;
162 | border-color: currentcolor;
163 | border-style: dashed;
164 | opacity: 0.2;
165 | }
166 |
167 | /* img */
168 | img {
169 | margin: 2em auto;
170 | max-width: 100%;
171 | border-radius: var(--common-rd);
172 | }
173 |
174 | /* Table */
175 | table {
176 | width: 100%;
177 | margin-block: 1em;
178 | border-radius: var(--common-rd);
179 | overflow: hidden;
180 |
181 | th {
182 | text-align: center;
183 | }
184 |
185 | th,
186 | td {
187 | padding: 0.3em 0.8em;
188 | }
189 |
190 | thead {
191 | background-color: var(--common-bg);
192 | }
193 |
194 | tr:nth-child(even) {
195 | background-color: var(--common-bg);
196 | }
197 | }
198 |
199 | /* other */
200 | sup {
201 | padding: 0 0.3em;
202 |
203 | &::before {
204 | content: '[';
205 | }
206 |
207 | &::after {
208 | content: ']';
209 | }
210 | }
211 | }
212 |
--------------------------------------------------------------------------------