Skip to content

Commit ac7e08a

Browse files
committed
Redesign SideNav & implement add repo func
1 parent 9c5cbd3 commit ac7e08a

11 files changed

Lines changed: 1062 additions & 211 deletions

File tree

browser/lib/RepositoryManager.js

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
const keygen = require('browser/lib/keygen')
2+
const fs = require('fs')
3+
const path = require('path')
4+
const CSON = require('season')
5+
const _ = require('lodash')
6+
7+
/**
8+
* # Repo structure
9+
*
10+
* ```
11+
* root
12+
* |- data
13+
* |-note1.cson
14+
* |-note2.cson
15+
* |-note3.cson
16+
* |- boostrepo.json
17+
* ```
18+
*
19+
* ## `boostrepo.json`
20+
*
21+
* ```js
22+
* {
23+
* name: String,
24+
* author: String, // Same convention of package.json, `John Doe <email@example.com> (http://example.com)`
25+
* remotes: [{
26+
* name: String,
27+
* url: String, // url of git remote
28+
* branch: String // if branch isn't set, it will try to use `master` branch.
29+
* }],
30+
* folders: [{
31+
* key: String // Unique sha1 hash key to identify folder,
32+
* name: String,
33+
* color: String // All CSS color formats available.
34+
* }]
35+
* }
36+
* ```
37+
*
38+
* ## `data` directory
39+
*
40+
* Every note will be saved here as a single CSON file to `git diff` efficiently.
41+
* > This is because CSON supports Multiline string.
42+
* File name of each cson file will be used to identify note.
43+
* Commonly, Boostnote will automatically generate sha1 key and use it as a file name when creating a new note.
44+
*
45+
* ### `note.cson`
46+
*
47+
* ```cson
48+
* name: String
49+
* tags: [String] // tags
50+
* folder: String // hash key of folder
51+
* mode: String // syntax mode
52+
* title: String
53+
* content: String
54+
* createdAt: Date
55+
* updatedAt: Date
56+
* ```
57+
*/
58+
59+
/**
60+
* # Resolve directory.
61+
*
62+
* If directory doesn't exist, it will try to make a new one.
63+
* If failed return rejected promise
64+
*
65+
* @param {String} targetPath Target path of directory
66+
* @return {Promise} [description]
67+
*/
68+
function _resolveDirectory (targetPath) {
69+
return new Promise(function (resolve, reject) {
70+
// check the directory exists
71+
fs.stat(targetPath, function (err, stat) {
72+
// Reject errors except no suchfile
73+
if (err != null && err.code !== 'ENOENT') {
74+
return reject(err)
75+
}
76+
77+
// Handle no suchfile error only
78+
// Make new Folder by given path
79+
if (err != null) {
80+
return fs.mkdir(targetPath, function (err, stat) {
81+
// If failed to make a new directory, reject it.
82+
if (err != null) {
83+
return reject(err)
84+
}
85+
resolve(targetPath)
86+
})
87+
}
88+
89+
// Check the target is not a directory
90+
if (!stat.isDirectory()) {
91+
return reject(new Error(targetPath + ' path is not a directory'))
92+
}
93+
resolve(targetPath)
94+
})
95+
})
96+
}
97+
98+
function _generateDefaultRepoJSON (override) {
99+
return Object.assign({
100+
name: 'default',
101+
remotes: [],
102+
folders: [{
103+
key: keygen(),
104+
name: 'general',
105+
color: 'green'
106+
}]
107+
}, override)
108+
}
109+
110+
/**
111+
* # Resolve RepoJSON
112+
*
113+
* Every repository must have `boostrepo.json`
114+
*
115+
* If boostrepo.json doesn't exist, create new one.
116+
*
117+
* @param {[type]} targetPath [description]
118+
* @return {[type]} [description]
119+
*/
120+
function _resolveRepoJSON (targetPath) {
121+
return new Promise(function checkIfExists (resolve, reject) {
122+
// If JSON doesn't exist, make a new one.
123+
if (CSON.resolve(targetPath) == null) {
124+
let newRepoJSON = _generateDefaultRepoJSON()
125+
return CSON.writeFile(targetPath, newRepoJSON, function (err) {
126+
if (err != null) return reject(err)
127+
resolve(newRepoJSON)
128+
})
129+
}
130+
131+
CSON.readFile(targetPath, function (err, obj) {
132+
if (err != null) return reject(err)
133+
resolve(obj)
134+
})
135+
})
136+
}
137+
138+
/**
139+
* Get all repository stats from localStorage
140+
* it is stored to `repositories` key.
141+
* if the data is corrupted, re-intialize it.
142+
*
143+
* @return {Array} registered repositories
144+
* ```
145+
* [{
146+
* key: String,
147+
* name: String,
148+
* path: String // path of repository
149+
* }]
150+
* ```
151+
*/
152+
function getAllRepoStats () {
153+
let data
154+
try {
155+
data = JSON.parse(localStorage.getItem('repositories'))
156+
if (!_.isArray(data)) {
157+
throw new Error('Data is corrupted. it must be an array.')
158+
}
159+
} catch (err) {
160+
console.log(err)
161+
data = []
162+
_saveAllRepoStats(data)
163+
}
164+
return data
165+
}
166+
167+
/**
168+
* Save All Repos
169+
*/
170+
function _saveAllRepoStats (repoStats) {
171+
localStorage.setItem('repoStats', JSON.stringify(repoStats))
172+
}
173+
174+
/**
175+
* Add repository and return new Repo
176+
* @param {Object} newRepo [description]
177+
* ```
178+
* {
179+
* key: String,
180+
* name: String,
181+
* path: String,
182+
* status: String,
183+
* folders: [{
184+
* key: String,
185+
* color: String,
186+
* name: String
187+
* }],
188+
* notes: [{
189+
* key: String,
190+
* title: String,
191+
* content: String,
192+
* folder: String,
193+
* tags: [String],
194+
* createdAt: Date,
195+
* updatedAt: Date
196+
* }]
197+
* }
198+
* ```
199+
*/
200+
function addRepo (newRepo) {
201+
let { targetPath, name } = newRepo
202+
targetPath = path.resolve(targetPath)
203+
204+
let repoStat, repoJSON
205+
return _resolveDirectory(targetPath)
206+
.then(function initializeRepo () {
207+
let resolveDataDirectory = _resolveDirectory(path.resolve(targetPath, 'data'))
208+
let resolveBoostrepoJSON = _resolveRepoJSON(path.resolve(targetPath, 'boostrepo.json'))
209+
return Promise.all([resolveDataDirectory, resolveBoostrepoJSON])
210+
})
211+
.then(function setLoalStorage (data) {
212+
let dataPath = data[0]
213+
repoJSON = data[1]
214+
215+
let repoStats = getAllRepoStats()
216+
217+
// generate unique key
218+
let key = keygen()
219+
while (repoStats.some((repoStat) => repoStat.key === key)) {
220+
key = keygen()
221+
}
222+
223+
repoStat = {
224+
key,
225+
name: name,
226+
path: targetPath
227+
}
228+
229+
repoStats.push(repoStat)
230+
_saveAllRepoStats(repoStats)
231+
232+
return dataPath
233+
})
234+
.then(function fetchNotes (dataPath) {
235+
let noteNames = fs.readdirSync(dataPath)
236+
let notes = noteNames
237+
.map((noteName) => {
238+
let notePath = path.resolve(dataPath, noteNames)
239+
240+
return new Promise(function (resolve, reject) {
241+
CSON.readFile(notePath, function (err, obj) {
242+
if (err != null) {
243+
console.log(err)
244+
return resolve(null)
245+
}
246+
obj.key = path.basename(noteName, '.cson')
247+
return resolve(obj)
248+
})
249+
})
250+
})
251+
.filter((note) => note != null)
252+
253+
return Promise.all(notes)
254+
})
255+
.then(function resolveRepo (notes) {
256+
return Object.assign({}, repoJSON, repoStat, {
257+
status: 'IDLE',
258+
notes
259+
})
260+
})
261+
}
262+
263+
function getRepos () {
264+
let repoStats
265+
try {
266+
repoStats = JSON.parse(localStorage.getItem('repoStats'))
267+
if (repoStats == null) repoStats = []
268+
} catch (err) {
269+
repoStats = []
270+
}
271+
return repoStats
272+
.map((repoStat) => {
273+
let repoJSON, notes
274+
try {
275+
repoJSON = CSON.readFileSync(path.resolve(repoStat.path, 'boostrepo.json'))
276+
let notePaths = fs.readdirSync(path.resolve(repoStat.path, 'data'))
277+
notes = notePaths.map((notePath) => CSON.readFileSync(notePath))
278+
} catch (err) {
279+
return Object.assign({}, repoStat, {
280+
status: 'ERROR',
281+
error: err
282+
})
283+
}
284+
return Object.assign({}, repoJSON, repoStat, {
285+
status: 'IDLE',
286+
notes
287+
})
288+
})
289+
}
290+
291+
export default {
292+
getAllRepoStats,
293+
addRepo,
294+
getRepos
295+
}

0 commit comments

Comments
 (0)