diff --git a/client/app/account/account.js b/client/app/account/account.js
index defbb45..0fed4c4 100644
--- a/client/app/account/account.js
+++ b/client/app/account/account.js
@@ -1,22 +1,22 @@
'use strict';
angular.module('manticoreApp')
.config(function ($stateProvider) {
$stateProvider
- .state('login', {
+ .state('manticore.login', {
url: '/login',
templateUrl: 'app/account/login/login.html',
controller: 'LoginCtrl'
})
- .state('signup', {
+ .state('manticore.signup', {
url: '/signup',
templateUrl: 'app/account/signup/signup.html',
controller: 'SignupCtrl'
})
- .state('settings', {
+ .state('manticore.settings', {
url: '/settings',
templateUrl: 'app/account/settings/settings.html',
controller: 'SettingsCtrl',
authenticate: true
});
- });
\ No newline at end of file
+ });
diff --git a/client/app/app.js b/client/app/app.js
index 0eee4ed..a46089a 100644
--- a/client/app/app.js
+++ b/client/app/app.js
@@ -1,58 +1,58 @@
'use strict';
angular.module('manticoreApp', [
'ngCookies',
'ngResource',
'ngSanitize',
'ui.router',
'ui.bootstrap',
'angularFileUpload',
'fileSaver',
'smart-table',
'angularMoment',
'angularLoad'
])
- .config(function ($stateProvider, $urlRouterProvider, $locationProvider, $httpProvider) {
+ .config(function ($stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider, $locationProvider, $httpProvider) {
$urlRouterProvider
.otherwise('/');
-
+ $urlMatcherFactoryProvider.strictMode(false);
$locationProvider.html5Mode(true);
$httpProvider.interceptors.push('authInterceptor');
})
.factory('authInterceptor', function ($rootScope, $q, $cookieStore, $location) {
return {
// Add authorization token to headers
request: function (config) {
config.headers = config.headers || {};
if ($cookieStore.get('token')) {
config.headers.Authorization = 'Bearer ' + $cookieStore.get('token');
}
return config;
},
// Intercept 401s and redirect you to login
responseError: function(response) {
if(response.status === 401) {
$location.path('/login');
// remove any stale tokens
$cookieStore.remove('token');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
};
})
.run(function ($rootScope, $location, Auth) {
// Redirect to login if route requires auth and you're not logged in
$rootScope.$on('$stateChangeStart', function (event, next) {
Auth.isLoggedInAsync(function(loggedIn) {
if (next.authenticate && !loggedIn) {
$location.path('/login');
}
});
});
});
diff --git a/client/app/editor/editor.js b/client/app/editor/editor.js
index 2503631..c6a67b6 100644
--- a/client/app/editor/editor.js
+++ b/client/app/editor/editor.js
@@ -1,41 +1,41 @@
'use strict';
angular.module('manticoreApp')
.config(function ($stateProvider) {
$stateProvider
- .state('editor', {
+ .state('manticore.editor', {
abstract: true,
url: '/document',
reload: true,
template: ''
})
- .state('editor.forDocument', {
+ .state('manticore.editor.forDocument', {
url: '/:id',
resolve: {
socketio: function (angularLoad) {
return angularLoad.loadScript('socket.io/socket.io.js');
},
document: function ($stateParams, $http) {
return $http.get('/api/documents/' + $stateParams.id)
.then(function(response) {
return response.data;
});
}
},
templateUrl: 'app/editor/editor.html',
controller: function ($scope, document) {
$scope.document = document;
}
})
- .state('editor.fromTemplate', {
+ .state('manticore.editor.fromTemplate', {
url: '/:id/new',
resolve: {
document: function ($stateParams, $state, $http) {
return $http.get('/api/documents/fromTemplate/' + $stateParams.id)
.then(function (response) {
- $state.go('editor.forDocument', { id: response.data._id }, { location: 'replace' });
+ $state.go('manticore.editor.forDocument', { id: response.data._id }, { location: 'replace' });
});
}
}
});
});
diff --git a/client/app/main/main.js b/client/app/main/main.js
index 392014e..a5f052e 100644
--- a/client/app/main/main.js
+++ b/client/app/main/main.js
@@ -1,11 +1,24 @@
'use strict';
angular.module('manticoreApp')
.config(function ($stateProvider) {
$stateProvider
- .state('main', {
+ .state('manticore', {
+ url: '',
+ abstract: true,
+ resolve: {
+ config: function ($http) {
+ return $http.get('/config');
+ }
+ },
+ template: '',
+ controller: function ($rootScope, config) {
+ $rootScope.config = config.data;
+ }
+ })
+ .state('manticore.main', {
url: '/',
templateUrl: 'app/main/main.html',
controller: 'MainCtrl'
});
- });
\ No newline at end of file
+ });
diff --git a/client/app/main/main.styl b/client/app/main/main.styl
index dab28f8..a2e373b 100644
--- a/client/app/main/main.styl
+++ b/client/app/main/main.styl
@@ -1,29 +1,30 @@
.document-form
margin 20px 0
#banner
border-bottom none
margin-top -20px
#banner h1
font-size 60px
letter-spacing -1px
line-height 1
.hero-unit
color #000
padding 30px 15px
position relative
text-align center
text-shadow 0 1px 0 rgba(0, 0, 0, 0.1)
img
max-width: 100%;
.footer
border-top 1px solid #E5E5E5
margin-top 70px
padding 30px 0
text-align center
.btn
- border-radius 0 !important
+ border-radius 1 !important
+ border: none !important
diff --git a/client/app/templates/templates.js b/client/app/templates/templates.js
index 6572cd3..341ef36 100644
--- a/client/app/templates/templates.js
+++ b/client/app/templates/templates.js
@@ -1,11 +1,11 @@
'use strict';
angular.module('manticoreApp')
.config(function ($stateProvider) {
$stateProvider
- .state('templates', {
+ .state('manticore.templates', {
url: '/templates',
templateUrl: 'app/templates/templates.html',
controller: 'TemplatesCtrl'
});
- });
\ No newline at end of file
+ });
diff --git a/client/app/users/users.js b/client/app/users/users.js
index 8564f3e..ea6e60d 100644
--- a/client/app/users/users.js
+++ b/client/app/users/users.js
@@ -1,11 +1,11 @@
'use strict';
angular.module('manticoreApp')
.config(function ($stateProvider) {
$stateProvider
- .state('users', {
+ .state('manticore.users', {
url: '/users',
templateUrl: 'app/users/users.html',
controller: 'UsersCtrl'
});
});
diff --git a/client/components/createMenu/createMenu.jade b/client/components/createMenu/createMenu.jade
index 461d7b9..71256f3 100644
--- a/client/components/createMenu/createMenu.jade
+++ b/client/components/createMenu/createMenu.jade
@@ -1,7 +1,7 @@
ul.dropdown-menu.create-menu
li(ng-repeat='template in templates')
- a(ui-sref='editor.fromTemplate({ id: template._id })' target='_blank')
+ a(ui-sref='manticore.editor.fromTemplate({ id: template._id })' target='_blank')
span.text-strong {{template.title}}
br
div.text-muted {{template.description}}
diff --git a/client/components/documentList/documentList.jade b/client/components/documentList/documentList.jade
index cd5712b..bd542a5 100644
--- a/client/components/documentList/documentList.jade
+++ b/client/components/documentList/documentList.jade
@@ -1,16 +1,16 @@
.container(ng-switch='documents.length === 0')
.no-docs(ng-switch-when='true')
p.advice No documents yet. Why not add some?
.document-list(ng-switch-when='false')
table.table.table-striped(st-table='displayedDocuments' st-safe-src='documents')
thead
tr
th Title
th Creator
th(st-sort='date' st-sort-default='reverse') Updated
tbody
tr(ng-repeat='document in displayedDocuments')
td.title
- a(ui-sref='editor.forDocument({id: document._id})' target='_blank') {{document.title}}
+ a(ui-sref='manticore.editor.forDocument({id: document._id})' target='_blank') {{document.title}}
td {{document.creator.name}}
td {{document.date | amCalendar}}
diff --git a/client/components/exportButton/exportButton.controller.js b/client/components/exportButton/exportButton.controller.js
index 45f6867..c55a0f5 100644
--- a/client/components/exportButton/exportButton.controller.js
+++ b/client/components/exportButton/exportButton.controller.js
@@ -1,25 +1,73 @@
'use strict';
angular.module('manticoreApp')
-.controller('ExportButtonCtrl', function ($scope, SaveAs) {
+.controller('ExportButtonCtrl', function ($scope, $http, $timeout, SaveAs) {
+ $scope.label = 'Download';
+ $scope.isExporting = false;
+ $scope.conversionHost = $scope.$root.config.conversionHost;
+
$scope.items = [
- {
- label: 'OpenDocument Text (.odt)',
- format: 'odt'
- }
+ { format: 'odt', label: 'OpenDocument Text (.odt)' },
];
+ if ($scope.conversionHost) {
+ $scope.items = $scope.items.concat([
+ { format: 'pdf', label: 'Portable Document Format (.pdf)' },
+ { format: 'txt', label: 'Plain Text (.txt)' },
+ { format: 'docx', label: 'Microsoft Word (.docx)' },
+ { format: 'doc', label: 'Microsoft Word, old (.doc)' },
+ { format: 'html', label: 'HTML (.html)' },
+ ]);
+ }
$scope.export = function (format) {
- var fileName = $scope.editor.getMetadata('dc:title') || $scope.document.title,
- addExtension = fileName.split('.').pop() !== 'odt';
+ $scope.label = 'Downloading...';
+ $scope.isExporting = true;
+
+ var title = $scope.editor.getMetadata('dc:title') || $scope.document.title,
+ fileName = title.replace(/\.[^.$]+$/, '');
- if (format === 'odt') {
- $scope.editor.getDocumentAsByteArray(function (err, data) {
+ $scope.editor.getDocumentAsByteArray(function (err, data) {
+ if (format === 'odt') {
SaveAs.download(
[data.buffer],
- fileName + (addExtension ? '.odt' : ''),
- { type: 'application/vnd.oasis.opendocument.text' });
- });
- }
+ fileName + '.odt',
+ { type: 'application/vnd.oasis.opendocument.text' }
+ );
+ $scope.label = 'Download';
+ $scope.isExporting = false;
+ } else {
+ var formData = new FormData();
+ formData.append('document', new Blob([data.buffer], { type: 'application/vnd.oasis.opendocument.text' }));
+ $http({
+ method: 'POST',
+ url: $scope.conversionHost + '/convert/' + format,
+ data: formData,
+ responseType: 'arraybuffer',
+ transformRequest: angular.identity,
+ transformResponse: angular.identity,
+ headers: { 'Content-Type': undefined }
+ })
+ .success(function (data, status, headers) {
+ SaveAs.download(
+ [data],
+ fileName + '.' + format,
+ { type: headers('content-type') }
+ );
+ $timeout(function () {
+ $scope.label = 'Download';
+ $scope.isExporting = false;
+ });
+ })
+ .error(function () {
+ $timeout(function () {
+ $scope.label = 'Error while downloading';
+ });
+ $timeout(function () {
+ $scope.label = 'Download';
+ $scope.isExporting = false;
+ }, 1000);
+ });
+ }
+ });
};
});
diff --git a/client/components/exportButton/exportButton.directive.js b/client/components/exportButton/exportButton.directive.js
index 0a9c7e2..d8b8dad 100644
--- a/client/components/exportButton/exportButton.directive.js
+++ b/client/components/exportButton/exportButton.directive.js
@@ -1,10 +1,11 @@
'use strict';
angular.module('manticoreApp')
.directive('exportButton', function () {
return {
templateUrl: 'components/exportButton/exportButton.html',
restrict: 'E',
- controller: 'ExportButtonCtrl'
+ controller: 'ExportButtonCtrl',
+ scope: true
};
});
diff --git a/client/components/exportButton/exportButton.jade b/client/components/exportButton/exportButton.jade
index 3854e59..7cb6499 100644
--- a/client/components/exportButton/exportButton.jade
+++ b/client/components/exportButton/exportButton.jade
@@ -1,7 +1,9 @@
div.export-button.btn-group(dropdown)
- button.btn.btn-danger(type='button' ng-click='export(items[0].format)') Download
- button.btn.btn-danger.dropdown-toggle(dropdown-toggle)
+ button.btn.btn-danger(type='button' ng-click='export(items[0].format)' ng-disabled='isExporting')
+ i.fa(ng-class='isExporting ? "fa-spin fa-circle-o-notch": "fa-cloud-download"')
+ | {{label}}
+ button.btn.btn-danger.dropdown-toggle(dropdown-toggle ng-show='conversionHost' ng-disabled='isExporting')
span.caret
ul.dropdown-menu(role='menu')
li(ng-repeat='item in items')
a(href='#' ng-click='export(item.format)') {{item.label}}...
diff --git a/client/components/exportButton/exportButton.styl b/client/components/exportButton/exportButton.styl
index 6d6c501..0a2e1c2 100644
--- a/client/components/exportButton/exportButton.styl
+++ b/client/components/exportButton/exportButton.styl
@@ -1,3 +1,2 @@
.export-button
- padding: 10px 10px;
- height: 50px;
+ margin: 10px 10px;
diff --git a/client/components/navbar/navbar.jade b/client/components/navbar/navbar.jade
index b2404e0..001a9ba 100644
--- a/client/components/navbar/navbar.jade
+++ b/client/components/navbar/navbar.jade
@@ -1,46 +1,46 @@
div.navbar.navbar-default.navbar-static-top(ng-controller='NavbarCtrl')
div.container
div.navbar-header
button.navbar-toggle(type='button', ng-click='isCollapsed = !isCollapsed')
span.sr-only Toggle navigation
span.icon-bar
span.icon-bar
span.icon-bar
- a.nav-text.navbar-brand(href='/') manticore
+ a.nav-text.navbar-brand(ui-sref='manticore.main') manticore
div#navbar-main.navbar-collapse.collapse(collapse='isCollapsed')
ul.nav.navbar-nav
li(ng-show='isAdmin()', ng-class='{active: isActive("/users")}')
- a.nav-text(ui-sref='users') Users
+ a.nav-text(ui-sref='manticore.users') Users
li(ng-show='isAdmin()', ng-class='{active: isActive("/templates")}')
- a.nav-text(ui-sref='templates') Templates
+ a.nav-text(ui-sref='manticore.templates') Templates
li.dropdown(ng-if='isLoggedIn() && isActive("/")' dropdown)
a.nav-text.dropdown-toggle(href='#' dropdown-toggle role='button')
| New
span.caret
create-menu
li.dropdown(ng-if='isLoggedIn() && isActive("/")' dropdown auto-close='disabled')
a.nav-text.dropdown-toggle(href='#' dropdown-toggle role='button')
| Import
span.caret
div.dropdown-menu(ng-include='"components/import/import.html"')
ul.nav.navbar-nav.navbar-right
li(ng-hide='isLoggedIn()', ng-class='{active: isActive("/signup")}')
a.nav-text(href='/signup') Sign up
li(ng-hide='isLoggedIn()', ng-class='{active: isActive("/login")}')
a.nav-text(href='/login') Login
li(ng-show='isLoggedIn()')
p.nav-text.navbar-text Hello {{ getCurrentUser().name }}
li(ng-show='isLoggedIn()', ng-class='{active: isActive("/settings")}')
a.nav-text(href='/settings')
span.glyphicon.glyphicon-cog
li(ng-show='isLoggedIn()', ng-class='{active: isActive("/logout")}')
a.nav-text(href='', ng-click='logout()') Logout
diff --git a/client/components/saveButton/saveButton.controller.js b/client/components/saveButton/saveButton.controller.js
index d0f6f13..8ffcbfd 100644
--- a/client/components/saveButton/saveButton.controller.js
+++ b/client/components/saveButton/saveButton.controller.js
@@ -1,28 +1,28 @@
'use strict';
angular.module('manticoreApp')
.controller('SaveButtonCtrl', function ($scope, $timeout) {
$scope.label = 'Save';
$scope.isSaving = false;
$scope.save = function () {
$scope.label = 'Saving';
$scope.isSaving = true;
var socket = $scope.editor.clientAdaptor.getSocket();
socket.emit('save', function (err) {
$timeout(function () {
if (err) {
$scope.label = 'Error while saving';
} else {
$scope.label = 'Saved just now';
}
});
$timeout(function () {
$scope.label = 'Save';
$scope.isSaving = false;
- }, 5000);
+ }, 1000);
});
};
});
diff --git a/client/components/saveButton/saveButton.directive.js b/client/components/saveButton/saveButton.directive.js
index 3aa2c27..bce295b 100644
--- a/client/components/saveButton/saveButton.directive.js
+++ b/client/components/saveButton/saveButton.directive.js
@@ -1,10 +1,11 @@
'use strict';
angular.module('manticoreApp')
.directive('saveButton', function () {
return {
templateUrl: 'components/saveButton/saveButton.html',
restrict: 'E',
- controller: 'SaveButtonCtrl'
+ controller: 'SaveButtonCtrl',
+ scope: true
};
});
diff --git a/client/components/saveButton/saveButton.jade b/client/components/saveButton/saveButton.jade
index 642d4b0..d4c3aa6 100644
--- a/client/components/saveButton/saveButton.jade
+++ b/client/components/saveButton/saveButton.jade
@@ -1,2 +1,4 @@
div.save-button
- button.btn.btn-primary(type='button' ng-click='save()' ng-disabled='isSaving') {{label}}
+ button.btn.btn-primary(type='button' ng-click='save()' ng-disabled='isSaving')
+ i.fa(ng-class='isSaving ? "fa-spin fa-circle-o-notch": "fa-cloud-upload"')
+ | {{label}}
diff --git a/client/components/saveButton/saveButton.styl b/client/components/saveButton/saveButton.styl
index a52ebc5..906c612 100644
--- a/client/components/saveButton/saveButton.styl
+++ b/client/components/saveButton/saveButton.styl
@@ -1,3 +1,2 @@
.save-button
- padding: 10px 10px;
- height: 50px;
+ margin: 10px 10px;
diff --git a/client/components/titleEditor/titleEditor.styl b/client/components/titleEditor/titleEditor.styl
index 3d04259..bde1ae7 100644
--- a/client/components/titleEditor/titleEditor.styl
+++ b/client/components/titleEditor/titleEditor.styl
@@ -1,20 +1,19 @@
.title-container
display: block;
max-width: 400px;
- padding: 10px 10px;
- height: 50px;
+ margin: 10px 10px;
.title-editor
float: left;
font-size: 16px;
line-height: 20px;
padding: 5px;
border: none;
outline: none;
text-overflow: ellipsis;
resize: horizontal;
width: 100%;
text-decoration: underline;
background-color: white;
&:hover, &:focus
background-color: #eeeeee;
diff --git a/server/config/environment/index.js b/server/config/environment/index.js
index f1b3de7..e25996a 100644
--- a/server/config/environment/index.js
+++ b/server/config/environment/index.js
@@ -1,73 +1,75 @@
'use strict';
var path = require('path');
var _ = require('lodash');
function requiredProcessEnv(name) {
if(!process.env[name]) {
throw new Error('You must set the ' + name + ' environment variable');
}
return process.env[name];
}
// All configurations will extend these options
// ============================================
var all = {
env: process.env.NODE_ENV,
// Root path of server
root: path.normalize(__dirname + '/../../..'),
// Server port
port: process.env.PORT || 9000,
// Should we populate the DB with sample data?
seedDB: true,
// Secret for session, you will want to change this and make it an environment variable
secrets: {
session: 'manticore-secret'
},
// List of user roles
userRoles: ['guest', 'user', 'admin'],
// MongoDB connection options
mongo: {
options: {
db: {
safe: true
}
}
},
+ conversionHost: process.env.LOCODOC_SERVER,
+
auth: {
type: process.env.AUTH || 'local',
'webdav': {
server: process.env.WEBDAV_SERVER,
path: process.env.WEBDAV_PATH,
key: process.env.WEBDAV_ENCRYPTION_KEY
},
'ldap': {
server: process.env.LDAP_SERVER,
base: process.env.LDAP_BASE,
filter: process.env.LDAP_FILTER,
bindDn: process.env.LDAP_BIND_DN,
bindPw: process.env.LDAP_BIND_PW
}
},
storage: {
type: process.env.STORAGE || 'local',
'webdav': {
server: process.env.WEBDAV_SERVER,
path: process.env.WEBDAV_PATH,
key: process.env.WEBDAV_ENCRYPTION_KEY
}
}
};
// Export the config object based on the NODE_ENV
// ==============================================
module.exports = _.merge(
all,
require('./' + process.env.NODE_ENV + '.js') || {});
diff --git a/server/config/local.env.sample.js b/server/config/local.env.sample.js
index ca97837..c9a4bde 100644
--- a/server/config/local.env.sample.js
+++ b/server/config/local.env.sample.js
@@ -1,46 +1,49 @@
'use strict';
// Use local.env.js for environment variables that grunt will set when the server starts locally.
// Use for your api keys, secrets, etc. This file should not be tracked by git.
//
// You will need to set these on the server you deploy to.
module.exports = {
DOMAIN: 'http://localhost:9000',
SESSION_SECRET: 'manticore-secret',
// Control debug level for modules using visionmedia/debug
DEBUG: '',
/*
* Supported authentication strategies.
* 1. 'local' for using Manticore's built-in accounts system. Allow signups.
* 2. 'webdav' for authenticating against a WebDAV server. Only login, no signups.
* 3. 'ldap' for authenticating against an LDAP service. Only login, no signups.
*/
AUTH: 'local',
/*
* Supported storage backends.
* 1. 'local' for storing everything in Mongo/GridFS. The fastest and most reliable way.
* Can be used with any AUTH strategy.
* 2. 'webdav' for two-way synchronizing of documents with a WebDAV server.
* Can be used if AUTH is 'ldap' or 'webdav'; those credentials are used to talk to the webdav server.
*/
STORAGE: 'local',
/*
* WebDAV server config, only if AUTH or STORAGE is 'webdav'.
* Make sure you provide an encryption key to protect users' webdav credentials.
*/
WEBDAV_SERVER: 'https://kolabmachine',
WEBDAV_PATH: '/iRony/files/Files',
WEBDAV_ENCRYPTION_KEY: 'your-AES-encryption-key',
// LDAP server config, required iff AUTH is 'ldap'. {{username}} will be replaced with users' logins
LDAP_SERVER: 'ldaps://kolabmachine',
LDAP_BASE: 'ou=People,dc=test,dc=example,dc=org',
LDAP_FILTER: '(&(objectclass=person)(|(uid={{username}})(mail={{username}})))',
LDAP_BIND_DN: 'uid=kolab-service,ou=Special Users,dc=test,dc=example,dc=org',
- LDAP_BIND_PW: 'kolab-service-pass'
+ LDAP_BIND_PW: 'kolab-service-pass',
+
+ // locodoc (document format conversion) server
+ LOCODOC_SERVER: 'http://localhost:3030'
};
diff --git a/server/routes.js b/server/routes.js
index 4290697..c4c3470 100644
--- a/server/routes.js
+++ b/server/routes.js
@@ -1,28 +1,35 @@
/**
* Main application routes
*/
'use strict';
var errors = require('./components/errors');
+var config = require('./config/environment');
module.exports = function(app) {
// Insert routes below
app.use('/api/templates', require('./api/template'));
app.use('/api/documents', require('./api/document'));
app.use('/api/users', require('./api/user'));
app.use('/auth', require('./auth'));
+ app.get('/config', function (req, res) {
+ res.json(200, {
+ conversionHost: config.conversionHost
+ });
+ });
+
// All undefined asset or api routes should return a 404
app.route('/:url(api|auth|components|app|bower_components|assets)/*')
.get(errors[404]);
// All other routes (except dynamically-added genesis routes)
// should redirect to the index.html
app.route(/^\/(?!genesis).*/)
.get(function(req, res) {
res.sendfile(app.get('appPath') + '/index.html');
});
};