cici 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-build-manifest.json +13 -13
  3. package/.next/standalone/.next/app-path-routes-manifest.json +1 -1
  4. package/.next/standalone/.next/build-manifest.json +2 -2
  5. package/.next/standalone/.next/prerender-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  7. package/.next/standalone/.next/server/app/api/asset/[...path]/route.js +1 -1
  8. package/.next/standalone/.next/server/app/api/asset/[...path]/route.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/api/blog/[id]/highlights/[hid]/comments/[cid]/reactions/route.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/api/blog/[id]/highlights/[hid]/comments/[cid]/route.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/api/blog/[id]/highlights/[hid]/replies/route.js.nft.json +1 -1
  12. package/.next/standalone/.next/server/app/api/blog/[id]/highlights/[hid]/resolve/route.js.nft.json +1 -1
  13. package/.next/standalone/.next/server/app/api/blog/[id]/highlights/[hid]/route.js.nft.json +1 -1
  14. package/.next/standalone/.next/server/app/api/blog/[id]/highlights/route.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/api/graphql/route.js.nft.json +1 -1
  16. package/.next/standalone/.next/server/app/api/mile/prompts.body +1 -1
  17. package/.next/standalone/.next/server/app/api/upload/route.js.nft.json +1 -1
  18. package/.next/standalone/.next/server/app/atom.xml/route.js +3 -3
  19. package/.next/standalone/.next/server/app/atom.xml/route.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/atom.xml.body +1 -1677
  21. package/.next/standalone/.next/server/app/blog/[id]/page.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/blog/[id]/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/blog/page.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/blog/page_client-reference-manifest.js +1 -1
  25. package/.next/standalone/.next/server/app/editor/page.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/editor/page_client-reference-manifest.js +1 -1
  27. package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  28. package/.next/standalone/.next/server/app/memos/page.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/memos/page_client-reference-manifest.js +1 -1
  30. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/robots.txt/route.js +1 -1
  33. package/.next/standalone/.next/server/app/rss.xml/route.js +2 -2
  34. package/.next/standalone/.next/server/app/rss.xml/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/rss.xml.body +1 -227
  36. package/.next/standalone/.next/server/app/sitemap.xml/route.js +5 -5
  37. package/.next/standalone/.next/server/app/sitemap.xml.body +5 -101
  38. package/.next/standalone/.next/server/app/unavailable/page_client-reference-manifest.js +1 -1
  39. package/.next/standalone/.next/server/app-paths-manifest.json +6 -6
  40. package/.next/standalone/.next/server/chunks/3464.js +1 -1
  41. package/.next/standalone/.next/server/chunks/3607.js +1 -1
  42. package/.next/standalone/.next/server/chunks/719.js +1 -1
  43. package/.next/standalone/.next/server/pages/500.html +1 -1
  44. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  45. package/.next/standalone/package.json +2 -2
  46. package/.next/standalone/sample-content/blog/hello-cici.md +25 -0
  47. package/.next/standalone/sample-content/likes.json +1 -0
  48. package/.next/standalone/sample-content/memos.json +7 -0
  49. package/.next/standalone/sample-content/site-config.json +18 -0
  50. package/README.md +17 -1
  51. package/bin/cici.js +126 -23
  52. package/package.json +2 -2
  53. package/.next/standalone/data/assets/1727016079577.jpg +0 -0
  54. package/.next/standalone/data/assets/images/2024-12-03/1733256504772.jpg +0 -0
  55. package/.next/standalone/data/assets/images/2024-12-12/.gitkeep +0 -0
  56. package/.next/standalone/data/assets/images/2024-12-12/1734033798997.png +0 -0
  57. package/.next/standalone/data/assets/images/2024-12-21/.gitkeep +0 -0
  58. package/.next/standalone/data/assets/images/2024-12-21/1734785902890.jpeg +0 -0
  59. package/.next/standalone/data/assets/images/2024-12-23/.gitkeep +0 -0
  60. package/.next/standalone/data/assets/images/2024-12-23/1734990512413.jpeg +0 -0
  61. package/.next/standalone/data/assets/images/2024-12-23/1734991833894.jpeg +0 -0
  62. package/.next/standalone/data/assets/images/2024-12-23/1734993188114.jpeg +0 -0
  63. package/.next/standalone/data/assets/images/2024-12-23/1734993280313.jpeg +0 -0
  64. package/.next/standalone/data/assets/images/2024-12-23/1734993317984.jpeg +0 -0
  65. package/.next/standalone/data/assets/images/2024-12-23/1734993411252.jpeg +0 -0
  66. package/.next/standalone/data/assets/images/2024-12-23/1734993425042.jpeg +0 -0
  67. package/.next/standalone/data/assets/images/2024-12-23/1734993480342.jpeg +0 -0
  68. package/.next/standalone/data/assets/images/2024-12-23/1734993562660.jpeg +0 -0
  69. package/.next/standalone/data/assets/images/2024-12-23/1734993625024.jpeg +0 -0
  70. package/.next/standalone/data/assets/images/2024-12-23/1734993677103.jpeg +0 -0
  71. package/.next/standalone/data/assets/images/2024-12-23/1734993893285.jpeg +0 -0
  72. package/.next/standalone/data/assets/images/2024-12-23/1734993996131.jpeg +0 -0
  73. package/.next/standalone/data/assets/images/2024-12-23/1734994035144.jpeg +0 -0
  74. package/.next/standalone/data/assets/images/2024-12-23/1734994064317.jpeg +0 -0
  75. package/.next/standalone/data/assets/images/2024-12-23/1734994128679.jpeg +0 -0
  76. package/.next/standalone/data/assets/images/2024-12-23/1734994189257.jpeg +0 -0
  77. package/.next/standalone/data/assets/images/2024-12-23/1734994521799.jpeg +0 -0
  78. package/.next/standalone/data/assets/images/2024-12-23/1734994530669.jpeg +0 -0
  79. package/.next/standalone/data/assets/images/2024-12-24/.gitkeep +0 -0
  80. package/.next/standalone/data/assets/images/2024-12-24/1735032434591.jpeg +0 -0
  81. package/.next/standalone/data/assets/images/2024-12-24/1735068363518.jpeg +0 -0
  82. package/.next/standalone/data/assets/images/Cofe-app.png +0 -0
  83. package/.next/standalone/data/blog/.gitkeep +0 -0
  84. package/.next/standalone/data/blog/2017-summary.md +0 -59
  85. package/.next/standalone/data/blog/a-complex-web-app-refactor.md +0 -72
  86. package/.next/standalone/data/blog/a-day-of-remote-worker.md +0 -82
  87. package/.next/standalone/data/blog/async-action-in-redux.md +0 -257
  88. package/.next/standalone/data/blog/cofe.md +0 -398
  89. package/.next/standalone/data/blog/expensee-shortcut-numbers-guide.md +0 -99
  90. package/.next/standalone/data/blog/first-golang-project-fx.md +0 -113
  91. package/.next/standalone/data/blog/work-going-index.md +0 -49
  92. package/.next/standalone/data/blog//344/270/212/346/265/267/345/210/260/351/230/277/345/247/206/346/226/257/347/211/271/344/270/271.md +0 -114
  93. package/.next/standalone/data/blog//344/272/214/346/234/210/350/221/241/350/220/204/347/211/231/346/270/270/350/256/260.md +0 -107
  94. package/.next/standalone/data/blog//345/215/216/344/270/272/351/270/277/350/222/231-harmaryos-next-/347/272/277/344/270/213/346/264/273/345/212/250/345/260/217/350/256/260.md +0 -180
  95. package/.next/standalone/data/blog//345/233/233/346/234/210/347/232/204/345/260/276/345/267/264/351/200/233/345/276/267/345/233/275-/346/237/217/346/236/227.md +0 -76
  96. package/.next/standalone/data/blog//345/234/250/344/274/246/346/225/246-shoreditch-/345/221/206/344/270/211/345/244/251.md +0 -40
  97. package/.next/standalone/data/blog//346/204/217/345/244/247/345/210/251/347/275/227/351/251/254/345/244/217/345/244/251/345/233/233/346/227/245/346/270/270.md +0 -59
  98. package/.next/standalone/data/blog//346/210/221/345/201/232/344/272/206/344/270/200/346/254/276/346/227/205/350/241/214/350/256/260/345/275/225/345/272/224/347/224/250/357/274/232mile.md +0 -40
  99. package/.next/standalone/data/blog//350/245/277/347/217/255/347/211/231/344/270/203/345/244/251/347/232/204/346/227/205/350/241/214.md +0 -128
  100. package/.next/standalone/data/blog//351/200/233/351/200/233/346/265/216/345/267/236/345/262/233.md +0 -78
  101. package/.next/standalone/data/blog-manifest.json +0 -21
  102. package/.next/standalone/data/highlights/expensee-shortcut-numbers-guide.json +0 -77
  103. package/.next/standalone/data/likes.json +0 -56
  104. package/.next/standalone/data/memos.json +0 -1255
  105. package/.next/standalone/data/site-config.json +0 -22
  106. /package/.next/standalone/.next/static/{ACh2hmfOOZR7INKDQAX5I → Pl18p4h2shbbqjypPxStc}/_buildManifest.js +0 -0
  107. /package/.next/standalone/.next/static/{ACh2hmfOOZR7INKDQAX5I → Pl18p4h2shbbqjypPxStc}/_ssgManifest.js +0 -0
  108. /package/.next/standalone/{data → sample-content/assets}/.gitkeep +0 -0
  109. /package/.next/standalone/{data/assets/images/2024-12-03 → sample-content/highlights}/.gitkeep +0 -0
