import Vue from 'vue'; import yaml from 'js-yaml'; import utils from '../services/utils'; import defaultWorkspaces from '../data/defaults/defaultWorkspaces'; import defaultSettings from '../data/defaults/defaultSettings.yml'; import defaultLocalSettings from '../data/defaults/defaultLocalSettings'; import defaultLayoutSettings from '../data/defaults/defaultLayoutSettings'; import plainHtmlTemplate from '../data/templates/plainHtmlTemplate.html'; import styledHtmlTemplate from '../data/templates/styledHtmlTemplate.html'; import styledHtmlWithTocTemplate from '../data/templates/styledHtmlWithTocTemplate.html'; import jekyllSiteTemplate from '../data/templates/jekyllSiteTemplate.html'; import constants from '../data/constants'; import features from '../data/features'; import badgeSvc from '../services/badgeSvc'; const itemTemplate = (id, data = {}) => ({ id, type: 'data', data, hash: 0, }); const empty = (id) => { switch (id) { case 'workspaces': return itemTemplate(id, defaultWorkspaces()); case 'settings': return itemTemplate(id, '\n'); case 'localSettings': return itemTemplate(id, defaultLocalSettings()); case 'layoutSettings': return itemTemplate(id, defaultLayoutSettings()); default: return itemTemplate(id); } }; // Item IDs that will be stored in the localStorage const localStorageIdSet = new Set(constants.localStorageDataIds); // Getter/setter/patcher factories const getter = id => (state) => { const itemsById = localStorageIdSet.has(id) ? state.lsItemsById : state.itemsById; if (itemsById[id]) { return itemsById[id].data; } return empty(id).data; }; const setter = id => ({ commit }, data) => commit('setItem', itemTemplate(id, data)); const patcher = id => ({ state, commit }, data) => { const itemsById = localStorageIdSet.has(id) ? state.lsItemsById : state.itemsById; const item = Object.assign(empty(id), itemsById[id]); commit('setItem', { ...empty(id), data: typeof data === 'object' ? { ...item.data, ...data, } : data, }); }; // For layoutSettings const toggleLayoutSetting = (name, value, featureId, getters, dispatch) => { const currentValue = getters.layoutSettings[name]; const patch = { [name]: value === undefined ? !currentValue : !!value, }; if (patch[name] !== currentValue) { dispatch('patchLayoutSettings', patch); badgeSvc.addBadge(featureId); } }; const layoutSettingsToggler = (propertyName, featureId) => ({ getters, dispatch }, value) => toggleLayoutSetting(propertyName, value, featureId, getters, dispatch); const notEnoughSpace = (layoutConstants, showGutter) => document.body.clientWidth < layoutConstants.editorMinWidth + layoutConstants.explorerWidth + layoutConstants.sideBarWidth + layoutConstants.buttonBarWidth + (showGutter ? layoutConstants.gutterWidth : 0); // For templates const makeAdditionalTemplate = (name, value, helpers = '\n') => ({ name, value, helpers, isAdditional: true, }); const defaultTemplates = { plainText: makeAdditionalTemplate('Plain text', '{{{files.0.content.text}}}'), plainHtml: makeAdditionalTemplate('Plain HTML', plainHtmlTemplate), styledHtml: makeAdditionalTemplate('Styled HTML', styledHtmlTemplate), styledHtmlWithToc: makeAdditionalTemplate('Styled HTML with TOC', styledHtmlWithTocTemplate), jekyllSite: makeAdditionalTemplate('Jekyll site', jekyllSiteTemplate), }; // For tokens const tokenAdder = providerId => ({ getters, dispatch }, token) => { dispatch('patchTokensByType', { [providerId]: { ...getters[`${providerId}TokensBySub`], [token.sub]: token, }, }); }; export default { namespaced: true, state: { // Data items stored in the DB itemsById: {}, // Data items stored in the localStorage lsItemsById: {}, }, mutations: { setItem: ({ itemsById, lsItemsById }, value) => { // Create an empty item and override its data field const emptyItem = empty(value.id); const data = typeof value.data === 'object' ? Object.assign(emptyItem.data, value.data) : value.data; // Make item with hash const item = utils.addItemHash({ ...emptyItem, data, }); // Store item in itemsById or lsItemsById if its stored in the localStorage Vue.set(localStorageIdSet.has(item.id) ? lsItemsById : itemsById, item.id, item); }, deleteItem({ itemsById }, id) { // Only used by localDbSvc to clean itemsById from object moved to localStorage Vue.delete(itemsById, id); }, }, getters: { serverConf: getter('serverConf'), workspaces: getter('workspaces'), // Not to be used, prefer workspace/workspacesById settings: getter('settings'), computedSettings: (state, { settings }) => { const customSettings = yaml.safeLoad(settings); const parsedSettings = yaml.safeLoad(defaultSettings); const override = (obj, opt) => { const objType = Object.prototype.toString.call(obj); const optType = Object.prototype.toString.call(opt); if (objType !== optType) { return obj; } else if (objType !== '[object Object]') { return opt; } Object.keys(obj).forEach((key) => { if (key === 'shortcuts') { obj[key] = Object.assign(obj[key], opt[key]); } else { obj[key] = override(obj[key], opt[key]); } }); return obj; }; return override(parsedSettings, customSettings); }, localSettings: getter('localSettings'), layoutSettings: getter('layoutSettings'), templatesById: getter('templates'), allTemplatesById: (state, { templatesById }) => ({ ...templatesById, ...defaultTemplates, }), lastCreated: getter('lastCreated'), lastOpened: getter('lastOpened'), lastOpenedIds: (state, { lastOpened }, rootState) => { const result = { ...lastOpened, }; const currentFileId = rootState.file.currentId; if (currentFileId && !result[currentFileId]) { result[currentFileId] = Date.now(); } return Object.keys(result) .filter(id => rootState.file.itemsById[id]) .sort((id1, id2) => result[id2] - result[id1]) .slice(0, 20); }, syncDataById: getter('syncData'), syncDataByItemId: (state, { syncDataById }, rootState, rootGetters) => { const result = {}; if (rootGetters['workspace/currentWorkspaceIsGit']) { Object.entries(rootGetters.gitPathsByItemId).forEach(([id, path]) => { const syncDataEntry = syncDataById[path]; if (syncDataEntry) { result[id] = syncDataEntry; } }); } else { Object.entries(syncDataById).forEach(([, syncDataEntry]) => { result[syncDataEntry.itemId] = syncDataEntry; }); } return result; }, dataSyncDataById: getter('dataSyncData'), tokensByType: getter('tokens'), googleTokensBySub: (state, { tokensByType }) => tokensByType.google || {}, couchdbTokensBySub: (state, { tokensByType }) => tokensByType.couchdb || {}, dropboxTokensBySub: (state, { tokensByType }) => tokensByType.dropbox || {}, githubTokensBySub: (state, { tokensByType }) => tokensByType.github || {}, giteeTokensBySub: (state, { tokensByType }) => tokensByType.gitee || {}, gitlabTokensBySub: (state, { tokensByType }) => tokensByType.gitlab || {}, giteaTokensBySub: (state, { tokensByType }) => tokensByType.gitea || {}, wordpressTokensBySub: (state, { tokensByType }) => tokensByType.wordpress || {}, zendeskTokensBySub: (state, { tokensByType }) => tokensByType.zendesk || {}, smmsTokensBySub: (state, { tokensByType }) => tokensByType.smms || {}, customTokensBySub: (state, { tokensByType }) => tokensByType.custom || {}, badgeCreations: getter('badgeCreations'), badgeTree: (state, { badgeCreations }) => features .map(feature => feature.toBadge(badgeCreations)), allBadges: (state, { badgeTree }) => { const result = []; const processBadgeNodes = nodes => nodes.forEach((node) => { result.push(node); if (node.children) { processBadgeNodes(node.children); } }); processBadgeNodes(badgeTree); return result; }, }, actions: { setServerConf: setter('serverConf'), setSettings: setter('settings'), switchThemeSetting: ({ commit, getters }) => { const customSettingStr = getters.settings; let { colorTheme } = getters.computedSettings; if (!colorTheme || colorTheme === 'light') { colorTheme = 'dark'; } else { colorTheme = 'light'; } const themeStr = `colorTheme: ${colorTheme}`; let settingsStr = (customSettingStr && customSettingStr.trim()) || '# 增加您的自定义配置覆盖默认配置'; settingsStr = settingsStr.indexOf('colorTheme:') > -1 ? settingsStr.replace(/.*colorTheme:.*/, themeStr) : `${settingsStr}\n${themeStr}`; commit('setItem', itemTemplate('settings', settingsStr)); badgeSvc.addBadge('switchTheme'); }, patchLocalSettings: patcher('localSettings'), patchLayoutSettings: patcher('layoutSettings'), toggleNavigationBar: layoutSettingsToggler('showNavigationBar', 'toggleNavigationBar'), toggleEditor: layoutSettingsToggler('showEditor', 'toggleEditor'), toggleSidePreview: layoutSettingsToggler('showSidePreview', 'toggleSidePreview'), toggleStatusBar: layoutSettingsToggler('showStatusBar', 'toggleStatusBar'), toggleScrollSync: layoutSettingsToggler('scrollSync', 'toggleScrollSync'), toggleFocusMode: layoutSettingsToggler('focusMode', 'toggleFocusMode'), toggleSideBar: ({ getters, dispatch, rootGetters }, value) => { // Reset side bar dispatch('setSideBarPanel'); // Toggle it toggleLayoutSetting('showSideBar', value, 'toggleSideBar', getters, dispatch); // Close explorer if not enough space if (getters.layoutSettings.showSideBar && notEnoughSpace(rootGetters['layout/constants'], rootGetters['discussion/currentDiscussion']) ) { dispatch('patchLayoutSettings', { showExplorer: false, }); } }, toggleExplorer: ({ getters, dispatch, rootGetters }, value) => { // Toggle explorer toggleLayoutSetting('showExplorer', value, 'toggleExplorer', getters, dispatch); // Close side bar if not enough space if (getters.layoutSettings.showExplorer && notEnoughSpace(rootGetters['layout/constants'], rootGetters['discussion/currentDiscussion']) ) { dispatch('patchLayoutSettings', { showSideBar: false, }); } }, setSideBarPanel: ({ dispatch }, value) => dispatch('patchLayoutSettings', { sideBarPanel: value === undefined ? 'menu' : value, }), setTemplatesById: ({ commit }, templatesById) => { const templatesToCommit = { ...templatesById, }; // We don't store additional templates Object.keys(defaultTemplates).forEach((id) => { delete templatesToCommit[id]; }); commit('setItem', itemTemplate('templates', templatesToCommit)); }, setLastCreated: setter('lastCreated'), setLastOpenedId: ({ getters, commit, rootState }, fileId) => { const lastOpened = { ...getters.lastOpened }; lastOpened[fileId] = Date.now(); // Remove entries that don't exist anymore const cleanedLastOpened = {}; Object.entries(lastOpened).forEach(([id, value]) => { if (rootState.file.itemsById[id]) { cleanedLastOpened[id] = value; } }); commit('setItem', itemTemplate('lastOpened', cleanedLastOpened)); }, setSyncDataById: setter('syncData'), patchSyncDataById: patcher('syncData'), patchDataSyncDataById: patcher('dataSyncData'), patchTokensByType: patcher('tokens'), addGoogleToken: tokenAdder('google'), addCouchdbToken: tokenAdder('couchdb'), addDropboxToken: tokenAdder('dropbox'), addGithubToken: tokenAdder('github'), addGiteeToken: tokenAdder('gitee'), addGitlabToken: tokenAdder('gitlab'), addGiteaToken: tokenAdder('gitea'), addWordpressToken: tokenAdder('wordpress'), addZendeskToken: tokenAdder('zendesk'), patchBadgeCreations: patcher('badgeCreations'), addSmmsToken: tokenAdder('smms'), addCustomToken: tokenAdder('custom'), }, };