-
-
You have to link an account to start publishing files.
-
{{currentFileName}} is already published.
@@ -20,6 +17,9 @@
Manage publication locations for {{currentFileName}}.
+
+
You have to link an account to start publishing files.
+
@@ -181,8 +181,13 @@ export default {
return tokensToArray(store.getters['data/zendeskTokensBySub']);
},
noToken() {
- return Object.values(store.getters['data/tokensByType'])
- .every(tokens => !Object.keys(tokens).length);
+ return !this.bloggerTokens.length
+ && !this.dropboxTokens.length
+ && !this.githubTokens.length
+ && !this.gitlabTokens.length
+ && !this.googleDriveTokens.length
+ && !this.wordpressTokens.length
+ && !this.zendeskTokens.length;
},
},
methods: {
diff --git a/src/components/menus/SyncMenu.vue b/src/components/menus/SyncMenu.vue
index 6c5451e4..e76e9889 100644
--- a/src/components/menus/SyncMenu.vue
+++ b/src/components/menus/SyncMenu.vue
@@ -4,9 +4,6 @@
{{currentFileName}} can't be synced as it's a temporary file.
-
-
You have to link an account to start syncing files.
-
{{currentFileName}} is already synchronized.
@@ -20,6 +17,9 @@
Manage synchronized locations for {{currentFileName}}.
+
+
You have to link an account to start syncing files.
+
diff --git a/src/components/menus/WorkspaceBackupMenu.vue b/src/components/menus/WorkspaceBackupMenu.vue
index 309f107c..d5281fc0 100644
--- a/src/components/menus/WorkspaceBackupMenu.vue
+++ b/src/components/menus/WorkspaceBackupMenu.vue
@@ -17,15 +17,19 @@
@@ -76,19 +86,24 @@ export default {
.badge-entry {
font-size: 0.8em;
- margin: 0.75rem 0 0;
- }
-
- svg {
- width: 1.67em;
- height: 1.67em;
- margin-right: 0.25em;
- opacity: 0.33;
- flex: none;
+ margin: 0.75rem 0;
}
}
-.badge-entry--earned svg {
+.badge-entry__icon {
+ width: 1.67em;
+ height: 1.67em;
+ margin-right: 0.25em;
+ opacity: 0.3;
+ flex: none;
+}
+
+.badge-entry__icon--some-earned {
+ opacity: 0.5;
+ color: goldenrod;
+}
+
+.badge-entry__icon--earned {
opacity: 1;
color: goldenrod;
}
@@ -100,10 +115,10 @@ export default {
.badge-entry__name {
font-size: 1.2em;
font-weight: bold;
- opacity: 0.5;
+ opacity: 0.4;
+}
- .badge-entry--earned & {
- opacity: 1;
- }
+.badge-entry__name--earned {
+ opacity: 1;
}
diff --git a/src/components/modals/WorkspaceManagementModal.vue b/src/components/modals/WorkspaceManagementModal.vue
index 52120d58..3e9b49ac 100644
--- a/src/components/modals/WorkspaceManagementModal.vue
+++ b/src/components/modals/WorkspaceManagementModal.vue
@@ -4,7 +4,7 @@
- The following workspaces are locally available:
+ The following workspaces are accessible:
+
+
+ available offline
+
+
-
- ProTip: Workspaces are accessible offline, try it!
-
@@ -61,11 +63,13 @@
@@ -208,4 +221,13 @@ $small-button-size: 22px;
background-color: rgba(0, 0, 0, 0.1);
}
}
+
+.workspace-entry__offline {
+ font-size: 0.8rem;
+ line-height: 1;
+ padding: 0.15em 0.35em;
+ border-radius: 3px;
+ color: #fff;
+ background-color: darken($error-color, 10);
+}
diff --git a/src/data/constants.js b/src/data/constants.js
index 187c76c6..fbf94c92 100644
--- a/src/data/constants.js
+++ b/src/data/constants.js
@@ -19,7 +19,8 @@ export default {
'settings',
'layoutSettings',
'tokens',
- 'badges',
+ 'badgeCreations',
+ 'serverConf',
],
textMaxLength: 250000,
defaultName: 'Untitled',
diff --git a/src/data/features.js b/src/data/features.js
index e478fd29..3457ef42 100644
--- a/src/data/features.js
+++ b/src/data/features.js
@@ -1,13 +1,3 @@
-class Badge {
- constructor(featureId, name, description, children, isEarned) {
- this.featureId = featureId;
- this.name = name;
- this.description = description;
- this.children = children;
- this.isEarned = isEarned;
- }
-}
-
class Feature {
constructor(id, badgeName, description, children = null) {
this.id = id;
@@ -16,13 +6,20 @@ class Feature {
this.children = children;
}
- toBadge(earnings) {
+ toBadge(badgeCreations) {
const children = this.children
- ? this.children.map(child => child.toBadge(earnings))
+ ? this.children.map(child => child.toBadge(badgeCreations))
: null;
- return new Badge(this.id, this.badgeName, this.description, children, children
- ? children.every(child => child.isEarned)
- : !!earnings[this.id]);
+ return {
+ featureId: this.id,
+ name: this.badgeName,
+ description: this.description,
+ children,
+ isEarned: children
+ ? children.every(child => child.isEarned)
+ : !!badgeCreations[this.id],
+ hasSomeEarned: children && children.some(child => child.isEarned),
+ };
}
}
@@ -42,6 +39,16 @@ export default [
'Renamer',
'Use the name field in the navigation bar to rename the current file.',
),
+ new Feature(
+ 'toggleExplorer',
+ 'Explorer toggler',
+ 'Use the navigation bar to toggle the explorer.',
+ ),
+ new Feature(
+ 'toggleSideBar',
+ 'Side bar toggler',
+ 'Use the navigation bar to toggle the side bar.',
+ ),
],
),
new Feature(
@@ -65,9 +72,14 @@ export default [
'Use the file explorer to create a new folder in your workspace.',
),
new Feature(
- 'moveFiles',
+ 'moveFile',
'File mover',
- 'Drag files in the file explorer to move them around.',
+ 'Drag a file in the file explorer to move it in another folder.',
+ ),
+ new Feature(
+ 'moveFolder',
+ 'Folder mover',
+ 'Drag a folder in the file explorer to move it in another folder.',
),
new Feature(
'renameFile',
@@ -80,15 +92,57 @@ export default [
'Use the file explorer to rename a folder in your workspace.',
),
new Feature(
- 'removeFiles',
+ 'removeFile',
'File remover',
- 'Use the file explorer to remove files in your workspace.',
+ 'Use the file explorer to remove a file in your workspace.',
+ ),
+ new Feature(
+ 'removeFolder',
+ 'Folder remover',
+ 'Use the file explorer to remove a folder in your workspace.',
+ ),
+ ],
+ ),
+ new Feature(
+ 'buttonBar',
+ 'Button bar expert',
+ 'Use the button bar to customize the editor layout and to toggle features.',
+ [
+ new Feature(
+ 'toggleNavigationBar',
+ 'Navigation bar toggler',
+ 'Use the button bar to toggle the navigation bar.',
+ ),
+ new Feature(
+ 'toggleSidePreview',
+ 'Side preview toggler',
+ 'Use the button bar to toggle the side preview.',
+ ),
+ new Feature(
+ 'toggleEditor',
+ 'Editor toggler',
+ 'Use the button bar to toggle the editor.',
+ ),
+ new Feature(
+ 'toggleFocusMode',
+ 'Focused',
+ 'Use the button bar to toggle the focus mode. This mode keeps the caret vertically centered while typing.',
+ ),
+ new Feature(
+ 'toggleScrollSync',
+ 'Scroll sync toggler',
+ 'Use the button bar to toggle the scroll sync feature. This feature links the editor and the preview scrollbars.',
+ ),
+ new Feature(
+ 'toggleStatusBar',
+ 'Status bar toggler',
+ 'Use the button bar to toggle the status bar.',
),
],
),
new Feature(
'signIn',
- 'Logged in',
+ 'Signed in',
'Sign in with Google, sync your main workspace and unlock functionalities.',
[
new Feature(
@@ -143,7 +197,7 @@ export default [
new Feature(
'manageAccounts',
'Account manager',
- 'Link all kinds of external accounts and use the "User accounts" dialog to manage them.',
+ 'Link all kinds of external accounts and use the "Accounts" dialog to manage them.',
[
new Feature(
'addBloggerAccount',
@@ -188,7 +242,7 @@ export default [
new Feature(
'removeAccount',
'Revoker',
- 'Use the "User accounts" dialog to remove access to an external account.',
+ 'Use the "Accounts" dialog to remove access to an external account.',
),
],
),
diff --git a/src/data/welcomeFile.md b/src/data/welcomeFile.md
index 1a2b147e..9de422a2 100644
--- a/src/data/welcomeFile.md
+++ b/src/data/welcomeFile.md
@@ -13,7 +13,7 @@ The file explorer is accessible using the button in left corner of the navigatio
## Switch to another file
-All your files are listed in the file explorer. You can switch from one to another by clicking a file in the list.
+All your files and folders are presented as a tree in the file explorer. You can switch from one to another by clicking a file in the tree.
## Rename a file
diff --git a/src/services/badgeSvc.js b/src/services/badgeSvc.js
index a0b27171..d627d349 100644
--- a/src/services/badgeSvc.js
+++ b/src/services/badgeSvc.js
@@ -16,7 +16,7 @@ const showInfo = () => {
export default {
addBadge(featureId) {
- if (!store.getters['data/badges'][featureId]) {
+ if (!store.getters['data/badgeCreations'][featureId]) {
if (!lastEarnedFeatureIds) {
const earnedFeatureIds = store.getters['data/allBadges']
.filter(badge => badge.isEarned)
@@ -24,7 +24,7 @@ export default {
lastEarnedFeatureIds = new Set(earnedFeatureIds);
}
- store.dispatch('data/patchBadges', {
+ store.dispatch('data/patchBadgeCreations', {
[featureId]: {
created: Date.now(),
},
diff --git a/src/services/explorerSvc.js b/src/services/explorerSvc.js
index 109c582e..0735f73f 100644
--- a/src/services/explorerSvc.js
+++ b/src/services/explorerSvc.js
@@ -62,7 +62,6 @@ export default {
} else {
workspaceSvc.deleteFile(id);
}
- badgeSvc.addBadge('removeFiles');
};
if (selectedNode === store.getters['explorer/selectedNode']) {
@@ -78,8 +77,10 @@ export default {
store.commit('folder/deleteItem', folderNode.item.id);
};
recursiveDelete(selectedNode);
+ badgeSvc.addBadge('removeFolder');
} else {
deleteFile(selectedNode.item.id);
+ badgeSvc.addBadge('removeFile');
}
if (doClose) {
// Close the current file by opening the last opened, not deleted one
diff --git a/src/services/localDbSvc.js b/src/services/localDbSvc.js
index cab9beb8..855e3f7d 100644
--- a/src/services/localDbSvc.js
+++ b/src/services/localDbSvc.js
@@ -1,23 +1,23 @@
-import FileSaver from 'file-saver';
import utils from './utils';
import store from '../store';
import welcomeFile from '../data/welcomeFile.md';
import workspaceSvc from './workspaceSvc';
import constants from '../data/constants';
+const deleteMarkerMaxAge = 1000;
const dbVersion = 1;
const dbStoreName = 'objects';
-const { exportWorkspace } = utils.queryParams;
const { silent } = utils.queryParams;
-const resetApp = utils.queryParams.reset;
-const deleteMarkerMaxAge = 1000;
+const resetApp = localStorage.getItem('resetStackEdit');
+if (resetApp) {
+ localStorage.removeItem('resetStackEdit');
+}
class Connection {
- constructor() {
+ constructor(workspaceId = store.getters['workspace/currentWorkspace'].id) {
this.getTxCbs = [];
// Make the DB name
- const workspaceId = store.getters['workspace/currentWorkspace'].id;
this.dbName = utils.getDbName(workspaceId);
// Init connection
@@ -264,7 +264,7 @@ const localDbSvc = {
// DB item is different from the corresponding store item
this.hashMap[dbItem.type][dbItem.id] = dbItem.hash;
// Update content only if it exists in the store
- if (storeItem || !contentTypes[dbItem.type] || exportWorkspace) {
+ if (storeItem || !contentTypes[dbItem.type]) {
// Put item in the store
dbItem.tx = undefined;
store.commit(`${dbItem.type}/setItem`, dbItem);
@@ -327,7 +327,7 @@ const localDbSvc = {
* Create the connection and start syncing.
*/
async init() {
- // Reset the app if reset flag was passed
+ // Reset the app if the reset flag was passed
if (resetApp) {
await Promise.all(Object.keys(store.getters['workspace/workspacesById'])
.map(workspaceId => workspaceSvc.removeWorkspace(workspaceId)));
@@ -344,16 +344,6 @@ const localDbSvc = {
// Load the DB
await localDbSvc.sync();
- // If exportWorkspace parameter was provided
- if (exportWorkspace) {
- const backup = JSON.stringify(store.getters.allItemsById);
- const blob = new Blob([backup], {
- type: 'text/plain;charset=utf-8',
- });
- FileSaver.saveAs(blob, 'StackEdit workspace.json');
- throw new Error('RELOAD');
- }
-
// Watch workspace deletions and persist them as soon as possible
// to make the changes available to reloading workspace tabs.
store.watch(
@@ -438,6 +428,27 @@ const localDbSvc = {
{ immediate: true },
);
},
+
+ getWorkspaceItems(workspaceId, onItem, onFinish = () => {}) {
+ const connection = new Connection(workspaceId);
+ connection.createTx((tx) => {
+ const dbStore = tx.objectStore(dbStoreName);
+ const index = dbStore.index('tx');
+ index.openCursor().onsuccess = (event) => {
+ const cursor = event.target.result;
+ if (cursor) {
+ onItem(cursor.value);
+ cursor.continue();
+ } else {
+ connection.db.close();
+ onFinish();
+ }
+ };
+ });
+
+ // Return a cancel function
+ return () => connection.db.close();
+ },
};
const loader = type => fileId => localDbSvc.loadItem(`${fileId}/${type}`)
diff --git a/src/services/networkSvc.js b/src/services/networkSvc.js
index 377a4d66..06f24b37 100644
--- a/src/services/networkSvc.js
+++ b/src/services/networkSvc.js
@@ -8,7 +8,10 @@ const silentAuthorizeTimeout = 15 * 1000; // 15 secondes (which will be reattemp
const networkTimeout = 30 * 1000; // 30 sec
let isConnectionDown = false;
const userInactiveAfter = 3 * 60 * 1000; // 3 minutes (twice the default sync period)
-
+let lastActivity = 0;
+let lastFocus = 0;
+let isConfLoading = false;
+let isConfLoaded = false;
function parseHeaders(xhr) {
const pairs = xhr.getAllResponseHeaders().trim().split('\n');
@@ -31,21 +34,20 @@ function isRetriable(err) {
}
export default {
- init() {
+ async init() {
// Keep track of the last user activity
- this.lastActivity = 0;
const setLastActivity = () => {
- this.lastActivity = Date.now();
+ lastActivity = Date.now();
};
window.document.addEventListener('mousedown', setLastActivity);
window.document.addEventListener('keydown', setLastActivity);
window.document.addEventListener('touchstart', setLastActivity);
// Keep track of the last window focus
- this.lastFocus = 0;
+ lastFocus = 0;
const setLastFocus = () => {
- this.lastFocus = Date.now();
- localStorage.setItem(store.getters['workspace/lastFocusKey'], this.lastFocus);
+ lastFocus = Date.now();
+ localStorage.setItem(store.getters['workspace/lastFocusKey'], lastFocus);
setLastActivity();
};
if (document.hasFocus()) {
@@ -53,7 +55,7 @@ export default {
}
window.addEventListener('focus', setLastFocus);
- // Check browser is online periodically
+ // Check that browser is online periodically
const checkOffline = async () => {
const isBrowserOffline = window.navigator.onLine === false;
if (!isBrowserOffline
@@ -90,23 +92,42 @@ export default {
store.dispatch('notification/error', 'You are offline.');
} else {
store.dispatch('notification/info', 'You are back online!');
+ this.getServerConf();
}
}
};
+
utils.setInterval(checkOffline, 1000);
window.addEventListener('online', () => {
isConnectionDown = false;
checkOffline();
});
window.addEventListener('offline', checkOffline);
+ await checkOffline();
+ this.getServerConf();
+ },
+ async getServerConf() {
+ if (!store.state.offline && !isConfLoading && !isConfLoaded) {
+ try {
+ isConfLoading = true;
+ const res = await this.request({ url: 'conf' });
+ await store.dispatch('data/setServerConf', res.body);
+ isConfLoaded = true;
+ } finally {
+ isConfLoading = false;
+ }
+ }
},
isWindowFocused() {
// We don't use state.workspace.lastFocus as it's not reactive
const storedLastFocus = localStorage.getItem(store.getters['workspace/lastFocusKey']);
- return parseInt(storedLastFocus, 10) === this.lastFocus;
+ return parseInt(storedLastFocus, 10) === lastFocus;
},
isUserActive() {
- return this.lastActivity > Date.now() - userInactiveAfter && this.isWindowFocused();
+ return lastActivity > Date.now() - userInactiveAfter && this.isWindowFocused();
+ },
+ isConfLoaded() {
+ return !!Object.keys(store.getters['data/serverConf']).length;
},
async loadScript(url) {
if (!scriptLoadingPromises[url]) {
@@ -217,15 +238,15 @@ export default {
throw e;
}
},
- async request(configParam, offlineCheck = false) {
+ async request(config, offlineCheck = false) {
let retryAfter = 500; // 500 ms
const maxRetryAfter = 10 * 1000; // 10 sec
- const config = Object.assign({}, configParam);
- config.timeout = config.timeout || networkTimeout;
- config.headers = Object.assign({}, config.headers);
- if (config.body && typeof config.body === 'object') {
- config.body = JSON.stringify(config.body);
- config.headers['Content-Type'] = 'application/json';
+ const sanitizedConfig = Object.assign({}, config);
+ sanitizedConfig.timeout = sanitizedConfig.timeout || networkTimeout;
+ sanitizedConfig.headers = Object.assign({}, sanitizedConfig.headers);
+ if (sanitizedConfig.body && typeof sanitizedConfig.body === 'object') {
+ sanitizedConfig.body = JSON.stringify(sanitizedConfig.body);
+ sanitizedConfig.headers['Content-Type'] = 'application/json';
}
const attempt = async () => {
@@ -236,7 +257,7 @@ export default {
}
const xhr = new window.XMLHttpRequest();
- xhr.withCredentials = config.withCredentials || false;
+ xhr.withCredentials = sanitizedConfig.withCredentials || false;
const timeoutId = setTimeout(() => {
xhr.abort();
@@ -247,7 +268,7 @@ export default {
} else {
reject(new Error('Network request timeout.'));
}
- }, config.timeout);
+ }, sanitizedConfig.timeout);
xhr.onload = () => {
if (offlineCheck) {
@@ -257,9 +278,9 @@ export default {
const result = {
status: xhr.status,
headers: parseHeaders(xhr),
- body: config.blob ? xhr.response : xhr.responseText,
+ body: sanitizedConfig.blob ? xhr.response : xhr.responseText,
};
- if (!config.raw && !config.blob) {
+ if (!sanitizedConfig.raw && !sanitizedConfig.blob) {
try {
result.body = JSON.parse(result.body);
} catch (e) {
@@ -284,17 +305,17 @@ export default {
}
};
- const url = utils.addQueryParams(config.url, config.params);
- xhr.open(config.method || 'GET', url);
- Object.entries(config.headers).forEach(([key, value]) => {
+ const url = utils.addQueryParams(sanitizedConfig.url, sanitizedConfig.params);
+ xhr.open(sanitizedConfig.method || 'GET', url);
+ Object.entries(sanitizedConfig.headers).forEach(([key, value]) => {
if (value) {
xhr.setRequestHeader(key, `${value}`);
}
});
- if (config.blob) {
+ if (sanitizedConfig.blob) {
xhr.responseType = 'blob';
}
- xhr.send(config.body || null);
+ xhr.send(sanitizedConfig.body || null);
});
} catch (err) {
// Try again later in case of retriable error
diff --git a/src/services/providers/dropboxProvider.js b/src/services/providers/dropboxProvider.js
index 8e8016ed..3bdb8ec1 100644
--- a/src/services/providers/dropboxProvider.js
+++ b/src/services/providers/dropboxProvider.js
@@ -124,7 +124,11 @@ export default new Provider({
};
},
async listFileRevisions({ token, syncLocation }) {
- const entries = await dropboxHelper.listRevisions(token, syncLocation.dropboxFileId);
+ const entries = await dropboxHelper.listRevisions({
+ token,
+ path: makePathRelative(token, syncLocation.path),
+ fileId: syncLocation.dropboxFileId,
+ });
return entries.map(entry => ({
id: entry.rev,
sub: `${dropboxHelper.subPrefix}:${(entry.sharing_info || {}).modified_by || token.sub}`,
diff --git a/src/services/providers/helpers/dropboxHelper.js b/src/services/providers/helpers/dropboxHelper.js
index b2937ff0..f9082c24 100644
--- a/src/services/providers/helpers/dropboxHelper.js
+++ b/src/services/providers/helpers/dropboxHelper.js
@@ -5,9 +5,9 @@ import badgeSvc from '../../badgeSvc';
const getAppKey = (fullAccess) => {
if (fullAccess) {
- return 'lq6mwopab8wskas';
+ return store.getters['data/serverConf'].dropboxAppKeyFull;
}
- return 'sw0hlixhr8q1xk0';
+ return store.getters['data/serverConf'].dropboxAppKey;
};
const httpHeaderSafeJson = args => args && JSON.stringify(args)
@@ -60,6 +60,7 @@ export default {
* https://www.dropbox.com/developers/documentation/http/documentation#users-get_current_account
*/
async startOauth2(fullAccess, sub = null, silent = false) {
+ // Get an OAuth2 code
const { accessToken } = await networkSvc.startOauth2(
'https://www.dropbox.com/oauth2/authorize',
{
@@ -146,14 +147,21 @@ export default {
/**
* https://www.dropbox.com/developers/documentation/http/documentation#list-revisions
*/
- async listRevisions(token, fileId) {
+ async listRevisions({
+ token,
+ path,
+ fileId,
+ }) {
const res = await request(token, {
method: 'POST',
url: 'https://api.dropboxapi.com/2/files/list_revisions',
- body: {
+ body: fileId ? {
path: fileId,
mode: 'id',
limit: 100,
+ } : {
+ path,
+ limit: 100,
},
});
return res.body.entries;
diff --git a/src/services/providers/helpers/githubHelper.js b/src/services/providers/helpers/githubHelper.js
index 435884af..2f10a5bc 100644
--- a/src/services/providers/helpers/githubHelper.js
+++ b/src/services/providers/helpers/githubHelper.js
@@ -4,7 +4,6 @@ import store from '../../../store';
import userSvc from '../../userSvc';
import badgeSvc from '../../badgeSvc';
-const clientId = GITHUB_CLIENT_ID;
const getScopes = token => [token.repoFullAccess ? 'repo' : 'public_repo', 'gist'];
const request = (token, options) => networkSvc.request({
@@ -64,6 +63,9 @@ export default {
* https://developer.github.com/apps/building-oauth-apps/authorization-options-for-oauth-apps/
*/
async startOauth2(scopes, sub = null, silent = false) {
+ const clientId = store.getters['data/serverConf'].githubClientId;
+
+ // Get an OAuth2 code
const { code } = await networkSvc.startOauth2(
'https://github.com/login/oauth/authorize',
{
diff --git a/src/services/providers/helpers/gitlabHelper.js b/src/services/providers/helpers/gitlabHelper.js
index 5a1ec746..03312e61 100644
--- a/src/services/providers/helpers/gitlabHelper.js
+++ b/src/services/providers/helpers/gitlabHelper.js
@@ -51,6 +51,7 @@ export default {
* https://docs.gitlab.com/ee/api/oauth2.html
*/
async startOauth2(serverUrl, applicationId, sub = null, silent = false) {
+ // Get an OAuth2 code
const { accessToken } = await networkSvc.startOauth2(
`${serverUrl}/oauth/authorize`,
{
diff --git a/src/services/providers/helpers/googleHelper.js b/src/services/providers/helpers/googleHelper.js
index 2764488e..84934fe8 100644
--- a/src/services/providers/helpers/googleHelper.js
+++ b/src/services/providers/helpers/googleHelper.js
@@ -4,8 +4,6 @@ import store from '../../../store';
import userSvc from '../../userSvc';
import badgeSvc from '../../badgeSvc';
-const clientId = GOOGLE_CLIENT_ID;
-const apiKey = 'AIzaSyC_M4RA9pY6XmM9pmFxlT59UPMO7aHr9kk';
const appsDomain = null;
const tokenExpirationMargin = 5 * 60 * 1000; // 5 min (tokens expire after 1h)
@@ -21,6 +19,7 @@ const checkIdToken = (idToken) => {
try {
const token = idToken.split('.');
const payload = JSON.parse(utils.decodeBase64(token[1]));
+ const clientId = store.getters['data/serverConf'].googleClientId;
return payload.aud === clientId && Date.now() + tokenExpirationMargin < payload.exp * 1000;
} catch (e) {
return false;
@@ -40,6 +39,7 @@ if (utils.queryParams.providerId === 'googleDrive') {
* https://developers.google.com/people/api/rest/v1/people/get
*/
const getUser = async (sub, token) => {
+ const apiKey = store.getters['data/serverConf'].googleApiKey;
const url = `https://people.googleapis.com/v1/people/${sub}?personFields=names,photos&key=${apiKey}`;
const { body } = await networkSvc.request(sub === 'me' && token
? {
@@ -111,6 +111,9 @@ export default {
* https://developers.google.com/identity/protocols/OpenIDConnect
*/
async startOauth2(scopes, sub = null, silent = false) {
+ const clientId = store.getters['data/serverConf'].googleClientId;
+
+ // Get an OAuth2 code
const { accessToken, expiresIn, idToken } = await networkSvc.startOauth2(
'https://accounts.google.com/o/oauth2/v2/auth',
{
@@ -265,16 +268,6 @@ export default {
badgeSvc.addBadge('addGooglePhotosAccount');
return token;
},
- async getSponsorship(token) {
- const refreshedToken = await this.refreshToken(token);
- return networkSvc.request({
- method: 'GET',
- url: 'userInfo',
- params: {
- idToken: refreshedToken.idToken,
- },
- }, true);
- },
/**
* https://developers.google.com/drive/v3/reference/files/create
diff --git a/src/services/providers/helpers/wordpressHelper.js b/src/services/providers/helpers/wordpressHelper.js
index 3fb66144..6d77d7ee 100644
--- a/src/services/providers/helpers/wordpressHelper.js
+++ b/src/services/providers/helpers/wordpressHelper.js
@@ -2,7 +2,6 @@ import networkSvc from '../../networkSvc';
import store from '../../../store';
import badgeSvc from '../../badgeSvc';
-const clientId = '23361';
const tokenExpirationMargin = 5 * 60 * 1000; // 5 min (WordPress tokens expire after 2 weeks)
const request = (token, options) => networkSvc.request({
@@ -19,6 +18,9 @@ export default {
* https://developer.wordpress.com/docs/oauth2/
*/
async startOauth2(sub = null, silent = false) {
+ const clientId = store.getters['data/serverConf'].wordpressClientId;
+
+ // Get an OAuth2 code
const { accessToken, expiresIn } = await networkSvc.startOauth2(
'https://public-api.wordpress.com/oauth2/authorize',
{
diff --git a/src/services/providers/helpers/zendeskHelper.js b/src/services/providers/helpers/zendeskHelper.js
index b909ca91..2d50d086 100644
--- a/src/services/providers/helpers/zendeskHelper.js
+++ b/src/services/providers/helpers/zendeskHelper.js
@@ -17,6 +17,7 @@ export default {
* https://support.zendesk.com/hc/en-us/articles/203663836-Using-OAuth-authentication-with-your-application
*/
async startOauth2(subdomain, clientId, sub = null, silent = false) {
+ // Get an OAuth2 code
const { accessToken } = await networkSvc.startOauth2(
`https://${subdomain}.zendesk.com/oauth/authorizations/new`,
{
diff --git a/src/services/syncSvc.js b/src/services/syncSvc.js
index f51f5f69..b8a72bda 100644
--- a/src/services/syncSvc.js
+++ b/src/services/syncSvc.js
@@ -728,7 +728,7 @@ const syncWorkspace = async (skipContents = false) => {
if (workspace.id === 'main') {
await syncDataItem('settings');
await syncDataItem('workspaces');
- await syncDataItem('badges');
+ await syncDataItem('badgeCreations');
}
await syncDataItem('templates');
diff --git a/src/store/data.js b/src/store/data.js
index 8f80a1a4..0831f9a6 100644
--- a/src/store/data.js
+++ b/src/store/data.js
@@ -11,6 +11,7 @@ import styledHtmlWithTocTemplate from '../data/templates/styledHtmlWithTocTempla
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,
@@ -63,9 +64,12 @@ const patcher = id => ({ state, commit }, data) => {
};
// For layoutSettings
-const layoutSettingsToggler = propertyName => ({ getters, dispatch }, value) => dispatch('patchLayoutSettings', {
- [propertyName]: value === undefined ? !getters.layoutSettings[propertyName] : value,
-});
+const layoutSettingsToggler = (propertyName, featureId) => ({ getters, dispatch }, value) => {
+ dispatch('patchLayoutSettings', {
+ [propertyName]: value === undefined ? !getters.layoutSettings[propertyName] : value,
+ });
+ badgeSvc.addBadge(featureId);
+};
const notEnoughSpace = (getters) => {
const layoutConstants = getters['layout/constants'];
const showGutter = getters['discussion/currentDiscussion'];
@@ -132,6 +136,7 @@ export default {
},
},
getters: {
+ serverConf: getter('serverConf'),
workspaces: getter('workspaces'), // Not to be used, prefer workspace/workspacesById
settings: getter('settings'),
computedSettings: (state, { settings }) => {
@@ -204,8 +209,9 @@ export default {
gitlabTokensBySub: (state, { tokensByType }) => tokensByType.gitlab || {},
wordpressTokensBySub: (state, { tokensByType }) => tokensByType.wordpress || {},
zendeskTokensBySub: (state, { tokensByType }) => tokensByType.zendesk || {},
- badges: getter('badges'),
- badgeTree: (state, { badges }) => features.map(feature => feature.toBadge(badges)),
+ badgeCreations: getter('badgeCreations'),
+ badgeTree: (state, { badgeCreations }) => features
+ .map(feature => feature.toBadge(badgeCreations)),
allBadges: (state, { badgeTree }) => {
const result = [];
const processBadgeNodes = nodes => nodes.forEach((node) => {
@@ -219,15 +225,16 @@ export default {
},
},
actions: {
+ setServerConf: setter('serverConf'),
setSettings: setter('settings'),
patchLocalSettings: patcher('localSettings'),
patchLayoutSettings: patcher('layoutSettings'),
- toggleNavigationBar: layoutSettingsToggler('showNavigationBar'),
- toggleEditor: layoutSettingsToggler('showEditor'),
- toggleSidePreview: layoutSettingsToggler('showSidePreview'),
- toggleStatusBar: layoutSettingsToggler('showStatusBar'),
- toggleScrollSync: layoutSettingsToggler('scrollSync'),
- toggleFocusMode: layoutSettingsToggler('focusMode'),
+ 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');
@@ -240,6 +247,7 @@ export default {
patch.showExplorer = false;
}
dispatch('patchLayoutSettings', patch);
+ badgeSvc.addBadge('toggleSideBar');
},
toggleExplorer: ({ getters, dispatch, rootGetters }, value) => {
// Close side bar if not enough space
@@ -250,6 +258,7 @@ export default {
patch.showSideBar = false;
}
dispatch('patchLayoutSettings', patch);
+ badgeSvc.addBadge('toggleExplorer');
},
setSideBarPanel: ({ dispatch }, value) => dispatch('patchLayoutSettings', {
sideBarPanel: value === undefined ? 'menu' : value,
@@ -288,6 +297,6 @@ export default {
addGitlabToken: tokenAdder('gitlab'),
addWordpressToken: tokenAdder('wordpress'),
addZendeskToken: tokenAdder('zendesk'),
- patchBadges: patcher('badges'),
+ patchBadgeCreations: patcher('badgeCreations'),
},
};
diff --git a/test/unit/jest.conf.js b/test/unit/jest.conf.js
index a73d98d1..1420d115 100644
--- a/test/unit/jest.conf.js
+++ b/test/unit/jest.conf.js
@@ -28,8 +28,6 @@ module.exports = {
'!**/node_modules/**',
],
globals: {
- GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
- GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
NODE_ENV: 'production',
},
};
diff --git a/test/unit/specs/components/ButtonBar.spec.js b/test/unit/specs/components/ButtonBar.spec.js
index 5c9bf60c..a8900ee5 100644
--- a/test/unit/specs/components/ButtonBar.spec.js
+++ b/test/unit/specs/components/ButtonBar.spec.js
@@ -3,39 +3,45 @@ import store from '../../../../src/store';
import specUtils from '../specUtils';
describe('ButtonBar.vue', () => {
- it('should toggle the navigation bar', () => specUtils.checkToggler(
+ it('should toggle the navigation bar', async () => specUtils.checkToggler(
ButtonBar,
wrapper => wrapper.find('.button-bar__button--navigation-bar-toggler').trigger('click'),
() => store.getters['data/layoutSettings'].showNavigationBar,
+ 'toggleNavigationBar',
));
- it('should toggle the side preview', () => specUtils.checkToggler(
+ it('should toggle the side preview', async () => specUtils.checkToggler(
ButtonBar,
wrapper => wrapper.find('.button-bar__button--side-preview-toggler').trigger('click'),
() => store.getters['data/layoutSettings'].showSidePreview,
+ 'toggleSidePreview',
));
- it('should toggle the editor', () => specUtils.checkToggler(
+ it('should toggle the editor', async () => specUtils.checkToggler(
ButtonBar,
wrapper => wrapper.find('.button-bar__button--editor-toggler').trigger('click'),
() => store.getters['data/layoutSettings'].showEditor,
+ 'toggleEditor',
));
- it('should toggle the focus mode', () => specUtils.checkToggler(
+ it('should toggle the focus mode', async () => specUtils.checkToggler(
ButtonBar,
wrapper => wrapper.find('.button-bar__button--focus-mode-toggler').trigger('click'),
() => store.getters['data/layoutSettings'].focusMode,
+ 'toggleFocusMode',
));
- it('should toggle the scroll sync', () => specUtils.checkToggler(
+ it('should toggle the scroll sync', async () => specUtils.checkToggler(
ButtonBar,
wrapper => wrapper.find('.button-bar__button--scroll-sync-toggler').trigger('click'),
() => store.getters['data/layoutSettings'].scrollSync,
+ 'toggleScrollSync',
));
- it('should toggle the status bar', () => specUtils.checkToggler(
+ it('should toggle the status bar', async () => specUtils.checkToggler(
ButtonBar,
wrapper => wrapper.find('.button-bar__button--status-bar-toggler').trigger('click'),
() => store.getters['data/layoutSettings'].showStatusBar,
+ 'toggleStatusBar',
));
});
diff --git a/test/unit/specs/components/Explorer.spec.js b/test/unit/specs/components/Explorer.spec.js
index 8bb88a2f..1cd1099e 100644
--- a/test/unit/specs/components/Explorer.spec.js
+++ b/test/unit/specs/components/Explorer.spec.js
@@ -14,7 +14,7 @@ const ensureNotExists = file => expect(store.getters.allItemsById).not.toHavePro
const refreshItem = item => store.getters.allItemsById[item.id];
describe('Explorer.vue', () => {
- it('should create new files in the root folder', () => {
+ it('should create new file in the root folder', async () => {
expect(store.state.explorer.newChildNode.isNil).toBeTruthy();
const wrapper = mount();
wrapper.find('.side-title__button--new-file').trigger('click');
@@ -25,7 +25,7 @@ describe('Explorer.vue', () => {
});
});
- it('should create new files in a folder', async () => {
+ it('should create new file in a folder', async () => {
const folder = await workspaceSvc.storeItem({ type: 'folder' });
const wrapper = mount();
select(folder.id);
@@ -36,7 +36,7 @@ describe('Explorer.vue', () => {
});
});
- it('should not create new files in the trash folder', () => {
+ it('should not create new files in the trash folder', async () => {
const wrapper = mount();
select('trash');
wrapper.find('.side-title__button--new-file').trigger('click');
@@ -46,7 +46,7 @@ describe('Explorer.vue', () => {
});
});
- it('should create new folders in the root folder', () => {
+ it('should create new folders in the root folder', async () => {
expect(store.state.explorer.newChildNode.isNil).toBeTruthy();
const wrapper = mount();
wrapper.find('.side-title__button--new-folder').trigger('click');
@@ -68,7 +68,7 @@ describe('Explorer.vue', () => {
});
});
- it('should not create new folders in the trash folder', () => {
+ it('should not create new folders in the trash folder', async () => {
const wrapper = mount();
select('trash');
wrapper.find('.side-title__button--new-folder').trigger('click');
@@ -78,7 +78,7 @@ describe('Explorer.vue', () => {
});
});
- it('should not create new folders in the temp folder', () => {
+ it('should not create new folders in the temp folder', async () => {
const wrapper = mount();
select('temp');
wrapper.find('.side-title__button--new-folder').trigger('click');
@@ -96,6 +96,7 @@ describe('Explorer.vue', () => {
wrapper.find('.side-title__button--delete').trigger('click');
ensureExists(file);
expect(refreshItem(file).parentId).toEqual('trash');
+ await specUtils.expectBadge('removeFile');
});
it('should not delete the trash folder', async () => {
@@ -103,15 +104,17 @@ describe('Explorer.vue', () => {
select('trash');
wrapper.find('.side-title__button--delete').trigger('click');
await specUtils.resolveModal('trashDeletion');
+ await specUtils.expectBadge('removeFile', false);
});
- it('should not delete files in the trash folder', async () => {
+ it('should not delete file in the trash folder', async () => {
const file = await workspaceSvc.createFile({ parentId: 'trash' }, true);
const wrapper = mount();
select(file.id);
wrapper.find('.side-title__button--delete').trigger('click');
await specUtils.resolveModal('trashDeletion');
ensureExists(file);
+ await specUtils.expectBadge('removeFile', false);
});
it('should delete the temp folder after confirmation', async () => {
@@ -121,9 +124,10 @@ describe('Explorer.vue', () => {
wrapper.find('.side-title__button--delete').trigger('click');
await specUtils.resolveModal('tempFolderDeletion');
ensureNotExists(file);
+ await specUtils.expectBadge('removeFolder');
});
- it('should delete temp files after confirmation', async () => {
+ it('should delete temp file after confirmation', async () => {
const file = await workspaceSvc.createFile({ parentId: 'temp' }, true);
const wrapper = mount();
select(file.id);
@@ -131,6 +135,7 @@ describe('Explorer.vue', () => {
ensureExists(file);
await specUtils.resolveModal('tempFileDeletion');
ensureNotExists(file);
+ await specUtils.expectBadge('removeFile');
});
it('should delete folder after confirmation', async () => {
@@ -144,9 +149,10 @@ describe('Explorer.vue', () => {
// Make sure file has been moved to Trash
ensureExists(file);
expect(refreshItem(file).parentId).toEqual('trash');
+ await specUtils.expectBadge('removeFolder');
});
- it('should rename files', async () => {
+ it('should rename file', async () => {
const file = await workspaceSvc.createFile({}, true);
const wrapper = mount();
select(file.id);
@@ -154,7 +160,7 @@ describe('Explorer.vue', () => {
expect(store.getters['explorer/editingNode'].item.id).toEqual(file.id);
});
- it('should rename folders', async () => {
+ it('should rename folder', async () => {
const folder = await workspaceSvc.storeItem({ type: 'folder' });
const wrapper = mount();
select(folder.id);
@@ -182,6 +188,7 @@ describe('Explorer.vue', () => {
Explorer,
wrapper => wrapper.find('.side-title__button--close').trigger('click'),
() => store.getters['data/layoutSettings'].showExplorer,
+ 'toggleExplorer',
);
});
});
diff --git a/test/unit/specs/components/ExplorerNode.spec.js b/test/unit/specs/components/ExplorerNode.spec.js
index beb5827b..5793bfc4 100644
--- a/test/unit/specs/components/ExplorerNode.spec.js
+++ b/test/unit/specs/components/ExplorerNode.spec.js
@@ -49,15 +49,25 @@ const dragAndDrop = (sourceItem, targetItem) => {
describe('ExplorerNode.vue', () => {
const modifiedName = 'Name';
- it('should open files on select after a timeout', async () => {
+ it('should open file on select after a timeout', async () => {
const node = await makeFileNode();
mountAndSelect(node);
expect(store.getters['file/current'].id).not.toEqual(node.item.id);
await new Promise(resolve => setTimeout(resolve, 10));
expect(store.getters['file/current'].id).toEqual(node.item.id);
+ await specUtils.expectBadge('switchFile');
});
- it('should open folders on select after a timeout', async () => {
+ it('should not open already open file', async () => {
+ const node = await makeFileNode();
+ store.commit('file/setCurrentId', node.item.id);
+ mountAndSelect(node);
+ await new Promise(resolve => setTimeout(resolve, 10));
+ expect(store.getters['file/current'].id).toEqual(node.item.id);
+ await specUtils.expectBadge('switchFile', false);
+ });
+
+ it('should open folder on select after a timeout', async () => {
const node = await makeFolderNode();
const wrapper = mountAndSelect(node);
expect(wrapper.classes()).not.toContain('explorer-node--open');
@@ -65,7 +75,7 @@ describe('ExplorerNode.vue', () => {
expect(wrapper.classes()).toContain('explorer-node--open');
});
- it('should open folders on new child', async () => {
+ it('should open folder on new child', async () => {
const node = await makeFolderNode();
const wrapper = mountAndSelect(node);
// Close the folder
@@ -76,7 +86,7 @@ describe('ExplorerNode.vue', () => {
expect(wrapper.classes()).toContain('explorer-node--open');
});
- it('should create new files in a folder', async () => {
+ it('should create new file in a folder', async () => {
const node = await makeFolderNode();
const wrapper = mount(node);
wrapper.trigger('contextmenu');
@@ -91,9 +101,10 @@ describe('ExplorerNode.vue', () => {
parentId: node.item.id,
});
expect(wrapper.contains('.explorer-node__new-child')).toBe(false);
+ await specUtils.expectBadge('createFile');
});
- it('should cancel a file creation on escape', async () => {
+ it('should cancel file creation on escape', async () => {
const node = await makeFolderNode();
const wrapper = mount(node);
wrapper.trigger('contextmenu');
@@ -110,15 +121,16 @@ describe('ExplorerNode.vue', () => {
parentId: node.item.id,
});
expect(wrapper.contains('.explorer-node__new-child')).toBe(false);
+ await specUtils.expectBadge('createFile', false);
});
- it('should not create new files in a file', async () => {
+ it('should not create new file in a file', async () => {
const node = await makeFileNode();
mount(node).trigger('contextmenu');
expect(specUtils.getContextMenuItem('New file').disabled).toBe(true);
});
- it('should not create new files in the trash folder', async () => {
+ it('should not create new file in the trash folder', async () => {
const node = store.getters['explorer/nodeMap'].trash;
mount(node).trigger('contextmenu');
expect(specUtils.getContextMenuItem('New file').disabled).toBe(true);
@@ -139,9 +151,10 @@ describe('ExplorerNode.vue', () => {
parentId: node.item.id,
});
expect(wrapper.contains('.explorer-node__new-child--folder')).toBe(false);
+ await specUtils.expectBadge('createFolder');
});
- it('should cancel a folder creation on escape', async () => {
+ it('should cancel folder creation on escape', async () => {
const node = await makeFolderNode();
const wrapper = mount(node);
wrapper.trigger('contextmenu');
@@ -158,27 +171,28 @@ describe('ExplorerNode.vue', () => {
parentId: node.item.id,
});
expect(wrapper.contains('.explorer-node__new-child--folder')).toBe(false);
+ await specUtils.expectBadge('createFolder', false);
});
- it('should not create new folders in a file', async () => {
+ it('should not create new folder in a file', async () => {
const node = await makeFileNode();
mount(node).trigger('contextmenu');
expect(specUtils.getContextMenuItem('New folder').disabled).toBe(true);
});
- it('should not create new folders in the trash folder', async () => {
+ it('should not create new folder in the trash folder', async () => {
const node = store.getters['explorer/nodeMap'].trash;
mount(node).trigger('contextmenu');
expect(specUtils.getContextMenuItem('New folder').disabled).toBe(true);
});
- it('should not create new folders in the temp folder', async () => {
+ it('should not create new folder in the temp folder', async () => {
const node = store.getters['explorer/nodeMap'].temp;
mount(node).trigger('contextmenu');
expect(specUtils.getContextMenuItem('New folder').disabled).toBe(true);
});
- it('should rename files', async () => {
+ it('should rename file', async () => {
const node = await makeFileNode();
const wrapper = mount(node);
wrapper.trigger('contextmenu');
@@ -187,20 +201,10 @@ describe('ExplorerNode.vue', () => {
wrapper.setData({ editingValue: modifiedName });
wrapper.find('.explorer-node__item-editor .text-input').trigger('blur');
expect(store.getters['explorer/selectedNode'].item.name).toEqual(modifiedName);
+ await specUtils.expectBadge('renameFile');
});
- it('should rename folders', async () => {
- const node = await makeFolderNode();
- const wrapper = mount(node);
- wrapper.trigger('contextmenu');
- await specUtils.resolveContextMenu('Rename');
- expect(wrapper.contains('.explorer-node__item-editor')).toBe(true);
- wrapper.setData({ editingValue: modifiedName });
- wrapper.find('.explorer-node__item-editor .text-input').trigger('blur');
- expect(store.getters['explorer/selectedNode'].item.name).toEqual(modifiedName);
- });
-
- it('should cancel rename on escape', async () => {
+ it('should cancel rename file on escape', async () => {
const node = await makeFileNode();
const wrapper = mount(node);
wrapper.trigger('contextmenu');
@@ -211,6 +215,33 @@ describe('ExplorerNode.vue', () => {
keyCode: 27,
});
expect(store.getters['explorer/selectedNode'].item.name).not.toEqual(modifiedName);
+ await specUtils.expectBadge('renameFile', false);
+ });
+
+ it('should rename folder', async () => {
+ const node = await makeFolderNode();
+ const wrapper = mount(node);
+ wrapper.trigger('contextmenu');
+ await specUtils.resolveContextMenu('Rename');
+ expect(wrapper.contains('.explorer-node__item-editor')).toBe(true);
+ wrapper.setData({ editingValue: modifiedName });
+ wrapper.find('.explorer-node__item-editor .text-input').trigger('blur');
+ expect(store.getters['explorer/selectedNode'].item.name).toEqual(modifiedName);
+ await specUtils.expectBadge('renameFolder');
+ });
+
+ it('should cancel rename folder on escape', async () => {
+ const node = await makeFolderNode();
+ const wrapper = mount(node);
+ wrapper.trigger('contextmenu');
+ await specUtils.resolveContextMenu('Rename');
+ expect(wrapper.contains('.explorer-node__item-editor')).toBe(true);
+ wrapper.setData({ editingValue: modifiedName });
+ wrapper.find('.explorer-node__item-editor .text-input').trigger('keydown', {
+ keyCode: 27,
+ });
+ expect(store.getters['explorer/selectedNode'].item.name).not.toEqual(modifiedName);
+ await specUtils.expectBadge('renameFolder', false);
});
it('should not rename the trash folder', async () => {
@@ -225,23 +256,26 @@ describe('ExplorerNode.vue', () => {
expect(specUtils.getContextMenuItem('Rename').disabled).toBe(true);
});
- it('should move a file into a folder', async () => {
+ it('should move file into a folder', async () => {
const sourceItem = await workspaceSvc.createFile({}, true);
const targetItem = await workspaceSvc.storeItem({ type: 'folder' });
dragAndDrop(sourceItem, targetItem);
+ await specUtils.expectBadge('moveFile');
});
- it('should move a folder into a folder', async () => {
+ it('should move folder into a folder', async () => {
const sourceItem = await workspaceSvc.storeItem({ type: 'folder' });
const targetItem = await workspaceSvc.storeItem({ type: 'folder' });
dragAndDrop(sourceItem, targetItem);
+ await specUtils.expectBadge('moveFolder');
});
- it('should move a file into a file parent folder', async () => {
+ it('should move file into a file parent folder', async () => {
const targetItem = await workspaceSvc.storeItem({ type: 'folder' });
const file = await workspaceSvc.createFile({ parentId: targetItem.id }, true);
const sourceItem = await workspaceSvc.createFile({}, true);
dragAndDrop(sourceItem, file);
+ await specUtils.expectBadge('moveFile');
});
it('should not move the trash folder', async () => {
@@ -256,14 +290,14 @@ describe('ExplorerNode.vue', () => {
expect(store.state.explorer.dragSourceId).not.toEqual('temp');
});
- it('should not move a file to the temp folder', async () => {
+ it('should not move file to the temp folder', async () => {
const targetNode = store.getters['explorer/nodeMap'].temp;
const wrapper = mount(targetNode);
wrapper.trigger('dragenter');
expect(store.state.explorer.dragTargetId).not.toEqual('temp');
});
- it('should not move a file to a file in the temp folder', async () => {
+ it('should not move file to a file in the temp folder', async () => {
const file = await workspaceSvc.createFile({ parentId: 'temp' }, true);
const targetNode = store.getters['explorer/nodeMap'][file.id];
const wrapper = mount(targetNode);
diff --git a/test/unit/specs/components/NavigationBar.spec.js b/test/unit/specs/components/NavigationBar.spec.js
index 94c2216a..5573df0f 100644
--- a/test/unit/specs/components/NavigationBar.spec.js
+++ b/test/unit/specs/components/NavigationBar.spec.js
@@ -3,15 +3,17 @@ import store from '../../../../src/store';
import specUtils from '../specUtils';
describe('NavigationBar.vue', () => {
- it('should toggle the explorer', () => specUtils.checkToggler(
+ it('should toggle the explorer', async () => specUtils.checkToggler(
NavigationBar,
wrapper => wrapper.find('.navigation-bar__button--explorer-toggler').trigger('click'),
() => store.getters['data/layoutSettings'].showExplorer,
+ 'toggleExplorer',
));
- it('should toggle the side bar', () => specUtils.checkToggler(
+ it('should toggle the side bar', async () => specUtils.checkToggler(
NavigationBar,
wrapper => wrapper.find('.navigation-bar__button--stackedit').trigger('click'),
() => store.getters['data/layoutSettings'].showSideBar,
+ 'toggleSideBar',
));
});
diff --git a/test/unit/specs/specUtils.js b/test/unit/specs/specUtils.js
index f47ff7c1..fbf308c0 100644
--- a/test/unit/specs/specUtils.js
+++ b/test/unit/specs/specUtils.js
@@ -25,12 +25,13 @@ beforeEach(() => {
});
export default {
- checkToggler(Component, toggler, checker) {
+ async checkToggler(Component, toggler, checker, featureId) {
const wrapper = shallowMount(Component, { store });
const valueBefore = checker();
toggler(wrapper);
const valueAfter = checker();
expect(valueAfter).toEqual(!valueBefore);
+ await this.expectBadge(featureId);
},
async resolveModal(type) {
const config = store.getters['modal/config'];
@@ -47,5 +48,11 @@ export default {
expect(item).toBeTruthy();
store.state.contextMenu.resolve(item);
await new Promise(resolve => setTimeout(resolve, 1));
- }
+ },
+ async expectBadge(featureId, isEarned = true) {
+ await new Promise(resolve => setTimeout(resolve, 1));
+ expect(store.getters['data/allBadges'].filter(badge => badge.featureId === featureId)[0]).toMatchObject({
+ isEarned,
+ });
+ },
};