Stackedit/src/services/workspaceSvc.js

268 lines
7.6 KiB
JavaScript
Raw Normal View History

2018-05-04 18:07:28 +00:00
import store from '../store';
import utils from './utils';
2018-07-17 19:58:40 +00:00
import constants from '../data/constants';
2018-05-04 18:07:28 +00:00
const forbiddenFolderNameMatcher = /^\.stackedit-data$|^\.stackedit-trash$|\.md$|\.sync$|\.publish$/;
export default {
2018-05-04 18:07:28 +00:00
/**
* Create a file in the store with the specified fields.
*/
2018-05-13 13:27:33 +00:00
async createFile({
2018-05-06 00:46:33 +00:00
name,
parentId,
text,
properties,
discussions,
comments,
} = {}, background = false) {
2018-05-04 18:07:28 +00:00
const id = utils.uid();
2018-06-07 23:56:11 +00:00
const item = {
2018-05-04 18:07:28 +00:00
id,
2018-05-06 00:46:33 +00:00
name: utils.sanitizeName(name),
parentId: parentId || null,
2018-05-04 18:07:28 +00:00
};
const content = {
id: `${id}/content`,
2018-05-06 00:46:33 +00:00
text: utils.sanitizeText(text || store.getters['data/computedSettings'].newFileContent),
properties: utils
.sanitizeText(properties || store.getters['data/computedSettings'].newFileProperties),
discussions: discussions || {},
comments: comments || {},
2018-05-04 18:07:28 +00:00
};
const workspaceUniquePaths = store.getters['workspace/currentWorkspaceHasUniquePaths'];
2018-05-13 13:27:33 +00:00
// Show warning dialogs
if (!background) {
// If name is being stripped
2018-07-17 19:58:40 +00:00
if (item.name !== constants.defaultName && item.name !== name) {
2018-06-07 23:56:11 +00:00
await store.dispatch('modal/open', {
type: 'stripName',
item,
});
2018-05-13 13:27:33 +00:00
}
// Check if there is already a file with that path
if (workspaceUniquePaths) {
2018-06-21 19:16:33 +00:00
const parentPath = store.getters.pathsByItemId[item.parentId] || '';
2018-06-07 23:56:11 +00:00
const path = parentPath + item.name;
2018-06-21 19:16:33 +00:00
if (store.getters.itemsByPath[path]) {
2018-06-07 23:56:11 +00:00
await store.dispatch('modal/open', {
type: 'pathConflict',
item,
});
2018-05-13 13:27:33 +00:00
}
}
}
// Save file and content in the store
store.commit('content/setItem', content);
2018-06-07 23:56:11 +00:00
store.commit('file/setItem', item);
2018-05-04 18:07:28 +00:00
if (workspaceUniquePaths) {
2018-05-13 13:27:33 +00:00
this.makePathUnique(id);
2018-05-04 18:07:28 +00:00
}
2018-05-13 13:27:33 +00:00
// Return the new file item
2018-06-21 19:16:33 +00:00
return store.state.file.itemsById[id];
2018-05-04 18:07:28 +00:00
},
/**
* Make sanity checks and then create/update the folder/file in the store.
*/
2018-05-13 13:27:33 +00:00
async storeItem(item) {
2018-05-04 18:07:28 +00:00
const id = item.id || utils.uid();
const sanitizedName = utils.sanitizeName(item.name);
if (item.type === 'folder' && forbiddenFolderNameMatcher.exec(sanitizedName)) {
2018-06-07 23:56:11 +00:00
await store.dispatch('modal/open', {
type: 'unauthorizedName',
item,
});
2018-05-04 18:07:28 +00:00
throw new Error('Unauthorized name.');
}
// Show warning dialogs
2018-05-13 13:27:33 +00:00
// If name has been stripped
2018-07-17 19:58:40 +00:00
if (sanitizedName !== constants.defaultName && sanitizedName !== item.name) {
2018-06-07 23:56:11 +00:00
await store.dispatch('modal/open', {
type: 'stripName',
item,
});
2018-05-13 13:27:33 +00:00
}
2018-06-21 19:16:33 +00:00
2018-05-13 13:27:33 +00:00
// Check if there is a path conflict
if (store.getters['workspace/currentWorkspaceHasUniquePaths']) {
2018-06-21 19:16:33 +00:00
const parentPath = store.getters.pathsByItemId[item.parentId] || '';
2018-05-13 13:27:33 +00:00
const path = parentPath + sanitizedName;
2018-06-21 19:16:33 +00:00
const items = store.getters.itemsByPath[path] || [];
if (items.some(itemWithSamePath => itemWithSamePath.id !== id)) {
2018-06-07 23:56:11 +00:00
await store.dispatch('modal/open', {
type: 'pathConflict',
item,
});
2018-05-04 18:07:28 +00:00
}
}
2018-05-13 13:27:33 +00:00
return this.setOrPatchItem({
...item,
2018-05-04 18:07:28 +00:00
id,
});
2018-05-13 13:27:33 +00:00
},
/**
* Create/update the folder/file in the store and make sure its path is unique.
*/
setOrPatchItem(patch) {
const item = {
2018-06-21 19:16:33 +00:00
...store.getters.allItemsById[patch.id] || patch,
2018-05-13 13:27:33 +00:00
};
if (!item.id) {
return null;
}
if (patch.parentId !== undefined) {
item.parentId = patch.parentId || null;
}
if (patch.name) {
const sanitizedName = utils.sanitizeName(patch.name);
if (item.type !== 'folder' || !forbiddenFolderNameMatcher.exec(sanitizedName)) {
item.name = sanitizedName;
}
}
// Save item in the store
store.commit(`${item.type}/setItem`, item);
2018-05-04 18:07:28 +00:00
// Ensure path uniqueness
if (store.getters['workspace/currentWorkspaceHasUniquePaths']) {
2018-05-13 13:27:33 +00:00
this.makePathUnique(item.id);
2018-05-04 18:07:28 +00:00
}
2018-05-13 13:27:33 +00:00
2018-06-21 19:16:33 +00:00
return store.getters.allItemsById[item.id];
2018-05-04 18:07:28 +00:00
},
/**
* Delete a file in the store and all its related items.
*/
deleteFile(fileId) {
// Delete the file
store.commit('file/deleteItem', fileId);
// Delete the content
store.commit('content/deleteItem', `${fileId}/content`);
// Delete the syncedContent
store.commit('syncedContent/deleteItem', `${fileId}/syncedContent`);
// Delete the contentState
store.commit('contentState/deleteItem', `${fileId}/contentState`);
// Delete sync locations
(store.getters['syncLocation/groupedByFileId'][fileId] || [])
.forEach(item => store.commit('syncLocation/deleteItem', item.id));
// Delete publish locations
(store.getters['publishLocation/groupedByFileId'][fileId] || [])
.forEach(item => store.commit('publishLocation/deleteItem', item.id));
},
/**
2018-06-21 19:16:33 +00:00
* Ensure two files/folders don't have the same path if the workspace doesn't allow it.
2018-05-04 18:07:28 +00:00
*/
2018-06-21 19:16:33 +00:00
ensureUniquePaths(idsToKeep = {}) {
if (store.getters['workspace/currentWorkspaceHasUniquePaths']) {
2018-06-21 19:16:33 +00:00
if (Object.keys(store.getters.pathsByItemId)
.some(id => !idsToKeep[id] && this.makePathUnique(id))
) {
// Just changed one item path, restart
this.ensureUniquePaths(idsToKeep);
2018-05-04 18:07:28 +00:00
}
}
},
/**
* Return false if the file/folder path is unique.
* Add a prefix to its name and return true otherwise.
*/
makePathUnique(id) {
2018-06-21 19:16:33 +00:00
const { itemsByPath, allItemsById, pathsByItemId } = store.getters;
const item = allItemsById[id];
2018-05-04 18:07:28 +00:00
if (!item) {
return false;
}
2018-06-21 19:16:33 +00:00
let path = pathsByItemId[id];
if (itemsByPath[path].length === 1) {
2018-05-04 18:07:28 +00:00
return false;
}
const isFolder = item.type === 'folder';
if (isFolder) {
// Remove trailing slash
path = path.slice(0, -1);
}
for (let suffix = 1; ; suffix += 1) {
2018-06-21 19:16:33 +00:00
let pathWithSuffix = `${path}.${suffix}`;
2018-05-04 18:07:28 +00:00
if (isFolder) {
2018-06-21 19:16:33 +00:00
pathWithSuffix += '/';
2018-05-04 18:07:28 +00:00
}
2018-06-21 19:16:33 +00:00
if (!itemsByPath[pathWithSuffix]) {
2018-05-04 18:07:28 +00:00
store.commit(`${item.type}/patchItem`, {
id: item.id,
name: `${item.name}.${suffix}`,
});
return true;
}
}
},
addSyncLocation(location) {
store.commit('syncLocation/setItem', {
...location,
id: utils.uid(),
});
// Sanitize the workspace
this.ensureUniqueLocations();
},
addPublishLocation(location) {
store.commit('publishLocation/setItem', {
...location,
id: utils.uid(),
});
// Sanitize the workspace
this.ensureUniqueLocations();
},
/**
* Ensure two sync/publish locations of the same file don't have the same hash.
*/
ensureUniqueLocations(idsToKeep = {}) {
['syncLocation', 'publishLocation'].forEach((type) => {
store.getters[`${type}/items`].forEach((item) => {
if (!idsToKeep[item.id]
&& store.getters[`${type}/groupedByFileIdAndHash`][item.fileId][item.hash].length > 1
) {
store.commit(`${item.type}/deleteItem`, item.id);
}
});
});
},
/**
* Drop the database and clean the localStorage for the specified workspaceId.
*/
async removeWorkspace(id) {
// Remove from the store first as workspace tabs will reload.
// Workspace deletion will be persisted as soon as possible
// by the store.getters['data/workspaces'] watcher in localDbSvc.
store.dispatch('workspace/removeWorkspace', id);
// Drop the database
await new Promise((resolve) => {
const dbName = utils.getDbName(id);
const request = indexedDB.deleteDatabase(dbName);
request.onerror = resolve; // Ignore errors
request.onsuccess = resolve;
});
// Clean the local storage
localStorage.removeItem(`${id}/lastSyncActivity`);
localStorage.removeItem(`${id}/lastWindowFocus`);
},
2018-05-04 18:07:28 +00:00
};