diff --git a/.jshintrc b/.jshintrc
index de8510f32..6ecd915eb 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -21,6 +21,10 @@
"unused": "vars",
"white": true,
"globals": {
+ // deps that should be removed
+ "$": false,
+ "alert": false,
+
// app deps
"angular": false,
diff --git a/app/components/content-edit/content-edit-body.html b/app/components/content-edit/content-edit-body.html
new file mode 100644
index 000000000..9b60d45f4
--- /dev/null
+++ b/app/components/content-edit/content-edit-body.html
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/components/content-edit/content-edit-body.js b/app/components/content-edit/content-edit-body.js
new file mode 100644
index 000000000..865b6cc79
--- /dev/null
+++ b/app/components/content-edit/content-edit-body.js
@@ -0,0 +1,22 @@
+'use strict';
+
+angular.module('bulbs.cms.content.edit.body', [
+ 'bulbs.cms.site.config'
+])
+ .directive('contentEditBody', [
+ 'CmsConfig',
+ function (CmsConfig) {
+ return {
+ link: function (scope) {
+ scope.inlineObjectsPath = CmsConfig.getInlineObjecsPath();
+ },
+ restrict: 'E',
+ scope: {
+ article: '=',
+ linkDomain: '@',
+ searchHandler: '@'
+ },
+ templateUrl: CmsConfig.buildComponentPath('content-edit/content-edit-body.html')
+ };
+ }
+ ]);
diff --git a/app/components/content-edit/content-edit-main-image.html b/app/components/content-edit/content-edit-main-image.html
new file mode 100644
index 000000000..0aa6dfd20
--- /dev/null
+++ b/app/components/content-edit/content-edit-main-image.html
@@ -0,0 +1,10 @@
+
diff --git a/app/components/content-edit/content-edit-main-image.js b/app/components/content-edit/content-edit-main-image.js
new file mode 100644
index 000000000..bf17be6cc
--- /dev/null
+++ b/app/components/content-edit/content-edit-main-image.js
@@ -0,0 +1,18 @@
+'use strict';
+
+angular.module('bulbs.cms.content.edit.mainImage', [
+ 'BettyCropper',
+ 'bulbs.cms.site.config'
+])
+ .directive('contentEditMainImage', [
+ 'CmsConfig',
+ function (CmsConfig) {
+ return {
+ restrict: 'E',
+ scope: {
+ article: '='
+ },
+ templateUrl: CmsConfig.buildComponentPath('content-edit/content-edit-main-image.html')
+ };
+ }
+ ]);
diff --git a/app/components/content-edit/content-edit-title.html b/app/components/content-edit/content-edit-title.html
new file mode 100644
index 000000000..107f12c0f
--- /dev/null
+++ b/app/components/content-edit/content-edit-title.html
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/app/components/content-edit/content-edit-title.js b/app/components/content-edit/content-edit-title.js
new file mode 100644
index 000000000..db8219162
--- /dev/null
+++ b/app/components/content-edit/content-edit-title.js
@@ -0,0 +1,17 @@
+'use strict';
+
+angular.module('bulbs.cms.content.edit.title', [
+ 'bulbs.cms.site.config'
+])
+ .directive('contentEditTitle', [
+ 'CmsConfig',
+ function (CmsConfig) {
+ return {
+ restrict: 'E',
+ scope: {
+ article: '='
+ },
+ templateUrl: CmsConfig.buildComponentPath('content-edit/content-edit-title.html')
+ };
+ }
+ ]);
diff --git a/app/components/content-edit/content-edit.js b/app/components/content-edit/content-edit.js
new file mode 100644
index 000000000..1ab667251
--- /dev/null
+++ b/app/components/content-edit/content-edit.js
@@ -0,0 +1,7 @@
+'use strict';
+
+angular.module('bulbs.cms.content', [
+ 'bulbs.cms.content.edit.body',
+ 'bulbs.cms.content.edit.mainImage',
+ 'bulbs.cms.content.edit.title'
+]);
diff --git a/app/components/editorial/editor-item.html b/app/components/editorial/editor-item.html
new file mode 100644
index 000000000..00e402421
--- /dev/null
+++ b/app/components/editorial/editor-item.html
@@ -0,0 +1,14 @@
+
+
+
+ {{ item.sender.first_name }} {{ item.sender.last_name }}
+
{{ parseCreated(item.created) }}
+
+
{{ parseStatus(item.notes) }}
+
{{ parseNote(item.notes) }}
+
+
diff --git a/app/components/editorial/editor-item.js b/app/components/editorial/editor-item.js
new file mode 100644
index 000000000..37e5796c8
--- /dev/null
+++ b/app/components/editorial/editor-item.js
@@ -0,0 +1,54 @@
+'use strict';
+
+angular.module('bulbs.cms.editorial.editorItem', [
+ 'bulbs.cms.site.config'
+])
+ .directive('editorItem', function ($http, EditorItems, moment, CmsConfig) {
+ return {
+ restrict: 'E',
+ templateUrl: CmsConfig.buildComponentPath('editorial/editor-item.html'),
+ scope: {
+ article: '='
+ },
+ link: function (scope, element, attrs) {
+
+ EditorItems.getItems((scope.article.id || ''));
+
+ scope.editorItems = EditorItems;
+
+ scope.parseCreated = function (date) {
+ return moment(date).format('h:mm A MMM D');
+ };
+
+ /*\
+ These two are really, really bad.
+
+ The EditorItem note is in the form of:
+
+ "Status: {{ status_text }}\n\n{{ note_text }}"
+
+ Parse accordingly.
+ \*/
+
+ scope.parseNote = function (note) {
+ var parsed = note.split(
+ 'Status: ' // Removing 'Status: '
+ )[1].split('\n\n'); // Split the note from the status text
+
+ parsed.shift(); // Removing the status_text
+
+ return parsed.join('\n\n'); // In case there are newlines in the note_text
+ };
+
+ scope.parseStatus = function (status) {
+ var parsed = status.split(
+ 'Status: ' // Removing 'Status: '
+ )[1].split(
+ '\n\n' // Split the note from the status text
+ ).shift(); // Get the status_text
+
+ return parsed !== 'N/A' ? parsed : '';
+ };
+ }
+ };
+ });
diff --git a/app/components/editorial/editor-item.less b/app/components/editorial/editor-item.less
new file mode 100644
index 000000000..e10b4e27f
--- /dev/null
+++ b/app/components/editorial/editor-item.less
@@ -0,0 +1,41 @@
+editor-item {
+ > div {
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ margin-right: -200px;
+ max-height: 500px;
+ overflow-y: scroll;
+ position: absolute;
+ width: 200px;
+ }
+
+ .note {
+ border-bottom: 1px solid #e3e3e3;
+ padding: 0px 10px 10px 15px;
+
+ &:last-child {
+ border-bottom: 0;
+ }
+
+ .note-author{
+ margin-bottom: 5px;
+
+ div {
+ color: #a3a3a3;
+ font-size: 11px;
+ font-weight: 400;
+ }
+ }
+
+ .note-status {
+ font-size: 13px;
+ font-weight: 500;
+ margin-bottom: 13px;
+ margin-top: 0px;
+ }
+
+ .note-text {
+ font-size: 13px;
+ }
+ }
+}
diff --git a/app/components/editorial/editor-items.js b/app/components/editorial/editor-items.js
new file mode 100644
index 000000000..2643faa7e
--- /dev/null
+++ b/app/components/editorial/editor-items.js
@@ -0,0 +1,14 @@
+'use strict';
+
+angular.module('bulbs.cms.editorial.editorItems', [])
+ .service('EditorItems', function EditorItems($http) {
+ this.data = [];
+ var self = this;
+ this.getItems = function (article) {
+ $http.get(
+ '/cms/api/v1/content/' + article + '/send/'
+ ).success(function (data, status) {
+ self.data = data.editor_items;
+ }).error(function (data, status) {});
+ };
+ });
diff --git a/app/components/editorial/editorial.js b/app/components/editorial/editorial.js
new file mode 100644
index 000000000..9151c74ea
--- /dev/null
+++ b/app/components/editorial/editorial.js
@@ -0,0 +1,4 @@
+angular.module('bulbs.cms.editorial', [
+ 'bulbs.cms.editorial.sendToEditorButton',
+ 'bulbs.cms.editorial.editorItems'
+]);
diff --git a/app/components/editorial/send-to-editor-button.js b/app/components/editorial/send-to-editor-button.js
new file mode 100644
index 000000000..6d4776715
--- /dev/null
+++ b/app/components/editorial/send-to-editor-button.js
@@ -0,0 +1,33 @@
+'use strict';
+
+angular.module('bulbs.cms.editorial.sendToEditorButton', [
+ 'bulbs.cms.editorial.sendToEditorModal',
+ 'bulbs.cms.site.config'
+])
+ .controller('SendToEditorButtonCtrl', [
+ '$scope', '$modal', 'moment', 'CmsConfig',
+ function ($scope, $modal, moment, CmsConfig) {
+ $scope.TIMEZONE_LABEL = moment.tz(CmsConfig.getTimezoneName()).format('z');
+
+ $scope.sendToEditorModal = function (article) {
+ return $modal.open({
+ templateUrl: CmsConfig.buildComponentPath('editorial/send-to-editor-modal.html'),
+ controller: 'SendtoeditormodalCtrl',
+ scope: $scope,
+ resolve: {
+ article: function(){ return article; }
+ }
+ });
+ };
+
+ $scope.getStatus = function (article) {
+ if(!article || !article.published){
+ return 'unpublished';
+ }else if(moment(article.published) > moment()){
+ return 'scheduled';
+ }else{
+ return 'published';
+ }
+ };
+ }
+ ]);
diff --git a/app/components/editorial/send-to-editor-modal.html b/app/components/editorial/send-to-editor-modal.html
new file mode 100644
index 000000000..f8718f63a
--- /dev/null
+++ b/app/components/editorial/send-to-editor-modal.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/app/components/editorial/send-to-editor-modal.js b/app/components/editorial/send-to-editor-modal.js
new file mode 100644
index 000000000..4176e0983
--- /dev/null
+++ b/app/components/editorial/send-to-editor-modal.js
@@ -0,0 +1,59 @@
+'use strict';
+
+angular.module('bulbs.cms.editorial.sendToEditorModal', [])
+ .controller('SendtoeditormodalCtrl', [
+ '$scope', '$http', '$modalInstance', 'EditorItems', 'Login', 'article',
+ function ($scope, $http, $modalInstance, EditorItems, Login, article) {
+ // see http://stackoverflow.com/questions/18716113/scope-issue-in-angularjs-using-angularui-bootstrap-modal
+ $scope.articleStatus = {};
+
+ $scope.buttonConfig = {
+ idle: 'Send',
+ busy: 'Sending',
+ finished: 'Sent!',
+ error: 'Error!'
+ };
+
+ $scope.articleStatuses = [
+ '- Article Status -',
+ 'Needs First Pass',
+ 'Needs Final Edit',
+ 'Needs Copy Edit',
+ 'Ready to Publish'
+ ];
+
+ $scope.sendToEditor = function (article) {
+ var status, note, noteToEditor;
+
+ noteToEditor = $scope.noteToEditor || 'No note attached';
+
+ // Leaving most of the hacks here in the temporary code
+ // until we figure out a more permanent solution
+ if ($scope.articleStatus.s !== $scope.articleStatuses[0]) {
+ status = $scope.articleStatus.s;
+ note = 'Status: ' + $scope.articleStatus.s + '\n\n' + noteToEditor;
+ } else {
+ status = '';
+ note = 'Status: N/A' + '\n\n' + noteToEditor;
+ }
+
+ return $http({
+ url: '/cms/api/v1/content/' + article.id + '/send/',
+ method: 'POST',
+ data: {
+ notes: note,
+ status: status
+ }
+ }).success(function (data) {
+ EditorItems.getItems(article.id);
+ $scope.publishSuccessCbk({article: article, response: data});
+ $modalInstance.close();
+ }).error(function (error, status, data) {
+ if(status === 403) {
+ Login.showLoginModal();
+ }
+ $modalInstance.dismiss();
+ });
+ };
+ }
+ ]);
diff --git a/app/components/editorial/send-to-editor.less b/app/components/editorial/send-to-editor.less
new file mode 100644
index 000000000..1acfd7d83
--- /dev/null
+++ b/app/components/editorial/send-to-editor.less
@@ -0,0 +1,4 @@
+.modal-body .article-statuses {
+ width: 200px;
+ margin-bottom: 35px;
+}
diff --git a/app/components/status-filter/status-filter-directive.js b/app/components/status-filter/status-filter-directive.js
index c5010e218..7d0d2030b 100644
--- a/app/components/status-filter/status-filter-directive.js
+++ b/app/components/status-filter/status-filter-directive.js
@@ -4,35 +4,21 @@ angular.module('statusFilter.directive', [
'bulbs.cms.site.config',
'contentServices.listService'
])
- .provider('StatusFilterOptions', function (moment) {
- var _statuses = [
- {label: 'Draft', key: 'status', value: 'draft'},
- {label: 'Published', key: 'before', value: function () { return moment().format('YYYY-MM-DDTHH:mmZ'); }},
- {label: 'Scheduled', key: 'after', value: function () { return moment().format('YYYY-MM-DDTHH:mmZ'); }},
- {label: 'All', key: null, value: null}
- ];
-
- this.setStatuses = function (statuses) {
- _statuses = statuses;
- };
-
- this.$get = function () {
- return {
- getStatuses: function () {
- return _statuses;
- }
- };
- };
-
- })
- .directive('statusFilter', function ($location, _, StatusFilterOptions, ContentListService, CmsConfig) {
+ .directive('statusFilter', function ($location, _, ContentListService, CmsConfig) {
return {
templateUrl: CmsConfig.buildComponentPath('status-filter/status-filter.html'),
restrict: 'E',
scope: {},
controller: 'ContentlistCtrl',
link: function postLink(scope, element, attrs) {
- scope.options = StatusFilterOptions.getStatuses();
+ var dateFormat = 'YYYY-MM-DDTHH:mmZ';
+ scope.options = [
+ { label: 'Draft', key: 'status', value: 'Draft'},
+ { label: 'Edit', key: 'status', value: 'Waiting for Editor'},
+ { label: 'Scheduled', key: 'after', value: function () { return moment().format(dateFormat); } },
+ { label: 'Published', key: 'before', value: function () { return moment().format(dateFormat); } },
+ { label: 'All', key: null, value: null }
+ ];
/**
* Test if a particular option is currently active by comparing it to
diff --git a/app/components/video/video-search.html b/app/components/video/video-search.html
new file mode 100644
index 000000000..ac7c6d621
--- /dev/null
+++ b/app/components/video/video-search.html
@@ -0,0 +1,12 @@
+
+
+
diff --git a/app/components/video/video-search.js b/app/components/video/video-search.js
new file mode 100644
index 000000000..171a0b1f1
--- /dev/null
+++ b/app/components/video/video-search.js
@@ -0,0 +1,59 @@
+'use strict';
+
+angular.module('bulbs.cms.video.videoSearch', [
+ 'bulbs.cms.site.config',
+ 'lodash',
+ 'VideohubClient.api',
+ 'VideohubClient.settings',
+ 'uuid4'
+])
+ .directive('videoSearch', [
+ '_', 'CmsConfig', 'uuid4',
+ function (_, CmsConfig, uuid4) {
+ return {
+ restrict: 'E',
+ require: 'ngModel',
+ templateUrl: CmsConfig.buildComponentPath('video/video-search.html'),
+ scope: {
+ label: '@videoSearchLabel',
+ onSelect: '&videoSearchLabel'
+ },
+ controller: [
+ '$scope', 'Video', 'VIDEOHUB_DEFAULT_CHANNEL',
+ function ($scope, Video, VIDEOHUB_DEFAULT_CHANNEL) {
+ $scope.videoChannel = VIDEOHUB_DEFAULT_CHANNEL;
+
+ $scope.itemDisplayFormatter = function (video) {
+ if (_.isObject(video)) {
+ return video.id + ' - ' + video.title;
+ }
+ };
+
+ $scope.searchVideos = function (query) {
+ return Video.$postSearch({
+ query: query,
+ channel: VIDEOHUB_DEFAULT_CHANNEL
+ });
+ };
+ }
+ ],
+ link: function (scope, elements, attrs, ngModelCtrl) {
+ scope.uuid = uuid4.generate();
+
+ if (ngModelCtrl) {
+ scope.ngModel = ngModelCtrl;
+
+ ngModelCtrl.$render = function () {
+ if (_.has(ngModelCtrl.$modelValue, 'title') && !scope.initialValue) {
+ scope.initialValue = scope.itemDisplayFormatter(ngModelCtrl.$modelValue);
+ }
+ };
+
+ scope.onSelect = function (selection) {
+ ngModelCtrl.$commitViewValue();
+ };
+ }
+ }
+ };
+ }
+ ]);
diff --git a/app/components/video/video.js b/app/components/video/video.js
new file mode 100644
index 000000000..b3aa0fc3a
--- /dev/null
+++ b/app/components/video/video.js
@@ -0,0 +1 @@
+angular.module('bulbs.cms.video', ['bulbs.cms.video.videoSearch']);
diff --git a/app/index.html b/app/index.html
index 853c683c6..88785cc50 100644
--- a/app/index.html
+++ b/app/index.html
@@ -37,6 +37,7 @@
+
@@ -52,16 +53,8 @@
-
-
-
-
- You can't see this!
-
-
@@ -118,6 +111,7 @@
+
@@ -141,6 +135,11 @@
+
+
+
+
+
@@ -149,10 +148,14 @@
-
+
+
+
+
+
@@ -211,6 +214,8 @@
+
+
@@ -243,7 +248,6 @@
-
@@ -342,6 +346,7 @@
.setFirebaseUrl('https://luminous-fire-8340.firebaseio.com/')
.setImageApiUrl('http://clickholeimg.local')
.setImageApiKey('123abc')
+ .setInlineObjectsPath('/views/inline-objects.json')
.setInternalUrl('onion.local')
.setNavLogoPath('/images/onion-logo.png')
.setVideoPath('videos/embed?id=')
@@ -359,6 +364,14 @@
resultsElement.html("Searching for: " + term + "
Fart
Butt
Dumb
");
};
+ angular.module('bulbsCmsAppDev', ['bulbsCmsApp.mockApi', 'bulbsCmsApp', 'bulbs.api.mock']);
+
+ // this has something to do with angular routing
+ // forcing it to no be a single-page app
+ $("body").on("click", "a", function(e){
+ if($(this).attr("target") == "_blank") return true;
+ $(this).attr("target", "_self");
+ });
@@ -375,24 +388,6 @@
-
-
diff --git a/app/scripts/app.js b/app/scripts/app.js
index afc949893..f66b8eb8e 100644
--- a/app/scripts/app.js
+++ b/app/scripts/app.js
@@ -15,6 +15,8 @@ angular.module('OnionEditor', []).constant('OnionEditor', window.OnionEditor);
angular.module('bulbsCmsApp', [
'bulbs.cms.site.config',
+ 'bulbs.cms.editorial',
+ 'bulbs.cms.content',
'ngCookies',
'ngResource',
@@ -34,10 +36,10 @@ angular.module('bulbsCmsApp', [
'ipCookie',
'bulbs.api',
'OnionEditor',
- // shared
+
'contentServices',
'cms.tunic',
- // components
+
'bettyEditable',
'bugReporter',
'campaigns',
@@ -46,7 +48,6 @@ angular.module('bulbsCmsApp', [
'filterListWidget',
'polls',
'promotedContent',
- 'statusFilter',
'templateTypeField',
'specialCoverage',
'sections',
diff --git a/app/scripts/directives/add-image.js b/app/scripts/directives/add-image.js
deleted file mode 100644
index 1e57a8576..000000000
--- a/app/scripts/directives/add-image.js
+++ /dev/null
@@ -1,45 +0,0 @@
-'use strict';
-
-angular.module('bulbsCmsApp')
- .directive('addImage', function ($http, $window) {
- return {
- restrict: 'E',
- templateUrl: '/views/add-image.html',
- scope: {
- article: '='
- },
- link: function (scope, element, attrs) {
- var attrName = attrs.attrName || 'images';
- scope.article[attrName] = scope.article[attrName] || [];
-
- if (attrs.caption === 'false') { scope.hideCaption = true; }
- scope.format = attrs.format || 'jpg';
- scope.crop = attrs.crop || '16x9';
- scope.placeholderText = attrs.placeholderText || 'Optional Image';
-
- scope.addAnImage = function () {
- $window.uploadImage({
- onSuccess: function (data) {
- scope.$apply(function () {
- scope.article[attrName].push({
- id: data.id.toString(),
- alt: null,
- caption: null
- });
- setTimeout($window.picturefill, 200);
- });
- },
- onError: function (data) {
- scope.$apply(function () {
- $window.alert('Error: ', data);
- });
- },
- onProgress: function (data) {
-
- }
- });
- };
- }
-
- };
- });
diff --git a/app/scripts/libraries/upload-video.js b/app/scripts/libraries/upload-video.js
new file mode 100644
index 000000000..4fd61a3fb
--- /dev/null
+++ b/app/scripts/libraries/upload-video.js
@@ -0,0 +1,313 @@
+!(function(window){
+ 'use strict';
+ //TODO: get from VIDEO ENCODING settings via VideoAttrs
+
+ var POSTER_THUMBNAIL_COUNT = 20;
+ var PUBLIC_URL = 'http://v.theonion.com/avclubmedia/';
+ //append hidden file input
+ $('#s3upload-file-input').remove();
+ $('body').append('');
+
+ //append video edit modal
+ $('#video-edit-modal').remove();
+ $('body').append($($('#video-edit-modal-template').html())); //template lives in jstemplates.html
+
+ //save button listener
+ $('#video-edit-modal').find('.save-button').on('click', function(e){
+ var newData = $('#video-edit-modal')[0].videoData;
+ newData['poster_url'] = $('#video-edit-modal').find('input[name="poster_url"]').val();
+ console.log('saving video. new data:', newData);
+ saveVideo(newData, {
+ onSuccess: function(data){
+ console.log('save success');
+ $('[data-videoid="' + data.id + '"]').find('iframe')[0].contentWindow.location.reload();
+ },
+ onError: function(data){
+ console.log('save error', data);
+ }
+ });
+ });
+
+ $('#video-edit-modal').find('.reencode-button').on('click', function(e){
+ console.log('reencode click');
+ var data = $('#video-edit-modal')[0].videoData;
+ encode(data.id);
+ });
+
+ $('#video-edit-modal .next-thumb').click(function(e) {
+ //increment video thumb
+ var index = getPosterIndex() + 1;
+ if (POSTER_THUMBNAIL_COUNT - 1 < index) {
+ index = 0;
+ }
+ setPosterUrl(posterUrl(index));
+ });
+
+ $('#video-edit-modal .prev-thumb').click(function(e) {
+ //decrement video thumb
+ var index = getPosterIndex() -1;
+ if (index < 0) {
+ index = POSTER_THUMBNAIL_COUNT - 1;
+ }
+ setPosterUrl(posterUrl(index));
+ });
+
+ $('#video-edit-modal input[name="poster_url"]')
+ .bind('change', updatePosterPreview)
+ .bind('keyup', updatePosterPreview);
+
+ function setPosterUrl(url) {
+ $('#video-edit-modal input[name="poster_url"]').val(url);
+ updatePosterPreview();
+ }
+
+ function getPosterIndex() {
+ var url = $('#video-edit-modal input[name="poster_url"]').val();
+ var match = url.match(/frame_(\d{4})\.jpg/);
+ if (match) {
+ console.log('match');
+ return parseInt(match[1]); }
+ else {
+ return -1;
+ }
+ }
+
+ function posterUrl(index) {
+ console.log($('#video-edit-modal')[0].videoData);
+ var id = $('#video-edit-modal')[0].videoData.id;
+ return PUBLIC_URL + 'video/' + id + '/frame_' + ('000'+ index).slice(-4) + '.jpg';
+ }
+
+ function updatePosterPreview() {
+ var posterUrl = $('#video-edit-modal input[name="poster_url"]').val();
+ $('#video-edit-modal .poster-preview').attr('src', posterUrl);
+ }
+
+ function uploadVideo(element, callbacks) {
+ console.log('upload video here');
+ var input = $('#s3upload-file-input'); //make this more 'angular'
+ input.click();
+ input.unbind('change');
+ input.bind('change', function(e) {
+ doVideoUpload(element, callbacks);
+ });
+ }
+
+ window.uploadVideo = uploadVideo;
+
+ function doVideoUpload(element, callbacks) {
+ console.log('doVideoUpload');
+ var fileInput = $('#s3upload-file-input')[0];
+ var file;
+
+ if (fileInput.files.length !== 0) {
+ file = fileInput.files[0];
+ // We have a file upload limit of 1024MB
+ if (file.size > (1024 * 1024 * 1024)) {
+ alert('Upload file cannot be larger than 1024MB.');
+ return;
+ }
+
+ if (file.type.indexOf('video/') !== 0) {
+ alert('You must upload a video file.');
+ return;
+ }
+
+ } else {
+ return;
+ }
+
+ console.log('posting');
+
+ var onError = function () {};
+ var onSuccess = function () {};
+
+ if (callbacks) {
+ if (callbacks.onSuccess) {
+ onSuccess = callbacks.onSuccess;
+ }
+ if (callbacks.onError) {
+ onError = callbacks.onError;
+ }
+ }
+
+ $.ajax({
+ type: 'POST',
+ url: '/videos/api/video/',
+ data: {'name': file.name},
+ success: function(data){
+ onSuccess(data.id);
+ $(element).attr('data-videoid', data.id);
+ initVideoWidget(data.id);
+ upload(file, data.id, element, callbacks);
+ },
+ error: function(data, status, error){
+ console.log('error', data, status, error);
+ if (data.status === 403) {
+ window.showLoginModal();
+ }
+ onError(data);
+ }
+ }); }
+
+ function upload(file, videoid, element, callbacks){
+ var url = 'https://' + window.videoAttrs.bucket + '.s3.amazonaws.com';
+ var formData = new FormData();
+ var path = window.videoAttrs.directory + '/' + videoid + '/original';
+
+ formData.append('key', path);
+ formData.append('AWSAccessKeyId', window.videoAttrs.AWSAccessKeyId);
+ formData.append('acl', window.videoAttrs.acl);
+ formData.append('success_action_status', window.videoAttrs.success_action_status);
+ formData.append('policy', window.videoAttrs.policy);
+ formData.append('signature', window.videoAttrs.signature);
+ formData.append('file', file);
+
+ $(element).append($($('#video-upload-status-overlay').html()));
+ $(element).show();
+ $(element).find('.upload-progress').show();
+ callbacks.onProgress && callbacks.onProgress(0); // jshint ignore:line
+
+ // TODO: set it so user cant leave page right now
+
+ $.ajax(url, {
+ processData: false,
+ contentType: false,
+ data: formData,
+ type: 'POST',
+ xhr: function() {
+ var req = $.ajaxSettings.xhr();
+ if (req) {
+ req.upload.addEventListener('progress', function(e) {
+ var percent = (e.loaded / e.total ) * 100;
+ $(element).find('.upload-progress .progress-bar').width(percent + '%');
+ }, false);
+ }
+
+ return req;
+ },
+
+ success: function(data) {
+ $(element).find('.video-status-container').remove();
+ // TODO: user can leave page now
+ encode(videoid, callbacks);
+ },
+
+ error: function(data){
+ console.log('upload ERROR', data);
+ callbacks.onError && callbacks.onError(); // jshint ignore:line
+ }
+ });
+ }
+
+ function encode(videoid){
+ // Kick off the zencoder job
+ console.log('encoding');
+ $.ajax({
+ url: '/videos/api/video/' + videoid + '/encode/',
+ method: 'POST',
+
+ success: function(data){
+ console.log('encode success', data);
+ var jobid = data.id;
+ appendEncodeProgress(videoid, jobid);
+ },
+
+ error: function(){
+ console.log('encode error');
+ }
+ });
+ }
+
+ function initVideoWidget(id){
+ console.log('init video widget');
+ //get video data
+ $.get('/videos/api/video/' + id + '/', function(data){
+ bindEditEvents(data);
+ var status = data.status;
+ if (status === 'In Progress') {
+ appendEncodeProgress(id, data.job_id);
+ }
+ });
+ }
+
+ window.initVideoWidget = initVideoWidget;
+
+ function bindEditEvents(data){
+ var container = $('[data-videoid="' + data.id + '"]');
+ container.css('position', 'relative');
+
+ //show/hide button listeners
+ container.find('.edit-button').on('click', function(e) {
+ $('#video-edit-modal').find('.video-name').text(data.name);
+ $('#video-edit-modal').find('input[name="poster_url"]').val(data.poster_url);
+ $('#video-edit-modal')[0].videoData = data;
+ $('#video-edit-modal .poster-preview').attr('src', data.poster_url);
+ $('#video-edit-modal').modal('show');
+ });
+ }
+
+ function editVideo(id){
+ $.get('/videos/api/video/' + id + '/', function(data){
+ $('#video-edit-modal').find('h3.video-name').text(data.name);
+ $('#video-edit-modal').find('input[name="poster_url"]').val(data.poster_url);
+ $('#video-edit-modal .poster-preview').attr('src', data.poster_url);
+ $('#video-edit-modal')[0].videoData = data;
+ $('#video-edit-modal').modal('show');
+ });
+ }
+
+ window.editVideo = editVideo;
+
+ function saveVideo(data, callbacks){
+ console.log('save video here', data);
+ $.ajax('/videos/api/video/' + data.id + '/',
+ {
+ type: 'PUT',
+ contentType: 'application/json; charset=utf-8',
+ data: JSON.stringify(data),
+ processData: false,
+ success: callbacks.onSuccess,
+ error: callbacks.onError
+ }
+ );
+ }
+
+ function appendEncodeProgress(videoid, jobid){
+ console.log('append encode progress');
+ var container = $('[data-videoid="' + videoid + '"]');
+ container.find('.video-status-container').remove();
+ container.append($('#video-encode-status-overlay').html());
+ container.find('.video-edit-container').addClass('encoding');
+
+ updateEncodeProgress(jobid, container);
+ }
+
+ function updateEncodeProgress(jobid, container){
+ $.ajax('https://app.zencoder.com/api/v2/jobs/' + jobid + '/progress.json', {
+ type: 'GET',
+ data: {
+ api_key: window.videoAttrs.zencoderApiKey
+ },
+
+ success: function(data) {
+ if (data.state === 'waiting' || data.state === 'pending' || data.state === 'processing') {
+ if (data.progress > 5) {
+ $(container).find('.progress-bar').width(data.progress + '%');
+ setTimeout(function(){ updateEncodeProgress(jobid, container); }, 2000);
+ } else {
+ setTimeout(function(){ updateEncodeProgress(jobid, container); }, 2000);
+ }
+ } else {
+ encodingWidgetCleanup(container);
+ }
+ }
+ });
+ }
+
+ function encodingWidgetCleanup(container){
+ container.find('.video-status-container').remove();
+ container.find('.video-edit-container').removeClass('encoding');
+ container.find('iframe')[0].contentWindow.location.reload();
+ }
+}(window)); // jshint ignore:line
diff --git a/app/shared/cms-config/cms-config-base.js b/app/shared/cms-config/cms-config-base.js
index 8ee261666..44f6ce363 100644
--- a/app/shared/cms-config/cms-config-base.js
+++ b/app/shared/cms-config/cms-config-base.js
@@ -3,7 +3,8 @@
angular.module('bulbs.cms.base.config', [
'bulbs.cms.config',
'bulbs.cms.customSearch.config',
- 'ngClipboard'
+ 'ngClipboard',
+ 'statusFilter'
])
.config([
'CmsConfigProvider', 'CustomSearchConfigProvider', 'ngClipProvider',
diff --git a/app/shared/cms-config/cms-config.js b/app/shared/cms-config/cms-config.js
index 8b83d86f0..e1c57334e 100644
--- a/app/shared/cms-config/cms-config.js
+++ b/app/shared/cms-config/cms-config.js
@@ -52,6 +52,8 @@ angular.module('bulbs.cms.config', [
var imageApiKey = '';
// url for internal links, those that are not accessible to the public
var internalUrl = '';
+ // path to inline editor buttons configuration
+ var inlineObjectsPath = '';
// path to cms logo static asset
var navLogoPath = '';
// path to shared directory
@@ -140,6 +142,14 @@ angular.module('bulbs.cms.config', [
return this;
};
+ this.setInlineObjectsPath = function (value) {
+ inlineObjectsPath = checkOrError(
+ value, _.isString,
+ 'inline objects path must be a string!'
+ );
+ return this;
+ };
+
this.setImageApiUrl = function (value) {
imageApiUrl = checkOrError(
value, _.isString,
@@ -282,6 +292,7 @@ angular.module('bulbs.cms.config', [
getCmsName: _.constant(cmsName),
getFirebaseMaxArticleHistory: _.constant(firebaseMaxArticleHistory),
getImageApiKey: _.constant(imageApiKey),
+ getInlineObjecsPath: _.constant(inlineObjectsPath),
getNavLogoPath: _.constant(navLogoPath),
getTimezoneName: _.constant(timezoneName),
getTopBarMapping: function (name) {
diff --git a/app/shared/cms-config/cms-config.tests.js b/app/shared/cms-config/cms-config.tests.js
index 1dce390d6..6c9e1dbae 100644
--- a/app/shared/cms-config/cms-config.tests.js
+++ b/app/shared/cms-config/cms-config.tests.js
@@ -275,6 +275,33 @@ describe('CmsConfig', function () {
});
});
+ context('inline objects path', function () {
+
+ it('should provide a getter and setter', function () {
+ var path = '/inline-objects.json';
+
+ configs.setInlineObjectsPath(path);
+
+ expect(sealedConfigs().getInlineObjecsPath()).to.equal(path);
+ });
+
+ it('should throw an error if given value is not a string', function () {
+
+ expect(function () {
+ configs.setInlineObjectsPath(123);
+ }).to.throw(
+ BulbsCmsConfigError,
+ 'Configuration Error (CmsConfig): inline objects path must be a string!'
+ );
+ });
+
+ it('should return config object', function () {
+
+ expect(configs.setInlineObjectsPath('/inline-objects.json'))
+ .to.equal(configs);
+ });
+ });
+
context('internal url', function () {
it('should provide a setter and getter', function () {
diff --git a/app/styles/_components.less b/app/styles/_components.less
index 37aaf9a77..b681bec0e 100644
--- a/app/styles/_components.less
+++ b/app/styles/_components.less
@@ -1,18 +1,20 @@
@import "_mixin";
// injector:less_components
-@import "../components/filter-widget/filter-widget.less";
+@import "../components/filter-list-widget/filter-list-widget.less";
@import "../components/promoted-content/promoted-content.less";
-@import "../components/polls/polls-edit/polls-edit.less";
+@import "../components/filter-widget/filter-widget.less";
@import "../components/campaigns/campaigns-edit/campaigns-edit-sponsor-pixel/campaigns-edit-sponsor-pixel.less";
@import "../components/custom-search/custom-search-content-item/custom-search-content-item.less";
@import "../components/custom-search/custom-search-group/custom-search-group-condition/custom-search-group-condition.less";
@import "../components/custom-search/custom-search-group/custom-search-group.less";
@import "../components/custom-search/custom-search-simple-content-search/custom-search-simple-content-search.less";
@import "../components/custom-search/custom-search.less";
-@import "../components/filter-list-widget/filter-list-widget.less";
+@import "../components/editorial/editor-item.less";
+@import "../components/editorial/send-to-editor.less";
@import "../components/promoted-content/promoted-content-time-picker/promoted-content-time-picker.less";
@import "../components/campaigns/campaigns-edit/campaigns-edit.less";
+@import "../components/polls/polls-edit/polls-edit.less";
@import "../components/polls/polls-list/polls-list.less";
@import "../components/promoted-content/promoted-content-article/promoted-content-article.less";
@import "../components/promoted-content/promoted-content-list/promoted-content-list.less";
diff --git a/app/styles/edit-page.less b/app/styles/edit-page.less
index c00d00109..7c5bcb4a1 100644
--- a/app/styles/edit-page.less
+++ b/app/styles/edit-page.less
@@ -90,4 +90,4 @@ template-type-field {
label {
font-size: 12px;
}
-}
\ No newline at end of file
+}
diff --git a/app/styles/upload-video.less b/app/styles/upload-video.less
new file mode 100644
index 000000000..b021203ee
--- /dev/null
+++ b/app/styles/upload-video.less
@@ -0,0 +1,78 @@
+.video-edit-container{
+ position: absolute;
+ top: 0px;
+ width: 100%;
+ height: 0px;
+
+ .video-edit-overlay{
+ width: 100%;
+ height: 100%;
+ background-color: rgba(100,100,100,0.5);
+ color: white;
+ padding: 15px;
+ display: none;
+ input{ width: 100%; }
+ }
+
+ .edit-button, .clear-button{
+ position: absolute;
+ top: 15px;
+ left: 15px;
+ }
+}
+
+.video-edit-container.editing{
+ height: 100%;
+ .video-edit-overlay{ display: block; }
+ .edit-button{ display: none; }
+}
+
+.video-edit-container.encoding{
+ .reencode-button{ display: none; }
+}
+
+.video-status-container{
+ position: absolute !important;
+ bottom: 0px !important;
+ width: 100% !important;
+ height: auto !important;
+ padding-bottom: 34px !important;
+}
+
+.s3uploadButton {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ margin-top: -34px; /* This is a little hack. */
+ z-index: 15;
+ position: relative;
+}
+
+.s3uploadProgress {
+ height: 34px !important;
+ z-index: 10;
+ position: absolute;
+ bottom: 0px;
+ margin-bottom: 0px;
+ width: 100%;
+ background-color: #eee;
+}
+
+.s3uploadProgress .progress-bar {
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+ z-index: 10;
+ position: relative;
+}
+
+.s3uploadProgress .progress-bar span {
+ line-height: 34px;
+ padding-left: 20px;
+ font-size: 1em;
+ font-weight: 500;
+ color: white;
+ text-shadow:
+ -1px -1px 0 #000,
+ 1px -1px 0 #000,
+ -1px 1px 0 #000,
+ 1px 1px 0 #000;
+}