diff --git a/src/components/Modal.vue b/src/components/Modal.vue
index dc5d9d37..eb20789f 100644
--- a/src/components/Modal.vue
+++ b/src/components/Modal.vue
@@ -219,7 +219,7 @@ export default {
z-index: 1;
width: 100%;
color: darken($error-color, 10%);
- background-color: transparentize(lighten($error-color, 33%), 0.1);
+ background-color: transparentize(lighten($error-color, 33%), 0.075);
font-size: 0.9em;
line-height: 1.33;
text-align: center;
diff --git a/src/components/menus/HistoryMenu.vue b/src/components/menus/HistoryMenu.vue
index 58bc46f3..37d239af 100644
--- a/src/components/menus/HistoryMenu.vue
+++ b/src/components/menus/HistoryMenu.vue
@@ -64,7 +64,7 @@ let cachedHistoryContextHash;
let revisionsPromise;
let revisionContentPromises;
const pageSize = 30;
-const spacerThreshold = 60 * 60 * 1000; // 1h
+const spacerThreshold = 6 * 60 * 60 * 1000; // 6h
export default {
components: {
@@ -129,8 +129,8 @@ export default {
if (fileSyncData && contentSyncData) {
return {
...historyContext,
- fileSyncData,
- contentSyncData,
+ fileSyncDataId: fileSyncData.id,
+ contentSyncDataId: contentSyncData.id,
};
}
}
diff --git a/src/components/menus/WorkspacesMenu.vue b/src/components/menus/WorkspacesMenu.vue
index 9ce7c570..006e0d62 100644
--- a/src/components/menus/WorkspacesMenu.vue
+++ b/src/components/menus/WorkspacesMenu.vue
@@ -9,23 +9,19 @@
- CouchDB workspace
- Add a workspace synced with a CouchDB database.
+ Add a CouchDB backed workspace
- GitHub workspace
- Add a workspace synced with a GitHub repository.
+ Add a GitHub backed workspace
- GitLab workspace
- Add a workspace synced with a GitLab project.
+ Add a GitLab backed workspace
- Google Drive workspace
- Add a workspace synced with a Google Drive folder.
+ Add a Google Drive backed workspace
diff --git a/src/components/modals/providers/GitlabAccountModal.vue b/src/components/modals/providers/GitlabAccountModal.vue
index cbcca682..ee339b42 100644
--- a/src/components/modals/providers/GitlabAccountModal.vue
+++ b/src/components/modals/providers/GitlabAccountModal.vue
@@ -6,7 +6,8 @@
Link your GitLab account to StackEdit .
-
+
+
Example: https://gitlab.example.com/
@@ -42,14 +43,15 @@ export default modalTemplate({
},
methods: {
resolve() {
- if (!this.serverUrl) {
+ const serverUrl = this.config.forceServerUrl || this.serverUrl;
+ if (!serverUrl) {
this.setError('serverUrl');
}
if (!this.applicationId) {
this.setError('applicationId');
}
- if (this.serverUrl && this.applicationId) {
- const parsedUrl = this.serverUrl.match(/^(https:\/\/[^/]+)/);
+ if (serverUrl && this.applicationId) {
+ const parsedUrl = serverUrl.match(/^(https:\/\/[^/]+)/);
if (!parsedUrl) {
this.setError('serverUrl');
} else {
diff --git a/src/components/modals/providers/GitlabOpenModal.vue b/src/components/modals/providers/GitlabOpenModal.vue
index 8098fc58..facfcd0a 100644
--- a/src/components/modals/providers/GitlabOpenModal.vue
+++ b/src/components/modals/providers/GitlabOpenModal.vue
@@ -8,7 +8,7 @@
- Example: {{config.token.serverUrl}}path/to/project
+ Example: {{config.token.serverUrl}}/path/to/project
diff --git a/src/data/constants.js b/src/data/constants.js
index 90c4c138..00f35800 100644
--- a/src/data/constants.js
+++ b/src/data/constants.js
@@ -1,7 +1,7 @@
const origin = `${window.location.protocol}//${window.location.host}`;
export default {
- cleanTrashAfter: 0 * 24 * 60 * 60 * 1000, // 7 days
+ cleanTrashAfter: 7 * 24 * 60 * 60 * 1000, // 7 days
origin,
oauth2RedirectUri: `${origin}/oauth2/callback`,
types: [
diff --git a/src/services/providers/common/Provider.js b/src/services/providers/common/Provider.js
index 0e13dc4b..1a74aae1 100644
--- a/src/services/providers/common/Provider.js
+++ b/src/services/providers/common/Provider.js
@@ -44,15 +44,18 @@ export default class Provider {
* Parse content serialized with serializeContent()
*/
static parseContent(serializedContent, id) {
- const result = utils.deepCopy(store.state.content.itemsById[id]) || emptyContent(id);
- result.text = utils.sanitizeText(serializedContent);
- result.history = [];
+ let text = serializedContent;
const extractedData = dataExtractor.exec(serializedContent);
- if (extractedData) {
+ let result;
+ if (!extractedData) {
+ // In case stackedit's data has been manually removed, try to restore them
+ result = utils.deepCopy(store.state.content.itemsById[id]) || emptyContent(id);
+ } else {
+ result = emptyContent(id);
try {
const serializedData = extractedData[1].replace(/\s/g, '');
const parsedData = JSON.parse(utils.decodeBase64(serializedData));
- result.text = utils.sanitizeText(serializedContent.slice(0, extractedData.index));
+ text = text.slice(0, extractedData.index);
if (parsedData.properties) {
result.properties = utils.sanitizeText(parsedData.properties);
}
@@ -62,11 +65,15 @@ export default class Provider {
if (parsedData.comments) {
result.comments = parsedData.comments;
}
- result.history = parsedData.history || [];
+ result.history = parsedData.history;
} catch (e) {
// Ignore
}
}
+ result.text = utils.sanitizeText(text);
+ if (!result.history) {
+ result.history = [];
+ }
return utils.addItemHash(result);
}
diff --git a/src/services/providers/couchdbWorkspaceProvider.js b/src/services/providers/couchdbWorkspaceProvider.js
index a4c58882..0a656d3a 100644
--- a/src/services/providers/couchdbWorkspaceProvider.js
+++ b/src/services/providers/couchdbWorkspaceProvider.js
@@ -194,8 +194,8 @@ export default new Provider({
},
};
},
- async listFileRevisions({ token, contentSyncData }) {
- const body = await couchdbHelper.retrieveDocumentWithRevisions(token, contentSyncData.id);
+ async listFileRevisions({ token, contentSyncDataId }) {
+ const body = await couchdbHelper.retrieveDocumentWithRevisions(token, contentSyncDataId);
const revisions = [];
body._revs_info.forEach((revInfo, idx) => { // eslint-disable-line no-underscore-dangle
if (revInfo.status === 'available') {
@@ -209,19 +209,19 @@ export default new Provider({
});
return revisions;
},
- async loadFileRevision({ token, contentSyncData, revision }) {
+ async loadFileRevision({ token, contentSyncDataId, revision }) {
if (revision.loaded) {
return false;
}
- const body = await couchdbHelper.retrieveDocument(token, contentSyncData.id, revision.id);
+ const body = await couchdbHelper.retrieveDocument(token, contentSyncDataId, revision.id);
revision.sub = body.sub;
revision.created = body.time;
revision.loaded = true;
return true;
},
- async getFileRevisionContent({ token, contentSyncData, revisionId }) {
+ async getFileRevisionContent({ token, contentSyncDataId, revisionId }) {
const body = await couchdbHelper
- .retrieveDocumentWithAttachments(token, contentSyncData.id, revisionId);
+ .retrieveDocumentWithAttachments(token, contentSyncDataId, revisionId);
return Provider.parseContent(body.attachments.data, body.item.id);
},
});
diff --git a/src/services/providers/githubWorkspaceProvider.js b/src/services/providers/githubWorkspaceProvider.js
index 749160f7..1f1020d5 100644
--- a/src/services/providers/githubWorkspaceProvider.js
+++ b/src/services/providers/githubWorkspaceProvider.js
@@ -46,12 +46,19 @@ export default new Provider({
async initWorkspace() {
const { owner, repo, branch } = utils.queryParams;
const workspaceParams = this.getWorkspaceParams({ owner, repo, branch });
+ if (!branch) {
+ workspaceParams.branch = 'master';
+ }
+
+ // Extract path param
const path = (utils.queryParams.path || '')
+ .trim()
.replace(/^\/*/, '') // Remove leading `/`
.replace(/\/*$/, '/'); // Add trailing `/`
if (path !== '/') {
workspaceParams.path = path;
}
+
const workspaceId = utils.makeWorkspaceId(workspaceParams);
const workspace = store.getters['workspace/workspacesById'][workspaceId];
@@ -217,14 +224,14 @@ export default new Provider({
},
};
},
- async listFileRevisions({ token, fileSyncData }) {
+ async listFileRevisions({ token, fileSyncDataId }) {
const { owner, repo, branch } = store.getters['workspace/currentWorkspace'];
const entries = await githubHelper.getCommits({
token,
owner,
repo,
sha: branch,
- path: getAbsolutePath(fileSyncData),
+ path: getAbsolutePath({ id: fileSyncDataId }),
});
return entries.map(({
@@ -242,11 +249,12 @@ export default new Provider({
const sub = `${githubHelper.subPrefix}:${user.id}`;
userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
const date = (commit.author && commit.author.date)
- || (commit.committer && commit.committer.date);
+ || (commit.committer && commit.committer.date)
+ || 1;
return {
id: sha,
sub,
- created: date ? new Date(date).getTime() : 1,
+ created: new Date(date).getTime(),
};
});
},
@@ -257,14 +265,14 @@ export default new Provider({
async getFileRevisionContent({
token,
contentId,
- fileSyncData,
+ fileSyncDataId,
revisionId,
}) {
const { data } = await githubHelper.downloadFile({
...store.getters['workspace/currentWorkspace'],
token,
branch: revisionId,
- path: getAbsolutePath(fileSyncData),
+ path: getAbsolutePath({ id: fileSyncDataId }),
});
return Provider.parseContent(data, contentId);
},
diff --git a/src/services/providers/gitlabProvider.js b/src/services/providers/gitlabProvider.js
index b0805cfa..b48d256c 100644
--- a/src/services/providers/gitlabProvider.js
+++ b/src/services/providers/gitlabProvider.js
@@ -135,24 +135,17 @@ export default new Provider({
token,
});
- return entries.map(({
- author,
- committer,
- commit,
- sha,
- }) => {
- let user;
- if (author && author.login) {
- user = author;
- } else if (committer && committer.login) {
- user = committer;
- }
- const sub = `${gitlabHelper.subPrefix}:${user.id}`;
- userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
- const date = (commit.author && commit.author.date)
- || (commit.committer && commit.committer.date);
+ return entries.map((entry) => {
+ const email = entry.author_email || entry.committer_email;
+ const sub = `${gitlabHelper.subPrefix}:${token.serverUrl}/${email}`;
+ userSvc.addInfo({
+ id: sub,
+ name: entry.author_name || entry.committer_name,
+ imageUrl: '',
+ });
+ const date = entry.authored_date || entry.committed_date || 1;
return {
- id: sha,
+ id: entry.id,
sub,
created: date ? new Date(date).getTime() : 1,
};
diff --git a/src/services/providers/gitlabWorkspaceProvider.js b/src/services/providers/gitlabWorkspaceProvider.js
index 8439cca8..a0a95443 100644
--- a/src/services/providers/gitlabWorkspaceProvider.js
+++ b/src/services/providers/gitlabWorkspaceProvider.js
@@ -45,29 +45,44 @@ export default new Provider({
return getAbsolutePath({ id });
},
async initWorkspace() {
- const { projectPath, branch } = utils.queryParams;
- const workspaceParams = this.getWorkspaceParams({ projectPath, branch });
+ const { serverUrl, branch } = utils.queryParams;
+ const workspaceParams = this.getWorkspaceParams({ serverUrl, branch });
+ if (!branch) {
+ workspaceParams.branch = 'master';
+ }
+
+ // Extract project path param
+ const projectPath = (utils.queryParams.projectPath || '')
+ .trim()
+ .replace(/^\/*/, '') // Remove leading `/`
+ .replace(/\/*$/, ''); // Remove trailing `/`
+ workspaceParams.projectPath = projectPath;
+
+ // Extract path param
const path = (utils.queryParams.path || '')
+ .trim()
.replace(/^\/*/, '') // Remove leading `/`
.replace(/\/*$/, '/'); // Add trailing `/`
if (path !== '/') {
workspaceParams.path = path;
}
+
const workspaceId = utils.makeWorkspaceId(workspaceParams);
const workspace = store.getters['workspace/workspacesById'][workspaceId];
// See if we already have a token
- let token;
- if (workspace) {
- // Token sub is in the workspace
- token = store.getters['data/gitlabTokensBySub'][workspace.sub];
- }
+ const sub = workspace ? workspace.sub : utils.queryParams.sub;
+ let token = store.getters['data/gitlabTokensBySub'][sub];
if (!token) {
- const { serverUrl, applicationId } = await store.dispatch('modal/open', { type: 'gitlabAccount' });
- token = await gitlabHelper.addAccount(serverUrl, applicationId);
+ const { applicationId } = await store.dispatch('modal/open', {
+ type: 'gitlabAccount',
+ forceServerUrl: serverUrl,
+ });
+ token = await gitlabHelper.addAccount(serverUrl, applicationId, sub);
}
if (!workspace) {
+ const projectId = await gitlabHelper.getProjectId(token, workspaceParams);
const pathEntries = (path || '').split('/');
const projectPathEntries = (projectPath || '').split('/');
const name = pathEntries[pathEntries.length - 2] // path ends with `/`
@@ -75,6 +90,7 @@ export default new Provider({
store.dispatch('workspace/patchWorkspacesById', {
[workspaceId]: {
...workspaceParams,
+ projectId,
id: workspaceId,
sub: token.sub,
name,
@@ -178,12 +194,13 @@ export default new Provider({
async uploadWorkspaceContent({ token, content, file }) {
const path = store.getters.gitPathsByItemId[file.id];
const absolutePath = `${store.getters['workspace/currentWorkspace'].path || ''}${path}`;
- const res = await gitlabHelper.uploadFile({
+ const sha = gitWorkspaceSvc.shaByPath[path];
+ await gitlabHelper.uploadFile({
...store.getters['workspace/currentWorkspace'],
token,
path: absolutePath,
content: Provider.serializeContent(content),
- sha: gitWorkspaceSvc.shaByPath[path],
+ sha,
});
// Return new sync data
@@ -192,7 +209,7 @@ export default new Provider({
id: store.getters.gitPathsByItemId[content.id],
type: content.type,
hash: content.hash,
- sha: res.content.sha,
+ sha,
},
fileSyncData: {
id: path,
@@ -223,33 +240,26 @@ export default new Provider({
},
};
},
- async listFileRevisions({ token, fileSyncData }) {
+ async listFileRevisions({ token, fileSyncDataId }) {
const { projectId, branch } = store.getters['workspace/currentWorkspace'];
const entries = await gitlabHelper.getCommits({
token,
projectId,
sha: branch,
- path: getAbsolutePath(fileSyncData),
+ path: getAbsolutePath({ id: fileSyncDataId }),
});
- return entries.map(({
- author,
- committer,
- commit,
- sha,
- }) => {
- let user;
- if (author && author.login) {
- user = author;
- } else if (committer && committer.login) {
- user = committer;
- }
- const sub = `${gitlabHelper.subPrefix}:${user.id}`;
- userSvc.addInfo({ id: sub, name: user.login, imageUrl: user.avatar_url });
- const date = (commit.author && commit.author.date)
- || (commit.committer && commit.committer.date);
+ return entries.map((entry) => {
+ const email = entry.author_email || entry.committer_email;
+ const sub = `${gitlabHelper.subPrefix}:${token.serverUrl}/${email}`;
+ userSvc.addInfo({
+ id: sub,
+ name: entry.author_name || entry.committer_name,
+ imageUrl: '', // No way to get user's avatar url...
+ });
+ const date = entry.authored_date || entry.committed_date || 1;
return {
- id: sha,
+ id: entry.id,
sub,
created: date ? new Date(date).getTime() : 1,
};
@@ -262,14 +272,14 @@ export default new Provider({
async getFileRevisionContent({
token,
contentId,
- fileSyncData,
+ fileSyncDataId,
revisionId,
}) {
const { data } = await gitlabHelper.downloadFile({
...store.getters['workspace/currentWorkspace'],
token,
branch: revisionId,
- path: getAbsolutePath(fileSyncData),
+ path: getAbsolutePath({ id: fileSyncDataId }),
});
return Provider.parseContent(data, contentId);
},
diff --git a/src/services/providers/googleDriveAppDataProvider.js b/src/services/providers/googleDriveAppDataProvider.js
index 7aeceee7..196f6354 100644
--- a/src/services/providers/googleDriveAppDataProvider.js
+++ b/src/services/providers/googleDriveAppDataProvider.js
@@ -167,8 +167,8 @@ export default new Provider({
},
};
},
- async listFileRevisions({ token, contentSyncData }) {
- const revisions = await googleHelper.getAppDataFileRevisions(token, contentSyncData.id);
+ async listFileRevisions({ token, contentSyncDataId }) {
+ const revisions = await googleHelper.getAppDataFileRevisions(token, contentSyncDataId);
return revisions.map(revision => ({
id: revision.id,
sub: `${googleHelper.subPrefix}:${revision.lastModifyingUser.permissionId}`,
@@ -179,9 +179,9 @@ export default new Provider({
// Revisions are already loaded
return false;
},
- async getFileRevisionContent({ token, contentSyncData, revisionId }) {
+ async getFileRevisionContent({ token, contentSyncDataId, revisionId }) {
const content = await googleHelper
- .downloadAppDataFileRevision(token, contentSyncData.id, revisionId);
+ .downloadAppDataFileRevision(token, contentSyncDataId, revisionId);
return JSON.parse(content);
},
});
diff --git a/src/services/providers/googleDriveWorkspaceProvider.js b/src/services/providers/googleDriveWorkspaceProvider.js
index d4d8ac8d..cc7134f9 100644
--- a/src/services/providers/googleDriveWorkspaceProvider.js
+++ b/src/services/providers/googleDriveWorkspaceProvider.js
@@ -508,8 +508,8 @@ export default new Provider({
},
};
},
- async listFileRevisions({ token, fileSyncData }) {
- const revisions = await googleHelper.getFileRevisions(token, fileSyncData.id);
+ async listFileRevisions({ token, fileSyncDataId }) {
+ const revisions = await googleHelper.getFileRevisions(token, fileSyncDataId);
return revisions.map(revision => ({
id: revision.id,
sub: `${googleHelper.subPrefix}:${revision.lastModifyingUser.permissionId}`,
@@ -523,10 +523,10 @@ export default new Provider({
async getFileRevisionContent({
token,
contentId,
- fileSyncData,
+ fileSyncDataId,
revisionId,
}) {
- const content = await googleHelper.downloadFileRevision(token, fileSyncData.id, revisionId);
+ const content = await googleHelper.downloadFileRevision(token, fileSyncDataId, revisionId);
return Provider.parseContent(content, contentId);
},
});
diff --git a/src/services/providers/helpers/gitlabHelper.js b/src/services/providers/helpers/gitlabHelper.js
index 5ebd14ea..90fdfbb7 100644
--- a/src/services/providers/helpers/gitlabHelper.js
+++ b/src/services/providers/helpers/gitlabHelper.js
@@ -88,8 +88,8 @@ export default {
store.dispatch('data/addGitlabToken', token);
return token;
},
- addAccount(serverUrl, applicationId) {
- return this.startOauth2(serverUrl, applicationId);
+ addAccount(serverUrl, applicationId, sub = null) {
+ return this.startOauth2(serverUrl, applicationId, sub);
},
/**
@@ -133,7 +133,7 @@ export default {
path,
}) {
return request(token, {
- url: `projects/${encodeURIComponent(projectId)}/repository/tree`,
+ url: `projects/${encodeURIComponent(projectId)}/repository/commits`,
params: {
ref_name: branch,
path,
diff --git a/src/services/userSvc.js b/src/services/userSvc.js
index fb18e8b2..8f6898db 100644
--- a/src/services/userSvc.js
+++ b/src/services/userSvc.js
@@ -23,7 +23,7 @@ export default {
if (!loginToken) {
return null;
}
- const loginType = store.getters['workspace/loginToken'];
+ const loginType = store.getters['workspace/loginType'];
const prefix = subPrefixesByType[loginType];
return prefix ? `${prefix}:${loginToken.sub}` : loginToken.sub;
},
@@ -44,7 +44,7 @@ export default {
const [type, sub] = parseUserId(userId);
// Try to find a token with this sub to resolve name as soon as possible
- const token = store.getters[`data/${type}TokensBySub`][sub];
+ const token = store.getters['data/tokensByType'][type][sub];
if (token) {
store.commit('userInfo/addItem', {
id: userId,
diff --git a/src/services/utils.js b/src/services/utils.js
index 5f01264c..ce7722d7 100644
--- a/src/services/utils.js
+++ b/src/services/utils.js
@@ -94,11 +94,15 @@ export default {
},
sanitizeName(name) {
return `${name || ''}`
- // Replace `/`, control characters and other kind of spaces with a space
- .replace(/[/\x00-\x1F\x7f-\xa0\s]+/g, ' ').trim() // eslint-disable-line no-control-regex
// Keep only 250 characters
.slice(0, 250) || constants.defaultName;
},
+ sanitizeFilename(name) {
+ return this.sanitizeName(`${name || ''}`
+ // Replace `/`, control characters and other kind of spaces with a space
+ .replace(/[/\x00-\x1F\x7f-\xa0\s]+/g, ' ') // eslint-disable-line no-control-regex
+ .trim()) || constants.defaultName;
+ },
deepCopy,
serializeObject(obj) {
return obj === undefined ? obj : JSON.stringify(obj, (key, value) => {
@@ -128,6 +132,7 @@ export default {
return array.cl_map(value => alphabet[value % radix]).join('');
},
hash(str) {
+ // https://stackoverflow.com/a/7616484/1333165
let hash = 0;
if (!str) return hash;
for (let i = 0; i < str.length; i += 1) {
diff --git a/src/services/workspaceSvc.js b/src/services/workspaceSvc.js
index 4aeaf138..e653a5d8 100644
--- a/src/services/workspaceSvc.js
+++ b/src/services/workspaceSvc.js
@@ -20,7 +20,7 @@ export default {
const id = utils.uid();
const item = {
id,
- name: utils.sanitizeName(name),
+ name: utils.sanitizeFilename(name),
parentId: parentId || null,
};
const content = {
@@ -72,7 +72,7 @@ export default {
*/
async storeItem(item) {
const id = item.id || utils.uid();
- const sanitizedName = utils.sanitizeName(item.name);
+ const sanitizedName = utils.sanitizeFilename(item.name);
if (item.type === 'folder' && forbiddenFolderNameMatcher.exec(sanitizedName)) {
await store.dispatch('modal/open', {
@@ -125,7 +125,7 @@ export default {
item.parentId = patch.parentId || null;
}
if (patch.name) {
- const sanitizedName = utils.sanitizeName(patch.name);
+ const sanitizedName = utils.sanitizeFilename(patch.name);
if (item.type !== 'folder' || !forbiddenFolderNameMatcher.exec(sanitizedName)) {
item.name = sanitizedName;
}
diff --git a/src/styles/app.scss b/src/styles/app.scss
index 22c05148..b0ac8f23 100644
--- a/src/styles/app.scss
+++ b/src/styles/app.scss
@@ -186,7 +186,7 @@ textarea {
&[disabled] {
cursor: not-allowed;
- background-color: #f2f2f2;
+ background-color: #f0f0f0;
color: #999;
}
}
diff --git a/src/styles/base.scss b/src/styles/base.scss
index 9cbca137..18e55a2f 100644
--- a/src/styles/base.scss
+++ b/src/styles/base.scss
@@ -85,12 +85,12 @@ samp {
blockquote {
color: rgba(0, 0, 0, 0.5);
padding-left: 1.5em;
- border-left: 5px solid rgba(0, 0, 0, 0.075);
+ border-left: 5px solid rgba(0, 0, 0, 0.1);
.app--dark .layout__panel--editor &,
.app--dark .layout__panel--preview & {
color: rgba(255, 255, 255, 0.4);
- border-left-color: rgba(255, 255, 255, 0.075);
+ border-left-color: rgba(255, 255, 255, 0.1);
}
}
diff --git a/src/styles/variables.scss b/src/styles/variables.scss
index f34ed491..cc17493c 100644
--- a/src/styles/variables.scss
+++ b/src/styles/variables.scss
@@ -13,7 +13,7 @@ $code-border-radius: 3px;
$link-color: #0c93e4;
$error-color: #f31;
$border-radius-base: 3px;
-$hr-color: rgba(128, 128, 128, 0.2);
+$hr-color: rgba(128, 128, 128, 0.33);
$navbar-bg: #2c2c2c;
$navbar-color: mix($navbar-bg, #fff, 33%);
$navbar-hover-color: #fff;