@@ -1,257 +0,0 @@
1
- ---
2
- title: 深入浅出Redux的异步Actions
3
- comments: true
4
- date: 2016-10-17 09:13:34
5
- updated: 2016-10-17 09:13:34
6
- tags: Redux
7
- categories: frontend
8
- ---
9
-
10
- Redux 是一个超棒的 state 容器,它几乎已经成为了 React 前端项目的必备 state 管理库,当然 Redux 可以用于任何应用 JavaScript 应用中,让我们看看这个很棒的库的代码情况吧。
11
-
12
- ```
13
- ➜ src git:(master) ✗ pwd
14
- /Users/ming/Codes/redux/src
15
- ➜ src git:(master) ✗ cloc .
16
- 7 text files.
17
- 7 unique files.
18
- 0 files ignored.
19
-
20
- http://cloc.sourceforge.net v 1.65 T=0.03 s (212.3 files/s, 18136.0 lines/s)
21
- -------------------------------------------------------------------------------
22
- Language files blank comment code
23
- -------------------------------------------------------------------------------
24
- Javascript 7 62 199 337
25
- -------------------------------------------------------------------------------
26
- SUM: 7 62 199 337
27
- -------------------------------------------------------------------------------
28
- ```
29
-
30
- 这么棒的东西, 其实就几百行代码而已,不到两个小时你就几乎可以阅读完了,当然要深入理解其理念,还是需要一些时间的,让我们慢慢来。
31
-
32
- ### 基本
33
-
34
- ```
35
- import { createStore } from 'redux'
36
-
37
- function counter(state = 0, action) {
38
- switch (action.type) {
39
- case 'INCREMENT':
40
- return state + 1
41
- case 'DECREMENT':
42
- return state - 1
43
- default:
44
- return state
45
- }
46
- }
47
-
48
- let store = createStore(counter)
49
-
50
- store.subscribe(() =>
51
- console.log(store.getState())
52
- )
53
- store.dispatch({ type: 'INCREMENT' })
54
- // 1
55
- store.dispatch({ type: 'INCREMENT' })
56
- // 2
57
- store.dispatch({ type: 'DECREMENT' })
58
- ```
59
-
60
- 这就是一个很完整的基于 Redux 的 JavaScript 应用了, 我们可以看到这是一个 Counter, 这个 Counter 可以进行 INCREMENT, DECREMENT 操作。 这不是就是简单的观察者模式吗,有人可能会说,是的,其实就是一个简单的观察者模式。还有一些有经验的工程师可能会说,这代码完全没有办法重用和拓展啊,别急,我们慢慢来。
61
-
62
- 让我们简单重构一下我们的代码吧:
63
-
64
- reducer.js
65
-
66
- ```
67
- const initState = 0;
68
-
69
- export default function reducer(state = initState, action) {
70
- switch (action.type) {
71
- case 'INCREMENT':
72
- return state + 1
73
- case 'DECREMENT':
74
- return state - 1
75
- default:
76
- return state
77
- }
78
- }
79
- ```
80
-
81
- actions.js
82
-
83
- ```
84
- export function incr() {
85
- return {
86
- type: 'INCREMENT',
87
- };
88
- }
89
-
90
- export function decr() {
91
- return {
92
- type: 'DECREMENT',
93
- }
94
- }
95
- ```
96
-
97
- main.js
98
-
99
- ```
100
- import { createStore } from 'redux'
101
-
102
- let store = createStore(counter)
103
- store.subscribe(() =>
104
- console.log(store.getState())
105
- )
106
- store.dispatch({ type: 'INCREMENT' })
107
- // 1
108
- store.dispatch({ type: 'INCREMENT' })
109
- // 2
110
- store.dispatch({ type: 'DECREMENT' })
111
- ```
112
- 这样是不是感觉熟悉了很多呢,很多时候,简单遵循一些原则,代码就好看很多,比如这里的单一责任原则(Single responsibility principle).
113
-
114
- 从上面的代码可以看到,我们的 actions 全都是返回 plain object 的 function, 因为 store 只能 dispatch 纯的 Object, 而且携带这 type 属性。那么当我们有 actions 是的异步的怎么呢,比如我们有一个 ADD 的 action, 需要先去取到一个 value 然后才能进行添加操作,那么我们应该怎么做呢?
115
-
116
- ### 异步 actions
117
-
118
- ```
119
- function add(value) {
120
- return {
121
- type: 'ADD',
122
- value,
123
- }
124
- }
125
-
126
- function fetchValue() {
127
- return new Promise((resolve) => {
128
- setTimeout(() => {
129
- resolve(2)
130
- }, 1000)
131
- })
132
- }
133
-
134
- function asyncAdd() {
135
- return function(dispatch) {
136
- fetchValue().then((val) => {
137
- dispatch(add(val))
138
- });
139
- }
140
- }
141
-
142
- ```
143
-
144
- 可以看到,我们在 dispatch 这个 add action之前,需要先得到需要 add 的 value, 而这个 value 的获取是异步的。所以我需要 dispatch 的是 asyncAdd() 这个 action, 但是这个 action 返回不是一个 plain object, 而是一个 function, 那么我们怎么才能让 store 能够直接 dispatch 一个 function 呢,我们需要一个中间件。
145
-
146
- Redux 提供了 redux-thunk 这个中间件来帮组我们做这件事情。
147
-
148
- ```
149
- import { createStore, applyMiddleware } from 'redux'
150
-
151
- const store = createStore(redux, applyMiddleware(thunk));
152
- store.dispatch(asyncAdd());
153
- ```
154
-
155
- 可以看到我们 dispatch 了一个返回 function 的 action, 这个返回的 function 接收 dispatch 作为参数,在进行了异步操作之后, dispatch 一个 type 为了 ADD 的 action, 这个action 会被 reducer 捕捉然后更新 store. 一句话来总结 redux-thunk 的作用,那就是: 让 store 可以 dispatch 一个 function, 而不仅仅是 plain object.
156
-
157
- 对于简单的应用来, redux-thunk 也许可以完全胜任我们对于异步 action 的需求,但是它有着很明显的问题:
158
-
159
- * actions 不在仅仅是一些返回 plain object 的function.
160
- * dispatch 散落在不同的地方
161
- * 测试变得复杂
162
-
163
- 是否可以有一种更友好进步的方式来管理异步 action 呢,所有 dispatch 出来的 action, 实际上都是广播形式,所有 store 的 listeners 都可以监听到,那么我们就可以让中间件去处理集中管理所有的异步操作, 等到特定结果的时候才 dispatch 相应的结果 action 出来,reducer 监听特定的 actions 去做 state 的更新操作, 这就是 redux-saga 这个第三方中间件的作用.
164
-
165
- saga.js
166
-
167
- ```
168
- import { takeEvery } from 'redux-saga'
169
- import { put } from 'redux-saga/effects'
170
-
171
- function* addWorker() {
172
- const value = yield call([fetchValue]);
173
- yield put({ type: 'ADD_SUCCESS', value });
174
- }
175
-
176
- function* addWatcher() {
177
- yield* takeEvery('ADD', addWorker);
178
- }
179
-
180
- export default addWatcher;
181
- ```
182
-
183
- main.js
184
-
185
- ```
186
- const sagaMiddleware = createSagaMiddleware();
187
- const store = createStore(reducer, applyMiddleware(sagaMiddleware));
188
- sagaMiddleware.run(addWatcher);
189
- ```
190
-
191
- 这个代码,可以让我们处理逻辑就变得十分清晰.
192
-
193
- * watcher 监听目标 actions, 然后分发到相应的 worker 去执行
194
- * worker 负责真正的异步操作,完成之后发布特定的 action 到 reducer
195
- * actions 全部都是 plain object
196
-
197
- 而且我们的代码非常容易进行测试,actions 全是 plain object, 表征应用的行为,而 saga 负责异步调用, 其测试只需要测试 watcher 是否能正确监听 actions, 然后分发到正确的 worker, 而 worker 是否完成异步操作之后发布正确的 action.
198
-
199
- 当然这样做有一个不好的地方就是,所有的 action,都会同时被 saga 和 reducer 所监听,所以当你的 actions 设计不当,容易造成循环依赖。这个问题曾经在我的项目出现过一次,debug 起来较为麻烦, 所以在设计 actions 的时候一定要区分好那些 action 是 reducer 去 handle, 而那些 actions 是 saga 去 handle.
200
-
201
- ### 测试
202
- 其实关于 redux-saga 的测试,估计很少有人写,因为其实用 redux-saga 的其实应该不多, 更多人应该用 redux-thunk, 但其实 redux-thunk 很多人也没有写测试吧, 让我们来看看如何优雅的写好 redux-saga 的单元测试吧。
203
-
204
- ```
205
- function constructExpectCallReturn(func, args) {
206
- return (
207
- {
208
- '@@redux-saga/IO': true,
209
- CALL: {
210
- context: {
211
- server: 'http://localhost:3000',
212
- baseURL: 'http://localhost:3000/v1',
213
- },
214
- fn: func,
215
- args,
216
- },
217
- }
218
- );
219
- }
220
-
221
- describe('watcher', () => {
222
- const action = { type: 'ADD' };
223
-
224
- it('should take on ADD action ', () => {
225
- actualYield = addWatcher.next().value;
226
- expectedYield = take(ADD, addWorker);
227
- expect(expectedYield).to.eql(actualYield);
228
- });
229
-
230
- it('should fork the saga handler with action', () => {
231
- expectedYield = fork(addWorker, action);
232
- actualYield = addWatcher.next(action).value;
233
- expect(expectedYield).to.eql(actualYield);
234
- });
235
-
236
- it('should return to capturing the FETCH action again', () => {
237
- actualYield = addWatcher.next().value;
238
- expectedYield = take(ADD, addWorker);
239
- expect(actualYield).to.eql(expectedYield);
240
- });
241
- });
242
-
243
- describe('worker', () => {
244
- it('should handle add', () => {
245
- const action = { type: ADD };
246
- const addIterator = addWorker(action);
247
-
248
- expectedYield = constructExpectCallReturn(fetchValue, []);
249
- actualYield = addIterator.next().value;
250
- expect(expectedYield).to.eql(actualYield);
251
- });
252
- });
253
-
254
- ```
255
-
256
- 可以看到我们的代码完全是可测试, 而且责任分的十分清楚,模块化程度十分高。
257
- 上面的代码都是在非 React 的应用中,可以看出我的初衷,也就是 Redux 其实适用于所有的 JavaScript 应用,当然在 React 中,更是缺之不可啊,当然用这些优秀工具的同时,我们更重要的是理解其中的理念,知道这样做,也要知道为什么需要这样做,这样我们才能在没有这些东西的时候,我们如何解构我们的代码,甚至是做出自己的类 Redux, 类 redux-thunk, redux-saga 的工具。
@@ -1,398 +0,0 @@
1
- ---
2
- title: The Complete Cofe Guide
3
- date: 2025-10-06T10:30:00.000Z
4
- status: draft
5
- latitude: 37.78297388090404
6
- longitude: -122.41031261300729
7
- city: San Francisco
8
- street: Market Street
9
- external_discussions:
10
- - platform: hackernews
11
- url: https://news.ycombinator.com/item?id=example
12
- ---
13
-
14
- [**Cofe**](https://github.com/metrue/cofe) is blog and memo taking app, which fully powered by Github API, and your blog posts and memos are all stored in your own repo, and all the writings are in version control.
15
-
16
-
17
-
18
-
19
-
20
- **Everything you need to know about setting up, customizing, and mastering Cofe.**
21
-
22
- Cofe is a beautifully simple blog and memo app that stores everything in your GitHub repository. No databases, no complex setups - just write and publish.
23
-
24
- > **Live Example**: See Cofe in action at [blog.minghe.me](https://blog.minghe.me) - a real blog powered by Cofe.
25
-
26
- ## Quick Start
27
-
28
- ### 1. GitHub OAuth Setup
29
-
30
- First, create a GitHub OAuth App:
31
-
32
- 1. Go to [GitHub Settings > Developer Settings > OAuth Apps](https://github.com/settings/developers)
33
- 2. Click "New OAuth App"
34
- 3. Fill in the details:
35
- - **Application name**: `Cofe Blog`
36
- - **Homepage URL**: `http://localhost:3000` (for development)
37
- - **Authorization callback URL**: `http://localhost:3000/api/auth/callback/github`
38
- 4. Save your **Client ID** and **Client Secret**
39
-
40
- ### 2. Environment Setup
41
-
42
- Clone the repository and install dependencies:
43
-
44
- ```bash
45
- git clone https://github.com/metrue/Cofe.git
46
- cd Cofe
47
- npm install
48
- ```
49
-
50
- Create your environment variables:
51
-
52
- ```bash
53
- # Required for GitHub integration
54
- export GITHUB_USERNAME='your-github-username'
55
- export GITHUB_ID='your-github-oauth-client-id'
56
- export GITHUB_SECRET='your-github-oauth-client-secret'
57
-
58
- # Required for authentication
59
- export NEXTAUTH_SECRET='your-random-secret-string'
60
- export NEXTAUTH_URL='http://localhost:3000'
61
-
62
- # Optional: Analytics (see Analytics section)
63
- export NEXT_PUBLIC_ANALYTICS_ENABLED=false
64
- ```
65
-
66
- ### 3. Start Writing
67
-
68
- ```bash
69
- npm run dev
70
- ```
71
-
72
- Visit `http://localhost:3000`, sign in with GitHub, and start creating!
73
-
74
- ## Core Features
75
-
76
- ### Blog Posts
77
-
78
- Rich blog posts with full markdown support:
79
-
80
- - **Markdown**: Standard and extended syntax
81
- - **Code Highlighting**: Syntax highlighting for 100+ languages
82
- - **Math**: LaTeX equations with KaTeX
83
- - **Images**: Drag & drop or paste images
84
- - **Location**: Automatic location tagging
85
- - **Discussions**: Link external discussions from HN, Reddit, V2EX
86
-
87
- ### Quick Memos
88
-
89
- Capture thoughts instantly:
90
-
91
- - **Fast Entry**: One-click memo creation
92
- - **Location Tracking**: Automatic location capture
93
- - **Like System**: Engage with your content
94
- - **Masonry Layout**: Beautiful responsive grid
95
-
96
- ### External Discussions
97
-
98
- Connect your posts to external conversations:
99
-
100
- ```yaml
101
- external_discussions:
102
- - platform: hackernews
103
- url: https://news.ycombinator.com/item?id=123456
104
- - platform: reddit
105
- url: https://reddit.com/r/programming/comments/xyz/
106
- - platform: v2ex
107
- url: https://v2ex.com/t/123456
108
- ```
109
-
110
- Cofe automatically fetches and displays comment counts and links.
111
-
112
- ## Advanced Configuration
113
-
114
- ### Analytics Setup
115
-
116
- Enable privacy-first analytics with Umami:
117
-
118
- 1. **Get Umami**: Sign up at [cloud.umami.is](https://cloud.umami.is) or [self-host](https://umami.is/docs)
119
- 2. **Create Website**: Add your domain and get the Website ID
120
- 3. **Configure Environment**:
121
-
122
- ```bash
123
- export NEXT_PUBLIC_ANALYTICS_ENABLED=true
124
- export NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-website-id
125
-
126
- # Optional: Custom Umami instance
127
- export NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://your-umami.com/script.js
128
-
129
- # Optional: Domain restrictions
130
- export NEXT_PUBLIC_UMAMI_DOMAINS=yourdomain.com,www.yourdomain.com
131
- ```
132
-
133
- ### Site Configuration
134
-
135
- Customize your site in `lib/siteConfig.ts`:
136
-
137
- ```typescript
138
- export const getSiteConfig = () => ({
139
- title: 'Your Blog Name',
140
- description: 'Your blog description',
141
- author: {
142
- name: 'Your Name',
143
- email: 'your@email.com'
144
- },
145
- social: {
146
- twitter: 'yourusername',
147
- github: 'yourusername'
148
- },
149
- keywords: ['blog', 'tech', 'programming']
150
- })
151
- ```
152
-
153
- ### Custom Styling
154
-
155
- Cofe uses Tailwind CSS. Customize styles in:
156
-
157
- - `app/globals.css` - Global styles
158
- - `tailwind.config.js` - Tailwind configuration
159
- - Individual components for specific customizations
160
-
161
- ## Content Management
162
-
163
- ### File Structure
164
-
165
- Cofe stores content in your GitHub repository:
166
-
167
- ```
168
- data/
169
- ├── blog/ # Blog posts (.md files)
170
- ├── memos.json # All memos
171
- ├── links.json # External links
172
- └── likes.json # Like data
173
- ```
174
-
175
- ### Blog Post Format
176
-
177
- Blog posts use frontmatter:
178
-
179
- ```yaml
180
- ---
181
- title: Your Post Title
182
- date: 2025-10-06T10:30:00.000Z
183
- latitude: 37.7749
184
- longitude: -122.4194
185
- city: San Francisco
186
- street: Market Street
187
- external_discussions:
188
- - platform: hackernews
189
- url: https://news.ycombinator.com/item?id=123
190
- ---
191
-
192
- Your markdown content here...
193
- ```
194
-
195
- ### Memo Format
196
-
197
- Memos are stored as JSON objects:
198
-
199
- ```json
200
- {
201
- "id": "1728123456789",
202
- "content": "Your memo content with **markdown**",
203
- "timestamp": "2025-10-06T10:30:00.000Z",
204
- "latitude": 37.7749,
205
- "longitude": -122.4194,
206
- "city": "San Francisco",
207
- "street": "Market Street"
208
- }
209
- ```
210
-
211
- ## Deployment
212
-
213
- ### Vercel (Recommended)
214
-
215
- 1. **Deploy**: Click the deploy button or connect your GitHub repo
216
- 2. **Environment Variables**: Add all your environment variables
217
- 3. **Domain**: Configure your custom domain
218
- 4. **SSL**: Automatic HTTPS setup
219
-
220
- ### Other Platforms
221
-
222
- Cofe works on any platform that supports Next.js:
223
-
224
- - **Netlify**: Use the Next.js plugin
225
- - **Railway**: Direct deployment support
226
- - **DigitalOcean App Platform**: Node.js app
227
- - **Self-hosted**: Docker or Node.js server
228
-
229
- ## API Reference
230
-
231
- ### GraphQL Endpoints
232
-
233
- Cofe provides a GraphQL API at `/api/graphql`:
234
-
235
- #### Queries
236
-
237
- ```graphql
238
- # Get all blog posts
239
- query {
240
- blogPosts {
241
- id
242
- title
243
- content
244
- date
245
- city
246
- street
247
- }
248
- }
249
-
250
- # Get specific blog post
251
- query {
252
- blogPost(id: "post-id") {
253
- id
254
- title
255
- content
256
- discussions {
257
- platform
258
- url
259
- count
260
- }
261
- }
262
- }
263
-
264
- # Get all memos
265
- query {
266
- memos {
267
- id
268
- content
269
- timestamp
270
- city
271
- street
272
- }
273
- }
274
-
275
- # Get like information
276
- query {
277
- getLikes(itemType: "blog", id: "post-id") {
278
- count
279
- countries
280
- userLiked
281
- }
282
- }
283
- ```
284
-
285
- #### Mutations
286
-
287
- ```graphql
288
- # Create blog post
289
- mutation {
290
- createBlogPost(input: {
291
- title: "New Post"
292
- content: "Content here..."
293
- latitude: 37.7749
294
- longitude: -122.4194
295
- city: "San Francisco"
296
- street: "Market Street"
297
- }) {
298
- id
299
- title
300
- }
301
- }
302
-
303
- # Create memo
304
- mutation {
305
- createMemo(input: {
306
- content: "Quick thought..."
307
- latitude: 37.7749
308
- longitude: -122.4194
309
- city: "San Francisco"
310
- }) {
311
- id
312
- content
313
- }
314
- }
315
-
316
- # Toggle like
317
- mutation {
318
- toggleLike(itemType: "blog", id: "post-id") {
319
- liked
320
- count
321
- countries
322
- }
323
- }
324
- ```
325
-
326
- ## Troubleshooting
327
-
328
- ### Common Issues
329
-
330
- **Authentication not working?**
331
- - Check your GitHub OAuth callback URL
332
- - Verify NEXTAUTH_SECRET is set
333
- - Ensure NEXTAUTH_URL matches your domain
334
-
335
- **Images not loading?**
336
- - Verify GitHub token has repo access
337
- - Check image URLs are publicly accessible
338
- - Ensure proper file permissions
339
-
340
- **Build failures?**
341
- - Run `npm run lint` and fix any issues
342
- - Check `npx tsc --noEmit` for TypeScript errors
343
- - Verify all environment variables are set
344
-
345
- **Location not working?**
346
- - Enable location permissions in browser
347
- - Check HTTPS requirement for geolocation
348
- - Verify network connectivity for reverse geocoding
349
-
350
- ### Getting Help
351
-
352
- 1. **Check Issues**: [GitHub Issues](https://github.com/metrue/Cofe/issues)
353
- 2. **Discussions**: Use the external discussions feature
354
- 3. **Contributing**: See [AGENTS.md](./AGENTS.md) for development guidelines
355
-
356
- ## Migration
357
-
358
- ### From Jekyll
359
-
360
- 1. Copy markdown files to `data/blog/`
361
- 2. Update frontmatter format
362
- 3. Move images to GitHub repository
363
- 4. Update image URLs
364
-
365
- ### From Hugo
366
-
367
- 1. Convert content to standard markdown
368
- 2. Update frontmatter dates to ISO format
369
- 3. Migrate static assets
370
- 4. Configure redirects if needed
371
-
372
- ### From Ghost/WordPress
373
-
374
- 1. Export content to markdown
375
- 2. Clean up HTML remnants
376
- 3. Convert featured images
377
- 4. Set up URL redirects
378
-
379
- ## Performance Tips
380
-
381
- 1. **Image Optimization**: Use WebP format when possible
382
- 2. **Caching**: Enable Vercel Edge Caching
383
- 3. **Analytics**: Use minimal tracking for better performance
384
- 4. **Content**: Keep memo count reasonable for faster loading
385
-
386
- ## Security
387
-
388
- - **Authentication**: GitHub OAuth only
389
- - **Data Storage**: Your GitHub repository
390
- - **Privacy**: No external databases
391
- - **Analytics**: Privacy-first Umami integration
392
- - **HTTPS**: Required in production
393
-
394
- ---
395
-
396
- **That's everything!** Cofe is designed to be simple yet powerful. Start with the basics and customize as you grow.
397
-
398
- Questions? Open an issue or start a discussion. Happy writing! ☕