add auto-refresh / split Vuex store into modules
This commit is contained in:
parent
57632ffe8c
commit
386a3ab502
|
@ -3,6 +3,7 @@
|
||||||
namespace OCA\Notes\Controller;
|
namespace OCA\Notes\Controller;
|
||||||
|
|
||||||
use OCA\Notes\Service\NotesService;
|
use OCA\Notes\Service\NotesService;
|
||||||
|
use OCA\Notes\Service\MetaService;
|
||||||
use OCA\Notes\Service\SettingsService;
|
use OCA\Notes\Service\SettingsService;
|
||||||
|
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
|
@ -16,6 +17,8 @@ class NotesController extends Controller {
|
||||||
|
|
||||||
/** @var NotesService */
|
/** @var NotesService */
|
||||||
private $notesService;
|
private $notesService;
|
||||||
|
/** @var MetaService */
|
||||||
|
private $metaService;
|
||||||
/** @var SettingsService */
|
/** @var SettingsService */
|
||||||
private $settingsService;
|
private $settingsService;
|
||||||
/** @var Helper */
|
/** @var Helper */
|
||||||
|
@ -31,6 +34,7 @@ class NotesController extends Controller {
|
||||||
string $AppName,
|
string $AppName,
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
NotesService $notesService,
|
NotesService $notesService,
|
||||||
|
MetaService $metaService,
|
||||||
SettingsService $settingsService,
|
SettingsService $settingsService,
|
||||||
Helper $helper,
|
Helper $helper,
|
||||||
IConfig $settings,
|
IConfig $settings,
|
||||||
|
@ -39,6 +43,7 @@ class NotesController extends Controller {
|
||||||
) {
|
) {
|
||||||
parent::__construct($AppName, $request);
|
parent::__construct($AppName, $request);
|
||||||
$this->notesService = $notesService;
|
$this->notesService = $notesService;
|
||||||
|
$this->metaService = $metaService;
|
||||||
$this->settingsService = $settingsService;
|
$this->settingsService = $settingsService;
|
||||||
$this->helper = $helper;
|
$this->helper = $helper;
|
||||||
$this->settings = $settings;
|
$this->settings = $settings;
|
||||||
|
@ -50,8 +55,9 @@ class NotesController extends Controller {
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*/
|
*/
|
||||||
public function index() : JSONResponse {
|
public function index(int $pruneBefore = 0) : JSONResponse {
|
||||||
return $this->helper->handleErrorResponse(function () {
|
return $this->helper->handleErrorResponse(function () use ($pruneBefore) {
|
||||||
|
$now = new \DateTime(); // this must be before loading notes if there are concurrent changes possible
|
||||||
$settings = $this->settingsService->getAll($this->userId);
|
$settings = $this->settingsService->getAll($this->userId);
|
||||||
|
|
||||||
$errorMessage = null;
|
$errorMessage = null;
|
||||||
|
@ -65,9 +71,15 @@ class NotesController extends Controller {
|
||||||
$categories = null;
|
$categories = null;
|
||||||
try {
|
try {
|
||||||
$data = $this->notesService->getAll($this->userId);
|
$data = $this->notesService->getAll($this->userId);
|
||||||
|
$metas = $this->metaService->updateAll($this->userId, $data['notes']);
|
||||||
$categories = $data['categories'];
|
$categories = $data['categories'];
|
||||||
$notesData = array_map(function ($note) {
|
$notesData = array_map(function ($note) use ($metas, $pruneBefore) {
|
||||||
return $note->getData([ 'content' ]);
|
$lastUpdate = $metas[$note->getId()]->getLastUpdate();
|
||||||
|
if ($pruneBefore && $lastUpdate<$pruneBefore) {
|
||||||
|
return [ 'id' => $note->getId() ];
|
||||||
|
} else {
|
||||||
|
return $note->getData([ 'content' ]);
|
||||||
|
}
|
||||||
}, $data['notes']);
|
}, $data['notes']);
|
||||||
if ($lastViewedNote) {
|
if ($lastViewedNote) {
|
||||||
// check if note exists
|
// check if note exists
|
||||||
|
@ -83,13 +95,18 @@ class NotesController extends Controller {
|
||||||
$errorMessage = $this->l10n->t('The notes folder is not accessible: %s', $e->getMessage());
|
$errorMessage = $this->l10n->t('The notes folder is not accessible: %s', $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
$result = [
|
||||||
'notes' => $notesData,
|
'notes' => $notesData,
|
||||||
'categories' => $categories,
|
'categories' => $categories,
|
||||||
'settings' => $settings,
|
'settings' => $settings,
|
||||||
'lastViewedNote' => $lastViewedNote,
|
'lastViewedNote' => $lastViewedNote,
|
||||||
'errorMessage' => $errorMessage,
|
'errorMessage' => $errorMessage,
|
||||||
];
|
];
|
||||||
|
$etag = md5(json_encode($result));
|
||||||
|
return (new JSONResponse($result))
|
||||||
|
->setLastModified($now)
|
||||||
|
->setETag($etag)
|
||||||
|
;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,13 @@ class MetaService {
|
||||||
$meta->setFileId($note->getId());
|
$meta->setFileId($note->getId());
|
||||||
$meta->setLastUpdate(time());
|
$meta->setLastUpdate(time());
|
||||||
$this->updateIfNeeded($meta, $note, true);
|
$this->updateIfNeeded($meta, $note, true);
|
||||||
$this->metaMapper->insert($meta);
|
try {
|
||||||
|
$this->metaMapper->insert($meta);
|
||||||
|
/* @phan-suppress-next-line PhanUndeclaredClassCatch */
|
||||||
|
} catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException $e) {
|
||||||
|
// It's likely that a concurrent request created this entry, too.
|
||||||
|
// We can ignore this, since the result should be the same.
|
||||||
|
}
|
||||||
return $meta;
|
return $meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
src/App.vue
18
src/App.vue
|
@ -67,7 +67,7 @@ export default {
|
||||||
search: '',
|
search: '',
|
||||||
},
|
},
|
||||||
loading: {
|
loading: {
|
||||||
notes: false,
|
notes: true,
|
||||||
create: false,
|
create: false,
|
||||||
},
|
},
|
||||||
error: false,
|
error: false,
|
||||||
|
@ -79,7 +79,7 @@ export default {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
notes() {
|
notes() {
|
||||||
return store.state.notes
|
return store.state.notes.notes
|
||||||
},
|
},
|
||||||
|
|
||||||
filteredNotes() {
|
filteredNotes() {
|
||||||
|
@ -129,9 +129,12 @@ export default {
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
loadNotes() {
|
loadNotes() {
|
||||||
this.loading.notes = true
|
|
||||||
fetchNotes()
|
fetchNotes()
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
if (data === null) {
|
||||||
|
// nothing changed
|
||||||
|
return
|
||||||
|
}
|
||||||
if (data.notes !== null) {
|
if (data.notes !== null) {
|
||||||
this.error = false
|
this.error = false
|
||||||
this.routeDefault(data.lastViewedNote)
|
this.routeDefault(data.lastViewedNote)
|
||||||
|
@ -140,10 +143,14 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.error = true
|
// only show error state if not loading in background
|
||||||
|
if (this.loading.notes) {
|
||||||
|
this.error = true
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.loading.notes = false
|
this.loading.notes = false
|
||||||
|
setTimeout(this.loadNotes, 25000)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -151,7 +158,8 @@ export default {
|
||||||
if (this.$route.path !== '/') {
|
if (this.$route.path !== '/') {
|
||||||
this.$router.push('/')
|
this.$router.push('/')
|
||||||
}
|
}
|
||||||
store.commit('removeAll')
|
store.commit('removeAllNotes')
|
||||||
|
this.loading.notes = true
|
||||||
this.loadNotes()
|
this.loadNotes()
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -36,23 +36,39 @@ export const setSettings = settings => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchNotes = () => {
|
export const fetchNotes = () => {
|
||||||
|
const lastETag = store.state.sync.etag
|
||||||
|
const lastModified = store.state.sync.lastModified
|
||||||
|
const headers = {}
|
||||||
|
if (lastETag) {
|
||||||
|
headers['If-None-Match'] = lastETag
|
||||||
|
}
|
||||||
return axios
|
return axios
|
||||||
.get(url('/notes'))
|
.get(
|
||||||
|
url('/notes' + (lastModified ? '?pruneBefore=' + lastModified : '')),
|
||||||
|
{ headers }
|
||||||
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
store.commit('setSettings', response.data.settings)
|
store.commit('setSettings', response.data.settings)
|
||||||
store.commit('setCategories', response.data.categories)
|
store.commit('setCategories', response.data.categories)
|
||||||
if (response.data.notes !== null) {
|
if (response.data.notes !== null) {
|
||||||
store.dispatch('addAll', response.data.notes)
|
store.dispatch('updateNotes', response.data.notes)
|
||||||
}
|
}
|
||||||
if (response.data.errorMessage) {
|
if (response.data.errorMessage) {
|
||||||
showError(response.data.errorMessage)
|
showError(response.data.errorMessage)
|
||||||
}
|
}
|
||||||
|
store.commit('setSyncETag', response.headers['etag'])
|
||||||
|
store.commit('setSyncLastModified', response.headers['last-modified'])
|
||||||
return response.data
|
return response.data
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err)
|
if (err.response && err.response.status === 304) {
|
||||||
handleSyncError(t('notes', 'Fetching notes has failed.'))
|
store.commit('setSyncLastModified', err.response.headers['last-modified'])
|
||||||
throw err
|
return null
|
||||||
|
} else {
|
||||||
|
console.error(err)
|
||||||
|
handleSyncError(t('notes', 'Fetching notes has failed.'))
|
||||||
|
throw err
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +79,7 @@ export const fetchNote = noteId => {
|
||||||
const localNote = store.getters.getNote(parseInt(noteId))
|
const localNote = store.getters.getNote(parseInt(noteId))
|
||||||
// only overwrite if there are no unsaved changes
|
// only overwrite if there are no unsaved changes
|
||||||
if (!localNote || !localNote.unsaved) {
|
if (!localNote || !localNote.unsaved) {
|
||||||
store.commit('add', response.data)
|
store.commit('updateNote', response.data)
|
||||||
}
|
}
|
||||||
return response.data
|
return response.data
|
||||||
})
|
})
|
||||||
|
@ -97,7 +113,7 @@ export const createNote = category => {
|
||||||
return axios
|
return axios
|
||||||
.post(url('/notes'), { category: category })
|
.post(url('/notes'), { category: category })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
store.commit('add', response.data)
|
store.commit('updateNote', response.data)
|
||||||
return response.data
|
return response.data
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
@ -122,7 +138,7 @@ function _updateNote(note) {
|
||||||
if (updated.content === note.content) {
|
if (updated.content === note.content) {
|
||||||
note.unsaved = false
|
note.unsaved = false
|
||||||
}
|
}
|
||||||
store.commit('add', note)
|
store.commit('updateNote', note)
|
||||||
return note
|
return note
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
@ -140,7 +156,7 @@ export const undoDeleteNote = (note) => {
|
||||||
return axios
|
return axios
|
||||||
.post(url('/notes/undo'), note)
|
.post(url('/notes/undo'), note)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
store.commit('add', response.data)
|
store.commit('updateNote', response.data)
|
||||||
return response.data
|
return response.data
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
@ -159,13 +175,13 @@ export const deleteNote = noteId => {
|
||||||
return axios
|
return axios
|
||||||
.delete(url('/notes/' + noteId))
|
.delete(url('/notes/' + noteId))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
store.commit('remove', noteId)
|
store.commit('removeNote', noteId)
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
handleSyncError(t('notes', 'Deleting note {id} has failed.', { id: noteId }))
|
handleSyncError(t('notes', 'Deleting note {id} has failed.', { id: noteId }))
|
||||||
// remove note always since we don't know when the error happened
|
// remove note always since we don't know when the error happened
|
||||||
store.commit('remove', noteId)
|
store.commit('removeNote', noteId)
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -211,8 +227,8 @@ export const saveNote = (noteId, manualSave = false) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function _saveNotes() {
|
function _saveNotes() {
|
||||||
const unsavedNotes = Object.values(store.state.unsaved)
|
const unsavedNotes = Object.values(store.state.notes.unsaved)
|
||||||
if (store.state.isSaving || unsavedNotes.length === 0) {
|
if (store.state.app.isSaving || unsavedNotes.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
store.commit('setSaving', true)
|
store.commit('setSaving', true)
|
||||||
|
@ -237,7 +253,7 @@ export const noteExists = (noteId) => {
|
||||||
export const getCategories = (maxLevel, details) => {
|
export const getCategories = (maxLevel, details) => {
|
||||||
const categories = store.getters.getCategories(maxLevel, details)
|
const categories = store.getters.getCategories(maxLevel, details)
|
||||||
if (maxLevel === 0) {
|
if (maxLevel === 0) {
|
||||||
return [...new Set([...categories, ...store.state.categories])]
|
return [...new Set([...categories, ...store.state.notes.categories])]
|
||||||
} else {
|
} else {
|
||||||
return categories
|
return categories
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ export default {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
settings() {
|
settings() {
|
||||||
return store.state.settings
|
return store.state.app.settings
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -108,10 +108,10 @@ export default {
|
||||||
return this.note ? this.note.title : ''
|
return this.note ? this.note.title : ''
|
||||||
},
|
},
|
||||||
isManualSave() {
|
isManualSave() {
|
||||||
return store.state.isManualSave
|
return store.state.app.isManualSave
|
||||||
},
|
},
|
||||||
sidebarOpen() {
|
sidebarOpen() {
|
||||||
return store.state.sidebarOpen
|
return store.state.app.sidebarOpen
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
onUpdateTitle(title) {
|
onUpdateTitle(title) {
|
||||||
const defaultTitle = store.state.documentTitle
|
const defaultTitle = store.state.app.documentTitle
|
||||||
if (title) {
|
if (title) {
|
||||||
document.title = title + ' - ' + defaultTitle
|
document.title = title + ' - ' + defaultTitle
|
||||||
} else {
|
} else {
|
||||||
|
@ -212,7 +212,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
onToggleSidebar() {
|
onToggleSidebar() {
|
||||||
store.commit('setSidebarOpen', !store.state.sidebarOpen)
|
store.commit('setSidebarOpen', !store.state.app.sidebarOpen)
|
||||||
this.actionsOpen = false
|
this.actionsOpen = false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -224,7 +224,7 @@ export default {
|
||||||
unsaved: true,
|
unsaved: true,
|
||||||
autotitle: routeIsNewNote(this.$route),
|
autotitle: routeIsNewNote(this.$route),
|
||||||
}
|
}
|
||||||
store.commit('add', note)
|
store.commit('updateNote', note)
|
||||||
if (this.autosaveTimer === null) {
|
if (this.autosaveTimer === null) {
|
||||||
this.autosaveTimer = setTimeout(() => {
|
this.autosaveTimer = setTimeout(() => {
|
||||||
this.autosaveTimer = null
|
this.autosaveTimer = null
|
||||||
|
|
|
@ -138,7 +138,7 @@ export default {
|
||||||
return [ '', ...getCategories(0, false) ]
|
return [ '', ...getCategories(0, false) ]
|
||||||
},
|
},
|
||||||
sidebarOpen() {
|
sidebarOpen() {
|
||||||
return store.state.sidebarOpen
|
return store.state.app.sidebarOpen
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
166
src/store.js
166
src/store.js
|
@ -1,166 +1,16 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
|
|
||||||
|
import app from './store/app'
|
||||||
|
import notes from './store/notes'
|
||||||
|
import sync from './store/sync'
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
state: {
|
modules: {
|
||||||
settings: {},
|
app,
|
||||||
categories: [],
|
notes,
|
||||||
notes: [],
|
sync,
|
||||||
notesIds: {},
|
|
||||||
unsaved: {},
|
|
||||||
isSaving: false,
|
|
||||||
isManualSave: false,
|
|
||||||
documentTitle: null,
|
|
||||||
sidebarOpen: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
numNotes: (state) => () => {
|
|
||||||
return state.notes.length
|
|
||||||
},
|
|
||||||
|
|
||||||
noteExists: (state) => (id) => {
|
|
||||||
return state.notesIds[id] !== undefined
|
|
||||||
},
|
|
||||||
|
|
||||||
getNote: (state) => (id) => {
|
|
||||||
if (state.notesIds[id] === undefined) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return state.notesIds[id]
|
|
||||||
},
|
|
||||||
|
|
||||||
getCategories: (state) => (maxLevel, details) => {
|
|
||||||
function nthIndexOf(str, pattern, n) {
|
|
||||||
let i = -1
|
|
||||||
while (n-- && i++ < str.length) {
|
|
||||||
i = str.indexOf(pattern, i)
|
|
||||||
if (i < 0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
// get categories from notes
|
|
||||||
const categories = {}
|
|
||||||
for (const note of state.notes) {
|
|
||||||
let cat = note.category
|
|
||||||
if (maxLevel > 0) {
|
|
||||||
const index = nthIndexOf(cat, '/', maxLevel)
|
|
||||||
if (index > 0) {
|
|
||||||
cat = cat.substring(0, index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (categories[cat] === undefined) {
|
|
||||||
categories[cat] = 1
|
|
||||||
} else {
|
|
||||||
categories[cat] += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// get structured result from categories
|
|
||||||
const result = []
|
|
||||||
for (const category in categories) {
|
|
||||||
if (details) {
|
|
||||||
result.push({
|
|
||||||
name: category,
|
|
||||||
count: categories[category],
|
|
||||||
})
|
|
||||||
} else if (category) {
|
|
||||||
result.push(category)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (details) {
|
|
||||||
result.sort((a, b) => a.name.localeCompare(b.name))
|
|
||||||
} else {
|
|
||||||
result.sort()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
mutations: {
|
|
||||||
add(state, updated) {
|
|
||||||
const note = state.notesIds[updated.id]
|
|
||||||
if (note) {
|
|
||||||
// don't update meta-data over full data
|
|
||||||
if (updated.content !== undefined || note.content === undefined) {
|
|
||||||
note.title = updated.title
|
|
||||||
note.modified = updated.modified
|
|
||||||
note.content = updated.content
|
|
||||||
note.favorite = updated.favorite
|
|
||||||
note.category = updated.category
|
|
||||||
Vue.set(note, 'autotitle', updated.autotitle)
|
|
||||||
Vue.set(note, 'unsaved', updated.unsaved)
|
|
||||||
Vue.set(note, 'error', updated.error)
|
|
||||||
Vue.set(note, 'errorMessage', updated.errorMessage)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state.notes.push(updated)
|
|
||||||
Vue.set(state.notesIds, updated.id, updated)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setNoteAttribute(state, params) {
|
|
||||||
const note = state.notesIds[params.noteId]
|
|
||||||
if (note) {
|
|
||||||
Vue.set(note, params.attribute, params.value)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
remove(state, id) {
|
|
||||||
const index = state.notes.findIndex(note => note.id === id)
|
|
||||||
if (index !== -1) {
|
|
||||||
state.notes.splice(index, 1)
|
|
||||||
delete state.notesIds[id]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
removeAll(state) {
|
|
||||||
state.notes = []
|
|
||||||
state.notesIds = {}
|
|
||||||
},
|
|
||||||
|
|
||||||
addUnsaved(state, id) {
|
|
||||||
Vue.set(state.unsaved, id, state.notesIds[id])
|
|
||||||
},
|
|
||||||
|
|
||||||
clearUnsaved(state) {
|
|
||||||
state.unsaved = {}
|
|
||||||
},
|
|
||||||
|
|
||||||
setSettings(state, settings) {
|
|
||||||
state.settings = settings
|
|
||||||
},
|
|
||||||
|
|
||||||
setCategories(state, categories) {
|
|
||||||
state.categories = categories
|
|
||||||
},
|
|
||||||
|
|
||||||
setSaving(state, isSaving) {
|
|
||||||
state.isSaving = isSaving
|
|
||||||
},
|
|
||||||
|
|
||||||
setManualSave(state, isManualSave) {
|
|
||||||
state.isManualSave = isManualSave
|
|
||||||
},
|
|
||||||
|
|
||||||
setDocumentTitle(state, title) {
|
|
||||||
state.documentTitle = title
|
|
||||||
},
|
|
||||||
|
|
||||||
setSidebarOpen(state, open) {
|
|
||||||
state.sidebarOpen = open
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
addAll(context, notes) {
|
|
||||||
for (const note of notes) {
|
|
||||||
context.commit('add', note)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
const state = {
|
||||||
|
settings: {},
|
||||||
|
isSaving: false,
|
||||||
|
isManualSave: false,
|
||||||
|
documentTitle: null,
|
||||||
|
sidebarOpen: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
setSettings(state, settings) {
|
||||||
|
state.settings = settings
|
||||||
|
},
|
||||||
|
|
||||||
|
setSaving(state, isSaving) {
|
||||||
|
state.isSaving = isSaving
|
||||||
|
},
|
||||||
|
|
||||||
|
setManualSave(state, isManualSave) {
|
||||||
|
state.isManualSave = isManualSave
|
||||||
|
},
|
||||||
|
|
||||||
|
setDocumentTitle(state, title) {
|
||||||
|
state.documentTitle = title
|
||||||
|
},
|
||||||
|
|
||||||
|
setSidebarOpen(state, open) {
|
||||||
|
state.sidebarOpen = open
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { state, getters, mutations, actions }
|
|
@ -0,0 +1,151 @@
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
categories: [],
|
||||||
|
notes: [],
|
||||||
|
notesIds: {},
|
||||||
|
unsaved: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
numNotes: (state) => () => {
|
||||||
|
return state.notes.length
|
||||||
|
},
|
||||||
|
|
||||||
|
noteExists: (state) => (id) => {
|
||||||
|
return state.notesIds[id] !== undefined
|
||||||
|
},
|
||||||
|
|
||||||
|
getNote: (state) => (id) => {
|
||||||
|
if (state.notesIds[id] === undefined) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return state.notesIds[id]
|
||||||
|
},
|
||||||
|
|
||||||
|
getCategories: (state) => (maxLevel, details) => {
|
||||||
|
function nthIndexOf(str, pattern, n) {
|
||||||
|
let i = -1
|
||||||
|
while (n-- && i++ < str.length) {
|
||||||
|
i = str.indexOf(pattern, i)
|
||||||
|
if (i < 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// get categories from notes
|
||||||
|
const categories = {}
|
||||||
|
for (const note of state.notes) {
|
||||||
|
let cat = note.category
|
||||||
|
if (maxLevel > 0) {
|
||||||
|
const index = nthIndexOf(cat, '/', maxLevel)
|
||||||
|
if (index > 0) {
|
||||||
|
cat = cat.substring(0, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (categories[cat] === undefined) {
|
||||||
|
categories[cat] = 1
|
||||||
|
} else {
|
||||||
|
categories[cat] += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// get structured result from categories
|
||||||
|
const result = []
|
||||||
|
for (const category in categories) {
|
||||||
|
if (details) {
|
||||||
|
result.push({
|
||||||
|
name: category,
|
||||||
|
count: categories[category],
|
||||||
|
})
|
||||||
|
} else if (category) {
|
||||||
|
result.push(category)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (details) {
|
||||||
|
result.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
} else {
|
||||||
|
result.sort()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
updateNote(state, updated) {
|
||||||
|
const note = state.notesIds[updated.id]
|
||||||
|
if (note) {
|
||||||
|
// don't update meta-data over full data
|
||||||
|
if (updated.content !== undefined || note.content === undefined) {
|
||||||
|
note.title = updated.title
|
||||||
|
note.modified = updated.modified
|
||||||
|
note.content = updated.content
|
||||||
|
note.favorite = updated.favorite
|
||||||
|
note.category = updated.category
|
||||||
|
Vue.set(note, 'autotitle', updated.autotitle)
|
||||||
|
Vue.set(note, 'unsaved', updated.unsaved)
|
||||||
|
Vue.set(note, 'error', updated.error)
|
||||||
|
Vue.set(note, 'errorMessage', updated.errorMessage)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.notes.push(updated)
|
||||||
|
Vue.set(state.notesIds, updated.id, updated)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setNoteAttribute(state, params) {
|
||||||
|
const note = state.notesIds[params.noteId]
|
||||||
|
if (note) {
|
||||||
|
Vue.set(note, params.attribute, params.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
removeNote(state, id) {
|
||||||
|
const index = state.notes.findIndex(note => note.id === id)
|
||||||
|
if (index !== -1) {
|
||||||
|
state.notes.splice(index, 1)
|
||||||
|
delete state.notesIds[id]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
removeAllNotes(state) {
|
||||||
|
state.notes = []
|
||||||
|
state.notesIds = {}
|
||||||
|
},
|
||||||
|
|
||||||
|
addUnsaved(state, id) {
|
||||||
|
Vue.set(state.unsaved, id, state.notesIds[id])
|
||||||
|
},
|
||||||
|
|
||||||
|
clearUnsaved(state) {
|
||||||
|
state.unsaved = {}
|
||||||
|
},
|
||||||
|
|
||||||
|
setCategories(state, categories) {
|
||||||
|
state.categories = categories
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
updateNotes(context, notes) {
|
||||||
|
const noteIds = {}
|
||||||
|
// add/update new notes
|
||||||
|
for (const note of notes) {
|
||||||
|
noteIds[note.id] = true
|
||||||
|
// TODO check for parallel (local) changes!
|
||||||
|
// only update, if note has changes (see API "purgeBefore")
|
||||||
|
if (note.title !== undefined) {
|
||||||
|
context.commit('updateNote', note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// remove deleted notes
|
||||||
|
context.state.notes.forEach(note => {
|
||||||
|
if (noteIds[note.id] === undefined) {
|
||||||
|
context.commit('removeNote', note.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { state, getters, mutations, actions }
|
|
@ -0,0 +1,33 @@
|
||||||
|
const state = {
|
||||||
|
etag: null,
|
||||||
|
lastModified: 0,
|
||||||
|
active: false,
|
||||||
|
// TODO add list of notes with changes during sync
|
||||||
|
}
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
setSyncETag(state, etag) {
|
||||||
|
if (etag) {
|
||||||
|
state.etag = etag
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setSyncLastModified(state, strLastModified) {
|
||||||
|
const lastModified = Date.parse(strLastModified)
|
||||||
|
if (lastModified) {
|
||||||
|
state.lastModified = lastModified / 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setSyncActive(state, active) {
|
||||||
|
state.active = active
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { state, getters, mutations, actions }
|
Loading…
Reference in New Issue