<!DOCTYPE html>
<html ng-app="SPA.Module">
<!--add Module-->
<head>
<!--css file-->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.css" />
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.css" />
<!--javascript file-->
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//code.angularjs.org/1.3.1/angular.min.js"></script>
<script src="https://rawgit.com/angular-ui/ui-router/0.2.11/release/angular-ui-router.js"></script>
<script src="https://code.angularjs.org/1.3.1/angular-sanitize.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.js"></script>
<script src="https://code.angularjs.org/1.3.1/angular-animate.min.js"></script>
<!--firebase-->
<script src="https://cdn.firebase.com/js/client/2.0.3/firebase.js"></script>
<!--textAngular Reference:http://textangular.com/-->
<script src="plugins/textAngular.js"></script>
<!--angular-loading-bar Reference:http://chieffancypants.github.io/angular-loading-bar/-->
<link rel="stylesheet" href="plugins/loading-bar.css" />
<script src="plugins/loading-bar.js"></script>
<!--custom controller and css-->
<script src="SPA.js"></script>
<script src="resourceService.js"></script>
<link rel="stylesheet" href="css/container.css"/>
</head>
<body ng-controller="mainController">
<!--views parent controller-->
<div class="panel text-center" style="background: #1d9e74">
<h1 class="headerText">Single Page Application Blog
<small>Simple CRUD</small>
<div id="logoutDiv" ng-hide="userId" class="pull-right">
<a class="btn btn-primary" ng-click="login('google')"><i class="fa fa-google"></i>Sing in with Google</a>
</div>
<div id="loginDiv" ng-show="userId" class="btn-group pull-right">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" >
<img ng-src="{{picturePath}}" alt="Smiley face" height="24" width="24" style="border-radius: 20em">Hello {{userName}}~
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a ui-sref="create"><i class="fa fa-plus"> New Post</i></a></li>
<li><a ui-sref="home"><i class="fa fa-home"> Home</i></a></li>
<li><a href="javascript:void(0)" ng-click="logout()"><i class="fa fa-sign-out"> Logout</i></a></li>
</ul>
</div>
</h1>
</div>
<div class="row-fluid ">
<div class="col-md-10 col-md-offset-1 " ui-view></div>
</div>
</body>
</html>
### Firebase Authentication Blog JSON Format
"datas" has [{key:value},{key:value}] pattern and key is id,value is "template"
```javascript
{
"authenticationBlog":{
"datas":{
},
"template":{
"author":"",
"title":"",
"content":"",
"shortContent":"",
"postDate":"",
"userid":""
}
}
}
```
[textAngular]
[angular-loading-bar]
[textAngular]:http://textangular.com/
[angular-loading-bar]:http://chieffancypants.github.io/angular-loading-bar
/*
textAngular
Author : Austin Anderson
License : 2013 MIT
Version 1.1.2
See README.md or https://github.com/fraywing/textAngular/wiki for requirements and use.
*/
if(!window.console) console = {log: function() {}}; // fixes IE console undefined errors
var textAngular = angular.module("textAngular", ['ngSanitize']); //This makes ngSanitize required
textAngular.directive("textAngular", ['$compile', '$window', '$document', '$rootScope', '$timeout', 'taFixChrome', function($compile, $window, $document, $rootScope, $timeout, taFixChrome) {
// deepExtend instead of angular.extend in order to allow easy customization of "display" for default buttons
// snatched from: http://stackoverflow.com/a/15311794/2966847
function deepExtend(destination, source) {
for (var property in source) {
if (source[property] && source[property].constructor &&
source[property].constructor === Object) {
destination[property] = destination[property] || {};
arguments.callee(destination[property], source[property]);
} else {
destination[property] = source[property];
}
}
return destination;
};
// Here we set up the global display defaults, make sure we don't overwrite any that the user may have already set.
$rootScope.textAngularOpts = deepExtend({
toolbar: [['h1', 'h2', 'h3', 'p', 'pre'], ['bold', 'italics', 'underline', 'ul', 'ol', 'redo', 'undo'], ['justifyLeft','justifyCenter','justifyRight'],['insertImage', 'insertLink','clear','segments']],
classes: {
focussed: "focussed",
toolbar: "btn-toolbar",
toolbarGroup: "btn-group",
toolbarButton: "btn btn-default",
toolbarButtonActive: "active",
textEditor: 'form-control',
htmlEditor: 'form-control'
}
}, ($rootScope.textAngularOpts != null)? $rootScope.textAngularOpts : {});
// Setup the default toolbar tools, this way allows the user to add new tools like plugins
var queryFormatBlockState = function(command){
command = command.toLowerCase();
var val = $document[0].queryCommandValue('formatBlock').toLowerCase();
return val === command || val === command;
}
$rootScope.textAngularTools = deepExtend({
h1: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)'>H1</button>",
action: function() {
return this.$parent.wrapSelection("formatBlock", "<H1>");
},
activeState: function() { return queryFormatBlockState('h1'); }
},
h2: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)'>H2</button>",
action: function() {
return this.$parent.wrapSelection("formatBlock", "<H2>");
},
activeState: function() { return queryFormatBlockState('h2'); }
},
h3: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)'>H3</button>",
action: function() {
return this.$parent.wrapSelection("formatBlock", "<H3>");
},
activeState: function() { return queryFormatBlockState('h3'); }
},
p: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='Paragraph'>P</button>",
action: function() {
return this.$parent.wrapSelection("formatBlock", "<P>");
},
activeState: function() { return queryFormatBlockState('p'); }
},
pre: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' data-toggle='tooltip' title='code pane'><i class='fa fa-codepen'></i></button>",
action: function() {
return this.$parent.wrapSelection("formatBlock", "<PRE>");
},
activeState: function() { return queryFormatBlockState('pre'); }
},
ul: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)'><i class='fa fa-list-ul' title='li'></i></button>",
action: function() {
return this.$parent.wrapSelection("insertUnorderedList", null);
},
activeState: function() { return $document[0].queryCommandState('insertUnorderedList'); }
},
ol: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)'><i class='fa fa-list-ol' title='ol'></i></button>",
action: function() {
return this.$parent.wrapSelection("insertOrderedList", null);
},
activeState: function() { return $document[0].queryCommandState('insertOrderedList'); }
},
undo: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='Undo'><i class='fa fa-undo'></i></button>",
action: function() {
return this.$parent.wrapSelection("undo", null);
}
},
redo: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='Redo'><i class='fa fa-repeat'></i></button>",
action: function() {
return this.$parent.wrapSelection("redo", null);
}
},
bold: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)'><i class='fa fa-bold'></i></button>",
action: function() {
return this.$parent.wrapSelection("bold", null);
},
activeState: function() {
return $document[0].queryCommandState('bold');
}
},
justifyLeft: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='justifyLeft'><i class='fa fa-align-left'></i></button>",
action: function() {
return this.$parent.wrapSelection("justifyLeft", null);
},
activeState: function() {
return $document[0].queryCommandState('justifyLeft');
}
},
justifyRight: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='justifyRight'><i class='fa fa-align-right'></i></button>",
action: function() {
return this.$parent.wrapSelection("justifyRight", null);
},
activeState: function() {
return $document[0].queryCommandState('justifyRight');
}
},
justifyCenter: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='justifyRight'><i class='fa fa-align-center'></i></button>",
action: function() {
return this.$parent.wrapSelection("justifyCenter", null);
},
activeState: function() {
return $document[0].queryCommandState('justifyCenter');
}
},
italics: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='italics'><i class='fa fa-italic'></i></button>",
action: function() {
return this.$parent.wrapSelection("italic", null);
},
activeState: function() {
return $document[0].queryCommandState('italic');
}
},
underline: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='underline'><i class='fa fa-underline'></i></button>",
action: function() {
return this.$parent.wrapSelection("underline", null);
},
activeState: function() {
return $document[0].queryCommandState('underline');
}
},
clear: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='clear'><i class='fa fa-ban'></i></button>",
action: function() {
return this.$parent.wrapSelection("removeFormat", null);
}
},
segments:{
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='articleSegments when homepage showing'><i class='fa fa-code'></i></button>",
action: function() {
return this.$parent.wrapSelection("segments", null);
}
},
insertImage: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='insertImage'><i class='fa fa-picture-o'></i></button>",
action: function() {
var imageLink;
imageLink = prompt("Please enter an image URL to insert", 'http://');
if (imageLink !== '') {
return this.$parent.wrapSelection('insertImage', imageLink);
}
}
},
insertLink: {
display: "<button type='button' ng-click='action()' ng-class='displayActiveToolClass(active)' title='insertLink'><i class='fa fa-link'></i></button>",
action: function() {
var urlLink;
urlLink = prompt("Please enter an URL to insert", 'http://');
if (urlLink !== '') {
return this.$parent.wrapSelection('createLink', urlLink);
}
}
}
}, ($rootScope.textAngularTools != null)? $rootScope.textAngularTools : {})
return {
require: 'ngModel',
scope: {
segmentsText: '='
},
restrict: "EA",
link: function(scope, element, attrs, ngModel) {
var group, groupElement, keydown, keyup, tool, toolElement; //all these vars should not be accessable outside this directive
// get the settings from the defaults and add our specific functions that need to be on the scope
angular.extend(scope, $rootScope.textAngularOpts, {
// wraps the selection in the provided tag / execCommand function.
wrapSelection: function(command, opt) {
document.execCommand(command, false, opt);
// strip out the chrome specific rubbish that gets put in when using lists
if (command == 'segments'){
var htmlText = scope.displayElements.text.html()
var segmentsText = '<!--more-->'
if (scope.segmentsText) segmentsText = scope.segmentsText
htmlText = htmlText.replace(new RegExp(segmentsText, "gi"),"")
htmlText += segmentsText
scope.displayElements.text.html(htmlText)
}
if(command === 'insertUnorderedList' || command === 'insertOrderedList') taFixChrome(scope.displayElements.text);
// refocus on the shown display element, this fixes a display bug when using :focus styles to outline the box. You still have focus on the text/html input it just doesn't show up
if (scope.showHtml)
scope.displayElements.html[0].focus();
else
scope.displayElements.text[0].focus();
// note that wrapSelection is called via ng-click in the tool plugins so we are already within a $apply
scope.updateSelectedStyles();
if (!scope.showHtml) scope.updateTaBindtext(); // only update if NOT in html mode
},
showHtml: false
});
// setup the options from the optional attributes
if (!!attrs.taToolbar) scope.toolbar = scope.$eval(attrs.taToolbar);
if (!!attrs.taFocussedClass) scope.classes.focussed = scope.$eval(attrs.taFocussedClass);
if (!!attrs.taToolbarClass) scope.classes.toolbar = attrs.taToolbarClass;
if (!!attrs.taToolbarGroupClass) scope.classes.toolbarGroup = attrs.taToolbarGroupClass;
if (!!attrs.taToolbarButtonClass) scope.classes.toolbarButton = attrs.taToolbarButtonClass;
if (!!attrs.taToolbarActiveButtonClass) scope.classes.toolbarButtonActive = attrs.taToolbarActiveButtonClass;
if (!!attrs.taTextEditorClass) scope.classes.textEditor = attrs.taTextEditorClass;
if (!!attrs.taHtmlEditorClass) scope.classes.htmlEditor = attrs.taHtmlEditorClass;
// Setup the HTML elements as variable references for use later
scope.displayElements = {
toolbar: angular.element("<div></div>"),
forminput: angular.element("<input type='hidden' style='display: none;'>"),
html: angular.element("<textarea ng-show='showHtml' ta-bind='html' ng-model='html' ></textarea>"),
text: angular.element("<div style='overflow: auto;height:800px;max-height: 500px;' contentEditable='true' ng-hide='showHtml' ta-bind='text' ng-model='text' ></div>")
};
// add the main elements to the origional element
element.append(scope.displayElements.toolbar);
element.append(scope.displayElements.text);
element.append(scope.displayElements.html);
if(!!attrs.name){
scope.displayElements.forminput.attr('name', attrs.name);
element.append(scope.displayElements.forminput);
}
// compile the scope with the text and html elements only - if we do this with the main element it causes a compile loop
$compile(scope.displayElements.text)(scope);
$compile(scope.displayElements.html)(scope);
// add the classes manually last
element.addClass("ta-root");
scope.displayElements.toolbar.addClass("ta-toolbar " + scope.classes.toolbar);
// scope.displayElements.text.addClass("ta-text ta-editor " + scope.classes.textEditor);
// scope.displayElements.html.addClass("ta-html ta-editor " + scope.classes.textEditor);
// note that focusout > focusin is called everytime we click a button
element.on('focusin', function(){ // cascades to displayElements.text and displayElements.html automatically.
element.addClass(scope.classes.focussed);
$timeout(function(){ element.triggerHandler('focus'); }, 0); // to prevent multiple apply error defer to next seems to work.
});
element.on('focusout', function(){
$timeout(function(){
// if we have NOT focussed again on the text etc then fire the blur events
if(!($document[0].activeElement === scope.displayElements.html[0]) && !($document[0].activeElement === scope.displayElements.text[0])){
element.removeClass(scope.classes.focussed);
$timeout(function(){ element.triggerHandler('blur'); }, 0); // to prevent multiple apply error defer to next seems to work.
}
}, 0);
});
scope.tools = {}; // Keep a reference for updating the active states later
// create the tools in the toolbar
for (var _i = 0; _i < scope.toolbar.length; _i++) {
// setup the toolbar group
group = scope.toolbar[_i];
groupElement = angular.element("<div></div>");
groupElement.addClass(scope.classes.toolbarGroup);
for (var _j = 0; _j < group.length; _j++) {
// init and add the tools to the group
tool = group[_j]; // a tool name (key name from textAngularTools struct)
toolElement = angular.element($rootScope.textAngularTools[tool].display);
toolElement.addClass(scope.classes.toolbarButton);
toolElement.attr('unselectable', 'on'); // important to not take focus from the main text/html entry
toolElement.attr('ng-disabled', 'showHtml()');
var childScope = angular.extend(scope.$new(true), $rootScope.textAngularTools[tool], { // add the tool specific functions
name: tool,
showHtml: function(){
if(this.name !== 'html') return this.$parent.showHtml;
return false;
},
displayActiveToolClass: function(active){
return (active)? this.$parent.classes.toolbarButtonActive : '';
}
}); //creates a child scope of the main angularText scope and then extends the childScope with the functions of this particular tool
scope.tools[tool] = childScope; // reference to the scope kept
groupElement.append($compile(toolElement)(childScope)); // append the tool compiled with the childScope to the group element
}
scope.displayElements.toolbar.append(groupElement); // append the group to the toolbar
}
// changes to the model variable from outside the html/text inputs
ngModel.$render = function() {
scope.displayElements.forminput.val(ngModel.$viewValue);
if(ngModel.$viewValue === undefined) return;
// if the editors aren't focused they need to be updated, otherwise they are doing the updating
if (!($document[0].activeElement === scope.displayElements.html[0]) && !($document[0].activeElement === scope.displayElements.text[0])) {
var val = ngModel.$viewValue || ''; // in case model is null
scope.text = val;
scope.html = val;
}
};
scope.$watch('text', function(newValue, oldValue){
scope.html = newValue;
ngModel.$setViewValue(newValue);
scope.displayElements.forminput.val(newValue);
});
scope.$watch('html', function(newValue, oldValue){
scope.text = newValue;
ngModel.$setViewValue(newValue);
scope.displayElements.forminput.val(newValue);
});
// the following is for applying the active states to the tools that support it
scope.bUpdateSelectedStyles = false;
// loop through all the tools polling their activeState function if it exists
scope.updateSelectedStyles = function() {
for (var _k = 0; _k < scope.toolbar.length; _k++) {
var groups = scope.toolbar[_k];
for (var _l = 0; _l < groups.length; _l++) {
tool = groups[_l];
if (scope.tools[tool].activeState != null) {
scope.tools[tool].active = scope.tools[tool].activeState.apply(scope);
}
}
}
if (this.bUpdateSelectedStyles) $timeout(this.updateSelectedStyles, 200); // used to update the active state when a key is held down, ie the left arrow
};
// start updating on keydown
keydown = function(e) {
scope.bUpdateSelectedStyles = true;
scope.$apply(function() {
scope.updateSelectedStyles();
});
};
scope.displayElements.html.on('keydown', keydown);
scope.displayElements.text.on('keydown', keydown);
// stop updating on key up and update the display/model
keyup = function(e) {
scope.bUpdateSelectedStyles = false;
};
scope.displayElements.html.on('keyup', keyup);
scope.displayElements.text.on('keyup', keyup);
// update the toolbar active states when we click somewhere in the text/html boxed
mouseup = function(e) {
scope.$apply(function() {
scope.updateSelectedStyles();
});
};
scope.displayElements.html.on('mouseup', mouseup);
scope.displayElements.text.on('mouseup', mouseup);
}
};
}]).directive('taBind', ['$sanitize', '$document', 'taFixChrome','$sce', function($sanitize,$document, taFixChrome,$sce){
// Uses for this are textarea or input with ng-model and ta-bind='text' OR any non-form element with contenteditable="contenteditable" ta-bind="html|text" ng-model
return {
require: 'ngModel',
scope: {'taBind': '@'},
link: function(scope, element, attrs, ngModel){
var isContentEditable = element[0].tagName.toLowerCase() !== 'textarea' && element[0].tagName.toLowerCase() !== 'input' && element.attr('contenteditable') !== undefined;
// in here we are undoing the converts used elsewhere to prevent the < > and & being displayed when they shouldn't in the code.
var compileHtml = function(){
var result = taFixChrome(angular.element("<div>").append(element.html())).html();
if(scope.taBind === 'html' && isContentEditable) result = result.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, '&');
return result;
};
scope.$parent['updateTaBind' + scope.taBind] = function(){//used for updating when inserting wrapped elements
var compHtml = compileHtml();
var tempParsers = ngModel.$parsers;
ngModel.$parsers = []; // temp disable of the parsers
ngModel.$oldViewValue = compHtml;
ngModel.$setViewValue(compHtml);
ngModel.$parsers = tempParsers;
};
//this code is used to update the models when data is entered/deleted
if(isContentEditable){
element.on('keyup', function(e){
ngModel.$setViewValue(compileHtml());
});
}
ngModel.$parsers.push(function(value){
// all the code here takes the information from the above keyup function or any other time that the viewValue is updated and parses it for storage in the ngModel
if(ngModel.$oldViewValue === undefined) ngModel.$oldViewValue = value;
try{
$sanitize(value); // this is what runs when ng-bind-html is used on the variable
}catch(e){
return ngModel.$oldViewValue; //prevents the errors occuring when we are typing in html code
}
ngModel.$oldViewValue = value;
return value;
});
// changes to the model variable from outside the html/text inputs
ngModel.$render = function() {
if(ngModel.$viewValue === undefined) return;
// if the editor isn't focused it needs to be updated, otherwise it's receiving user input
if ($document[0].activeElement !== element[0]) {
var val = ngModel.$viewValue || ''; // in case model is null
ngModel.$oldViewValue = val;
if(scope.taBind === 'text'){ //WYSIWYG Mode
// $sce.trustAsHtml(val);
element.html(val);
element.find('a').on('click', function(e){
e.preventDefault();
return false;
});
}else if(isContentEditable || (element[0].tagName.toLowerCase() !== 'textarea' && element[0].tagName.toLowerCase() !== 'input')) // make sure the end user can SEE the html code.
element.html(val.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, '>'));
else element.val(val); // only for input and textarea inputs
}else if(!isContentEditable) element.val(val); // only for input and textarea inputs
};
}
};
}]).factory('taFixChrome', function(){
// get whaterever rubbish is inserted in chrome
var taFixChrome = function($html){ // should be an angular.element object, returns object for chaining convenience
// fix the chrome trash that gets inserted sometimes
var spans = angular.element($html).find('span'); // default wrapper is a span so find all of them
for(var s = 0; s < spans.length; s++){
var span = angular.element(spans[s]);
if(span.attr('style') && span.attr('style').match(/line-height: 1.428571429;|color: inherit; line-height: 1.1;/i)){ // chrome specific string that gets inserted into the style attribute, other parts may vary. Second part is specific ONLY to hitting backspace in Headers
if(span.next().length > 0 && span.next()[0].tagName === 'BR') span.next().remove()
span.replaceWith(span.html());
}
}
var result = $html.html().replace(/style="[^"]*?(line-height: 1.428571429;|color: inherit; line-height: 1.1;)[^"]*"/ig, ''); // regex to replace ONLY offending styles - these can be inserted into various other tags on delete
$html.html(result);
return $html;
};
return taFixChrome;
});
/* Make clicks pass-through */
#loading-bar,
#loading-bar-spinner {
pointer-events: none;
-webkit-pointer-events: none;
-webkit-transition: 350ms linear all;
-moz-transition: 350ms linear all;
-o-transition: 350ms linear all;
transition: 350ms linear all;
}
#loading-bar.ng-enter,
#loading-bar.ng-leave.ng-leave-active,
#loading-bar-spinner.ng-enter,
#loading-bar-spinner.ng-leave.ng-leave-active {
opacity: 0;
}
#loading-bar.ng-enter.ng-enter-active,
#loading-bar.ng-leave,
#loading-bar-spinner.ng-enter.ng-enter-active,
#loading-bar-spinner.ng-leave {
opacity: 1;
}
#loading-bar .bar {
-webkit-transition: width 350ms;
-moz-transition: width 350ms;
-o-transition: width 350ms;
transition: width 350ms;
background: #000000;
position: fixed;
z-index: 10002;
top: 0;
left: 0;
width: 100%;
height: 2px;
border-bottom-right-radius: 1px;
border-top-right-radius: 1px;
}
/* Fancy blur effect */
#loading-bar .peg {
position: absolute;
width: 70px;
right: 0;
top: 0;
height: 2px;
opacity: .45;
-moz-box-shadow: #000000 1px 0 6px 1px;
-ms-box-shadow: #000000 1px 0 6px 1px;
-webkit-box-shadow: #000000 1px 0 6px 1px;
box-shadow: #000000 1px 0 6px 1px;
-moz-border-radius: 100%;
-webkit-border-radius: 100%;
border-radius: 100%;
}
#loading-bar-spinner {
display: block;
position: fixed;
z-index: 10002;
top: 10px;
left: 10px;
}
#loading-bar-spinner .spinner-icon {
width: 14px;
height: 14px;
border: solid 2px transparent;
border-top-color: #000000;
border-left-color: #000000;
border-radius: 10px;
-webkit-animation: loading-bar-spinner 400ms linear infinite;
-moz-animation: loading-bar-spinner 400ms linear infinite;
-ms-animation: loading-bar-spinner 400ms linear infinite;
-o-animation: loading-bar-spinner 400ms linear infinite;
animation: loading-bar-spinner 400ms linear infinite;
}
@-webkit-keyframes loading-bar-spinner {
0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }
}
@-moz-keyframes loading-bar-spinner {
0% { -moz-transform: rotate(0deg); transform: rotate(0deg); }
100% { -moz-transform: rotate(360deg); transform: rotate(360deg); }
}
@-o-keyframes loading-bar-spinner {
0% { -o-transform: rotate(0deg); transform: rotate(0deg); }
100% { -o-transform: rotate(360deg); transform: rotate(360deg); }
}
@-ms-keyframes loading-bar-spinner {
0% { -ms-transform: rotate(0deg); transform: rotate(0deg); }
100% { -ms-transform: rotate(360deg); transform: rotate(360deg); }
}
@keyframes loading-bar-spinner {
0% { transform: rotate(0deg); transform: rotate(0deg); }
100% { transform: rotate(360deg); transform: rotate(360deg); }
}
/*
* angular-loading-bar
*
* intercepts XHR requests and creates a loading bar.
* Based on the excellent nprogress work by rstacruz (more info in readme)
*
* (c) 2013 Wes Cruver
* License: MIT
*/
(function() {
'use strict';
// Alias the loading bar for various backwards compatibilities since the project has matured:
angular.module('angular-loading-bar', ['cfp.loadingBarInterceptor']);
angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']);
/**
* loadingBarInterceptor service
*
* Registers itself as an Angular interceptor and listens for XHR requests.
*/
angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar'])
.config(['$httpProvider', function ($httpProvider) {
var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, cfpLoadingBar) {
/**
* The total number of requests made
*/
var reqsTotal = 0;
/**
* The number of requests completed (either successfully or not)
*/
var reqsCompleted = 0;
/**
* The amount of time spent fetching before showing the loading bar
*/
var latencyThreshold = cfpLoadingBar.latencyThreshold;
/**
* $timeout handle for latencyThreshold
*/
var startTimeout;
/**
* calls cfpLoadingBar.complete() which removes the
* loading bar from the DOM.
*/
function setComplete() {
$timeout.cancel(startTimeout);
cfpLoadingBar.complete();
reqsCompleted = 0;
reqsTotal = 0;
}
/**
* Determine if the response has already been cached
* @param {Object} config the config option from the request
* @return {Boolean} retrns true if cached, otherwise false
*/
function isCached(config) {
var cache;
var defaultCache = $cacheFactory.get('$http');
var defaults = $httpProvider.defaults;
// Choose the proper cache source. Borrowed from angular: $http service
if ((config.cache || defaults.cache) && config.cache !== false &&
(config.method === 'GET' || config.method === 'JSONP')) {
cache = angular.isObject(config.cache) ? config.cache
: angular.isObject(defaults.cache) ? defaults.cache
: defaultCache;
}
var cached = cache !== undefined ?
cache.get(config.url) !== undefined : false;
if (config.cached !== undefined && cached !== config.cached) {
return config.cached;
}
config.cached = cached;
return cached;
}
return {
'request': function(config) {
// Check to make sure this request hasn't already been cached and that
// the requester didn't explicitly ask us to ignore this request:
if (!config.ignoreLoadingBar && !isCached(config)) {
$rootScope.$broadcast('cfpLoadingBar:loading', {url: config.url});
if (reqsTotal === 0) {
startTimeout = $timeout(function() {
cfpLoadingBar.start();
}, latencyThreshold);
}
reqsTotal++;
cfpLoadingBar.set(reqsCompleted / reqsTotal);
}
return config;
},
'response': function(response) {
if (!response.config.ignoreLoadingBar && !isCached(response.config)) {
reqsCompleted++;
$rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url, result: response});
if (reqsCompleted >= reqsTotal) {
setComplete();
} else {
cfpLoadingBar.set(reqsCompleted / reqsTotal);
}
}
return response;
},
'responseError': function(rejection) {
if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) {
reqsCompleted++;
$rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url, result: rejection});
if (reqsCompleted >= reqsTotal) {
setComplete();
} else {
cfpLoadingBar.set(reqsCompleted / reqsTotal);
}
}
return $q.reject(rejection);
}
};
}];
$httpProvider.interceptors.push(interceptor);
}]);
/**
* Loading Bar
*
* This service handles adding and removing the actual element in the DOM.
* Generally, best practices for DOM manipulation is to take place in a
* directive, but because the element itself is injected in the DOM only upon
* XHR requests, and it's likely needed on every view, the best option is to
* use a service.
*/
angular.module('cfp.loadingBar', [])
.provider('cfpLoadingBar', function() {
this.includeSpinner = true;
this.includeBar = true;
this.latencyThreshold = 100;
this.startSize = 0.02;
this.parentSelector = 'body';
this.spinnerTemplate = '<div id="loading-bar-spinner"><div class="spinner-icon"></div></div>';
this.loadingBarTemplate = '<div id="loading-bar"><div class="bar"><div class="peg"></div></div></div>';
this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) {
var $animate;
var $parentSelector = this.parentSelector,
loadingBarContainer = angular.element(this.loadingBarTemplate),
loadingBar = loadingBarContainer.find('div').eq(0),
spinner = angular.element(this.spinnerTemplate);
var incTimeout,
completeTimeout,
started = false,
status = 0;
var includeSpinner = this.includeSpinner;
var includeBar = this.includeBar;
var startSize = this.startSize;
/**
* Inserts the loading bar element into the dom, and sets it to 2%
*/
function _start() {
if (!$animate) {
$animate = $injector.get('$animate');
}
var $parent = $document.find($parentSelector).eq(0);
$timeout.cancel(completeTimeout);
// do not continually broadcast the started event:
if (started) {
return;
}
$rootScope.$broadcast('cfpLoadingBar:started');
started = true;
if (includeBar) {
$animate.enter(loadingBarContainer, $parent);
}
if (includeSpinner) {
$animate.enter(spinner, $parent);
}
_set(startSize);
}
/**
* Set the loading bar's width to a certain percent.
*
* @param n any value between 0 and 1
*/
function _set(n) {
if (!started) {
return;
}
var pct = (n * 100) + '%';
loadingBar.css('width', pct);
status = n;
// increment loadingbar to give the illusion that there is always
// progress but make sure to cancel the previous timeouts so we don't
// have multiple incs running at the same time.
$timeout.cancel(incTimeout);
incTimeout = $timeout(function() {
_inc();
}, 250);
}
/**
* Increments the loading bar by a random amount
* but slows down as it progresses
*/
function _inc() {
if (_status() >= 1) {
return;
}
var rnd = 0;
// TODO: do this mathmatically instead of through conditions
var stat = _status();
if (stat >= 0 && stat < 0.25) {
// Start out between 3 - 6% increments
rnd = (Math.random() * (5 - 3 + 1) + 3) / 100;
} else if (stat >= 0.25 && stat < 0.65) {
// increment between 0 - 3%
rnd = (Math.random() * 3) / 100;
} else if (stat >= 0.65 && stat < 0.9) {
// increment between 0 - 2%
rnd = (Math.random() * 2) / 100;
} else if (stat >= 0.9 && stat < 0.99) {
// finally, increment it .5 %
rnd = 0.005;
} else {
// after 99%, don't increment:
rnd = 0;
}
var pct = _status() + rnd;
_set(pct);
}
function _status() {
return status;
}
function _completeAnimation() {
status = 0;
started = false;
}
function _complete() {
if (!$animate) {
$animate = $injector.get('$animate');
}
$rootScope.$broadcast('cfpLoadingBar:completed');
_set(1);
$timeout.cancel(completeTimeout);
// Attempt to aggregate any start/complete calls within 500ms:
completeTimeout = $timeout(function() {
var promise = $animate.leave(loadingBarContainer, _completeAnimation);
if (promise && promise.then) {
promise.then(_completeAnimation);
}
$animate.leave(spinner);
}, 500);
}
return {
start : _start,
set : _set,
status : _status,
inc : _inc,
complete : _complete,
includeSpinner : this.includeSpinner,
latencyThreshold : this.latencyThreshold,
parentSelector : this.parentSelector,
startSize : this.startSize
};
}]; //
}); // wtf javascript. srsly
})(); //
/**
* Created by WillChen on 2014/11/3.
*/
(function () {
angular.module('SPA.Module', ['ui.router', 'resourceServiceModule', 'ngSanitize', 'textAngular','chieffancypants.loadingBar', 'ngAnimate'])
.config(['$stateProvider', '$urlRouterProvider', '$locationProvider', function ($stateProvider, $urlRouterProvider, $locationProvider) {
$urlRouterProvider.otherwise("/home")
$stateProvider
.state('home', {
url: "/home",
templateUrl: "home.html",
controller: 'homeController'
})
.state('detail', {
url: "/details/:detailId",
templateUrl: "detail.html",
controller: 'detailController'
})
.state('edit', {
url: "/edit/:detailId",
templateUrl: "edit.html",
controller: "editController"
})
.state('create', {
url: '/create',
templateUrl: "edit.html",
controller: 'createController'
})
}])
.controller('mainController', function ($rootScope,$scope,$state, resourceService) {
resourceService.setAuthListener(onAuthChange,true)//監聽認證狀態
$rootScope.isAllBrowseState = true
$scope.search = {}
$rootScope.checkLoginUser = function(model){
return (model && $rootScope.userId == model.userId)
}
$scope.$watch('isAllBrowseState',function(newVal){
if (newVal){
$scope.browseStateBtnText = 'My Posts'
if ($scope.search.userId != undefined) delete $scope.search.userId
}else{
$scope.browseStateBtnText = 'All Posts'
$scope.search.userId = $rootScope.userId
}
})
$scope.browseStateChange = function(isAllBrowseState){
$rootScope.isAllBrowseState = isAllBrowseState
}
$scope.login = function (provider) {
resourceService.login(provider).then(function (authData) {
resourceService.setAuthListener(onAuthChange,true)//監聽認證狀態
}, function (error) {
resourceService.setAuthListener(onAuthChange,false)//解除監聽狀態
})
}
$scope.logout = function () {
resourceService.logout()
resourceService.setAuthListener(onAuthChange,false)//解除監聽狀態
$scope.browseStateChange(true)
$state.go('home')
}
function onAuthChange(authData){
if (authData) {
var cachedUserProfile = authData['google'].cachedUserProfile
setAuthData(authData.uid,cachedUserProfile.given_name,cachedUserProfile.picture)
}
else setAuthData(null,null,null)
}
function setAuthData(userId,userName,picturePath){
$rootScope.userId = userId
$rootScope.userName = userName
$rootScope.picturePath = picturePath
}
})
.controller('homeController', function ($scope, $sce,resourceService,cfpLoadingBar) {
cfpLoadingBar.start()
resourceService.read().then(function (data) {
$scope.datasource = data
cfpLoadingBar.complete()
})
$scope.getShortContentTrust = function(content){
return $sce.trustAsHtml(content)
}
})
.controller('detailController', function ($scope, $sce,$stateParams, $state, resourceService) {
$scope.selectedId = $stateParams.detailId.toString()
resourceService.read($scope.selectedId).then(function (data) {
$scope.model = data
$scope.model.content = $sce.trustAsHtml($scope.model.content)
})
$scope.deleteData = function (selectedId) {
resourceService.delete(selectedId)//待修改
$state.go('home')
}
})
.controller('editController', function ($rootScope,$scope,$stateParams, $state, resourceService, operatingService) {
$scope.segmentsText = '<!--segments-->'
$scope.selectedId = $stateParams.detailId.toString()
$scope.isTextAngular = true
resourceService.read($scope.selectedId).then(function (data) {
$scope.model = data
if (!$rootScope.checkLoginUser(data)) $state.go('home')//新增處
})
$scope.setPrettyEdit = function (isPrettyEdit) {
$scope.isTextAngular = isPrettyEdit
}
$scope.doModify = function (id, item) {
item.postDate = operatingService.getNowDatetime()
item.shortContent = operatingService.transformToShortContent(item.content, $scope.segmentsText)
item.userId = $rootScope.userId//新增處
item.author = $rootScope.userName
resourceService.update(id, item)
$state.go('home')
}
})
.controller('createController', function ($rootScope,$scope, $state, resourceService, operatingService) {
$scope.isTextAngular = true
resourceService.read(null, true).then(function (data) {
$scope.model = data
if (!$rootScope.userId) $state.go('home')//新增處
})
$scope.setPrettyEdit = function (isPrettyEdit) {
$scope.isTextAngular = isPrettyEdit
}
$scope.doInsert = function (item) {
item.postDate = operatingService.getNowDatetime()
item.shortContent = operatingService.transformToShortContent(item.content, $scope.segmentsText)
item.userId = $rootScope.userId//新增處
item.author = $rootScope.userName
resourceService.create(item)
$state.go('home')
}
})
.filter('objectToArray', function () {
return function (obj) {
if (!(obj instanceof Object)) return obj;
return Object.keys(obj).map(function (key) {
return Object.defineProperty(obj[key], 'key', {__proto__: null, value: key});
})
}
})
})()
/**
* Created by WillChen on 2014/11/4.
*/
(function () {
angular.module('resourceServiceModule', [])
.service('resourceService', function ($q) {
var directory = 'https://willapp.firebaseio.com/authenticationBlog/'
var dataDirectory = directory + 'datas/'
var blogDataRef = new Firebase(dataDirectory)
function _create(item) {
blogDataRef.push(item)
}
function _read(id, isTemplate) {
var tempRef
if (isTemplate) tempRef = new Firebase(directory + 'template')
else if (id) tempRef = new Firebase(dataDirectory + id)
else tempRef = blogDataRef
var deferred = $q.defer()
tempRef.on('value', function (snapshot) {
deferred.resolve(snapshot.val())
}, function (errorObject) {
deferred.reject(errorObject)
});
return deferred.promise
}
function _update(id, item) {
var obj = {}
obj[id] = item
blogDataRef.update(obj)
}
function _delete(id) {
var itemRef = blogDataRef.child(id)
itemRef.remove();
}
function _setAuthListener(authChange, isOn) {
if (isOn) blogDataRef.onAuth(authChange)
else blogDataRef.offAuth(authChange)
}
function _login(provider) {
var deferred = $q.defer()
blogDataRef.authWithOAuthPopup(provider, function (error, authData) {
if (!error) deferred.resolve(authData)
else deferred.reject(error)
})
return deferred.promise
}
function _logout() {
blogDataRef.unauth()
}
return{
create: _create,
read: _read,
update: _update,
delete: _delete,
setAuthListener: _setAuthListener,
login: _login,
logout: _logout
}
})
.service('operatingService', function () {
function _transformToShortContent(content, segmentsText) {
var rexExpText = '(.*?)' + segmentsText
var arr = content.match(new RegExp(rexExpText, "i"))
return arr ? arr[arr.length - 1] : ""
}
function _getNowDatetime() {
var now = new Date()
return now.toISOString()
}
return{
transformToShortContent: _transformToShortContent,
getNowDatetime: _getNowDatetime
}
})
})()
.headerText{
color: #ffffff;
text-shadow: 2px 1px 1px rgba(196, 196, 196, 1);
margin: 0;
padding: 1em;
}
.containerBorder {
margin-top: 1em;
padding: 0.5em;
-webkit-box-shadow: 1px -1px 5px 5px rgba(209,240,216,1);
-moz-box-shadow: 1px -1px 5px 5px rgba(209,240,216,1);
box-shadow: 1px -1px 5px 5px rgba(209,240,216,1);
}
.textboxRadius{
color: #000000;
border-radius:25px;
-moz-border-radius:25px;
-webkit-border-radius:25px;
}
.btn-circle {
width: 2.5em;
height: 2.5em;
text-align: center;
padding: 6px 0;
font-size: 12px;
line-height: 1.428571429em;
border-radius: 1.2em;
}
<div class="row">
<div class="col-md-10">
<div class="input-group ">
<span class="input-group-addon"><i class="fa fa-search fa-fw"></i></span>
<input class="form-control textboxRadius" type="text" placeholder="Search title..." ng-model="search.title">
</div>
</div>
<div class="col-md-2">
<button ng-if="userId" ng-class="{true:'btn btn-default btn-circle',false:'btn btn-success btn-circle'}[isAllBrowseState]" ng-click="browseStateChange(!isAllBrowseState)"><i class="fa fa-user"></i></button>
</div>
</div>
<div ng-repeat="item in datasource|objectToArray|filter:search|orderBy:'postDate':true" class="containerBorder">
<i class="fa fa-calendar-o" ><i>{{item.postDate|date:'yyyy/MM/dd hh:mm:ss'}}</i></i>
<a ng-if="checkLoginUser(item)" href="#" ui-sref="detail({detailId:item.key})" class="label label-success pull-right"><i class="fa fa-user"></i>My Post</a>
<hr/>
<h3><a href="#" ui-sref="detail({detailId:item.key})">{{item.title}}</a></h3>
<div ng-bind-html="getShortContentTrust(item.shortContent)"></div>
</div>
<div class="containerBorder">
<a class="btn btn-lg btn-default" ng-if="checkLoginUser(model)" ui-sref="edit({detailId: selectedId})"><i class="fa fa-edit fa-fw"></i></a>
<button type="button" ng-if="checkLoginUser(model)" class="btn btn-lg btn-default " ng-click="deleteData(selectedId)">
<i class="fa fa-trash-o fa-fw"></i>
</button>
<a class="btn btn-lg btn-default" ui-sref="home"><i class="fa fa-home fa-fw"></i></a>
<hr />
<div style="margin: 10px 0;">
<h3><a href="javascript:void(0)">{{model.title}}</a></h3>
<label class="label label-success"><i><i class="fa fa-comment-o"></i>{{model.postDate|date:'yyyy/MM/dd hh:mm:ss'}} by {{model.author}}</i></label>
<p>
<div ng-bind-html="model.content"></div>
</div>
</div>
<div class="containerBorder">
標題:
<input type="text" class="form-control" placeholder="文章標題" ng-model="model.title" />
<p></p>
<div class="btn-group">
<button type="button" class="btn btn-default btn-sm" ng-click="setPrettyEdit(true)" data-toggle="tooltip" data-placement="bottom" title="Edit Text"><i class="fa fa-pencil"></i></button>
<button type="button" class="btn btn-default btn-sm" ng-click="setPrettyEdit(false)" data-toggle="tooltip" data-placement="bottom" title="Edit SourceCode"><i class="fa fa-file-code-o"></i></button>
</div>
<p></p>
<div text-angular="text-angular" segments-text="segmentsText" ng-model="model.content" ng-show="isTextAngular"></div>
<div ng-hide="isTextAngular">
<textarea class="form-control" rows="20" ng-model="model.content"></textarea>
</div>
<br />
<a class="btn btn-success" ng-if="doInsert" ng-disabled="model.title == '' || model.content == ''" ng-click="doInsert(model)"><i class="fa fa-check fa-fw"></i>確定新增</a>
<a class="btn btn-success" ng-if="doModify" ng-disabled="model.title == '' || model.content == ''" ng-click="doModify(selectedId,model)"><i class="fa fa-check fa-fw"></i>修改完成</a>
<a class="btn btn-default" ui-sref="home"><i class="fa fa-home fa-fw"></i>回列表</a>
</div>