├── .gitmodules ├── CHANGELOG.md ├── module.suffix ├── module.prefix ├── .gitignore ├── .bowerrc ├── assets └── loading.gif ├── src ├── assets │ ├── favicon.png │ ├── loading.gif │ ├── background.png │ ├── generic_photo.png │ ├── fonts │ │ └── fontawesome-webfont.woff │ └── README.md ├── app │ ├── about │ │ ├── about.tpl.html │ │ └── about.js │ ├── list │ │ ├── list.spec.js │ │ ├── README.md │ │ ├── list.less │ │ ├── list.tpl.html │ │ └── list.js │ ├── app.spec.js │ ├── app.js │ └── README.md ├── less │ ├── variables.less │ ├── README.md │ └── main.less ├── common │ ├── README.md │ └── common.js ├── index.html └── README.md ├── .travis.yml ├── bower.json ├── vendor ├── ngProgress │ ├── ngProgress.less │ └── ngProgress.min.js ├── eventListener │ └── eventListener.js ├── notify │ └── notify.js ├── notifications │ └── notify.js ├── placeholders │ └── angular-placeholders-0.0.1-SNAPSHOT.min.js ├── font-awesome │ └── font-awesome.less └── moment │ └── moment.min.js ├── changelog.tpl ├── LICENSE ├── package.json ├── karma └── karma-unit.tpl.js ├── README.md ├── tools.md └── Gruntfile.js /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /module.suffix: -------------------------------------------------------------------------------- 1 | })( window, window.angular ); 2 | -------------------------------------------------------------------------------- /module.prefix: -------------------------------------------------------------------------------- 1 | (function ( window, angular, undefined ) { 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw* 2 | *~ 3 | build/ 4 | bin/ 5 | node_modules/ 6 | vendor/ 7 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "vendor", 3 | "json": "bower.json" 4 | } 5 | 6 | -------------------------------------------------------------------------------- /assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deiu/warp/master/assets/loading.gif -------------------------------------------------------------------------------- /src/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deiu/warp/master/src/assets/favicon.png -------------------------------------------------------------------------------- /src/assets/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deiu/warp/master/src/assets/loading.gif -------------------------------------------------------------------------------- /src/app/about/about.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | This is your about page! 3 |
4 | 5 | -------------------------------------------------------------------------------- /src/assets/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deiu/warp/master/src/assets/background.png -------------------------------------------------------------------------------- /src/assets/generic_photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deiu/warp/master/src/assets/generic_photo.png -------------------------------------------------------------------------------- /src/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/deiu/warp/master/src/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/assets/README.md: -------------------------------------------------------------------------------- 1 | # The `src/assets` Directory 2 | 3 | There's really not much to say here. Every file in this directory is recursively transferred to `dist/assets/`. 4 | 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | - npm install --quiet -g grunt-cli karma bower 9 | - npm install 10 | - bower install 11 | 12 | script: grunt 13 | 14 | -------------------------------------------------------------------------------- /src/less/variables.less: -------------------------------------------------------------------------------- 1 | /** 2 | * These are the variables used throughout the application. This is where 3 | * overwrites that are not specific to components should be maintained. 4 | */ 5 | 6 | /** 7 | * Typography-related. 8 | */ 9 | 10 | @sansFontFamily: 'Roboto', sans-serif; 11 | 12 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "version": "1.1.0", 4 | "devDependencies": { 5 | "angular": "~1.2", 6 | "angular-mocks": "~1.2", 7 | "bootstrap": "~3.1", 8 | "angular-bootstrap": "~0.10.0", 9 | "angular-ui-router": "~0.2" 10 | }, 11 | "dependencies": { 12 | "ng-file-upload": "~3.2.4" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/list/list.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests sit right alongside the file they are testing, which is more intuitive 3 | * and portable than separating `src` and `test` directories. Additionally, the 4 | * build process will exclude all `.spec.js` files from the build 5 | * automatically. 6 | */ 7 | describe('home section', function() { 8 | beforeEach( module( 'App.home' ) ); 9 | 10 | }); -------------------------------------------------------------------------------- /src/app/about/about.js: -------------------------------------------------------------------------------- 1 | angular.module( 'App.about', [ 2 | 'ui.router', 3 | 'placeholders', 4 | 'ui.bootstrap' 5 | ]) 6 | 7 | .config(function config( $stateProvider ) { 8 | $stateProvider.state( 'about', { 9 | url: '/about', 10 | views: { 11 | "main": { 12 | controller: 'AboutCtrl', 13 | templateUrl: 'about/about.tpl.html' 14 | } 15 | }, 16 | data:{ pageTitle: 'What is It?' } 17 | }); 18 | }) 19 | 20 | .controller( 'AboutCtrl', function AboutCtrl( $scope ) { 21 | // blank 22 | 23 | }) 24 | 25 | ; 26 | -------------------------------------------------------------------------------- /src/app/app.spec.js: -------------------------------------------------------------------------------- 1 | describe( 'MainCtrl', function() { 2 | describe( 'isCurrentUrl', function() { 3 | var MainCtrl, $location, $scope; 4 | 5 | beforeEach( module( 'App' ) ); 6 | 7 | beforeEach( inject( function( $controller, _$location_, $rootScope ) { 8 | $location = _$location_; 9 | $scope = $rootScope.$new(); 10 | MainCtrl = $controller( 'MainCtrl', { $location: $location, $scope: $scope }); 11 | })); 12 | 13 | it( 'should pass a dummy test', inject( function() { 14 | expect( MainCtrl ).toBeTruthy(); 15 | })); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /vendor/ngProgress/ngProgress.less: -------------------------------------------------------------------------------- 1 | /* Styling for the ngProgress itself */ 2 | #ngProgress { 3 | position: absolute; 4 | margin: 0; 5 | padding: 0; 6 | z-index: 99998; 7 | background-color: green; 8 | color: green; 9 | box-shadow: 0 0 10px 0; /* Inherits the font color */ 10 | height: 2px; 11 | opacity: 0; 12 | overflow-x: hidden; 13 | 14 | /* Add CSS3 styles for transition smoothing */ 15 | -webkit-transition: all 0.5s ease-in-out; 16 | -moz-transition: all 0.5s ease-in-out; 17 | -o-transition: all 0.5s ease-in-out; 18 | transition: all 0.5s ease-in-out; 19 | } 20 | 21 | /* Styling for the ngProgress-container */ 22 | #ngProgress-container { 23 | position: absolute; 24 | margin: 0; 25 | padding: 0; 26 | top: 0; 27 | left: 0; 28 | right: 0; 29 | z-index: 99999; 30 | } 31 | -------------------------------------------------------------------------------- /changelog.tpl: -------------------------------------------------------------------------------- 1 | 2 | # <%= version%> (<%= today%>) 3 | 4 | <% if (_(changelog.feat).size() > 0) { %> ## Features 5 | <% _(changelog.feat).forEach(function(changes, scope) { %> 6 | - **<%= scope%>:** 7 | <% changes.forEach(function(change) { %> - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>) 8 | <% }); %> 9 | <% }); %> <% } %> 10 | 11 | <% if (_(changelog.fix).size() > 0) { %> ## Fixes 12 | <% _(changelog.fix).forEach(function(changes, scope) { %> 13 | - **<%= scope%>:** 14 | <% changes.forEach(function(change) { %> - <%= change.msg%> (<%= helpers.commitLink(change.sha1) %>) 15 | <% }); %> 16 | <% }); %> <% } %> 17 | 18 | <% if (_(changelog.breaking).size() > 0) { %> ## Breaking Changes 19 | <% _(changelog.breaking).forEach(function(changes, scope) { %> 20 | - **<%= scope%>:** 21 | <% changes.forEach(function(change) { %> <%= change.msg%> 22 | <% }); %> 23 | <% }); %> <% } %> 24 | -------------------------------------------------------------------------------- /src/common/README.md: -------------------------------------------------------------------------------- 1 | # The `src/common/` Directory 2 | 3 | The `src/common/` directory houses internal and third-party re-usable 4 | components. Essentially, this folder is for everything that isn't completely 5 | specific to this application. 6 | 7 | Each component resides in its own directory that may then be structured any way 8 | the developer desires. The build system will read all `*.js` files that do not 9 | end in `.spec.js` as source files to be included in the final build, all 10 | `*.spec.js` files as unit tests to be executed, and all `*.tpl.html` files as 11 | templates to compiled into the `$templateCache`. There is currently no way to 12 | handle components that do not meet this pattern. 13 | 14 | ``` 15 | src/ 16 | |- common/ 17 | | |- plusOne/ 18 | ``` 19 | 20 | - `plusOne` - a simple directive to load a Google +1 Button on an element. 21 | 22 | Every component contained here should be drag-and-drop reusable in any other 23 | project; they should depend on no other components that aren't similarly 24 | drag-and-drop reusable. 25 | -------------------------------------------------------------------------------- /vendor/eventListener/eventListener.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jQuery postMessage - v0.5 - 9/11/2009 3 | * http://benalman.com/projects/jquery-postmessage-plugin/ 4 | * 5 | * Copyright (c) 2009 "Cowboy" Ben Alman 6 | * Dual licensed under the MIT and GPL licenses. 7 | * http://benalman.com/about/license/ 8 | */ 9 | (function($){var g,d,j=1,a,b=this,f=!1,h="postMessage",e="addEventListener",c,i=b[h]&&!$.browser.opera;$[h]=function(k,l,m){if(!l){return}k=typeof k==="string"?k:$.param(k);m=m||parent;if(i){m[h](k,l.replace(/([^:]+:\/\/[^\/]+).*/,"$1"))}else{if(l){m.location=l.replace(/#.*$/,"")+"#"+(+new Date)+(j++)+"&"+k}}};$.receiveMessage=c=function(l,m,k){if(i){if(l){a&&c();a=function(n){if((typeof m==="string"&&n.origin!==m)||($.isFunction(m)&&m(n.origin)===f)){return f}l(n)}}if(b[e]){b[l?e:"removeEventListener"]("message",a,f)}else{b[l?"attachEvent":"detachEvent"]("onmessage",a)}}else{g&&clearInterval(g);g=null;if(l){k=typeof m==="number"?m:typeof k==="number"?k:100;g=setInterval(function(){var o=document.location.hash,n=/^#?\d+&/;if(o!==d&&n.test(o)){d=o;l({data:o.replace(n,"")})}},k)}}}})(jQuery); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 2 | 3 | Josh David Miller 4 | Andrei Sambra 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this software and associated documentation files (the "Software"), to deal in 8 | the Software without restriction, including without limitation the rights to 9 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | of the Software, and to permit persons to whom the Software is furnished to do 11 | so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <% styles.forEach( function ( file ) { %> 12 | <% }); %> 13 | 14 | 15 | 16 | 17 | <% scripts.forEach( function ( file ) { %> 18 | <% }); %> 19 | 20 | 21 |
22 | 23 |
24 |
25 | 30 |
31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Your Name", 3 | "name": "App", 4 | "version": "1.1.0", 5 | "homepage": "https://github.com/user/appname", 6 | "licenses": { 7 | "type": "MIT", 8 | "url": "https://raw.github.com/user/appname/master/LICENSE" 9 | }, 10 | "bugs": "https://github.com/user/appname/issues", 11 | "repository": { 12 | "type": "git", 13 | "url": "git@github.com:user/appname.git" 14 | }, 15 | "dependencies": {}, 16 | "devDependencies": { 17 | "chai": "^1.9.1", 18 | "grunt": "~0.4.1", 19 | "grunt-bump": "0.0.6", 20 | "grunt-coffeelint": "~0.0.10", 21 | "grunt-contrib-clean": "^0.4.1", 22 | "grunt-contrib-coffee": "^0.7.0", 23 | "grunt-contrib-concat": "^0.3.0", 24 | "grunt-contrib-copy": "^0.4.1", 25 | "grunt-contrib-jshint": "^0.4.3", 26 | "grunt-contrib-less": "~0.11.0", 27 | "grunt-contrib-uglify": "^0.2.7", 28 | "grunt-contrib-watch": "^0.4.4", 29 | "grunt-conventional-changelog": "^0.1.2", 30 | "grunt-html2js": "^0.1.9", 31 | "grunt-karma": "^0.8.2", 32 | "grunt-ngmin": "0.0.2", 33 | "karma": "^0.12.9", 34 | "karma-chai": "^0.1.0", 35 | "karma-coffee-preprocessor": "^0.2.1", 36 | "karma-firefox-launcher": "^0.1.3", 37 | "karma-jasmine": "^0.1.5", 38 | "karma-mocha": "^0.1.3", 39 | "mocha": "^1.18.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/less/README.md: -------------------------------------------------------------------------------- 1 | # The `src/less` Directory 2 | 3 | This folder is actually fairly self-explanatory: it contains your LESS/CSS files to be compiled during the build. 4 | The only important thing to note is that *only* `main.less` will be processed during the build, meaning that all 5 | other stylesheets must be *imported* into that one. 6 | 7 | This should operate somewhat like the routing; the `main.less` file contains all of the site-wide styles, while 8 | any styles that are route-specific should be imported into here from LESS files kept alongside the JavaScript 9 | and HTML sources of that component. For example, the `home` section of the site has some custom styles, which 10 | are imported like so: 11 | 12 | ```css 13 | @import '../app/home/home.less'; 14 | ``` 15 | 16 | The same principal, though not demonstrated in the code, would also apply to reusable components. CSS or LESS 17 | files from external components would also be imported. If, for example, we had a Twitter feed directive with 18 | an accompanying template and style, we would similarly import it: 19 | 20 | ```css 21 | @import '../common/twitterFeed/twitterFeedDirective.less'; 22 | ``` 23 | 24 | Using this decentralized approach for all our code (JavaScript, HTML, and CSS) creates a framework where a 25 | component's directory can be dragged and dropped into *any other project* and it will "just work". 26 | 27 | I would like to eventually automate the importing during the build so that manually importing it here would no 28 | longer be required, but more thought must be put in to whether this is the best approach. 29 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # The `src` Directory 2 | 3 | ## Overview 4 | 5 | The `src/` directory contains all code used in the application along with all 6 | tests of such code. 7 | 8 | ``` 9 | src/ 10 | |- app/ 11 | | |- about/ 12 | | |- home/ 13 | | |- app.js 14 | | |- app.spec.js 15 | |- assets/ 16 | |- common/ 17 | | |- plusOne/ 18 | |- less/ 19 | | |- main.less 20 | | |- variables.less 21 | |- index.html 22 | ``` 23 | 24 | - `src/app/` - application-specific code, i.e. code not likely to be reused in 25 | another application. [Read more »](app/README.md) 26 | - `src/assets/` - static files like fonts and images. 27 | [Read more »](assets/README.md) 28 | - `src/common/` - third-party libraries or components likely to be reused in 29 | another application. [Read more »](common/README.md) 30 | - `src/less/` - LESS CSS files. [Read more »](less/README.md) 31 | - `src/index.html` - this is the HTML document of the single-page application. 32 | See below. 33 | 34 | See each directory for a detailed explanation. 35 | 36 | ## `index.html` 37 | 38 | The `index.html` file is the HTML document of the single-page application (SPA) 39 | that should contain all markup that applies to everything in the app, such as 40 | the header and footer. It declares with `ngApp` that this is `ngBoilerplate`, 41 | specifies the main `AppCtrl` controller, and contains the `ngView` directive 42 | into which route templates are placed. 43 | 44 | Unlike any other HTML document (e.g. the templates), `index.html` is compiled as 45 | a Grunt template, so variables from `Gruntfile.js` and `package.json` can be 46 | referenced from within it. Changing `name` in `package.json` from 47 | "ng-boilerplate" will rename the resultant CSS and JavaScript placed in `build/`, 48 | so this HTML references them by variable for convenience. 49 | -------------------------------------------------------------------------------- /karma/karma-unit.tpl.js: -------------------------------------------------------------------------------- 1 | module.exports = function ( karma ) { 2 | karma.set({ 3 | /** 4 | * From where to look for files, starting with the location of this file. 5 | */ 6 | basePath: '../', 7 | 8 | /** 9 | * This is the list of file patterns to load into the browser during testing. 10 | */ 11 | files: [ 12 | <% scripts.forEach( function ( file ) { %>'<%= file %>', 13 | <% }); %> 14 | 'src/**/*.js', 15 | 'src/**/*.coffee', 16 | ], 17 | exclude: [ 18 | 'src/assets/**/*.js' 19 | ], 20 | frameworks: [ 'jasmine' ], 21 | plugins: [ 'karma-jasmine', 'karma-firefox-launcher', 'karma-coffee-preprocessor' ], 22 | preprocessors: { 23 | '**/*.coffee': 'coffee', 24 | }, 25 | 26 | /** 27 | * How to report, by default. 28 | */ 29 | reporters: 'dots', 30 | 31 | /** 32 | * On which port should the browser connect, on which port is the test runner 33 | * operating, and what is the URL path for the browser to use. 34 | */ 35 | port: 9018, 36 | runnerPort: 9100, 37 | urlRoot: '/', 38 | 39 | /** 40 | * Disable file watching by default. 41 | */ 42 | autoWatch: true, 43 | 44 | /** 45 | * The list of browsers to launch to test on. This includes only "Firefox" by 46 | * default, but other browser names include: 47 | * Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS 48 | * 49 | * Note that you can also use the executable name of the browser, like "chromium" 50 | * or "firefox", but that these vary based on your operating system. 51 | * 52 | * You may also leave this blank and manually navigate your browser to 53 | * http://localhost:9018/ when you're running tests. The window/tab can be left 54 | * open and the tests will automatically occur there during the build. This has 55 | * the aesthetic advantage of not launching a browser every time you save. 56 | */ 57 | browsers: [ 58 | 'Firefox' 59 | ] 60 | }); 61 | }; 62 | 63 | -------------------------------------------------------------------------------- /vendor/notify/notify.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', function () { 2 | Notification.requestPermission(function (status) { 3 | // This allows to use Notification.permission with Chrome/Safari 4 | if (Notification.permission !== status) { 5 | Notification.permission = status; 6 | } 7 | }); 8 | }); 9 | 10 | function notify(type, body, timeout) { 11 | var icon = 'assets/favicon.png'; 12 | if (!timeout) { 13 | var timeout = 2000; 14 | } 15 | 16 | // Let's check if the browser supports notifications 17 | if (!("Notification" in window)) { 18 | console.log("This browser does not support desktop notification"); 19 | } 20 | 21 | // At last, if the user already denied any notification, and you 22 | // want to be respectful there is no need to bother him any more. 23 | // Let's check if the user is okay to get some notification 24 | if (Notification.permission === "granted") { 25 | // If it's okay let's create a notification 26 | var notification = new Notification(type, { 27 | dir: "auto", 28 | lang: "", 29 | icon: icon, 30 | body: body, 31 | tag: "notif" 32 | }); 33 | setTimeout(function() { notification.close(); }, timeout); 34 | } 35 | }; 36 | 37 | function authorizeNotifications() { 38 | var status = getNotifStatus(); 39 | // Let's check if the browser supports notifications 40 | if (!("Notification" in window)) { 41 | console.log("This browser does not support desktop notification"); 42 | } 43 | 44 | if (status !== 'granted') { 45 | Notification.requestPermission(function (permission) { 46 | // Whatever the user answers, we make sure we store the information 47 | Notification.permission = permission; 48 | }); 49 | } else if (status === 'granted') { 50 | Notification.permission = 'denied'; 51 | } 52 | }; 53 | 54 | function getNotifStatus() { 55 | // Let's check if the browser supports notifications 56 | if (!("Notification" in window)) { 57 | console.log("This browser does not support desktop notification"); 58 | return undefined 59 | } else { 60 | return Notification.permission; 61 | } 62 | }; -------------------------------------------------------------------------------- /vendor/notifications/notify.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('load', function () { 2 | Notification.requestPermission(function (status) { 3 | // This allows to use Notification.permission with Chrome/Safari 4 | if (Notification.permission !== status) { 5 | Notification.permission = status; 6 | } 7 | }); 8 | }); 9 | 10 | function notify(type, body, timeout) { 11 | var icon = 'assets/favicon.png'; 12 | if (!timeout) { 13 | var timeout = 2000; 14 | } 15 | 16 | // Let's check if the browser supports notifications 17 | if (!("Notification" in window)) { 18 | console.log("This browser does not support desktop notification"); 19 | } 20 | 21 | // At last, if the user already denied any notification, and you 22 | // want to be respectful there is no need to bother him any more. 23 | // Let's check if the user is okay to get some notification 24 | if (Notification.permission === "granted") { 25 | // If it's okay let's create a notification 26 | var notification = new Notification(type, { 27 | dir: "auto", 28 | lang: "", 29 | icon: icon, 30 | body: body, 31 | tag: "notif" 32 | }); 33 | setTimeout(function() { notification.close(); }, timeout); 34 | } 35 | }; 36 | 37 | function authorizeNotifications() { 38 | var status = getNotifStatus(); 39 | // Let's check if the browser supports notifications 40 | if (!("Notification" in window)) { 41 | console.log("This browser does not support desktop notification"); 42 | } 43 | 44 | if (status !== 'granted') { 45 | Notification.requestPermission(function (permission) { 46 | // Whatever the user answers, we make sure we store the information 47 | Notification.permission = permission; 48 | }); 49 | } else if (status === 'granted') { 50 | Notification.permission = 'denied'; 51 | } 52 | }; 53 | 54 | function getNotifStatus() { 55 | // Let's check if the browser supports notifications 56 | if (!("Notification" in window)) { 57 | console.log("This browser does not support desktop notification"); 58 | return undefined 59 | } else { 60 | return Notification.permission; 61 | } 62 | }; -------------------------------------------------------------------------------- /src/app/list/README.md: -------------------------------------------------------------------------------- 1 | # The `src/app/view` Directory 2 | 3 | ## Overview 4 | 5 | ``` 6 | src/ 7 | |- app/ 8 | | |- view/ 9 | | | |- view.js 10 | | | |- view.less 11 | | | |- view.spec.js 12 | | | |- view.tpl.html 13 | ``` 14 | 15 | - `view.js` - defines the module. 16 | - `view.less` - module-specific styles; this file is imported into 17 | `src/less/main.less` manually by the developer. 18 | - `view.spec.js` - module unit tests. 19 | - `view.tpl.html` - the route template. 20 | 21 | ## `view.js` 22 | 23 | This boilerplate is too simple to demonstrate it, but `src/app/view` could have 24 | several sub-folders representing additional modules that would then be listed 25 | as dependencies of this one. For example, a `note` section could have the 26 | submodules `note.create`, `note.delete`, `note.search`, etc. 27 | 28 | Regardless, so long as dependencies are managed correctly, the build process 29 | will automatically take take of the rest. 30 | 31 | The dependencies block is also where component dependencies should be 32 | specified, as shown below. 33 | 34 | ```js 35 | angular.module( 'ngBoilerplate.view', [ 36 | 'ui.router', 37 | 'titleService', 38 | 'plusOne' 39 | ]) 40 | ``` 41 | 42 | Each section or module of the site can also have its own routes. AngularJS will 43 | handle ensuring they are all available at run-time, but splitting it this way 44 | makes each module more self-contained. We use [ui-router](https://github.com/angular-ui/ui-router) to create 45 | a state for our 'view' page. We set the url we'd like to see in the address bar 46 | as well as the controller and template file to load. Specifying "main" as our view 47 | means the controller and template will be loaded into the
element 48 | of the root template (aka index.html). Read more over at the [ui-router wiki](https://github.com/angular-ui/ui-router/wiki). 49 | Finally we add a custom data property, pageTitle, which will be used to set the page's 50 | title (see the app.js controller). 51 | 52 | ```js 53 | .config(function config( $stateProvider ) { 54 | $stateProvider.state( 'view', { 55 | url: '/view', 56 | views: { 57 | "main": { 58 | controller: 'HomeCtrl', 59 | templateUrl: 'view/view.tpl.html' 60 | } 61 | }, 62 | data:{ pageTitle: 'Home' } 63 | }); 64 | }) 65 | ``` 66 | 67 | And of course we define a controller for our route, though in this case it does 68 | nothing. 69 | 70 | ```js 71 | .controller( 'HomeCtrl', function HomeController( $scope ) { 72 | }) 73 | ``` 74 | -------------------------------------------------------------------------------- /src/app/app.js: -------------------------------------------------------------------------------- 1 | // Globals 2 | var PROXY = "https://rww.io/proxy?uri={uri}"; 3 | var AUTH_PROXY = "https://rww.io/auth-proxy?uri="; 4 | var TIMEOUT = 90000; 5 | var DEBUG = true; 6 | 7 | // Angular 8 | angular.module( 'App', [ 9 | 'templates-app', 10 | 'templates-common', 11 | 'App.list', 12 | 'App.about', 13 | 'ui.router' 14 | ]) 15 | 16 | .config( function AppConfig ( $stateProvider, $urlRouterProvider ) { 17 | $urlRouterProvider.otherwise( '/list/' ); 18 | }) 19 | 20 | .run( function run () { 21 | }) 22 | 23 | .controller( 'MainCtrl', function MainCtrl ( $scope, $location, $timeout, ngProgress ) { 24 | // Some default values 25 | ngProgress.height('3px'); 26 | ngProgress.color('#ff3c1f'); 27 | $scope.appuri = window.location.hostname+window.location.pathname; 28 | $scope.userProfile = {}; 29 | $scope.userProfile.picture = 'assets/generic_photo.png'; 30 | $scope.notifStatus = getNotifStatus(); 31 | $scope.notifEnabledTxt = ($scope.notifStatus=='granted')?'Notifications enabled.':'Notifications disabled.'; 32 | 33 | $scope.logout = function () { 34 | // Logout WebID (only works in Firefox and IE) 35 | if (document.all == null) { 36 | if (window.crypto) { 37 | try{ 38 | window.crypto.logout(); //firefox ok -- no need to follow the link 39 | } catch (err) {//Safari, Opera, Chrome -- try with tis session breaking 40 | } 41 | } 42 | } else { // MSIE 6+ 43 | document.execCommand('ClearAuthenticationCache'); 44 | } 45 | 46 | // clear sessionStorage 47 | $scope.clearLocalCredentials(); 48 | $scope.userProfile = {}; 49 | $location.path('/list'); 50 | }; 51 | 52 | $scope.myList = function() { 53 | if ($scope.userProfile.storagespace && $scope.userProfile.storagespace.length > 0) { 54 | $location.path('/list/'+stripSchema($scope.userProfile.storagespace)); 55 | } 56 | }; 57 | 58 | // clear sessionStorage 59 | $scope.clearLocalCredentials = function () { 60 | sessionStorage.removeItem($scope.appuri); 61 | }; 62 | 63 | $scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ 64 | if ( angular.isDefined( toState.data.pageTitle ) ) { 65 | $scope.pageTitle = 'Warp | ' + toState.data.pageTitle; 66 | } 67 | }); 68 | 69 | // initialize by retrieving user info from sessionStorage 70 | // retrieve from sessionStorage 71 | if (sessionStorage.getItem($scope.appuri)) { 72 | var app = JSON.parse(sessionStorage.getItem($scope.appuri)); 73 | if (app.userProfile) { 74 | if (!$scope.userProfile) { 75 | $scope.userProfile = {}; 76 | } 77 | $scope.userProfile = app.userProfile; 78 | $scope.loggedin = true; 79 | } else { 80 | // clear sessionStorage in case there was a change to the data structure 81 | sessionStorage.removeItem($scope.appuri); 82 | } 83 | } 84 | 85 | $scope.checkNotif = function() { 86 | return getNotifStatus(); 87 | }; 88 | 89 | $scope.authorizeNotifications = function() { 90 | authorizeNotifications(); 91 | }; 92 | 93 | }); 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [warp](https://github.com/deiu/warp) 2 | 3 | Warp - the fastest Linked Data resource browser. 4 | 5 | *** 6 | 7 | ## Quick Start 8 | 9 | Install Node.js and then: 10 | 11 | ```sh 12 | $ git clone git://github.com/deiu/warp 13 | $ cd warp 14 | $ sudo npm -g install grunt-cli karma bower 15 | $ npm install 16 | $ bower install 17 | $ grunt watch 18 | ``` 19 | 20 | Finally, open `file:///path/to/warp/build/index.html` in your browser. 21 | 22 | Happy hacking! 23 | 24 | ## Purpose 25 | 26 | `warp` is the first client Web app that acts both as a resource browser app 27 | as well as a skin for data store servers (see [gold](https://github.com/linkeddata/gold)). 28 | It is built around: [Twitter Bootstrap](http://getbootstrap.com), 29 | [Angular UI](http://angular-ui.github.io), 30 | [Angular Bootstrap](http://angular-ui.github.io/bootstrap), 31 | [Font Awesome](http://fortawesome.github.com/Font-Awesome), and 32 | [LESS](http://lesscss.org). Lastly, it contains a sophisticated 33 | [Grunt](http://gruntjs.org)-based build system to ensure maximum productivity. 34 | [rdflib.js](https://github.com/linkeddata/rdflib.js) - a library for working with RDF on the web. 35 | All you have to do is clone it and start coding/using it right away! 36 | 37 | ## Learn 38 | 39 | ### Overall Directory Structure 40 | 41 | At a high level, the structure looks roughly like this: 42 | 43 | ``` 44 | warp/ 45 | |- grunt-tasks/ 46 | |- karma/ 47 | |- src/ 48 | | |- app/ 49 | | | |- 50 | | |- assets/ 51 | | | |- 52 | | |- common/ 53 | | | |- 54 | | |- less/ 55 | | | |- main.less 56 | |- vendor/ 57 | | |- angular-bootstrap/ 58 | | |- bootstrap/ 59 | | |- placeholders/ 60 | |- .bowerrc 61 | |- bower.json 62 | |- build.config.js 63 | |- Gruntfile.js 64 | |- module.prefix 65 | |- module.suffix 66 | |- package.json 67 | ``` 68 | 69 | What follows is a brief description of each entry, but most directories contain 70 | their own `README.md` file with additional documentation, so browse around to 71 | learn more. 72 | 73 | - `karma/` - test configuration. 74 | - `src/` - our application sources. [Read more »](src/README.md) 75 | - `vendor/` - third-party libraries. [Bower](http://bower.io) will install 76 | packages here. Anything added to this directory will need to be manually added 77 | to `build.config.js` and `karma/karma-unit.js` to be picked up by the build 78 | system. 79 | - `.bowerrc` - the Bower configuration file. This tells Bower to install 80 | components into the `vendor/` directory. 81 | - `bower.json` - this is our project configuration for Bower and it contains the 82 | list of Bower dependencies we need. 83 | - `build.config.js` - our customizable build settings; see "The Build System" 84 | below. 85 | - `Gruntfile.js` - our build script; see "The Build System" below. 86 | - `module.prefix` and `module.suffix` - our compiled application script is 87 | wrapped in these, which by default are used to place the application inside a 88 | self-executing anonymous function to ensure no clashes with other libraries. 89 | - `package.json` - metadata about the app, used by NPM and our build script. Our 90 | NPM dependencies are listed here. -------------------------------------------------------------------------------- /src/app/README.md: -------------------------------------------------------------------------------- 1 | # The `src/app` Directory 2 | 3 | ## Overview 4 | 5 | ``` 6 | src/ 7 | |- app/ 8 | | |- home/ 9 | | |- about/ 10 | | |- app.js 11 | | |- app.spec.js 12 | ``` 13 | 14 | The `src/app` directory contains all code specific to this application. Apart 15 | from `app.js` and its accompanying tests (discussed below), this directory is 16 | filled with subdirectories corresponding to high-level sections of the 17 | application, often corresponding to top-level routes. Each directory can have as 18 | many subdirectories as it needs, and the build system will understand what to 19 | do. For example, a top-level route might be "products", which would be a folder 20 | within the `src/app` directory that conceptually corresponds to the top-level 21 | route `/products`, though this is in no way enforced. Products may then have 22 | subdirectories for "create", "view", "search", etc. The "view" submodule may 23 | then define a route of `/products/:id`, ad infinitum. 24 | 25 | As `ngBoilerplate` is quite minimal, take a look at the two provided submodules 26 | to gain a better understanding of how these are used as well as to get a 27 | glimpse of how powerful this simple construct can be. 28 | 29 | ## `app.js` 30 | 31 | This is our main app configuration file. It kickstarts the whole process by 32 | requiring all the modules from `src/app` that we need. We must load these now to 33 | ensure the routes are loaded. If as in our "products" example there are 34 | subroutes, we only require the top-level module, and allow the submodules to 35 | require their own submodules. 36 | 37 | As a matter of course, we also require the template modules that are generated 38 | during the build. 39 | 40 | However, the modules from `src/common` should be required by the app 41 | submodules that need them to ensure proper dependency handling. These are 42 | app-wide dependencies that are required to assemble your app. 43 | 44 | ```js 45 | angular.module( 'ngBoilerplate', [ 46 | 'templates-app', 47 | 'templates-common', 48 | 'ngBoilerplate.home', 49 | 'ngBoilerplate.about' 50 | 'ui.router', 51 | 'ui.route' 52 | ]) 53 | ``` 54 | 55 | With app modules broken down in this way, all routing is performed by the 56 | submodules we include, as that is where our app's functionality is really 57 | defined. So all we need to do in `app.js` is specify a default route to follow, 58 | which route of course is defined in a submodule. In this case, our `home` module 59 | is where we want to start, which has a defined route for `/home` in 60 | `src/app/home/home.js`. 61 | 62 | ```js 63 | .config( function myAppConfig ( $stateProvider, $urlRouterProvider ) { 64 | $urlRouterProvider.otherwise( '/home' ); 65 | }) 66 | ``` 67 | 68 | Use the main applications run method to execute any code after services 69 | have been instantiated. 70 | 71 | ```js 72 | .run( function run () { 73 | }) 74 | ``` 75 | 76 | And then we define our main application controller. This is a good place for logic 77 | not specific to the template or route, such as menu logic or page title wiring. 78 | 79 | ```js 80 | .controller( 'AppCtrl', function AppCtrl ( $scope, $location ) { 81 | $scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ 82 | if ( angular.isDefined( toState.data.pageTitle ) ) { 83 | $scope.pageTitle = toState.data.pageTitle + ' | ngBoilerplate' ; 84 | } 85 | }); 86 | }) 87 | ``` 88 | 89 | ### Testing 90 | 91 | One of the design philosophies of `ngBoilerplate` is that tests should exist 92 | alongside the code they test and that the build system should be smart enough to 93 | know the difference and react accordingly. As such, the unit test for `app.js` 94 | is `app.spec.js`, though it is quite minimal. 95 | -------------------------------------------------------------------------------- /vendor/ngProgress/ngProgress.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | ngProgress 1.0.6 - slim, site-wide progressbar for AngularJS 3 | (C) 2013 - Victor Bjelkholm 4 | License: MIT 5 | Source: https://github.com/VictorBjelkholm/ngProgress 6 | Date Compiled: 2014-08-27 7 | */ 8 | angular.module("ngProgress.provider", ["ngProgress.directive"]).provider("ngProgress", function () { 9 | "use strict"; 10 | this.autoStyle = !0, this.count = 0, this.height = "2px", this.color = "firebrick", this.$get = ["$document", "$window", "$compile", "$rootScope", "$timeout", function (a, b, c, d, e) { 11 | var f = this.count, g = this.height, h = this.color, i = d, j = a.find("body")[0], k = c("")(i); 12 | j.appendChild(k[0]), i.count = f, void 0 !== g && k.eq(0).children().css("height", g), void 0 !== h && (k.eq(0).children().css("background-color", h), k.eq(0).children().css("color", h)); 13 | var l, m = 0; 14 | return { 15 | start: function () { 16 | this.show(); 17 | var a = this; 18 | clearInterval(m), m = setInterval(function () { 19 | if (isNaN(f))clearInterval(m), f = 0, a.hide(); else { 20 | var b = 100 - f; 21 | f += .15 * Math.pow(1 - Math.sqrt(b), 2), a.updateCount(f) 22 | } 23 | }, 200) 24 | }, updateCount: function (a) { 25 | i.count = a, i.$$phase || i.$apply() 26 | }, height: function (a) { 27 | return void 0 !== a && (g = a, i.height = g, i.$$phase || i.$apply()), g 28 | }, color: function (a) { 29 | return void 0 !== a && (h = a, i.color = h, i.$$phase || i.$apply()), h 30 | }, hide: function () { 31 | k.children().css("opacity", "0"); 32 | var a = this; 33 | a.animate(function () { 34 | k.children().css("width", "0%"), a.animate(function () { 35 | a.show() 36 | }, 500) 37 | }, 500) 38 | }, show: function () { 39 | var a = this; 40 | a.animate(function () { 41 | k.children().css("opacity", "1") 42 | }, 100) 43 | }, animate: function (a, b) { 44 | l && e.cancel(l), l = e(a, b) 45 | }, status: function () { 46 | return f 47 | }, stop: function () { 48 | clearInterval(m) 49 | }, set: function (a) { 50 | return this.show(), this.updateCount(a), f = a, clearInterval(m), f 51 | }, css: function (a) { 52 | return k.children().css(a) 53 | }, reset: function () { 54 | return clearInterval(m), f = 0, this.updateCount(f), 0 55 | }, complete: function () { 56 | f = 100, this.updateCount(f); 57 | var a = this; 58 | return clearInterval(m), e(function () { 59 | a.hide(), e(function () { 60 | f = 0, a.updateCount(f) 61 | }, 500) 62 | }, 1e3), f 63 | }, setParent: function (a) { 64 | if (null === a || void 0 === a)throw new Error("Provide a valid parent of type HTMLElement"); 65 | null !== j && void 0 !== j && j.removeChild(k[0]), j = a, j.appendChild(k[0]) 66 | }, getDomElement: function () { 67 | return k 68 | } 69 | } 70 | }], this.setColor = function (a) { 71 | return void 0 !== a && (this.color = a), this.color 72 | }, this.setHeight = function (a) { 73 | return void 0 !== a && (this.height = a), this.height 74 | } 75 | }), angular.module("ngProgress.directive", []).directive("ngProgress", ["$window", "$rootScope", function (a, b) { 76 | var c = { 77 | replace: !0, restrict: "E", link: function (a, c) { 78 | b.$watch("count", function (b) { 79 | (void 0 !== b || null !== b) && (a.counter = b, c.eq(0).children().css("width", b + "%")) 80 | }), b.$watch("color", function (b) { 81 | (void 0 !== b || null !== b) && (a.color = b, c.eq(0).children().css("background-color", b), c.eq(0).children().css("color", b)) 82 | }), b.$watch("height", function (b) { 83 | (void 0 !== b || null !== b) && (a.height = b, c.eq(0).children().css("height", b)) 84 | }) 85 | }, template: '
' 86 | }; 87 | return c 88 | }]), angular.module("ngProgress", ["ngProgress.directive", "ngProgress.provider"]); -------------------------------------------------------------------------------- /src/common/common.js: -------------------------------------------------------------------------------- 1 | var getProfile = function(scope, uri, profile, forWebID) { 2 | var RDF = $rdf.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#"); 3 | var FOAF = $rdf.Namespace("http://xmlns.com/foaf/0.1/"); 4 | var OWL = $rdf.Namespace("http://www.w3.org/2002/07/owl#"); 5 | var SPACE = $rdf.Namespace("http://www.w3.org/ns/pim/space#"); 6 | var SOLID = $rdf.Namespace("http://www.w3.org/ns/solid/terms#"); 7 | 8 | var g = $rdf.graph(); 9 | var f = $rdf.fetcher(g, TIMEOUT); 10 | // add CORS proxy 11 | $rdf.Fetcher.crossSiteProxyTemplate=PROXY; 12 | 13 | var webid = (forWebID)?forWebID:uri; 14 | var docURI = (uri.indexOf('#') >= 0)?uri.slice(0, uri.indexOf('#')):uri; 15 | var webidRes = $rdf.sym(webid); 16 | profile.loading = true; 17 | 18 | // fetch user data 19 | return new Promise(function(resolve) { 20 | f.nowOrWhenFetched(docURI,undefined,function(ok, body) { 21 | if (!ok) { 22 | profile.uri = webid; 23 | // if (!profile.name || profile.name.length === 0 || !forWebID) { 24 | // profile.name = webid; 25 | // } 26 | console.log('Warning - profile not found.'); 27 | profile.loading = false; 28 | scope.$apply(); 29 | return false; 30 | } else { 31 | if (!forWebID) { 32 | var sameAs = g.statementsMatching(webidRes, OWL('sameAs'), undefined); 33 | if (sameAs.length > 0) { 34 | sameAs.forEach(function(same){ 35 | if (same['object']['value'].length > 0) { 36 | getProfile(scope, same['object']['value'], profile, webid); 37 | } 38 | }); 39 | } 40 | var seeAlso = g.statementsMatching(webidRes, OWL('seeAlso'), undefined); 41 | if (seeAlso.length > 0) { 42 | seeAlso.forEach(function(see){ 43 | if (see['object']['value'].length > 0) { 44 | getProfile(scope, see['object']['value'], profile, webid); 45 | } 46 | }); 47 | } 48 | var prefs = g.statementsMatching(webidRes, SPACE('preferencesFile'), undefined); 49 | if (prefs.length > 0) { 50 | prefs.forEach(function(pref){ 51 | if (pref['object']['value'].length > 0) { 52 | getProfile(scope, pref['object']['value'], profile, webid); 53 | } 54 | }); 55 | } 56 | } 57 | 58 | var cls = g.statementsMatching(webidRes, RDF('type'), undefined)[0]; 59 | cls = (cls)?cls.value:''; 60 | 61 | var classType = (cls == FOAF('Group').value)?'agentClass':'agent'; 62 | // get some basic info 63 | var name = g.any(webidRes, FOAF('name')); 64 | // Clean up name 65 | name = (name)?name.value:''; 66 | var pic = g.any(webidRes, FOAF('img')); 67 | var depic = g.any(webidRes, FOAF('depiction')); 68 | // set avatar picture 69 | if (pic) { 70 | pic = pic.value; 71 | } else { 72 | if (depic) { 73 | pic = depic.value; 74 | } else { 75 | pic = ''; 76 | } 77 | } 78 | // get inbox 79 | var inbox = g.any(webidRes, SOLID('inbox')); 80 | // set values 81 | profile.webid = webid; 82 | if (!profile.predicate || profile.predicate.length === 0) { 83 | profile.predicate = classType; 84 | } 85 | if (!profile.fullname || profile.fullname.length === 0 || profile.fullname === webid) { 86 | profile.fullname = name; 87 | } 88 | if (!profile.picture || profile.picture.length === 0) { 89 | profile.picture = pic; 90 | } 91 | if (!profile.inbox || profile.inbox.length === 0) { 92 | if (inbox) { 93 | profile.inbox = inbox.uri; 94 | } 95 | } 96 | profile.loading = false; 97 | scope.$apply(); 98 | resolve(profile); 99 | } 100 | }); 101 | }); 102 | }; 103 | 104 | var isInteger = function(a) { 105 | return ((typeof a !== 'number') || (a % 1 !== 0)) ? false : true; 106 | }; 107 | 108 | stripSchema = function (url) { 109 | url = url.split('://'); 110 | var schema = (url[0].substring(0, 4) == 'http')?url[0]:''; 111 | var path = (url[1].length > 0)?url[1]:url[0]; 112 | return url[0]+'/'+url[1]; 113 | }; 114 | 115 | humanFileSize = function (bytes, si) { 116 | if (bytes == '-') { 117 | return bytes; 118 | } 119 | 120 | var thresh = (si)?1000:1024; 121 | if (bytes < thresh) { 122 | return bytes + ' B'; 123 | } 124 | var units = (si)? ['kB','MB','GB','TB','PB','EB','ZB','YB'] : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB']; 125 | var u = -1; 126 | do { 127 | bytes /= thresh; 128 | ++u; 129 | } while (bytes >= thresh); 130 | return bytes.toFixed(1)+' '+units[u]; 131 | }; 132 | 133 | dirname = function(path) { 134 | return path.replace(/\\/g, '/').replace(/\/[^\/]*\/?$/, ''); 135 | }; 136 | 137 | basename = function(path) { 138 | if (path.substring(path.length - 1) == '/') { 139 | path = path.substring(0, path.length - 1); 140 | } 141 | 142 | var a = path.split('/'); 143 | return a[a.length - 1]; 144 | }; 145 | 146 | // unquote string (utility) 147 | function unquote(value) { 148 | if (value.charAt(0) == '"' && value.charAt(value.length - 1) == '"') { 149 | return value.substring(1, value.length - 1); 150 | } 151 | return value; 152 | } 153 | 154 | function parseLinkHeader(header) { 155 | var linkexp = /<[^>]*>\s*(\s*;\s*[^\(\)<>@,;:"\/\[\]\?={} \t]+=(([^\(\)<>@,;:"\/\[\]\?={} \t]+)|("[^"]*")))*(,|$)/g; 156 | var paramexp = /[^\(\)<>@,;:"\/\[\]\?={} \t]+=(([^\(\)<>@,;:"\/\[\]\?={} \t]+)|("[^"]*"))/g; 157 | 158 | var matches = header.match(linkexp); 159 | var rels = {}; 160 | for (var i = 0; i < matches.length; i++) { 161 | var split = matches[i].split('>'); 162 | var href = split[0].substring(1); 163 | var ps = split[1]; 164 | var link = {}; 165 | link.href = href; 166 | var s = ps.match(paramexp); 167 | for (var j = 0; j < s.length; j++) { 168 | var p = s[j]; 169 | var paramsplit = p.split('='); 170 | var name = paramsplit[0]; 171 | link[name] = unquote(paramsplit[1]); 172 | } 173 | 174 | if (link.rel !== undefined) { 175 | rels[link.rel] = link; 176 | } 177 | } 178 | 179 | return rels; 180 | } -------------------------------------------------------------------------------- /src/app/list/list.less: -------------------------------------------------------------------------------- 1 | hr { 2 | margin-top: 0px; 3 | margin-bottom: 0px; 4 | } 5 | 6 | .index { 7 | font-size: 1.3em; 8 | position: relative; 9 | text-align: center; 10 | margin: 80px 10px 10px 10px; 11 | z-index: 800; 12 | } 13 | 14 | .index a { 15 | color: #000; 16 | cursor: pointer; 17 | text-decoration: initial; 18 | } 19 | 20 | .index table { 21 | width: 100%; 22 | } 23 | 24 | .index thead th { 25 | background: #ff3c1f; 26 | color: #fff; 27 | font-size: 1.2em; 28 | height: 35px; 29 | } 30 | .index th, caption { 31 | padding: 10px 10px 10px 5px; 32 | font-weight: bold; 33 | } 34 | 35 | .index tr { 36 | text-align: left; 37 | background: #fff; 38 | border-top: 1px dotted #ddd; 39 | } 40 | .index thead > tr { 41 | border-top: 1px solid #ddd; 42 | } 43 | .index tr:hover { 44 | background: #f2f2f2; 45 | } 46 | 47 | .index td { 48 | vertical-align: middle; 49 | padding: 0px 5px 0px 5px; 50 | word-break: normal; 51 | word-wrap: break-word; 52 | } 53 | 54 | .index td a { 55 | display: block; 56 | width: 100%; 57 | height: 100%; 58 | text-decoration: none; 59 | padding: 15px 20px 15px 0px; 60 | } 61 | 62 | .newdir { 63 | position: fixed; 64 | top: 150px; 65 | left: 100px; 66 | background: #fff; 67 | min-width: 300px; 68 | min-height: 300px; 69 | } 70 | 71 | .upload-files { 72 | width: 100%; 73 | } 74 | 75 | .upload-files td { 76 | min-width: 45px; 77 | padding-right: 5px; 78 | } 79 | 80 | .shadow { 81 | -moz-box-shadow: 0 2px 2px 1px #ddd; 82 | -webkit-box-shadow: 0 2px 2px 1px #ddd; 83 | box-shadow: 0 2px 2px 1px #ddd; 84 | } 85 | 86 | .box-shadow { 87 | -moz-box-shadow: 0 2px 2px 0px #ddd; 88 | -webkit-box-shadow: 0 2px 2px 0px #ddd; 89 | box-shadow: 0 2px 2px 0px #ddd; 90 | } 91 | 92 | .filename { 93 | width: 80%; 94 | } 95 | 96 | .dropzone { 97 | margin-top: 10px; 98 | margin-bottom: 10px; 99 | border: 4px dotted #ddd; 100 | padding: 20px; 101 | } 102 | .dropzone-on { 103 | border-color: #5cb85c; 104 | } 105 | .done { 106 | color: #5cb85c; 107 | } 108 | 109 | .actions { 110 | text-align: right; 111 | } 112 | 113 | .table-cell { 114 | display: table-cell; 115 | } 116 | 117 | .prepare-list button { 118 | height: 50px; 119 | position: fixed!important; 120 | } 121 | 122 | .create-new { 123 | position: relative; 124 | top: 10px; 125 | width: 100%; 126 | } 127 | .create-new button { 128 | width: 50px; 129 | height: 50px; 130 | } 131 | .create-new li { 132 | text-align: left; 133 | font-size: 1.3em; 134 | } 135 | .create-new td { 136 | padding-right: 10px; 137 | } 138 | .policy { 139 | display: block; 140 | padding-bottom: 15px; 141 | } 142 | .policies { 143 | width:100%; 144 | padding-left: 10px; 145 | } 146 | .permission-icons { 147 | min-height: 50px; 148 | min-width: 70px; 149 | vertical-align: top; 150 | text-align: center; 151 | } 152 | .boxes { 153 | display: inline-flex; 154 | } 155 | .new-user { 156 | width: 160px; 157 | height: 40px; 158 | font-size: 1.2em; 159 | } 160 | .mode-label { 161 | padding-left: 5px; 162 | padding-right: 5px; 163 | display: inline-flex; 164 | } 165 | .full-width { 166 | width: 100% !important; 167 | } 168 | .spacer { 169 | display: block; 170 | padding-top: 10px; 171 | } 172 | .nginput { 173 | transition:0.3s linear all; 174 | background: white; 175 | width: 100%; 176 | height: 50px; 177 | font-size: 1.3em; 178 | padding: 5px; 179 | } 180 | .nginput.ng-invalid { 181 | background: #ff3c1f; 182 | color:white; 183 | } 184 | .nginput-new { 185 | transition: 0.3s linear all; 186 | background: white; 187 | width: 100%; 188 | height: 250px; 189 | font-size: 0.8em; 190 | padding: 5px; 191 | } 192 | .nginput-new.ng-invalid { 193 | background: #ff3c1f; 194 | color: white; 195 | } 196 | /* ng-repeat Animation */ 197 | /* 198 | .repeat-animation.ng-enter { 199 | -webkit-transition: all linear 1s; 200 | -moz-transition: all linear 1s; 201 | -o-transition: all linear 1s; 202 | transition: all linear 1s; 203 | 204 | position:relative; 205 | left:5px; 206 | } 207 | 208 | .repeat-animation.ng-leave { 209 | -webkit-transition: all linear 1s; 210 | -moz-transition: all linear 1s; 211 | -o-transition: all linear 1s; 212 | transition: all linear 1s; 213 | 214 | position:relative; 215 | left:5px; 216 | } 217 | 218 | .repeat-animation.ng-enter { 219 | opacity: 0; 220 | } 221 | .repeat-animation.ng-enter.ng-enter-active { 222 | opacity: 1; 223 | } 224 | .repeat-animation.ng-leave { 225 | display: none; 226 | opacity: 1; 227 | } 228 | .repeat-animation.ng-leave.ng-leave-active { 229 | display: none; 230 | opacity: 0; 231 | } 232 | 233 | .repeat-enter-setup, .repeat-leave-setup { 234 | -webkit-transition:all linear 0.5s; 235 | -moz-transition:all linear 0.5s; 236 | -ms-transition:all linear 0.5s; 237 | -o-transition:all linear 0.5s; 238 | transition:all linear 0.5s; 239 | } 240 | 241 | .repeat-enter-setup { line-height:0; opacity:0; } 242 | .repeat-enter-setup.repeat-enter-start { line-height:20px; opacity:1; } 243 | 244 | .repeat-leave-setup { opacity:1; line-height:20px; } 245 | .repeat-leave-setup.repeat-leave-start { opacity:0; line-height:0; } 246 | */ 247 | 248 | #crumbs { 249 | position: relative; 250 | text-align: left; 251 | } 252 | #crumbs ul { 253 | list-style: none; 254 | display: inline-table; 255 | padding-left: 10px; 256 | } 257 | #crumbs ul li { 258 | display: inline; 259 | } 260 | #crumbs ul li a { 261 | display: block; 262 | float: left; 263 | height: 50px!important; 264 | background: #bbb; 265 | text-align: center; 266 | padding: 10px 10px 0 40px; 267 | position: relative; 268 | margin: 0 5px 0 0; 269 | 270 | font-size: 20px; 271 | text-decoration: none; 272 | color: #000; 273 | } 274 | #crumbs ul li a:after { 275 | content: ""; 276 | border-top: 25px solid transparent; 277 | border-bottom: 25px solid transparent; 278 | border-left: 20px solid #bbb; 279 | position: absolute; right: -20px; top: 0; 280 | z-index: 1; 281 | } 282 | 283 | #crumbs ul li a:before { 284 | content: ""; 285 | border-top: 25px solid transparent; 286 | border-bottom: 25px solid transparent; 287 | border-left: 20px solid; 288 | position: absolute; left: 0; top: 0; 289 | } 290 | 291 | #crumbs ul li:first-child a { 292 | border-top-left-radius: 10px; border-bottom-left-radius: 10px; 293 | } 294 | #crumbs ul li:first-child a:before { 295 | display: none; 296 | } 297 | #crumbs ul li:last-child a { 298 | padding-right: 80px; 299 | border-top-right-radius: 10px; border-bottom-right-radius: 10px; 300 | } 301 | #crumbs ul li:last-child a:after { 302 | display: none; 303 | } 304 | #crumbs ul li a:hover { 305 | background: #ff3c1f; 306 | } 307 | #crumbs ul li a:hover:after { 308 | border-left-color: #ff3c1f; 309 | } -------------------------------------------------------------------------------- /src/less/main.less: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the main application stylesheet. It should include or import all 3 | * stylesheets used throughout the application as this is the only stylesheet in 4 | * the Grunt configuration that is automatically processed. 5 | */ 6 | 7 | 8 | /** 9 | * First, we include the Twitter Bootstrap LESS files. Only the ones used in the 10 | * project should be imported as the rest are just wasting space. 11 | */ 12 | 13 | @import '../../vendor/bootstrap/less/bootstrap.less'; 14 | 15 | 16 | /** 17 | * This is our main variables file. We must include it last so we can overwrite any variable 18 | * definitions in our imported stylesheets. 19 | */ 20 | 21 | @import 'variables.less'; 22 | 23 | body { 24 | background-image: url('background.png'); 25 | font-size: 12px; 26 | } 27 | 28 | 29 | /** 30 | * Typography 31 | */ 32 | 33 | @font-face { 34 | font-family: 'Roboto'; 35 | font-style: normal; 36 | font-weight: 400; 37 | src: local('Roboto Regular'), local('Roboto-Regular'), url(fonts/Roboto-Regular.woff) format('woff'); 38 | } 39 | 40 | code, pre, .pre { 41 | padding: 5px; 42 | margin: 10px 0; 43 | background-color: #EFEFEF; 44 | border: 1px solid #DADADA; 45 | border-radius: 3px; 46 | } 47 | 48 | .truncate { 49 | white-space: nowrap; 50 | overflow: hidden; 51 | text-overflow: ellipsis; 52 | flex: 1; 53 | } 54 | 55 | code { 56 | padding: 0 3px; 57 | } 58 | 59 | pre { 60 | margin: 10px 0; 61 | padding: 5px; 62 | } 63 | 64 | .page-header { 65 | margin-top: 60px; 66 | 67 | &:first-child { 68 | margin-top: 20px; 69 | } 70 | } 71 | 72 | h2 { 73 | margin: 20px 0; 74 | color: #666; 75 | } 76 | 77 | 78 | /** 79 | * Navigation 80 | */ 81 | 82 | .navbar { 83 | margin-top: 20px; 84 | 85 | small { 86 | font-size: 60%; 87 | } 88 | } 89 | .navbar-collapse { 90 | padding-left: 0px!important; 91 | } 92 | 93 | .topbar { 94 | position: fixed; 95 | padding-left: 10px; 96 | padding-right: 10px; 97 | top: 0px; 98 | left: 0px; 99 | height: 70px; 100 | width: 100%; 101 | background: #000; 102 | z-index: 998; 103 | } 104 | 105 | /** 106 | * Main 107 | */ 108 | .main { 109 | text-align: center; 110 | margin-top: 50px; 111 | min-height: 400px; 112 | } 113 | 114 | /** 115 | * Footer 116 | */ 117 | 118 | .footer { 119 | margin-top: 10px; 120 | text-align: center; 121 | 122 | .social { 123 | float: right; 124 | margin: 0; 125 | list-style: none; 126 | 127 | li { 128 | float: left; 129 | margin-left: 20px; 130 | 131 | a, a:visited { 132 | color: @gray-light; 133 | text-decoration: none; 134 | font-size: 40px; 135 | .transition( 250ms ease-in-out ); 136 | 137 | &:hover { 138 | color: @gray; 139 | } 140 | } 141 | } 142 | } 143 | } 144 | 145 | /** 146 | * Navbar 147 | */ 148 | .icon-bars-button { 149 | position: fixed; 150 | display: inline-block; 151 | vertical-align:middle; 152 | height: 40px; 153 | padding-top: 9px; 154 | left: 10px; 155 | top: 15px; 156 | background-color: #FF3C1F; 157 | } 158 | .icon-bars-button-left { 159 | -moz-transition: left .3s; 160 | -webkit-transition: left .3s; 161 | -o-transition: left .3s; 162 | transition: left .3s; 163 | left: 10px; 164 | } 165 | .icon-bars-button-right { 166 | -moz-transition: left .3s; 167 | -webkit-transition: left .3s; 168 | -o-transition: left .3s; 169 | transition: left .3s; 170 | left: 160px; 171 | } 172 | .icon-bar { 173 | margin-bottom:4px; 174 | display: block; 175 | width: 22px; 176 | height: 4px; 177 | background-color: #fff; 178 | border-radius: 2px; 179 | } 180 | .sidebar { 181 | -moz-transition: left .3s; 182 | -webkit-transition: left .3s; 183 | -o-transition: left .3s; 184 | transition: left .3s; 185 | width: 150px; 186 | height: 100%; 187 | background-color: #34495e; 188 | position: absolute; 189 | top: 0px; 190 | padding-top: 10px; 191 | left: -150px; 192 | text-align: center; 193 | z-index: 999; 194 | } 195 | .sidebar .menu-item { 196 | width: 150px; 197 | min-height: 86px; 198 | } 199 | 200 | .sidebar div { 201 | color: #fff; 202 | text-align: center; 203 | } 204 | .sidebar a, .sidebar a:hover, .sidebar a:visited { 205 | color: #fff; 206 | text-decoration: none; 207 | } 208 | .sidebar a:hover i { 209 | color: #FF5140; 210 | } 211 | .sidebar p { 212 | color: #fff; 213 | font-size: 1.2em; 214 | font-weight: bold; 215 | } 216 | .slide-out { 217 | -moz-transition: left .3s; 218 | -webkit-transition: left .3s; 219 | -o-transition: left .3s; 220 | transition: left .3s; 221 | left: 0px; 222 | } 223 | 224 | /** 225 | * Profile 226 | */ 227 | .avatar-frame { 228 | border: 0px; 229 | margin-bottom: 10px; 230 | } 231 | .avatar-frame img { 232 | width: 50px; 233 | height: 50px; 234 | border: 1px solid #fff; 235 | } 236 | 237 | /** 238 | * Other 239 | */ 240 | .badge { 241 | position: relative; 242 | right: -10px; 243 | top: 10px; 244 | background-color: #FF3C1F; 245 | z-index:1000; 246 | } 247 | .avatar-frame .badge { 248 | right: -100px; 249 | top: -30px; 250 | } 251 | 252 | .orange { 253 | color: #FF3C1F; 254 | } 255 | .white { 256 | color: #fff; 257 | } 258 | 259 | .light-grey { 260 | color: #ddd; 261 | } 262 | 263 | .dark-grey { 264 | color: #4C5053; 265 | } 266 | 267 | .pull-left { 268 | float: left; 269 | } 270 | 271 | .pull-right { 272 | float: right; 273 | } 274 | 275 | .pull-top { 276 | top: 0px; 277 | } 278 | 279 | .center { 280 | text-align: center; 281 | } 282 | .left { 283 | text-align: left; 284 | } 285 | .right { 286 | text-align: right; 287 | } 288 | 289 | .vmiddle { 290 | vertical-align: middle; 291 | } 292 | .vtop { 293 | vertical-align: top; 294 | } 295 | .vbottom { 296 | vertical-align: bottom; 297 | } 298 | .inline-block { 299 | display: inline-block; 300 | } 301 | 302 | /** 303 | * Buttons 304 | */ 305 | .btn-primary { 306 | background-color: #5cb85c; 307 | border-color: #4cae4c; 308 | } 309 | .btn-primary:hover, .btn-primary:focus, .btn-primary:active { 310 | background-color: #4cae4c; 311 | border-color: #5cb85c; 312 | } 313 | .open .dropdown-toggle.btn-primary { 314 | background-color: #4cae4c; 315 | border-color: #5cb85c; 316 | } 317 | .dropdown-menu-right { 318 | right: 0 !important; 319 | } 320 | 321 | .btn-mini { 322 | font-size: 10px; 323 | } 324 | 325 | /** 326 | * Overrides 327 | */ 328 | h1 { 329 | margin-top: 15px!important; 330 | } 331 | 332 | .container { 333 | padding-left: 0px !important; 334 | } 335 | .modal-dialog { 336 | margin-top: 60px !important; 337 | } 338 | .modal-body { 339 | font-size: 1.1em !important; 340 | word-break: break-word; 341 | word-wrap: break-word; 342 | padding-bottom: 0px!important; 343 | padding: 10px!important; 344 | } 345 | .half-width { 346 | width: 50%; 347 | } 348 | .col-md-12 { 349 | padding-left: 10px; 350 | padding-right: 10px; 351 | } 352 | .clear-70 { 353 | margin-top: 70px; 354 | } 355 | 356 | .progress { 357 | margin-bottom: 0px!important; 358 | } 359 | 360 | [ng\:cloak], 361 | [ng-cloak], 362 | [data-ng-cloak], 363 | [x-ng-cloak], 364 | .ng-cloak, 365 | .x-ng-cloak { 366 | display: none !important; 367 | } 368 | 369 | /** 370 | * Now that all app-wide styles have been applied, we can load the styles for 371 | * all the submodules and components we are using. 372 | * 373 | * TODO: In a later version of this boilerplate, I'd like to automate this. 374 | */ 375 | 376 | 377 | -------------------------------------------------------------------------------- /tools.md: -------------------------------------------------------------------------------- 1 | # The Tools Used in `ngBoilerplate` 2 | 3 | ## Introduction 4 | 5 | `ngBoilerplate` is standards-based, so it uses all the usual tools to manage 6 | and develop client-side code. If you've developed modern, highly-organized 7 | JavaScript projects before, you are probably already familiar with at least most 8 | of these tools. What follows is a simple description of the tools of which this 9 | project makes use and how they fit in to the `ngBoilerplate` picture. 10 | 11 | ## Git 12 | 13 | [Git](http://git-scm.com/) is a distributed version control system. 14 | `ngBoilerplate` uses git to manage its codebase. While in theory you don't have 15 | to use Git once you download `ngBoilerplate`, this project makes the assumption 16 | that you do. If you're on GitHub, I assume you already have a basic 17 | understanding of Git, which is all you need to make effective use of this 18 | project. You just need to be able to commit and push and junk - nothing funky. 19 | If you're not familiar with it, check out the documentation linked to above. 20 | GitHub also has a great [help section](https://help.github.com/). 21 | 22 | ## Node.js & NPM 23 | 24 | [Node.js](http://nodejs.org) is a platform based on Chrome's JavaScript runtime, 25 | called [V8](http://code.google.com/p/v8/). It allows you to develop all kinds of 26 | software using the JavaScript you already know and love. 27 | 28 | A great feature of Node.js is its wide variety of existing libraries and tools. 29 | As the developer community is absolutely massive and incredibly active, Node.js 30 | has a basic package manager called NPM that you can use to install Node.js-based 31 | software and libraries from the command line. 32 | 33 | While `ngBoilerplate` makes heavy use of Node.js behind the scenes, you as the 34 | application developer don't need to really think about it much. Most of the 35 | interaction with Node.js will occur through Grunt (see next section), so you 36 | really only need to know how get the initial setup working. 37 | 38 | `package.json` is an NPM package description file written in JSON. It contains 39 | basic metadata about your application, like its name, version, and dependencies. 40 | By default, several packages are required for the build process to work; so when 41 | you first start with `ngBoilerplate` you have to tell NPM to install the 42 | packages; this is covered in detail in the [main README](README.md). Some of 43 | the required packages are Grunt build tasks (see below), while others are 44 | command-line tools either we (or the build system) need, like Karma, Grunt, and 45 | Bower. 46 | 47 | Don't worry about knowing Node.js in order to use `ngBoilerplate`; Grunt is 48 | where the magic happens. 49 | 50 | ## Grunt.js 51 | 52 | [Grunt](http://gruntjs.com) is a JavaScript task runner that runs on top of 53 | Node.js. Most importantly, Grunt brings us automation. There are lots of steps 54 | that go into taking our manageable codebase and making it into a 55 | production-ready website; we must gather, lint, test, annotate, and copy files 56 | about. Instead of doing all of that manually, we write (and use others') Grunt 57 | tasks to do things for us. 58 | 59 | When we want to build our site, we can just type: 60 | 61 | ```sh 62 | $ grunt 63 | ``` 64 | 65 | This will do everything needed and place our built code inside a folder called 66 | `bin/`. Even more magical, we can tell Grunt to watch for file changes we make 67 | so it can re-build our site on-the-fly: 68 | 69 | ```sh 70 | $ grunt watch 71 | ``` 72 | 73 | The built files will be in `build/`. See the main [README](README.md) for more 74 | info. 75 | 76 | The next time we change a source file, Grunt will re-build the changed parts of 77 | the site. If you have a Live Reload plugin installed in your browser, it will 78 | even auto-refresh your browser for you. You lazy bum. 79 | 80 | Grunt is controlled through `Gruntfile.js`. This file is heavily documented in 81 | the source, so I will only provide a high-altitude overview here. Also note that 82 | unless you need to modify the build process, you don't need to know anything 83 | else from this section. The two commands above really are all you need to know 84 | to get started with `ngBoilerplate`. But for those curious or looking to go a 85 | little more advanced, here's what you'll find. 86 | 87 | First, we tell Grunt which tasks we might want to use: 88 | 89 | ```js 90 | // ... 91 | grunt.loadNpmTasks('grunt-recess'); 92 | grunt.loadNpmTasks('grunt-contrib-clean'); 93 | grunt.loadNpmTasks('grunt-contrib-copy'); 94 | grunt.loadNpmTasks('grunt-contrib-jshint'); 95 | // ... 96 | ``` 97 | 98 | Each of these tasks must already be installed. Remember the dependencies from 99 | `package.json` that NPM installed for us? Well, this is where they get used! 100 | 101 | Then we get the opportunity to tell the tasks to behave like we want by 102 | defining a configuration object. While we can (and do) define all sorts of 103 | custom configuration values that we reference later on, tasks look for 104 | configuration properties of their own name. For example, the `clean` task just 105 | takes an array of files to delete when the task runs: 106 | 107 | ```js 108 | clean: [ '<%= build_dir %>', '<%= compile_dir %>' ], 109 | ``` 110 | 111 | In Grunt, the `<%= varName %>` is a way of re-using configuration variables. 112 | In the `build.config.js`, we defined what `build_dir` meant: 113 | 114 | ```js 115 | build_dir: 'build', 116 | ``` 117 | 118 | When the clean task runs, it will delete the `build/` folder entirely so that 119 | when our new build runs, we don't encounter any problems with stale or old 120 | files. Most tasks, however, have considerably more complicated configuration 121 | requirements, but I've tried to document what each one is doing and what the 122 | configuration properties mean. If I was vague or ambiguous or just plain 123 | unclear, please file an issue and I'll get it fixed. Boom - problem solved. 124 | 125 | After our configuration is complete, we can define some of our own tasks. For 126 | example, we could do the build by running all of the separate tasks that we 127 | installed from NPM and configured as above: 128 | 129 | ```sh 130 | $ grunt clean 131 | $ grunt html2js 132 | $ grunt jshint 133 | $ grunt karma:continuous 134 | $ grunt concat 135 | $ grunt ngmin:dist 136 | $ grunt uglify 137 | $ grunt recess 138 | $ grunt index 139 | $ grunt copy 140 | ``` 141 | 142 | But how automated is that? So instead we define a composite task that executes 143 | all that for us. The commands above make up the `default` tasks, which can be 144 | run by typing *either* of these commands: 145 | 146 | ```js 147 | $ grunt 148 | $ grunt default 149 | ``` 150 | 151 | We also define the `watch` task discussed earlier. This is covered in more 152 | detail in the main (README)[README.md]. 153 | 154 | Grunt is the engine behind `ngBoilerplate`. It's the magic that makes it move. 155 | Just getting started, you won't need to alter `Gruntfile.js` at all, but 156 | as you get into more advanced application development, you will probably need to 157 | add more tasks and change some steps around to make this build your own. 158 | Hopefully, this readme and the documentation within `Gruntfile.js` (as well as 159 | of course the documentation at gruntjs.com) will set you on the right path. 160 | 161 | ## Bower 162 | 163 | [Bower](bower.io) is a package manager for the web. It's similar in many 164 | respects to NPM, though it is significantly simpler and only contains code for 165 | web projects, like Twitter Bootstrap and its AngularJS counterpart Angular 166 | Bootstrap. Bower allows us to say that our app depends in some way on these 167 | other libraries so that we can manage all of them in one simple place. 168 | 169 | `ngBoilerplate` comes with a `bower.json` file that looks something like this: 170 | 171 | ```js 172 | { 173 | "name": "ng-boilerplate", 174 | "version": "0.2.0-SNAPSHOT", 175 | "devDependencies": { 176 | "angular": "~1.0.7", 177 | "angular-mocks": "~1.0.7", 178 | "bootstrap": "~2.3.2", 179 | "angular-bootstrap": "~0.3.0", 180 | "angular-ui-router": "~0.0.1", 181 | "angular-ui-utils": "~0.0.3" 182 | }, 183 | "dependencies": {} 184 | } 185 | ``` 186 | 187 | This file is fairly self-explanatory; it gives the package name and version 188 | (duplicated from `package.json`, but this is unavoidable) as well as a list of 189 | dependencies our application needs in order to work. If we simply call 190 | 191 | ```sh 192 | $ bower install 193 | ``` 194 | 195 | it will read these three dependencies and install them into the `vendor/` folder 196 | (along with any dependencies they have) so that we can use them in our app. If 197 | we want to add a new package like AngularUI's 198 | [ngGrid](http://angular-ui.github.io/ng-grid/), then we can tell Bower to 199 | install that from the web, place it into the `vendor/` folder for us to use, and 200 | then add it as a dependency to `bower.json`: 201 | 202 | ```js 203 | $ bower install angular-grid --save-dev 204 | ``` 205 | 206 | Bower can also update all of our packages for us at a later date, though that 207 | and its many other awesome features are beyond the scope of this simple 208 | overview. 209 | 210 | One last thing to note is that packages installed with Bower are not 211 | standardized, so we cannot automatically add them to the build process; anything 212 | installed with Bower (or placed in the `vendor/` directory manually) *must* be 213 | added to your `build.config.js` file manually; look for the Bower libs included 214 | in `ngBoilerplate` by default in there to see what I mean. 215 | 216 | ## Where to Go From Here 217 | 218 | That's it! Now that you have a basic understanding of the tools involved, read 219 | through the [main README](README.md) to dive another level deeper and apply what 220 | you've learned for great good. I promise it will all make sense it short order. 221 | 222 | Happy programming! 223 | 224 | -------------------------------------------------------------------------------- /vendor/placeholders/angular-placeholders-0.0.1-SNAPSHOT.min.js: -------------------------------------------------------------------------------- 1 | angular.module("placeholders",["placeholders.img","placeholders.txt"]),angular.module("placeholders.img",[]).directive("phImg",function(){return{restrict:"A",scope:{dimensions:"@phImg"},link:function(e,t,n){function s(){var t=[e.size.h,e.size.w].sort(),n=Math.round(t[1]/16);return Math.max(i.text_size,n)}function o(){r=r||document.createElement("canvas");var t=r.getContext("2d"),n,o;return r.width=e.size.w,r.height=e.size.h,t.fillStyle=i.fill_color,t.fillRect(0,0,e.size.w,e.size.h),n=s(),o=e.dimensions,t.fillStyle=i.text_color,t.textAlign="center",t.textBaseline="middle",t.font="bold "+n+"pt sans-serif",t.measureText(o).width/e.size.w>1&&(n=i.text_size/(t.measureText(o).width/e.size.w),t.font="bold "+n+"pt sans-serif"),t.fillText(e.dimensions,e.size.w/2,e.size.h/2),r.toDataURL("image/png")}var r,i={text_size:10,fill_color:"#EEEEEE",text_color:"#AAAAAA"};e.$watch("dimensions",function(){if(!angular.isDefined(e.dimensions))return;var n=e.dimensions.match(/^(\d+)x(\d+)$/),r;if(!n){console.error("Expected '000x000'. Got "+e.dimensions);return}e.size={w:n[1],h:n[2]},t.prop("title",e.dimensions),t.prop("alt",e.dimensions),r=o(),t.prop("tagName")==="IMG"?t.prop("src",r):t.css("background-image",'url("'+r+'")')})}}}),angular.module("placeholders.txt",[]).factory("TextGeneratorService",function(){function t(e,t){return Math.floor(Math.random()*(t-e+1))+e}var e=["lorem","ipsum","dolor","sit","amet,","consectetur","adipiscing","elit","ut","aliquam,","purus","sit","amet","luctus","venenatis,","lectus","magna","fringilla","urna,","porttitor","rhoncus","dolor","purus","non","enim","praesent","elementum","facilisis","leo,","vel","fringilla","est","ullamcorper","eget","nulla","facilisi","etiam","dignissim","diam","quis","enim","lobortis","scelerisque","fermentum","dui","faucibus","in","ornare","quam","viverra","orci","sagittis","eu","volutpat","odio","facilisis","mauris","sit","amet","massa","vitae","tortor","condimentum","lacinia","quis","vel","eros","donec","ac","odio","tempor","orci","dapibus","ultrices","in","iaculis","nunc","sed","augue","lacus,","viverra","vitae","congue","eu,","consequat","ac","felis","donec","et","odio","pellentesque","diam","volutpat","commodo","sed","egestas","egestas","fringilla","phasellus","faucibus","scelerisque","eleifend","donec","pretium","vulputate","sapien","nec","sagittis","aliquam","malesuada","bibendum","arcu","vitae","elementum","curabitur","vitae","nunc","sed","velit","dignissim","sodales","ut","eu","sem","integer","vitae","justo","eget","magna","fermentum","iaculis","eu","non","diam","phasellus","vestibulum","lorem","sed","risus","ultricies","tristique","nulla","aliquet","enim","tortor,","at","auctor","urna","nunc","id","cursus","metus","aliquam","eleifend","mi","in","nulla","posuere","sollicitudin","aliquam","ultrices","sagittis","orci,","a","scelerisque","purus","semper","eget","duis","at","tellus","at","urna","condimentum","mattis","pellentesque","id","nibh","tortor,","id","aliquet","lectus","proin","nibh","nisl,","condimentum","id","venenatis","a,","condimentum","vitae","sapien","pellentesque","habitant","morbi","tristique","senectus","et","netus","et","malesuada","fames","ac","turpis","egestas","sed","tempus,","urna","et","pharetra","pharetra,","massa","massa","ultricies","mi,","quis","hendrerit","dolor","magna","eget","est","lorem","ipsum","dolor","sit","amet,","consectetur","adipiscing","elit","pellentesque","habitant","morbi","tristique","senectus","et","netus","et","malesuada","fames","ac","turpis","egestas","integer","eget","aliquet","nibh","praesent","tristique","magna","sit","amet","purus","gravida","quis","blandit","turpis","cursus","in","hac","habitasse","platea","dictumst","quisque","sagittis,","purus","sit","amet","volutpat","consequat,","mauris","nunc","congue","nisi,","vitae","suscipit","tellus","mauris","a","diam","maecenas","sed","enim","ut","sem","viverra","aliquet","eget","sit","amet","tellus","cras","adipiscing","enim","eu","turpis","egestas","pretium","aenean","pharetra,","magna","ac","placerat","vestibulum,","lectus","mauris","ultrices","eros,","in","cursus","turpis","massa","tincidunt","dui","ut","ornare","lectus","sit","amet","est","placerat","in","egestas","erat","imperdiet","sed","euismod","nisi","porta","lorem","mollis","aliquam","ut","porttitor","leo","a","diam","sollicitudin","tempor","id","eu","nisl","nunc","mi","ipsum,","faucibus","vitae","aliquet","nec,","ullamcorper","sit","amet","risus","nullam","eget","felis","eget","nunc","lobortis","mattis","aliquam","faucibus","purus","in","massa","tempor","nec","feugiat","nisl","pretium","fusce","id","velit","ut","tortor","pretium","viverra","suspendisse","potenti","nullam","ac","tortor","vitae","purus","faucibus","ornare","suspendisse","sed","nisi","lacus,","sed","viverra","tellus","in","hac","habitasse","platea","dictumst","vestibulum","rhoncus","est","pellentesque","elit","ullamcorper","dignissim","cras","tincidunt","lobortis","feugiat","vivamus","at","augue","eget","arcu","dictum","varius","duis","at","consectetur","lorem","donec","massa","sapien,","faucibus","et","molestie","ac,","feugiat","sed","lectus","vestibulum","mattis","ullamcorper","velit","sed","ullamcorper","morbi","tincidunt","ornare","massa,","eget","egestas","purus","viverra","accumsan","in","nisl","nisi,","scelerisque","eu","ultrices","vitae,","auctor","eu","augue","ut","lectus","arcu,","bibendum","at","varius","vel,","pharetra","vel","turpis","nunc","eget","lorem","dolor,","sed","viverra","ipsum","nunc","aliquet","bibendum","enim,","facilisis","gravida","neque","convallis","a","cras","semper","auctor","neque,","vitae","tempus","quam","pellentesque","nec","nam","aliquam","sem","et","tortor","consequat","id","porta","nibh","venenatis","cras","sed","felis","eget","velit","aliquet","sagittis","id","consectetur","purus","ut","faucibus","pulvinar","elementum","integer","enim","neque,","volutpat","ac","tincidunt","vitae,","semper","quis","lectus","nulla","at","volutpat","diam","ut","venenatis","tellus","in","metus","vulputate","eu","scelerisque","felis","imperdiet","proin","fermentum","leo","vel","orci","porta","non","pulvinar","neque","laoreet","suspendisse","interdum","consectetur","libero,","id","faucibus","nisl","tincidunt","eget","nullam","non","nisi","est,","sit","amet","facilisis","magna","etiam","tempor,","orci","eu","lobortis","elementum,","nibh","tellus","molestie","nunc,","non","blandit","massa","enim","nec","dui","nunc","mattis","enim","ut","tellus","elementum","sagittis","vitae","et","leo","duis","ut","diam","quam","nulla","porttitor","massa","id","neque","aliquam","vestibulum","morbi","blandit","cursus","risus,","at","ultrices","mi","tempus","imperdiet","nulla","malesuada","pellentesque","elit","eget","gravida","cum","sociis","natoque","penatibus","et","magnis","dis","parturient","montes,","nascetur","ridiculus","mus","mauris","vitae","ultricies","leo","integer","malesuada","nunc","vel","risus","commodo","viverra","maecenas","accumsan,","lacus","vel","facilisis","volutpat,","est","velit","egestas","dui,","id","ornare","arcu","odio","ut","sem","nulla","pharetra","diam","sit","amet","nisl","suscipit","adipiscing","bibendum","est","ultricies","integer","quis","auctor","elit","sed","vulputate","mi","sit","amet","mauris","commodo","quis","imperdiet","massa","tincidunt","nunc","pulvinar","sapien","et","ligula","ullamcorper","malesuada","proin","libero","nunc,","consequat","interdum","varius","sit","amet,","mattis","vulputate","enim","nulla","aliquet","porttitor","lacus,","luctus","accumsan","tortor","posuere","ac","ut","consequat","semper","viverra","nam","libero","justo,","laoreet","sit","amet","cursus","sit","amet,","dictum","sit","amet","justo","donec","enim","diam,","vulputate","ut","pharetra","sit","amet,","aliquam","id","diam","maecenas","ultricies","mi","eget","mauris","pharetra","et","ultrices","neque","ornare","aenean","euismod","elementum","nisi,","quis","eleifend","quam","adipiscing","vitae","proin","sagittis,","nisl","rhoncus","mattis","rhoncus,","urna","neque","viverra","justo,","nec","ultrices","dui","sapien","eget","mi","proin","sed","libero","enim,","sed","faucibus","turpis","in","eu","mi","bibendum","neque","egestas","congue","quisque","egestas","diam","in","arcu","cursus","euismod","quis","viverra","nibh","cras","pulvinar","mattis","nunc,","sed","blandit","libero","volutpat","sed","cras","ornare","arcu","dui","vivamus","arcu","felis,","bibendum","ut","tristique","et,","egestas","quis","ipsum","suspendisse","ultrices","fusce","ut","placerat","orci","nulla","pellentesque","dignissim","enim,","sit","amet","venenatis","urna","cursus","eget","nunc","scelerisque","viverra","mauris,","in","aliquam","sem","fringilla","ut","morbi","tincidunt","augue","interdum","velit","euismod","in","pellentesque","massa","placerat","duis","ultricies","lacus","sed","turpis","tincidunt","id","aliquet","risus","feugiat","in","ante","metus,","dictum","at","tempor","commodo,","ullamcorper","a","lacus","vestibulum","sed","arcu","non","odio","euismod","lacinia","at","quis","risus","sed","vulputate","odio","ut","enim","blandit","volutpat","maecenas","volutpat","blandit","aliquam","etiam","erat","velit,","scelerisque","in","dictum","non,","consectetur","a","erat","nam","at","lectus","urna","duis","convallis","convallis","tellus,","id","interdum","velit","laoreet","id","donec","ultrices","tincidunt","arcu,","non","sodales","neque","sodales","ut","etiam","sit","amet","nisl","purus,","in","mollis","nunc","sed","id","semper","risus","in","hendrerit","gravida","rutrum","quisque","non","tellus","orci,","ac","auctor","augue","mauris","augue","neque,","gravida","in","fermentum","et,","sollicitudin","ac","orci","phasellus","egestas","tellus","rutrum","tellus","pellentesque","eu","tincidunt","tortor","aliquam","nulla","facilisi","cras","fermentum,","odio","eu","feugiat","pretium,","nibh","ipsum","consequat","nisl,","vel","pretium","lectus","quam","id","leo","in","vitae","turpis","massa","sed","elementum","tempus","egestas","sed","sed","risus","pretium","quam","vulputate","dignissim","suspendisse","in","est","ante","in","nibh","mauris,","cursus","mattis","molestie","a,","iaculis","at","erat","pellentesque","adipiscing","commodo","elit,","at","imperdiet","dui","accumsan","sit","amet","nulla","facilisi","morbi","tempus","iaculis","urna,","id","volutpat","lacus","laoreet","non","curabitur","gravida","arcu","ac","tortor","dignissim","convallis","aenean","et","tortor","at","risus","viverra","adipiscing","at","in","tellus","integer","feugiat","scelerisque","varius","morbi","enim","nunc,","faucibus","a","pellentesque","sit","amet,","porttitor","eget","dolor","morbi","non","arcu","risus,","quis","varius","quam","quisque","id","diam","vel","quam","elementum","pulvinar","etiam","non","quam","lacus","suspendisse","faucibus","interdum","posuere","lorem","ipsum","dolor","sit","amet,","consectetur","adipiscing","elit","duis","tristique","sollicitudin","nibh","sit","amet","commodo","nulla","facilisi","nullam","vehicula","ipsum","a","arcu","cursus","vitae","congue","mauris","rhoncus","aenean","vel","elit","scelerisque","mauris","pellentesque","pulvinar","pellentesque","habitant","morbi","tristique","senectus","et","netus","et","malesuada","fames","ac","turpis","egestas","maecenas","pharetra","convallis","posuere","morbi","leo","urna,","molestie","at","elementum","eu,","facilisis","sed","odio","morbi","quis","commodo","odio","aenean","sed","adipiscing","diam","donec","adipiscing","tristique","risus","nec","feugiat","in","fermentum","posuere","urna","nec","tincidunt","praesent","semper","feugiat","nibh","sed","pulvinar","proin","gravida","hendrerit","lectus","a","molestie","gravida","dictum"];return{createSentence:function(n){var r,i;return n=n||t(5,20),r=t(0,e.length-n-1),i=e.slice(r,r+n).join(" ").replace(/\,$/g,"")+".",i=i.charAt(0).toUpperCase()+i.slice(1),i},createSentences:function(e){var n=[],r=0;e=e||t(3,5);for(r=0;r"+t+"

"},createParagraphs:function(e,n){var r=[],i=0;e=e||t(3,7);for(i=0;i - v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + 47 | ' * <%= pkg.homepage %>\n' + 48 | ' *\n' + 49 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' + 50 | ' * Licensed <%= pkg.licenses.type %> <<%= pkg.licenses.url %>>\n' + 51 | ' */\n' 52 | }, 53 | 54 | /** 55 | * Creates a changelog on a new version. 56 | */ 57 | changelog: { 58 | options: { 59 | dest: 'CHANGELOG.md', 60 | template: 'changelog.tpl' 61 | } 62 | }, 63 | 64 | /** 65 | * Increments the version number, etc. 66 | */ 67 | bump: { 68 | options: { 69 | files: [ 70 | "package.json", 71 | "bower.json" 72 | ], 73 | commit: false, 74 | commitMessage: 'chore(release): v%VERSION%', 75 | commitFiles: [ 76 | "package.json", 77 | "client/bower.json" 78 | ], 79 | createTag: false, 80 | tagName: 'v%VERSION%', 81 | tagMessage: 'Version %VERSION%', 82 | push: false, 83 | pushTo: 'origin' 84 | } 85 | }, 86 | 87 | /** 88 | * The directories to delete when `grunt clean` is executed. 89 | */ 90 | clean: [ 91 | '<%= build_dir %>', 92 | '<%= compile_dir %>' 93 | ], 94 | 95 | /** 96 | * The `copy` task just copies files from A to B. We use it here to copy 97 | * our project assets (images, fonts, etc.) and javascripts into 98 | * `build_dir`, and then to copy the assets to `compile_dir`. 99 | */ 100 | copy: { 101 | build_app_assets: { 102 | files: [ 103 | { 104 | src: [ '**' ], 105 | dest: '<%= build_dir %>/assets/', 106 | cwd: 'src/assets', 107 | expand: true 108 | } 109 | ] 110 | }, 111 | build_vendor_assets: { 112 | files: [ 113 | { 114 | src: [ '<%= vendor_files.assets %>' ], 115 | dest: '<%= build_dir %>/assets/', 116 | cwd: '.', 117 | expand: true, 118 | flatten: true 119 | } 120 | ] 121 | }, 122 | build_appjs: { 123 | files: [ 124 | { 125 | src: [ '<%= app_files.js %>' ], 126 | dest: '<%= build_dir %>/', 127 | cwd: '.', 128 | expand: true 129 | } 130 | ] 131 | }, 132 | build_vendorjs: { 133 | files: [ 134 | { 135 | src: [ '<%= vendor_files.js %>' ], 136 | dest: '<%= build_dir %>/', 137 | cwd: '.', 138 | expand: true 139 | } 140 | ] 141 | }, 142 | compile_assets: { 143 | files: [ 144 | { 145 | src: [ '**' ], 146 | dest: '<%= compile_dir %>/assets', 147 | cwd: '<%= build_dir %>/assets', 148 | expand: true 149 | } 150 | ] 151 | } 152 | }, 153 | 154 | /** 155 | * `grunt concat` concatenates multiple source files into a single file. 156 | */ 157 | concat: { 158 | /** 159 | * The `build_css` target concatenates compiled CSS and vendor CSS 160 | * together. 161 | */ 162 | build_css: { 163 | src: [ 164 | '<%= vendor_files.css %>', 165 | '<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css' 166 | ], 167 | dest: '<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css' 168 | }, 169 | /** 170 | * The `compile_js` target is the concatenation of our application source 171 | * code and all specified vendor source code into a single file. 172 | */ 173 | compile_js: { 174 | options: { 175 | banner: '<%= meta.banner %>' 176 | }, 177 | src: [ 178 | '<%= vendor_files.js %>', 179 | 'module.prefix', 180 | '<%= build_dir %>/src/**/*.js', 181 | '<%= html2js.app.dest %>', 182 | '<%= html2js.common.dest %>', 183 | 'module.suffix' 184 | ], 185 | dest: '<%= compile_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.js' 186 | } 187 | }, 188 | 189 | /** 190 | * `grunt coffee` compiles the CoffeeScript sources. To work well with the 191 | * rest of the build, we have a separate compilation task for sources and 192 | * specs so they can go to different places. For example, we need the 193 | * sources to live with the rest of the copied JavaScript so we can include 194 | * it in the final build, but we don't want to include our specs there. 195 | */ 196 | coffee: { 197 | source: { 198 | options: { 199 | bare: true 200 | }, 201 | expand: true, 202 | cwd: '.', 203 | src: [ '<%= app_files.coffee %>' ], 204 | dest: '<%= build_dir %>', 205 | ext: '.js' 206 | } 207 | }, 208 | 209 | /** 210 | * `ng-min` annotates the sources before minifying. That is, it allows us 211 | * to code without the array syntax. 212 | */ 213 | ngmin: { 214 | compile: { 215 | files: [ 216 | { 217 | src: [ '<%= app_files.js %>' ], 218 | cwd: '<%= build_dir %>', 219 | dest: '<%= build_dir %>', 220 | expand: true 221 | } 222 | ] 223 | } 224 | }, 225 | 226 | /** 227 | * Minify the sources! 228 | */ 229 | uglify: { 230 | compile: { 231 | options: { 232 | banner: '<%= meta.banner %>' 233 | }, 234 | files: { 235 | '<%= concat.compile_js.dest %>': '<%= concat.compile_js.dest %>' 236 | } 237 | } 238 | }, 239 | 240 | /** 241 | * `grunt-contrib-less` handles our LESS compilation and uglification automatically. 242 | * Only our `main.less` file is included in compilation; all other files 243 | * must be imported from this file. 244 | */ 245 | less: { 246 | build: { 247 | files: { 248 | '<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css': '<%= app_files.less %>' 249 | } 250 | }, 251 | compile: { 252 | files: { 253 | '<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css': '<%= app_files.less %>' 254 | }, 255 | options: { 256 | cleancss: true, 257 | compress: true 258 | } 259 | } 260 | }, 261 | 262 | /** 263 | * `jshint` defines the rules of our linter as well as which files we 264 | * should check. This file, all javascript sources, and all our unit tests 265 | * are linted based on the policies listed in `options`. But we can also 266 | * specify exclusionary patterns by prefixing them with an exclamation 267 | * point (!); this is useful when code comes from a third party but is 268 | * nonetheless inside `src/`. 269 | */ 270 | jshint: { 271 | src: [ 272 | '<%= app_files.js %>' 273 | ], 274 | test: [ 275 | '<%= app_files.jsunit %>' 276 | ], 277 | gruntfile: [ 278 | 'Gruntfile.js' 279 | ], 280 | options: { 281 | curly: true, 282 | immed: true, 283 | newcap: true, 284 | noarg: true, 285 | sub: true, 286 | boss: true, 287 | eqnull: true 288 | }, 289 | globals: {} 290 | }, 291 | 292 | /** 293 | * `coffeelint` does the same as `jshint`, but for CoffeeScript. 294 | * CoffeeScript is not the default in ngBoilerplate, so we're just using 295 | * the defaults here. 296 | */ 297 | coffeelint: { 298 | src: { 299 | files: { 300 | src: [ '<%= app_files.coffee %>' ] 301 | } 302 | }, 303 | test: { 304 | files: { 305 | src: [ '<%= app_files.coffeeunit %>' ] 306 | } 307 | } 308 | }, 309 | 310 | /** 311 | * HTML2JS is a Grunt plugin that takes all of your template files and 312 | * places them into JavaScript files as strings that are added to 313 | * AngularJS's template cache. This means that the templates too become 314 | * part of the initial payload as one JavaScript file. Neat! 315 | */ 316 | html2js: { 317 | /** 318 | * These are the templates from `src/app`. 319 | */ 320 | app: { 321 | options: { 322 | base: 'src/app' 323 | }, 324 | src: [ '<%= app_files.atpl %>' ], 325 | dest: '<%= build_dir %>/templates-app.js' 326 | }, 327 | 328 | /** 329 | * These are the templates from `src/common`. 330 | */ 331 | common: { 332 | options: { 333 | base: 'src/common' 334 | }, 335 | src: [ '<%= app_files.ctpl %>' ], 336 | dest: '<%= build_dir %>/templates-common.js' 337 | } 338 | }, 339 | 340 | /** 341 | * The Karma configurations. 342 | */ 343 | karma: { 344 | options: { 345 | configFile: '<%= build_dir %>/karma-unit.js' 346 | }, 347 | unit: { 348 | port: 9019, 349 | background: true 350 | }, 351 | continuous: { 352 | singleRun: true 353 | } 354 | }, 355 | 356 | /** 357 | * The `index` task compiles the `index.html` file as a Grunt template. CSS 358 | * and JS files co-exist here but they get split apart later. 359 | */ 360 | index: { 361 | 362 | /** 363 | * During development, we don't want to have wait for compilation, 364 | * concatenation, minification, etc. So to avoid these steps, we simply 365 | * add all script files directly to the `` of `index.html`. The 366 | * `src` property contains the list of included files. 367 | */ 368 | build: { 369 | dir: '<%= build_dir %>', 370 | src: [ 371 | '<%= vendor_files.js %>', 372 | '<%= build_dir %>/src/**/*.js', 373 | '<%= html2js.common.dest %>', 374 | '<%= html2js.app.dest %>', 375 | '<%= vendor_files.css %>', 376 | '<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css' 377 | ] 378 | }, 379 | 380 | /** 381 | * When it is time to have a completely compiled application, we can 382 | * alter the above to include only a single JavaScript and a single CSS 383 | * file. Now we're back! 384 | */ 385 | compile: { 386 | dir: '<%= compile_dir %>', 387 | src: [ 388 | '<%= concat.compile_js.dest %>', 389 | '<%= vendor_files.css %>', 390 | '<%= build_dir %>/assets/<%= pkg.name %>-<%= pkg.version %>.css' 391 | ] 392 | } 393 | }, 394 | 395 | /** 396 | * This task compiles the karma template so that changes to its file array 397 | * don't have to be managed manually. 398 | */ 399 | karmaconfig: { 400 | unit: { 401 | dir: '<%= build_dir %>', 402 | src: [ 403 | '<%= vendor_files.js %>', 404 | '<%= html2js.app.dest %>', 405 | '<%= html2js.common.dest %>', 406 | '<%= test_files.js %>' 407 | ] 408 | } 409 | }, 410 | 411 | /** 412 | * And for rapid development, we have a watch set up that checks to see if 413 | * any of the files listed below change, and then to execute the listed 414 | * tasks when they do. This just saves us from having to type "grunt" into 415 | * the command-line every time we want to see what we're working on; we can 416 | * instead just leave "grunt watch" running in a background terminal. Set it 417 | * and forget it, as Ron Popeil used to tell us. 418 | * 419 | * But we don't need the same thing to happen for all the files. 420 | */ 421 | delta: { 422 | /** 423 | * By default, we want the Live Reload to work for all tasks; this is 424 | * overridden in some tasks (like this file) where browser resources are 425 | * unaffected. It runs by default on port 35729, which your browser 426 | * plugin should auto-detect. 427 | */ 428 | options: { 429 | livereload: true 430 | }, 431 | 432 | /** 433 | * When the Gruntfile changes, we just want to lint it. In fact, when 434 | * your Gruntfile changes, it will automatically be reloaded! 435 | */ 436 | gruntfile: { 437 | files: 'Gruntfile.js', 438 | tasks: [ 'jshint:gruntfile' ], 439 | options: { 440 | livereload: false 441 | } 442 | }, 443 | 444 | /** 445 | * When our JavaScript source files change, we want to run lint them and 446 | * run our unit tests. 447 | */ 448 | jssrc: { 449 | files: [ 450 | '<%= app_files.js %>' 451 | ], 452 | tasks: [ 'jshint:src', 'karma:unit:run', 'copy:build_appjs' ] 453 | }, 454 | 455 | /** 456 | * When our CoffeeScript source files change, we want to run lint them and 457 | * run our unit tests. 458 | */ 459 | coffeesrc: { 460 | files: [ 461 | '<%= app_files.coffee %>' 462 | ], 463 | tasks: [ 'coffeelint:src', 'coffee:source', 'karma:unit:run', 'copy:build_appjs' ] 464 | }, 465 | 466 | /** 467 | * When assets are changed, copy them. Note that this will *not* copy new 468 | * files, so this is probably not very useful. 469 | */ 470 | assets: { 471 | files: [ 472 | 'src/assets/**/*' 473 | ], 474 | tasks: [ 'copy:build_app_assets', 'copy:build_vendor_assets' ] 475 | }, 476 | 477 | /** 478 | * When index.html changes, we need to compile it. 479 | */ 480 | html: { 481 | files: [ '<%= app_files.html %>' ], 482 | tasks: [ 'index:build' ] 483 | }, 484 | 485 | /** 486 | * When our templates change, we only rewrite the template cache. 487 | */ 488 | tpls: { 489 | files: [ 490 | '<%= app_files.atpl %>', 491 | '<%= app_files.ctpl %>' 492 | ], 493 | tasks: [ 'html2js' ] 494 | }, 495 | 496 | /** 497 | * When the CSS files change, we need to compile and minify them. 498 | */ 499 | less: { 500 | files: [ 'src/**/*.less' ], 501 | tasks: [ 'less:build' ] 502 | }, 503 | 504 | /** 505 | * When a JavaScript unit test file changes, we only want to lint it and 506 | * run the unit tests. We don't want to do any live reloading. 507 | */ 508 | jsunit: { 509 | files: [ 510 | '<%= app_files.jsunit %>' 511 | ], 512 | tasks: [ 'jshint:test', 'karma:unit:run' ], 513 | options: { 514 | livereload: false 515 | } 516 | }, 517 | 518 | /** 519 | * When a CoffeeScript unit test file changes, we only want to lint it and 520 | * run the unit tests. We don't want to do any live reloading. 521 | */ 522 | coffeeunit: { 523 | files: [ 524 | '<%= app_files.coffeeunit %>' 525 | ], 526 | tasks: [ 'coffeelint:test', 'karma:unit:run' ], 527 | options: { 528 | livereload: false 529 | } 530 | } 531 | } 532 | }; 533 | 534 | grunt.initConfig( grunt.util._.extend( taskConfig, userConfig ) ); 535 | 536 | /** 537 | * In order to make it safe to just compile or copy *only* what was changed, 538 | * we need to ensure we are starting from a clean, fresh build. So we rename 539 | * the `watch` task to `delta` (that's why the configuration var above is 540 | * `delta`) and then add a new task called `watch` that does a clean build 541 | * before watching for changes. 542 | */ 543 | grunt.renameTask( 'watch', 'delta' ); 544 | grunt.registerTask( 'watch', [ 'build', 'karma:unit', 'delta' ] ); 545 | 546 | /** 547 | * The default task is to build and compile. 548 | */ 549 | grunt.registerTask( 'default', [ 'build', 'compile' ] ); 550 | 551 | /** 552 | * The `build` task gets your app ready to run for development and testing. 553 | */ 554 | grunt.registerTask( 'build', [ 555 | 'clean', 'html2js', 'jshint', 'coffeelint', 'coffee', 'less:build', 556 | 'concat:build_css', 'copy:build_app_assets', 'copy:build_vendor_assets', 557 | 'copy:build_appjs', 'copy:build_vendorjs', 'index:build', 'karmaconfig', 558 | 'karma:continuous' 559 | ]); 560 | 561 | /** 562 | * The `compile` task gets your app ready for deployment by concatenating and 563 | * minifying your code. 564 | */ 565 | grunt.registerTask( 'compile', [ 566 | 'less:compile', 'copy:compile_assets', 'ngmin', 'concat:compile_js', 'uglify', 'index:compile' 567 | ]); 568 | 569 | /** 570 | * A utility function to get all app JavaScript sources. 571 | */ 572 | function filterForJS ( files ) { 573 | return files.filter( function ( file ) { 574 | return file.match( /\.js$/ ); 575 | }); 576 | } 577 | 578 | /** 579 | * A utility function to get all app CSS sources. 580 | */ 581 | function filterForCSS ( files ) { 582 | return files.filter( function ( file ) { 583 | return file.match( /\.css$/ ); 584 | }); 585 | } 586 | 587 | /** 588 | * The index.html template includes the stylesheet and javascript sources 589 | * based on dynamic names calculated in this Gruntfile. This task assembles 590 | * the list into variables for the template to use and then runs the 591 | * compilation. 592 | */ 593 | grunt.registerMultiTask( 'index', 'Process index.html template', function () { 594 | var dirRE = new RegExp( '^('+grunt.config('build_dir')+'|'+grunt.config('compile_dir')+')\/', 'g' ); 595 | var jsFiles = filterForJS( this.filesSrc ).map( function ( file ) { 596 | return file.replace( dirRE, '' ); 597 | }); 598 | var cssFiles = filterForCSS( this.filesSrc ).map( function ( file ) { 599 | return file.replace( dirRE, '' ); 600 | }); 601 | 602 | grunt.file.copy('src/index.html', this.data.dir + '/index.html', { 603 | process: function ( contents, path ) { 604 | return grunt.template.process( contents, { 605 | data: { 606 | scripts: jsFiles, 607 | styles: cssFiles, 608 | version: grunt.config( 'pkg.version' ) 609 | } 610 | }); 611 | } 612 | }); 613 | }); 614 | 615 | /** 616 | * In order to avoid having to specify manually the files needed for karma to 617 | * run, we use grunt to manage the list for us. The `karma/*` files are 618 | * compiled as grunt templates for use by Karma. Yay! 619 | */ 620 | grunt.registerMultiTask( 'karmaconfig', 'Process karma config templates', function () { 621 | var jsFiles = filterForJS( this.filesSrc ); 622 | 623 | grunt.file.copy( 'karma/karma-unit.tpl.js', grunt.config( 'build_dir' ) + '/karma-unit.js', { 624 | process: function ( contents, path ) { 625 | return grunt.template.process( contents, { 626 | data: { 627 | scripts: jsFiles 628 | } 629 | }); 630 | } 631 | }); 632 | }); 633 | 634 | }; 635 | -------------------------------------------------------------------------------- /vendor/font-awesome/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('fonts/fontawesome-webfont.eot?v=4.2.0');src:url('fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'),url('fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'),url('fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'),url('fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"} -------------------------------------------------------------------------------- /src/app/list/list.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 16 | 19 | 28 | 33 | 34 |
7 |
8 | 9 | 14 |
15 |
17 | 18 | 20 | 27 | 29 |
30 | 31 |
32 |
35 |
36 |
37 | 38 |
39 |
40 |

Please provide a location for the LDP server:

41 |
42 |
43 |
44 | 45 | 46 |
47 |
48 |
49 |
50 | 51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 66 | 67 | 68 | 81 | 82 |
NameSizeModifiedMore

Cannot list contents (access denied)

64 | {{res.name}} 65 | {{res.size|fileSize}}
{{res.mtime|fromNow}}
69 |
70 | 73 | 79 |
80 |
83 |
84 | 85 | 86 | 87 | 105 | 106 | 107 | 126 | 127 | 128 | 147 | 148 | 149 | 169 | 170 | 171 | 206 | 207 | 208 | 224 | 225 | 226 | 365 | 371 | 372 |
373 | -------------------------------------------------------------------------------- /vendor/moment/moment.min.js: -------------------------------------------------------------------------------- 1 | //! moment.js 2 | //! version : 2.5.1 3 | //! authors : Tim Wood, Iskren Chernev, Moment.js contributors 4 | //! license : MIT 5 | //! momentjs.com 6 | (function(a){function b(){return{empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function c(a,b){return function(c){return k(a.call(this,c),b)}}function d(a,b){return function(c){return this.lang().ordinal(a.call(this,c),b)}}function e(){}function f(a){w(a),h(this,a)}function g(a){var b=q(a),c=b.year||0,d=b.month||0,e=b.week||0,f=b.day||0,g=b.hour||0,h=b.minute||0,i=b.second||0,j=b.millisecond||0;this._milliseconds=+j+1e3*i+6e4*h+36e5*g,this._days=+f+7*e,this._months=+d+12*c,this._data={},this._bubble()}function h(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return b.hasOwnProperty("toString")&&(a.toString=b.toString),b.hasOwnProperty("valueOf")&&(a.valueOf=b.valueOf),a}function i(a){var b,c={};for(b in a)a.hasOwnProperty(b)&&qb.hasOwnProperty(b)&&(c[b]=a[b]);return c}function j(a){return 0>a?Math.ceil(a):Math.floor(a)}function k(a,b,c){for(var d=""+Math.abs(a),e=a>=0;d.lengthd;d++)(c&&a[d]!==b[d]||!c&&s(a[d])!==s(b[d]))&&g++;return g+f}function p(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=Tb[a]||Ub[b]||b}return a}function q(a){var b,c,d={};for(c in a)a.hasOwnProperty(c)&&(b=p(c),b&&(d[b]=a[c]));return d}function r(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}db[b]=function(e,f){var g,h,i=db.fn._lang[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=db().utc().set(d,a);return i.call(db.fn._lang,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function s(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function t(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function u(a){return v(a)?366:365}function v(a){return a%4===0&&a%100!==0||a%400===0}function w(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[jb]<0||a._a[jb]>11?jb:a._a[kb]<1||a._a[kb]>t(a._a[ib],a._a[jb])?kb:a._a[lb]<0||a._a[lb]>23?lb:a._a[mb]<0||a._a[mb]>59?mb:a._a[nb]<0||a._a[nb]>59?nb:a._a[ob]<0||a._a[ob]>999?ob:-1,a._pf._overflowDayOfYear&&(ib>b||b>kb)&&(b=kb),a._pf.overflow=b)}function x(a){return null==a._isValid&&(a._isValid=!isNaN(a._d.getTime())&&a._pf.overflow<0&&!a._pf.empty&&!a._pf.invalidMonth&&!a._pf.nullInput&&!a._pf.invalidFormat&&!a._pf.userInvalidated,a._strict&&(a._isValid=a._isValid&&0===a._pf.charsLeftOver&&0===a._pf.unusedTokens.length)),a._isValid}function y(a){return a?a.toLowerCase().replace("_","-"):a}function z(a,b){return b._isUTC?db(a).zone(b._offset||0):db(a).local()}function A(a,b){return b.abbr=a,pb[a]||(pb[a]=new e),pb[a].set(b),pb[a]}function B(a){delete pb[a]}function C(a){var b,c,d,e,f=0,g=function(a){if(!pb[a]&&rb)try{require("./lang/"+a)}catch(b){}return pb[a]};if(!a)return db.fn._lang;if(!m(a)){if(c=g(a))return c;a=[a]}for(;f0;){if(c=g(e.slice(0,b).join("-")))return c;if(d&&d.length>=b&&o(e,d,!0)>=b-1)break;b--}f++}return db.fn._lang}function D(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function E(a){var b,c,d=a.match(vb);for(b=0,c=d.length;c>b;b++)d[b]=Yb[d[b]]?Yb[d[b]]:D(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function F(a,b){return a.isValid()?(b=G(b,a.lang()),Vb[b]||(Vb[b]=E(b)),Vb[b](a)):a.lang().invalidDate()}function G(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(wb.lastIndex=0;d>=0&&wb.test(a);)a=a.replace(wb,c),wb.lastIndex=0,d-=1;return a}function H(a,b){var c,d=b._strict;switch(a){case"DDDD":return Ib;case"YYYY":case"GGGG":case"gggg":return d?Jb:zb;case"Y":case"G":case"g":return Lb;case"YYYYYY":case"YYYYY":case"GGGGG":case"ggggg":return d?Kb:Ab;case"S":if(d)return Gb;case"SS":if(d)return Hb;case"SSS":if(d)return Ib;case"DDD":return yb;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return Cb;case"a":case"A":return C(b._l)._meridiemParse;case"X":return Fb;case"Z":case"ZZ":return Db;case"T":return Eb;case"SSSS":return Bb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"ww":case"WW":return d?Hb:xb;case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"W":case"e":case"E":return xb;default:return c=new RegExp(P(O(a.replace("\\","")),"i"))}}function I(a){a=a||"";var b=a.match(Db)||[],c=b[b.length-1]||[],d=(c+"").match(Qb)||["-",0,0],e=+(60*d[1])+s(d[2]);return"+"===d[0]?-e:e}function J(a,b,c){var d,e=c._a;switch(a){case"M":case"MM":null!=b&&(e[jb]=s(b)-1);break;case"MMM":case"MMMM":d=C(c._l).monthsParse(b),null!=d?e[jb]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[kb]=s(b));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=s(b));break;case"YY":e[ib]=s(b)+(s(b)>68?1900:2e3);break;case"YYYY":case"YYYYY":case"YYYYYY":e[ib]=s(b);break;case"a":case"A":c._isPm=C(c._l).isPM(b);break;case"H":case"HH":case"h":case"hh":e[lb]=s(b);break;case"m":case"mm":e[mb]=s(b);break;case"s":case"ss":e[nb]=s(b);break;case"S":case"SS":case"SSS":case"SSSS":e[ob]=s(1e3*("0."+b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=I(b);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":a=a.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=b)}}function K(a){var b,c,d,e,f,g,h,i,j,k,l=[];if(!a._d){for(d=M(a),a._w&&null==a._a[kb]&&null==a._a[jb]&&(f=function(b){var c=parseInt(b,10);return b?b.length<3?c>68?1900+c:2e3+c:c:null==a._a[ib]?db().weekYear():a._a[ib]},g=a._w,null!=g.GG||null!=g.W||null!=g.E?h=Z(f(g.GG),g.W||1,g.E,4,1):(i=C(a._l),j=null!=g.d?V(g.d,i):null!=g.e?parseInt(g.e,10)+i._week.dow:0,k=parseInt(g.w,10)||1,null!=g.d&&ju(e)&&(a._pf._overflowDayOfYear=!0),c=U(e,0,a._dayOfYear),a._a[jb]=c.getUTCMonth(),a._a[kb]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=l[b]=d[b];for(;7>b;b++)a._a[b]=l[b]=null==a._a[b]?2===b?1:0:a._a[b];l[lb]+=s((a._tzm||0)/60),l[mb]+=s((a._tzm||0)%60),a._d=(a._useUTC?U:T).apply(null,l)}}function L(a){var b;a._d||(b=q(a._i),a._a=[b.year,b.month,b.day,b.hour,b.minute,b.second,b.millisecond],K(a))}function M(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function N(a){a._a=[],a._pf.empty=!0;var b,c,d,e,f,g=C(a._l),h=""+a._i,i=h.length,j=0;for(d=G(a._f,g).match(vb)||[],b=0;b0&&a._pf.unusedInput.push(f),h=h.slice(h.indexOf(c)+c.length),j+=c.length),Yb[e]?(c?a._pf.empty=!1:a._pf.unusedTokens.push(e),J(e,c,a)):a._strict&&!c&&a._pf.unusedTokens.push(e);a._pf.charsLeftOver=i-j,h.length>0&&a._pf.unusedInput.push(h),a._isPm&&a._a[lb]<12&&(a._a[lb]+=12),a._isPm===!1&&12===a._a[lb]&&(a._a[lb]=0),K(a),w(a)}function O(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function P(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function Q(a){var c,d,e,f,g;if(0===a._f.length)return a._pf.invalidFormat=!0,a._d=new Date(0/0),void 0;for(f=0;fg)&&(e=g,d=c));h(a,d||c)}function R(a){var b,c,d=a._i,e=Mb.exec(d);if(e){for(a._pf.iso=!0,b=0,c=Ob.length;c>b;b++)if(Ob[b][1].exec(d)){a._f=Ob[b][0]+(e[6]||" ");break}for(b=0,c=Pb.length;c>b;b++)if(Pb[b][1].exec(d)){a._f+=Pb[b][0];break}d.match(Db)&&(a._f+="Z"),N(a)}else a._d=new Date(d)}function S(b){var c=b._i,d=sb.exec(c);c===a?b._d=new Date:d?b._d=new Date(+d[1]):"string"==typeof c?R(b):m(c)?(b._a=c.slice(0),K(b)):n(c)?b._d=new Date(+c):"object"==typeof c?L(b):b._d=new Date(c)}function T(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function U(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function V(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function W(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function X(a,b,c){var d=hb(Math.abs(a)/1e3),e=hb(d/60),f=hb(e/60),g=hb(f/24),h=hb(g/365),i=45>d&&["s",d]||1===e&&["m"]||45>e&&["mm",e]||1===f&&["h"]||22>f&&["hh",f]||1===g&&["d"]||25>=g&&["dd",g]||45>=g&&["M"]||345>g&&["MM",hb(g/30)]||1===h&&["y"]||["yy",h];return i[2]=b,i[3]=a>0,i[4]=c,W.apply({},i)}function Y(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=db(a).add("d",f),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function Z(a,b,c,d,e){var f,g,h=U(a,0,1).getUTCDay();return c=null!=c?c:e,f=e-h+(h>d?7:0)-(e>h?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:u(a-1)+g}}function $(a){var b=a._i,c=a._f;return null===b?db.invalid({nullInput:!0}):("string"==typeof b&&(a._i=b=C().preparse(b)),db.isMoment(b)?(a=i(b),a._d=new Date(+b._d)):c?m(c)?Q(a):N(a):S(a),new f(a))}function _(a,b){db.fn[a]=db.fn[a+"s"]=function(a){var c=this._isUTC?"UTC":"";return null!=a?(this._d["set"+c+b](a),db.updateOffset(this),this):this._d["get"+c+b]()}}function ab(a){db.duration.fn[a]=function(){return this._data[a]}}function bb(a,b){db.duration.fn["as"+a]=function(){return+this/b}}function cb(a){var b=!1,c=db;"undefined"==typeof ender&&(a?(gb.moment=function(){return!b&&console&&console.warn&&(b=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),c.apply(null,arguments)},h(gb.moment,c)):gb.moment=db)}for(var db,eb,fb="2.5.1",gb=this,hb=Math.round,ib=0,jb=1,kb=2,lb=3,mb=4,nb=5,ob=6,pb={},qb={_isAMomentObject:null,_i:null,_f:null,_l:null,_strict:null,_isUTC:null,_offset:null,_pf:null,_lang:null},rb="undefined"!=typeof module&&module.exports&&"undefined"!=typeof require,sb=/^\/?Date\((\-?\d+)/i,tb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,ub=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,vb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,wb=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,xb=/\d\d?/,yb=/\d{1,3}/,zb=/\d{1,4}/,Ab=/[+\-]?\d{1,6}/,Bb=/\d+/,Cb=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Db=/Z|[\+\-]\d\d:?\d\d/gi,Eb=/T/i,Fb=/[\+\-]?\d+(\.\d{1,3})?/,Gb=/\d/,Hb=/\d\d/,Ib=/\d{3}/,Jb=/\d{4}/,Kb=/[+-]?\d{6}/,Lb=/[+-]?\d+/,Mb=/^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Nb="YYYY-MM-DDTHH:mm:ssZ",Ob=[["YYYYYY-MM-DD",/[+-]\d{6}-\d{2}-\d{2}/],["YYYY-MM-DD",/\d{4}-\d{2}-\d{2}/],["GGGG-[W]WW-E",/\d{4}-W\d{2}-\d/],["GGGG-[W]WW",/\d{4}-W\d{2}/],["YYYY-DDD",/\d{4}-\d{3}/]],Pb=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Qb=/([\+\-]|\d\d)/gi,Rb="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Sb={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Tb={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Ub={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Vb={},Wb="DDD w W M D d".split(" "),Xb="M D H h m s w W".split(" "),Yb={M:function(){return this.month()+1},MMM:function(a){return this.lang().monthsShort(this,a)},MMMM:function(a){return this.lang().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.lang().weekdaysMin(this,a)},ddd:function(a){return this.lang().weekdaysShort(this,a)},dddd:function(a){return this.lang().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return k(this.year()%100,2)},YYYY:function(){return k(this.year(),4)},YYYYY:function(){return k(this.year(),5)},YYYYYY:function(){var a=this.year(),b=a>=0?"+":"-";return b+k(Math.abs(a),6)},gg:function(){return k(this.weekYear()%100,2)},gggg:function(){return k(this.weekYear(),4)},ggggg:function(){return k(this.weekYear(),5)},GG:function(){return k(this.isoWeekYear()%100,2)},GGGG:function(){return k(this.isoWeekYear(),4)},GGGGG:function(){return k(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return s(this.milliseconds()/100)},SS:function(){return k(s(this.milliseconds()/10),2)},SSS:function(){return k(this.milliseconds(),3)},SSSS:function(){return k(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+k(s(a/60),2)+":"+k(s(a)%60,2)},ZZ:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+k(s(a/60),2)+k(s(a)%60,2)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()},Q:function(){return this.quarter()}},Zb=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Wb.length;)eb=Wb.pop(),Yb[eb+"o"]=d(Yb[eb],eb);for(;Xb.length;)eb=Xb.pop(),Yb[eb+eb]=c(Yb[eb],2);for(Yb.DDDD=c(Yb.DDD,3),h(e.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a){var b,c,d;for(this._monthsParse||(this._monthsParse=[]),b=0;12>b;b++)if(this._monthsParse[b]||(c=db.utc([2e3,b]),d="^"+this.months(c,"")+"|^"+this.monthsShort(c,""),this._monthsParse[b]=new RegExp(d.replace(".",""),"i")),this._monthsParse[b].test(a))return b},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=db([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b){var c=this._calendar[a];return"function"==typeof c?c.apply(b):c},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",preparse:function(a){return a},postformat:function(a){return a},week:function(a){return Y(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),db=function(c,d,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._i=c,g._f=d,g._l=e,g._strict=f,g._isUTC=!1,g._pf=b(),$(g)},db.utc=function(c,d,e,f){var g;return"boolean"==typeof e&&(f=e,e=a),g={},g._isAMomentObject=!0,g._useUTC=!0,g._isUTC=!0,g._l=e,g._i=c,g._f=d,g._strict=f,g._pf=b(),$(g).utc()},db.unix=function(a){return db(1e3*a)},db.duration=function(a,b){var c,d,e,f=a,h=null;return db.isDuration(a)?f={ms:a._milliseconds,d:a._days,M:a._months}:"number"==typeof a?(f={},b?f[b]=a:f.milliseconds=a):(h=tb.exec(a))?(c="-"===h[1]?-1:1,f={y:0,d:s(h[kb])*c,h:s(h[lb])*c,m:s(h[mb])*c,s:s(h[nb])*c,ms:s(h[ob])*c}):(h=ub.exec(a))&&(c="-"===h[1]?-1:1,e=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*c},f={y:e(h[2]),M:e(h[3]),d:e(h[4]),h:e(h[5]),m:e(h[6]),s:e(h[7]),w:e(h[8])}),d=new g(f),db.isDuration(a)&&a.hasOwnProperty("_lang")&&(d._lang=a._lang),d},db.version=fb,db.defaultFormat=Nb,db.updateOffset=function(){},db.lang=function(a,b){var c;return a?(b?A(y(a),b):null===b?(B(a),a="en"):pb[a]||C(a),c=db.duration.fn._lang=db.fn._lang=C(a),c._abbr):db.fn._lang._abbr},db.langData=function(a){return a&&a._lang&&a._lang._abbr&&(a=a._lang._abbr),C(a)},db.isMoment=function(a){return a instanceof f||null!=a&&a.hasOwnProperty("_isAMomentObject")},db.isDuration=function(a){return a instanceof g},eb=Zb.length-1;eb>=0;--eb)r(Zb[eb]);for(db.normalizeUnits=function(a){return p(a)},db.invalid=function(a){var b=db.utc(0/0);return null!=a?h(b._pf,a):b._pf.userInvalidated=!0,b},db.parseZone=function(a){return db(a).parseZone()},h(db.fn=f.prototype,{clone:function(){return db(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){var a=db(this).utc();return 00:!1},parsingFlags:function(){return h({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(a){var b=F(this,a||db.defaultFormat);return this.lang().postformat(b)},add:function(a,b){var c;return c="string"==typeof a?db.duration(+b,a):db.duration(a,b),l(this,c,1),this},subtract:function(a,b){var c;return c="string"==typeof a?db.duration(+b,a):db.duration(a,b),l(this,c,-1),this},diff:function(a,b,c){var d,e,f=z(a,this),g=6e4*(this.zone()-f.zone());return b=p(b),"year"===b||"month"===b?(d=432e5*(this.daysInMonth()+f.daysInMonth()),e=12*(this.year()-f.year())+(this.month()-f.month()),e+=(this-db(this).startOf("month")-(f-db(f).startOf("month")))/d,e-=6e4*(this.zone()-db(this).startOf("month").zone()-(f.zone()-db(f).startOf("month").zone()))/d,"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:j(e)},from:function(a,b){return db.duration(this.diff(a)).lang(this.lang()._abbr).humanize(!b)},fromNow:function(a){return this.from(db(),a)},calendar:function(){var a=z(db(),this).startOf("day"),b=this.diff(a,"days",!0),c=-6>b?"sameElse":-1>b?"lastWeek":0>b?"lastDay":1>b?"sameDay":2>b?"nextDay":7>b?"nextWeek":"sameElse";return this.format(this.lang().calendar(c,this))},isLeapYear:function(){return v(this.year())},isDST:function(){return this.zone()+db(a).startOf(b)},isBefore:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)<+db(a).startOf(b)},isSame:function(a,b){return b=b||"ms",+this.clone().startOf(b)===+z(a,this).startOf(b)},min:function(a){return a=db.apply(null,arguments),this>a?this:a},max:function(a){return a=db.apply(null,arguments),a>this?this:a},zone:function(a){var b=this._offset||0;return null==a?this._isUTC?b:this._d.getTimezoneOffset():("string"==typeof a&&(a=I(a)),Math.abs(a)<16&&(a=60*a),this._offset=a,this._isUTC=!0,b!==a&&l(this,db.duration(b-a,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return this._tzm?this.zone(this._tzm):"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(a){return a=a?db(a).zone():0,(this.zone()-a)%60===0},daysInMonth:function(){return t(this.year(),this.month())},dayOfYear:function(a){var b=hb((db(this).startOf("day")-db(this).startOf("year"))/864e5)+1;return null==a?b:this.add("d",a-b)},quarter:function(){return Math.ceil((this.month()+1)/3)},weekYear:function(a){var b=Y(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==a?b:this.add("y",a-b)},isoWeekYear:function(a){var b=Y(this,1,4).year;return null==a?b:this.add("y",a-b)},week:function(a){var b=this.lang().week(this);return null==a?b:this.add("d",7*(a-b))},isoWeek:function(a){var b=Y(this,1,4).week;return null==a?b:this.add("d",7*(a-b))},weekday:function(a){var b=(this.day()+7-this.lang()._week.dow)%7;return null==a?b:this.add("d",a-b)},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},get:function(a){return a=p(a),this[a]()},set:function(a,b){return a=p(a),"function"==typeof this[a]&&this[a](b),this},lang:function(b){return b===a?this._lang:(this._lang=C(b),this)}}),eb=0;eb size) { 71 | return string.substring(0, size)+'...'; 72 | } else { 73 | return string; 74 | } 75 | }; 76 | }) 77 | .filter('toURL', function() { 78 | return function(string) { 79 | return 'https://linkeddata.github.io/profile-editor/#/profile/view?webid='+encodeURIComponent(string); 80 | }; 81 | }) 82 | 83 | /** 84 | * Directives 85 | */ 86 | .directive('ngFocus', function($timeout) { 87 | return { 88 | link: function ( scope, element, attrs ) { 89 | scope.$watch( attrs.ngFocus, function ( val ) { 90 | if ( angular.isDefined( val ) && val ) { 91 | $timeout( function () { element[0].focus(); } ); 92 | } 93 | }, true); 94 | 95 | element.bind('blur', function () { 96 | if ( angular.isDefined( attrs.ngFocusLost ) ) { 97 | scope.$apply( attrs.ngFocusLost ); 98 | 99 | } 100 | }); 101 | } 102 | }; 103 | }) 104 | 105 | /** 106 | * And of course we define a controller for our route. 107 | */ 108 | .controller( 'ListCtrl', function ListController( $scope, $http, $location, $modal, $sce, $stateParams, ngProgress ) { 109 | // variables 110 | $scope.schema = ''; 111 | $scope.resources = []; 112 | $scope.listLocation = true; 113 | $scope.emptyDir = false; 114 | $scope.breadCrumbs = []; 115 | 116 | $scope.prepareList = function(url) { 117 | if (url && url.length > 0) { 118 | $scope.listLocation = true; 119 | $location.path('/list/'+stripSchema(url)); 120 | } else { 121 | $scope.listLocation = false; 122 | notify('Warning', 'Please provide a valid URL'); 123 | } 124 | }; 125 | 126 | $scope.changeLocation = function(path) { 127 | $location.path(path); 128 | }; 129 | 130 | // TODO: rdflib fetch does not respond properly to 404 131 | $scope.listDir = function (url, key) { 132 | var elms = url.split("/"); 133 | // elms = elms.splice(-1); 134 | var schema = ''; 135 | var path = ''; 136 | if (elms[0].substring(0, 5) == 'https') { 137 | schema = elms[0]; 138 | // elms = elms.splice(0,1); 139 | } else { 140 | schema = 'http'; 141 | } 142 | $scope.path = schema+'://'; 143 | 144 | for (i=1; i 0 && user.slice(0,4) == 'http') { 198 | getProfile($scope, user, $scope.userProfile).then(function(profile){ 199 | userProfile = profile; 200 | }); 201 | } 202 | 203 | // Build list of dirs and files 204 | var files = g.statementsMatching(undefined, LDP("contains"), undefined); 205 | var dirs = []; 206 | for (var i=files.length-1; i>=0; i--) { 207 | if (g.statementsMatching(files[i].object, RDF("type"), LDP("Container")).length > 0 || 208 | g.statementsMatching(files[i].object, RDF("type"), LDP("BasicContainer")).length > 0 || 209 | g.statementsMatching(files[i].object, RDF("type"), POSIX("Directory")).length > 0) { 210 | dirs.push(files[i]); 211 | files.splice(i,1); 212 | } 213 | } 214 | 215 | // add root 216 | var base = '#/list/'; 217 | // base += $stateParams.path; 218 | var rootURI = $scope.path.split('://')[1].split('/')[0]; 219 | var d = { 220 | id: $scope.resources.length+1, 221 | uri: $scope.path, 222 | path: base+dirname($stateParams.path)+'/', 223 | type: '-', 224 | name: '../', 225 | mtime: g.any($rdf.sym($scope.path), POSIX("mtime")).value, 226 | size: '-' 227 | }; 228 | if (d.path == base+schema+'/') { 229 | d.name = '/'; 230 | d.path = document.location.href; 231 | } 232 | $scope.resources.push(d); 233 | 234 | for (i in dirs) { 235 | if (key.length > 0 && dirs[i].object.value.indexOf(key) >= 0) { 236 | dirs[i].object.value = dirs[i].object.value.slice(0, -key.length); 237 | } 238 | d = { 239 | id: $scope.resources.length+1, 240 | uri: dirs[i].object.value, 241 | path: base+$stateParams.path+(basename(dirs[i].object.value))+'/', 242 | type: 'Directory', 243 | name: decodeURI(basename(dirs[i].object.value).replace("+", "%20")), 244 | mtime: g.any(dirs[i].object, POSIX("mtime")).value, 245 | size: '-' 246 | }; 247 | $scope.resources.push(d); 248 | } 249 | for (i in files) { 250 | var f = { 251 | id: $scope.resources.length+1, 252 | uri: files[i].object.value, 253 | path: files[i].object.value, 254 | type: 'File', // TODO: use the real type 255 | name: decodeURI(basename(files[i].object.value).replace("+", "%20")), 256 | mtime: g.any(files[i].object, POSIX("mtime")).value, 257 | size: g.any(files[i].object, POSIX("size")).value 258 | }; 259 | $scope.resources.push(f); 260 | $scope.$apply(); 261 | } 262 | if ($scope.resources.length === 0) { 263 | $scope.emptyDir = true; 264 | } 265 | ngProgress.complete(); 266 | } 267 | $scope.$apply(); 268 | }); 269 | }; 270 | 271 | $scope.upload = function () { 272 | console.log("Uploading files"); 273 | }; 274 | 275 | $scope.newDir = function(dirName) { 276 | // trim whitespaces 277 | dirName = dirName.replace(/^\s+|\s+$/g, ""); 278 | $http({ 279 | method: 'POST', 280 | url: $scope.path+$scope.key, 281 | data: '', 282 | headers: { 283 | 'Content-Type': 'text/turtle', 284 | 'Link': '; rel="type"', 285 | 'Slug': dirName 286 | }, 287 | withCredentials: true 288 | }). 289 | success(function(data, status, headers) { 290 | if (status == 200 || status == 201) { 291 | // add dir to local list 292 | var now = new Date().getTime(); 293 | var base = (document.location.href.charAt(document.location.href.length - 1) === '/')?document.location.href:document.location.href+'/'; 294 | var newURI = $scope.path+dirName; 295 | if (headers('Location')) { 296 | newURI = headers('Location'); 297 | if (newURI && newURI.slice(0,4) != 'http') { 298 | newURI = $scope.path.slice(0, $scope.path.lastIndexOf('/') + 1)+newURI; 299 | } 300 | } 301 | addResource($scope.resources, newURI, 'Directory'); 302 | $scope.emptyDir = false; 303 | notify('Success', 'Directory created.'); 304 | } 305 | }). 306 | error(function(data, status) { 307 | if (status == 401) { 308 | notify('Forbidden', 'Authentication required to create new directory.'); 309 | } else if (status == 403) { 310 | notify('Forbidden', 'You are not allowed to create new directory.'); 311 | } else { 312 | notify('Failed '+status, data, 5000); 313 | } 314 | }); 315 | }; 316 | 317 | $scope.newFile = function(fileName) { 318 | // trim whitespaces 319 | fileName = fileName.replace(/^\s+|\s+$/g, ""); 320 | var uri = $scope.path; 321 | $http({ 322 | method: 'POST', 323 | url: uri+$scope.key, 324 | data: '', 325 | headers: { 326 | 'Content-Type': 'text/turtle', 327 | 'Link': '; rel="type"', 328 | 'Slug': fileName 329 | }, 330 | withCredentials: true 331 | }). 332 | success(function(data, status, headers) { 333 | if (status == 200 || status == 201) { 334 | // Add resource to the list 335 | var newURI = $scope.path+fileName; 336 | if (headers('Location')) { 337 | newURI = headers('Location'); 338 | if (newURI && newURI.slice(0,4) != 'http') { 339 | newURI = uri.slice(0, uri.lastIndexOf('/'))+newURI; 340 | } 341 | } 342 | addResource($scope.resources, newURI, 'File'); 343 | refreshResource($http, $scope.resources, newURI, $scope.key); 344 | $scope.emptyDir = false; 345 | notify('Success', 'Resource created.'); 346 | } 347 | }). 348 | error(function(data, status) { 349 | if (status == 401) { 350 | notify('Forbidden', 'Authentication required to create new resource.'); 351 | } else if (status == 403) { 352 | notify('Forbidden', 'You are not allowed to create new resource.'); 353 | } else { 354 | notify('Failed '+status, data); 355 | } 356 | }); 357 | }; 358 | 359 | $scope.updateFile = function(fileContent) { 360 | var uri = $scope.uri; 361 | var data = $("#fileContent").val(); 362 | $http({ 363 | method: 'PUT', 364 | url: uri, 365 | data: data, 366 | headers: { 367 | 'Content-Type': 'text/turtle', 368 | 'Link': '; rel="type"' 369 | }, 370 | withCredentials: true 371 | }). 372 | success(function(data, status, headers) { 373 | if (status == 200 || status == 201) { 374 | // Add resource to the list 375 | //addResource($scope.resources, uri, 'File'); 376 | //refreshResource($http, $scope.resources, uri); 377 | //$scope.emptyDir = false; 378 | notify('Success', 'Resource updated.'); 379 | } 380 | }). 381 | error(function(data, status) { 382 | if (status == 401) { 383 | notify('Forbidden', 'Authentication required to update resource.'); 384 | } else if (status == 403) { 385 | notify('Forbidden', 'You are not allowed to update resource.'); 386 | } else { 387 | notify('Failed '+status, data); 388 | } 389 | }); 390 | }; 391 | 392 | $scope.deleteResource = function(resourceUri) { 393 | $http({ 394 | method: 'DELETE', 395 | url: resourceUri+$scope.key, 396 | withCredentials: true 397 | }). 398 | success(function(data, status, headers) { 399 | if (status == 200) { 400 | // remove resource from the view 401 | $scope.removeResource(resourceUri); 402 | //TODO: remove the acl and meta files. 403 | var lh = parseLinkHeader(headers('Link')); 404 | if (lh['meta'] && lh['meta']['href'].length > 0 && lh['meta']['href'] != resourceUri) { 405 | $http({ 406 | method: 'DELETE', 407 | url: lh['meta']['href']+$scope.key, 408 | withCredentials: true 409 | }). 410 | success(function (data, status) { 411 | $scope.removeResource(lh['meta']['href']); 412 | }). 413 | error(function(data, status) { 414 | if (status == 401) { 415 | notify('Forbidden', 'Authentication required to delete '+lh['meta']['href']); 416 | } else if (status == 403) { 417 | notify('Forbidden', 'You are not allowed to delete '+lh['meta']['href']); 418 | } else { 419 | console.log('Failed to delete '+lh['meta']['href']+" Server responded with HTTP "+status); 420 | } 421 | }); 422 | } 423 | if (lh['acl'] && lh['acl']['href'].length > 0 && lh['acl']['href'] != resourceUri) { 424 | $http({ 425 | method: 'DELETE', 426 | url: lh['acl']['href']+$scope.key, 427 | withCredentials: true 428 | }). 429 | success(function (data, status) { 430 | $scope.removeResource(lh['acl']['href']); 431 | }). 432 | error(function(data, status) { 433 | if (status == 401) { 434 | notify('Forbidden', 'Authentication required to delete '+lh['acl']['href']); 435 | } else if (status == 403) { 436 | notify('Forbidden', 'You are not allowed to delete '+lh['acl']['href']); 437 | } else { 438 | console.log('Failed to delete '+lh['acl']['href']+" Server responded with HTTP "+status); 439 | } 440 | }); 441 | } 442 | } 443 | }). 444 | error(function(data, status) { 445 | if (status == 401) { 446 | notify('Forbidden', 'Authentication required to delete '+basename(resourceUri)); 447 | } else if (status == 403) { 448 | notify('Forbidden', 'You are not allowed to delete '+basename(resourceUri)); 449 | } else if (status == 409) { 450 | notify('Failed', 'Conflict detected. In case of directory, check if not empty.'); 451 | } else { 452 | notify('Failed '+status, data); 453 | } 454 | }); 455 | }; 456 | 457 | $scope.removeResource = function(uri) { 458 | if ($scope.resources) { 459 | for(var i = $scope.resources.length - 1; i >= 0; i--){ 460 | if($scope.resources[i].uri == uri) { 461 | $scope.resources.splice(i,1); 462 | if ($scope.resources.length === 0) { 463 | $scope.emptyDir = true; 464 | } 465 | notify('Success', 'Deleted '+decodeURI(basename(uri)+'.')); 466 | } 467 | } 468 | } 469 | }; 470 | 471 | // New dir dialog 472 | $scope.openNewLocation = function () { 473 | var modalInstance = $modal.open({ 474 | templateUrl: 'newlocation.html', 475 | controller: ModalNewLocationCtrl, 476 | size: 'sm' 477 | }); 478 | modalInstance.result.then($scope.prepareList); 479 | }; 480 | // New dir dialog 481 | $scope.openNewDir = function () { 482 | var modalInstance = $modal.open({ 483 | templateUrl: 'newdir.html', 484 | controller: ModalNewDirCtrl, 485 | size: 'sm' 486 | }); 487 | modalInstance.result.then($scope.newDir); 488 | }; 489 | // New file creation dialog 490 | $scope.openNewFile = function () { 491 | var modalInstance = $modal.open({ 492 | templateUrl: 'newfile.html', 493 | controller: ModalNewFileCtrl, 494 | size: 'sm' 495 | }); 496 | modalInstance.result.then($scope.newFile); 497 | }; 498 | // Remove resource dialog 499 | $scope.openDelete = function (uri) { 500 | var modalInstance = $modal.open({ 501 | templateUrl: 'delete.html', 502 | controller: ModalDeleteCtrl, 503 | size: 'sm', 504 | resolve: { 505 | uri: function () { 506 | return uri; 507 | } 508 | } 509 | }); 510 | modalInstance.result.then($scope.deleteResource); 511 | }; 512 | //File editor dialog 513 | $scope.openFileEditor = function (uri) { 514 | $scope.uri = uri; 515 | var modalInstance = $modal.open({ 516 | templateUrl: 'fileEditor.html', 517 | controller: ModalFileEditorCtrl, 518 | size: 'sm', 519 | resolve: { 520 | uri: function () { 521 | return uri; 522 | } 523 | } 524 | }); 525 | modalInstance.result.then($scope.updateFile); 526 | }; 527 | // New file upload dialog 528 | $scope.openNewUpload = function (url) { 529 | var modalInstance = $modal.open({ 530 | templateUrl: 'uploadfiles.html', 531 | controller: ModalUploadCtrl, 532 | size: 'sm', 533 | resolve: { 534 | url: function () { 535 | return url; 536 | }, 537 | resources: function () { 538 | return $scope.resources; 539 | } 540 | } 541 | }); 542 | }; 543 | // ACL dialog 544 | $scope.openACLEditor = function (resources, uri, type) { 545 | var findACLURL = function(data, status, headers) { 546 | // add dir to local list 547 | var lh = parseLinkHeader(headers('Link')); 548 | var aclURI = (lh['acl'] && lh['acl']['href'].length > 0)?lh['acl']['href']:''; 549 | // check for relative URIs 550 | if (aclURI && aclURI.slice(0,4) != 'http') { 551 | aclURI = uri.slice(0, uri.lastIndexOf('/') + 1)+aclURI; 552 | } 553 | 554 | var checkACLfile = function(data, status, headers) { 555 | if (status === 200 || status === 404) { 556 | // missing ACL file is OK 557 | var modalInstance = $modal.open({ 558 | templateUrl: 'acleditor.html', 559 | controller: ModalACLEditor, 560 | resolve: { 561 | resources: function () { 562 | return resources; 563 | }, 564 | uri: function () { 565 | return uri; 566 | }, 567 | aclURI: function () { 568 | return aclURI; 569 | }, 570 | type: function() { 571 | return type; 572 | }, 573 | exists: function() { 574 | return (status === 200)?true:false; 575 | } 576 | } 577 | }); 578 | } else if (status === 401) { 579 | notify('Forbidden', 'Authentication required to change permissions for: '+decodeURI(basename(uri))); 580 | } else if (status === 403) { 581 | notify('Forbidden', 'You are not allowed to change permissions for: '+decodeURI(basename(uri))); 582 | } else { 583 | notify('Failed - HTTP '+status, data, 5000); 584 | } 585 | }; 586 | 587 | $http({ 588 | method: 'HEAD', 589 | url: aclURI, 590 | withCredentials: true 591 | }). 592 | success(checkACLfile).error(checkACLfile); 593 | }; 594 | // Find ACL uri and check if we can modify it 595 | $http({ 596 | method: 'HEAD', 597 | url: uri, 598 | withCredentials: true 599 | }).success(findACLURL).error(findACLURL); 600 | }; 601 | 602 | // Display list for current path 603 | if ($stateParams.path.length > 0) { 604 | $scope.key = ""; 605 | if ($stateParams.key && $stateParams.key.length > 0) { 606 | $scope.key += '?key='+$stateParams.key; 607 | } 608 | $scope.listDir($stateParams.path, $scope.key); 609 | } else { 610 | $scope.listLocation = false; 611 | } 612 | }); 613 | 614 | var refreshResource = function(http, resources, uri, key) { 615 | if (!key) { 616 | key = ''; 617 | } 618 | http({ 619 | method: 'HEAD', 620 | url: uri+key, 621 | withCredentials: true 622 | }). 623 | success(function(data, status, headers) { 624 | var l = headers('Content-Length'); 625 | if (l && l.length > 0) { 626 | for(var i = resources.length - 1; i >= 0; i--) { 627 | if (resources[i].uri == uri) { 628 | resources[i].size = l; 629 | break; 630 | } 631 | } 632 | } 633 | }); 634 | }; 635 | 636 | var resourceExists = function (resources, uri) { 637 | if (resources.length > 0) { 638 | for(var i = resources.length - 1; i >= 0; i--){ 639 | if(decodeURI(resources[i].uri) == decodeURI(uri)) { 640 | return resources[i]; 641 | } 642 | } 643 | } 644 | return undefined; 645 | }; 646 | 647 | var addResource = function (resources, uri, type, size) { 648 | // Add resource to the list 649 | var base = (document.location.href.charAt(document.location.href.length - 1) === '/')?document.location.href:document.location.href+'/'; 650 | var path = (type === 'File')?dirname(uri)+'/'+encodeURI(basename(uri)):base+basename(uri)+'/'; 651 | var now = new Date().getTime(); 652 | size = (size)?size:'-'; 653 | var f = { 654 | id: resources.length+1, 655 | uri: uri, 656 | path: path, 657 | type: type, // TODO: use the real type 658 | name: decodeURI(basename(uri)), 659 | mtime: now, 660 | size: size 661 | }; 662 | // overwrite previous resource 663 | if (!resourceExists(resources, uri)) { 664 | resources.push(f); 665 | } 666 | }; 667 | 668 | // Modal Ctrls 669 | var ModalNewLocationCtrl = function ($scope, $modalInstance) { 670 | $scope.isFocused = true; 671 | $scope.newLoc = ""; 672 | 673 | $scope.newLoc = function(locName) { 674 | $modalInstance.close(locName); 675 | }; 676 | 677 | $scope.ok = function () { 678 | $modalInstance.close(); 679 | }; 680 | 681 | $scope.cancel = function () { 682 | $modalInstance.dismiss('cancel'); 683 | }; 684 | }; 685 | 686 | var ModalNewDirCtrl = function ($scope, $modalInstance) { 687 | $scope.isFocused = true; 688 | 689 | $scope.newDir = function(dirName) { 690 | $modalInstance.close(dirName); 691 | }; 692 | 693 | $scope.ok = function () { 694 | $modalInstance.close(); 695 | }; 696 | 697 | $scope.cancel = function () { 698 | $modalInstance.dismiss('cancel'); 699 | }; 700 | }; 701 | 702 | var ModalNewFileCtrl = function ($scope, $modalInstance) { 703 | // TODO 704 | // $scope.expr = "/^[A-Za-z0-9_-(\.)]*$/"; 705 | 706 | $scope.isFocused = true; 707 | 708 | $scope.newFile = function(fileName) { 709 | $modalInstance.close(fileName); 710 | }; 711 | 712 | $scope.ok = function () { 713 | $modalInstance.close(); 714 | }; 715 | 716 | $scope.cancel = function () { 717 | $modalInstance.dismiss('cancel'); 718 | }; 719 | }; 720 | 721 | var ModalFileEditorCtrl = function ($scope, $modalInstance, uri, $http) { 722 | // TODO 723 | // $scope.expr = "/^[A-Za-z0-9_-(\.)]*$/"; 724 | 725 | $scope.isFocused = true; 726 | 727 | $scope.updateFile = function(fileContent) { 728 | $modalInstance.close(fileContent); 729 | }; 730 | 731 | $scope.ok = function () { 732 | $modalInstance.close(); 733 | }; 734 | 735 | $scope.cancel = function () { 736 | $modalInstance.dismiss('cancel'); 737 | }; 738 | 739 | $http({ 740 | method: 'GET', 741 | url: uri, 742 | headers: { 743 | 'Accept': 'text/turtle' 744 | }, 745 | withCredentials: true 746 | }). 747 | success(function(data, status, headers) { 748 | if (status == 200 || status == 201) { 749 | // Load the rdf to the textarea 750 | $("#fileContent").val(data); 751 | } 752 | }). 753 | error(function(data, status) { 754 | if (status == 401) { 755 | notify('Forbidden', 'Authentication required to edit the resource.'); 756 | } else if (status == 403) { 757 | notify('Forbidden', 'You are not allowed to edit the resource.'); 758 | } else { 759 | notify('Failed', status + " " + data); 760 | } 761 | }); 762 | }; 763 | 764 | var ModalDeleteCtrl = function ($scope, $modalInstance, uri) { 765 | $scope.delUri = uri; 766 | $scope.resource = decodeURI(basename(uri)); 767 | $scope.deleteResource = function() { 768 | $modalInstance.close($scope.delUri); 769 | }; 770 | 771 | $scope.ok = function () { 772 | $modalInstance.close(); 773 | }; 774 | 775 | $scope.cancel = function () { 776 | $modalInstance.dismiss('cancel'); 777 | }; 778 | }; 779 | 780 | var ModalUploadCtrl = function ($scope, $modalInstance, $stateParams, $http, $upload, url, resources) { 781 | $scope.url = url; 782 | $scope.resources = resources; 783 | $scope.container = basename(url); 784 | $scope.uploading = []; 785 | $scope.progress = []; 786 | $scope.filesUploading = 0; 787 | 788 | $scope.key = ''; 789 | if ($stateParams.key && $stateParams.key.length > 0) { 790 | $scope.key += '?key='+$stateParams.key; 791 | } 792 | 793 | // stop/abort the upload of a file 794 | $scope.abort = function(index) { 795 | console.log($scope.uploading.length); 796 | $scope.uploading[index].abort(); 797 | }; 798 | 799 | // remove file from upload list 800 | $scope.remove = function(index) { 801 | if ($scope.selectedFiles.length > 0) { 802 | for (var i = $scope.selectedFiles.length - 1; i >= 0; i--) { 803 | if(decodeURI($scope.selectedFiles[i].name) == decodeURI(index)) { 804 | $scope.selectedFiles.splice(i, 1); 805 | $scope.uploading[index].abort(); 806 | $scope.uploading[index] = null; 807 | break; 808 | } 809 | } 810 | } 811 | }; 812 | 813 | $scope.clearUploaded = function () { 814 | $scope.selectedFiles = []; 815 | }; 816 | 817 | $scope.$watch('filesUploading', function(newVal, oldVal) { 818 | if (oldVal > 0 && newVal === 0) { 819 | notify('Success', 'Finished uploading files!'); 820 | } 821 | }); 822 | 823 | // TODO: handle errors during upload 824 | $scope.doUpload = function(file) { 825 | $scope.progress[file.name] = 0; 826 | file.name = file.name.replace(/^\s+|\s+$/g, ""); 827 | $scope.uploading[file.name] = $upload.upload({ 828 | url: $scope.url+$scope.key, 829 | method: 'POST', 830 | withCredentials: true, 831 | file: file 832 | }).progress(function(evt) { 833 | var progVal = parseInt(100.0 * evt.loaded / evt.total, 10); 834 | $scope.progress[file.name] = progVal; 835 | }).success(function(data, status, headers, config) { 836 | // file is uploaded successfully 837 | $scope.filesUploading--; 838 | addResource($scope.resources, $scope.url+encodeURI(file.name), 'File', file.size); 839 | refreshResource($http, $scope.resources, $scope.url+encodeURI(file.name), $scope.key); 840 | }); 841 | }; 842 | 843 | $scope.onFileSelect = function($files) { 844 | $scope.selectedFiles = $files; 845 | $scope.filesUploading = $files.length; 846 | 847 | for (var i = 0; i < $files.length; i++) { 848 | var file = $files[i]; 849 | $scope.doUpload(file); 850 | } 851 | }; 852 | 853 | $scope.ok = function () { 854 | $modalInstance.close(); 855 | }; 856 | 857 | $scope.cancel = function () { 858 | $modalInstance.dismiss('cancel'); 859 | }; 860 | }; 861 | 862 | var ModalACLEditor = function ($scope, $modalInstance, $http, resources, uri, aclURI, type, exists) { 863 | $scope.resources = resources; 864 | $scope.uri = uri; 865 | $scope.aclURI = aclURI; 866 | $scope.resType = type; 867 | $scope.gotOwner = false; 868 | $scope.resource = decodeURI(basename(uri)); 869 | $scope.policies = []; 870 | $scope.newUser = []; 871 | $scope.newKey = []; 872 | $scope.webidresults = []; 873 | 874 | $scope.isFocused = true; 875 | $scope.loading = true; 876 | $scope.disableOk = false; 877 | 878 | // Load ACL triples 879 | var RDF = $rdf.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#"); 880 | var RDFS = $rdf.Namespace("http://www.w3.org/2000/01/rdf-schema#"); 881 | var WAC = $rdf.Namespace("http://www.w3.org/ns/auth/acl#"); 882 | var FOAF = $rdf.Namespace("http://xmlns.com/foaf/0.1/"); 883 | var DCT = $rdf.Namespace("http://purl.org/dc/terms/"); 884 | var LDP = $rdf.Namespace("http://www.w3.org/ns/ldp#"); 885 | var SIOC = $rdf.Namespace("http://rdfs.org/sioc/ns#"); 886 | var SOLID = $rdf.Namespace("http://www.w3.org/ns/solid/terms#"); 887 | 888 | var g = $rdf.graph(); 889 | 890 | // add CORS proxy 891 | $rdf.Fetcher.crossSiteProxyTemplate=PROXY; 892 | 893 | // truncate string 894 | $scope.trunc = function (str, size) { 895 | if (str !== undefined) { 896 | return (str.length > size)?str.slice(0, size)+'...':str; 897 | } else { 898 | return ''; 899 | } 900 | }; 901 | 902 | $scope.findModes = function(modes) { 903 | var ret = {Read: false, Write: false, Append: false, Control: false}; 904 | if (modes !== undefined && modes.length > 0) { 905 | for (var i in modes) { 906 | if (modes[i] !== undefined) { 907 | mode = modes[i].object.uri.slice(modes[i].object.uri.indexOf('#')+1, modes[i].object.uri.length); 908 | if (mode == "Read") { ret.Read = true; } 909 | else if (mode == "Write") { ret.Write = true; } 910 | else if (mode == "Append") { ret.Append = true; } 911 | else if (mode == "Control") { ret.Control = true; } 912 | } 913 | } 914 | } 915 | return ret; 916 | }; 917 | 918 | $scope.removePolicy = function (hashKey) { 919 | if ($scope.policies !== undefined) { 920 | angular.forEach($scope.policies, function(policy, key) { 921 | if(policy.$$hashKey === hashKey) { 922 | $scope.policies.splice(key,1); 923 | } 924 | }); 925 | } 926 | }; 927 | 928 | $scope.showNewUser = function (cat) { 929 | $scope.newUser[cat] = {}; 930 | var newUser = $scope.newUser[cat]; 931 | }; 932 | 933 | $scope.addNewUser = function(cat, webid) { 934 | var user = {}; 935 | user.webid = webid; 936 | user.cat = cat; 937 | if (cat == 'owner') { 938 | $scope.gotOwner = true; 939 | user.modes = {Read: true, Write: true, Control: true}; 940 | } 941 | user.fullname = webid; 942 | getProfile($scope, webid, user).then(function(profile){ 943 | user.loading = false; 944 | }); 945 | user.isNew = true; 946 | $scope.policies.push(user); 947 | 948 | $scope.newUser[cat] = undefined; 949 | }; 950 | 951 | $scope.showNewKey = function (cat) { 952 | $scope.newKey[cat] = {}; 953 | $scope.newKey[cat].key = ''; 954 | }; 955 | 956 | $scope.addNewKey = function(cat, key) { 957 | var user = {}; 958 | user.key = key; 959 | user.cat = cat; 960 | user.predicate = 'resourceKey'; 961 | if (cat == 'owner') { 962 | $scope.gotOwner = true; 963 | user.modes = {Read: true, Write: true, Control: true}; 964 | } 965 | user.fullname = key; 966 | $scope.policies.push(user); 967 | 968 | $scope.newKey[cat] = undefined; 969 | }; 970 | 971 | $scope.generateNewKey = function(obj) { 972 | var alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 973 | for( var i=0; i < 12; i++ ) { 974 | obj.key += alpha.charAt(Math.floor(Math.random() * alpha.length)); 975 | } 976 | }; 977 | 978 | // fetch user data 979 | if (exists) { 980 | var f = $rdf.fetcher(g, TIMEOUT); 981 | f.nowOrWhenFetched($scope.aclURI,undefined,function(ok, body) { 982 | if (!ok) { 983 | console.log('Error -- could not fetch ACL file. Is the server available?'); 984 | $scope.listLocation = false; 985 | $scope.loading = false; 986 | $scope.$apply(); 987 | } 988 | 989 | findModes = function(modes) { 990 | var ret = {Read: false, Write: false, Append: false, Control: false}; 991 | if (modes !== undefined && modes.length > 0) { 992 | for (var i in modes) { 993 | if (modes[i] !== undefined) { 994 | mode = modes[i].object.uri.slice(modes[i].object.uri.indexOf('#')+1, modes[i].object.uri.length); 995 | if (mode == "Read") { ret.Read = true; } 996 | else if (mode == "Write") { ret.Write = true; } 997 | else if (mode == "Append") { ret.Append = true; } 998 | else if (mode == "Control") { ret.Control = true; } 999 | } 1000 | } 1001 | } 1002 | return ret; 1003 | }; 1004 | 1005 | getPolicies = function(triples, cat, arr) { 1006 | if (triples !== undefined && triples.length > 0) { 1007 | for (i=0; i 0)?true:false; 1022 | if (triples[i].object.uri === FOAF("Agent").uri) { 1023 | policy.cat = 'any'; 1024 | } else if (policy.modes.Control === true) { 1025 | policy.cat = 'owner'; 1026 | $scope.gotOwner = true; 1027 | } else { 1028 | policy.cat = cat; 1029 | } 1030 | arr.push(policy); 1031 | } 1032 | 1033 | return true; 1034 | } else { 1035 | return false; 1036 | } 1037 | }; 1038 | 1039 | var policies = g.statementsMatching(undefined, WAC("accessTo"), $rdf.sym($scope.uri)); 1040 | if (policies.length > 0) { 1041 | getPolicies(g.statementsMatching(undefined, WAC("agent"), undefined), 'others', $scope.policies); 1042 | getPolicies(g.statementsMatching(undefined, WAC("agentClass"), undefined), 'others', $scope.policies); 1043 | getPolicies(g.statementsMatching(undefined, WAC("resourceKey"), undefined), 'others', $scope.policies); 1044 | 1045 | // add options for all, if no Any cat exists 1046 | var any = false; 1047 | $scope.policies.forEach(function(policy) { 1048 | if (policy.cat == 'any') { 1049 | any = true; 1050 | } 1051 | }); 1052 | if (!any) { 1053 | $scope.policies.push({ 1054 | webid: FOAF("Agent").uri, 1055 | cat: 'any', 1056 | predicate: 'agentClass' 1057 | }); 1058 | } 1059 | 1060 | $scope.loading = false; 1061 | $scope.$apply(); 1062 | } 1063 | }); 1064 | } else { 1065 | //todo add a default owner 1066 | $scope.addNewUser('owner', userProfile.webid); 1067 | var others = {}; 1068 | others.webid = FOAF("Agent").uri; 1069 | others.cat = 'any'; 1070 | others.predicate = 'agentClass'; 1071 | others.modes = $scope.findModes(); 1072 | $scope.policies.push(others); 1073 | $scope.loading = false; 1074 | } 1075 | 1076 | $scope.haveCategory = function (cat) { 1077 | if ($scope.policies) { 1078 | for (var i=0; i<$scope.policies; i++) { 1079 | if ($scope.policies[i].cat == cat) { 1080 | return true; 1081 | } 1082 | } 1083 | } 1084 | return false; 1085 | }; 1086 | 1087 | $scope.serializeTurtle = function () { 1088 | var RDF = $rdf.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#"); 1089 | var RDFS = $rdf.Namespace("http://www.w3.org/2000/01/rdf-schema#"); 1090 | var WAC = $rdf.Namespace("http://www.w3.org/ns/auth/acl#"); 1091 | var FOAF = $rdf.Namespace("http://xmlns.com/foaf/0.1/"); 1092 | 1093 | var g = new $rdf.graph(); 1094 | if ($scope.policies.length > 0) { 1095 | for (var i=0; i<$scope.policies.length;i++) { 1096 | if ($scope.policies[i].cat == 'any' && !$scope.policies[i].modes || (!$scope.policies[i].modes.Read && !$scope.policies[i].modes.Write && !$scope.policies[i].modes.Append)) { 1097 | continue; 1098 | } 1099 | g.add($rdf.sym("#policy"+i), RDF("type"), WAC('Authorization')); 1100 | g.add($rdf.sym("#policy"+i), WAC("accessTo"), $rdf.sym(decodeURI($scope.uri))); 1101 | var termObj = $rdf.sym($scope.policies[i].webid); 1102 | if ($scope.policies[i].key && $scope.policies[i].key.length > 0) { 1103 | termObj = $rdf.lit($scope.policies[i].key); 1104 | } 1105 | if (!$scope.policies[i].predicate) { 1106 | if ($scope.policies[i].webid == FOAF('Agent').uri) { 1107 | $scope.policies[i].predicate = 'agentClass'; 1108 | } else { 1109 | $scope.policies[i].predicate = 'agent'; 1110 | } 1111 | } 1112 | g.add($rdf.sym("#policy"+i), WAC($scope.policies[i].predicate), termObj); 1113 | if ($scope.resType != 'File') { 1114 | g.add($rdf.sym("#policy"+i), WAC("defaultForNew"), $rdf.sym(decodeURI($scope.uri))); 1115 | } 1116 | if ($scope.policies[i].cat == "owner" && $scope.aclURI.length > 0) { 1117 | g.add($rdf.sym("#policy"+i), WAC("accessTo"), $rdf.sym(decodeURI($scope.aclURI))); 1118 | } 1119 | for (var mode in $scope.policies[i].modes) { 1120 | if ($scope.policies[i].modes[mode] === true) { 1121 | g.add($rdf.sym("#policy"+i), WAC("mode"), WAC(mode)); 1122 | } 1123 | } 1124 | } 1125 | } 1126 | var s = new $rdf.Serializer(g).toN3(g); 1127 | return s; 1128 | }; 1129 | 1130 | // PUT the ACL policy on the server 1131 | $scope.setAcl = function () { 1132 | $scope.disableOk = true; 1133 | var acls = $scope.serializeTurtle(); 1134 | $http({ 1135 | method: 'PUT', 1136 | url: $scope.aclURI, 1137 | withCredentials: true, 1138 | headers: {"Content-Type": "text/turtle"}, 1139 | data: acls 1140 | }). 1141 | success(function() { 1142 | $modalInstance.close(); 1143 | var res = resourceExists($scope.resources, $scope.aclURI); 1144 | if (res === undefined && resources[0].uri == dirname($scope.aclURI)+'/') { 1145 | addResource($scope.resources, $scope.aclURI, "File", "-"); 1146 | } 1147 | refreshResource($http, $scope.resources, $scope.aclURI); 1148 | $scope.disableOk = false; 1149 | 1150 | // Send notifications to each user's inbox (if they have one) 1151 | for (var i=0; i<$scope.policies.length; i++) { 1152 | var p = $scope.policies[i]; 1153 | if (p.inbox && p.isNew && p.webid != userProfile.webid) { 1154 | $scope.sendNotification(p); 1155 | } 1156 | } 1157 | notify('Success', 'Updated ACL policies for: '+basename($scope.uri)); 1158 | }). 1159 | error(function(data, status, headers) { 1160 | $scope.disableOk = false; 1161 | notify('Error - '+status, data); 1162 | }); 1163 | }; 1164 | 1165 | $scope.sendNotification = function(policy) { 1166 | var listModes = function(modes) { 1167 | if (modes) { 1168 | var ret = []; 1169 | Object.keys(modes).forEach(function(m) { 1170 | if (m) { 1171 | ret.push(m.toLowerCase()); 1172 | } 1173 | }); 1174 | return ret.join(', '); 1175 | } 1176 | }; 1177 | 1178 | var g = new $rdf.graph(); 1179 | var me = $rdf.sym(policy.webid); 1180 | var body = 'You have been given '+listModes(policy.modes)+' access to '+$scope.uri+'.'; 1181 | g.add($rdf.sym(''), RDF('type'), SOLID('Notification')); 1182 | g.add($rdf.sym(''), DCT('title'), $rdf.lit('File share activity')); 1183 | g.add($rdf.sym(''), DCT('created'), $rdf.lit(new Date().toISOString(), '', $rdf.Symbol.prototype.XSDdateTime)); 1184 | g.add($rdf.sym(''), SIOC('content'), $rdf.lit(body)); 1185 | g.add($rdf.sym(''), SIOC('has_creator'), $rdf.sym('#author')); 1186 | 1187 | g.add($rdf.sym('#author'), RDF('type'), SIOC('UserAccount')); 1188 | g.add($rdf.sym('#author'), SIOC('account_of'), $rdf.sym(userProfile.webid)); 1189 | if (userProfile.fullname) { 1190 | g.add($rdf.sym('#author'), FOAF('name'), $rdf.lit(userProfile.fullname)); 1191 | } 1192 | if (userProfile.picture) { 1193 | g.add($rdf.sym('#author'), SIOC('avatar'), $rdf.sym(userProfile.picture)); 1194 | } 1195 | 1196 | var data = new $rdf.Serializer(g).toN3(g); 1197 | $http({ 1198 | method: 'POST', 1199 | url: policy.inbox, 1200 | withCredentials: true, 1201 | headers: { 1202 | "Content-Type": "text/turtle", 1203 | "Link": '<'+LDP('Resource').uri+'>; rel="type"' 1204 | }, 1205 | data: data 1206 | }). 1207 | success(function() { 1208 | console.log("Notification sent to "+policy.inbox); 1209 | }); 1210 | }; 1211 | 1212 | // attempt to find a person using webizen.org 1213 | $scope.lookupWebID = function(query) { 1214 | // get results from server 1215 | return $http.get('https://api.webizen.org/v1/search', { 1216 | params: { 1217 | q: query 1218 | } 1219 | }).then(function(response) { 1220 | var matches = []; 1221 | if (response.data) { 1222 | angular.forEach(response.data, function(value, key) { 1223 | var match = {}; 1224 | match.webid = key; 1225 | if (!value.img) { 1226 | match.img = 'assets/generic_photo.png'; 1227 | } else { 1228 | match.img = value.img[0]; 1229 | } 1230 | if (value.name && value.name[0]) { 1231 | match.name = value.name[0] + ' ('+key+')'; 1232 | } else { 1233 | match.name = key; 1234 | } 1235 | matches.push(match); 1236 | }); 1237 | } 1238 | return matches; 1239 | }); 1240 | }; 1241 | 1242 | $scope.cancelNewUser = function(cat) { 1243 | delete $scope.newUser[cat]; 1244 | }; 1245 | 1246 | $scope.cancelNewKey = function(cat) { 1247 | delete $scope.newKey[cat]; 1248 | }; 1249 | 1250 | $scope.cancel = function () { 1251 | $modalInstance.dismiss('cancel'); 1252 | }; 1253 | }; 1254 | --------------------------------------------------------------------------------