Worked on post uploads (#11) - added GUI
This commit is contained in:
parent
7ec3715cdd
commit
dfb1198143
8 changed files with 1047 additions and 4 deletions
|
@ -32,3 +32,12 @@ a {
|
|||
a:hover {
|
||||
color: #7b3;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1.5em auto;
|
||||
height: 1px;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
border-top: 1px solid #eee;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
display: table-row;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
width: 1%;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.form-label,
|
||||
.form-input {
|
||||
display: table-cell;
|
||||
|
@ -25,6 +30,7 @@
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.tag-input,
|
||||
textarea,
|
||||
input[type=text],
|
||||
input[type=password] {
|
||||
|
@ -45,7 +51,22 @@ input[type=button] {
|
|||
background: #eee;
|
||||
font-family: 'Droid Sans', sans-serif;
|
||||
font-size: 17px;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover,
|
||||
input[type=button]:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
button.highlight,
|
||||
input[type=button].highlight {
|
||||
background: #ad5;
|
||||
}
|
||||
button:hover.highlight,
|
||||
input[type=button]:hover.highlight {
|
||||
background: #dfa;
|
||||
}
|
||||
|
||||
|
||||
.file-handler {
|
||||
border: 3px dashed #eee;
|
||||
|
@ -58,3 +79,32 @@ input[type=button] {
|
|||
border-color: #6a2;
|
||||
background-color: #eeffcc;
|
||||
}
|
||||
|
||||
.tag-input {
|
||||
padding: 1px;
|
||||
line-height: normal !important;
|
||||
cursor: text;
|
||||
}
|
||||
.tag-input ul {
|
||||
list-style-type: none;
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.tag-input li {
|
||||
background: #ddd;
|
||||
display: inline-block;
|
||||
margin: 1px;
|
||||
padding: 2px 4px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.tag-input input {
|
||||
border: none;
|
||||
width: auto;
|
||||
}
|
||||
.tag-input li a {
|
||||
color: black;
|
||||
font-size: 14px;
|
||||
margin-left: 0.5em;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
167
public_html/css/post-upload.css
Normal file
167
public_html/css/post-upload.css
Normal file
|
@ -0,0 +1,167 @@
|
|||
#post-upload-step1 {
|
||||
display: table;
|
||||
width: 30em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
#post-upload-step1 .file-handler {
|
||||
padding: 3.5em;
|
||||
}
|
||||
|
||||
#post-upload-step1 .url-handler {
|
||||
margin-top: 0.5em;
|
||||
position: relative;
|
||||
}
|
||||
#post-upload-step1 .url-handler .input-wrapper {
|
||||
margin-right: 8.5em;
|
||||
}
|
||||
#post-upload-step1 .url-handler button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
#post-upload-step2 .hybrid-view {
|
||||
text-align: center;
|
||||
}
|
||||
@media all and (min-width: 62.5em) {
|
||||
#post-upload-step2 .hybrid-window:first-child {
|
||||
width: 40%;
|
||||
margin-right: 1em;
|
||||
float: left;
|
||||
}
|
||||
#post-upload-step2 .hybrid-window:last-child {
|
||||
display: inline-block;
|
||||
width: 57.5%;
|
||||
}
|
||||
}
|
||||
|
||||
#post-upload-step2 .thumbnail img {
|
||||
border: 1px solid black;
|
||||
vertical-align: middle;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#post-upload-step2 table {
|
||||
border-spacing: 0;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
}
|
||||
#post-upload-step2 table td,
|
||||
#post-upload-step2 table th {
|
||||
padding: 0.2em 0.5em;
|
||||
text-align: center;
|
||||
}
|
||||
#post-upload-step2 table th {
|
||||
font-weight: normal;
|
||||
}
|
||||
#post-upload-step2 table tr.selected {
|
||||
background: #faffca;
|
||||
}
|
||||
#post-upload-step2 table .checkbox {
|
||||
width: 30px;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
#post-upload-step2 table .checkbox input {
|
||||
margin: 0 auto;
|
||||
}
|
||||
#post-upload-step2 table .thumbnail {
|
||||
width: 40px;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
#post-upload-step2 table .safety {
|
||||
width: 60px;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
#post-upload-step2 table .tags {
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
#post-upload-step2 table .safety {
|
||||
text-align: center;
|
||||
}
|
||||
#post-upload-step2 table .safety [class^=safety-] {
|
||||
box-shadow: inset 0 0 0 3px rgba(0,0,0,0.1);
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
#post-upload-step2 table .safety-safe { background: #b2efa2; }
|
||||
#post-upload-step2 table .safety-sketchy { background: #f0e4a8; }
|
||||
#post-upload-step2 table .safety-unsafe { background: #fbc6b6; }
|
||||
#post-upload-step2 table .thumbnail img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: inline-block;
|
||||
background-size: 30px 30px;
|
||||
}
|
||||
#post-upload-step2 .operations {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#post-upload-step2 .operations li {
|
||||
display: inline-block;
|
||||
margin: 0.3em 0.3em 0 0;
|
||||
}
|
||||
|
||||
#post-upload-step2 form {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
}
|
||||
#post-upload-step2 .messages {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
#post-upload-step2 .form-slider {
|
||||
text-align: center;
|
||||
}
|
||||
#post-upload-step2 .form-slider .thumbnail img {
|
||||
max-width: 100%;
|
||||
max-height: 300px;
|
||||
margin: 0 auto 1em auto;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#post-upload-step2,
|
||||
.template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#lightbox {
|
||||
display: none;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
margin-left: 10px;
|
||||
}
|
||||
#lightbox img {
|
||||
max-width: 400px;
|
||||
max-height: 400px;
|
||||
background: white;
|
||||
border: 0.5em solid white;
|
||||
box-shadow: 0 0 0 1px #eee;
|
||||
position: relative;
|
||||
}
|
||||
#lightbox:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
top: 50%;
|
||||
margin-top: -8px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: white;
|
||||
border-left: 1px solid #eee;
|
||||
border-bottom: 1px solid #eee;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
#uploading-alert {
|
||||
display: none;
|
||||
text-align: left;
|
||||
}
|
|
@ -13,6 +13,7 @@
|
|||
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
|
||||
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/mousetrap/1.4.6/mousetrap.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css"/>
|
||||
<!-- build:template
|
||||
<link rel="stylesheet" type="text/css" href="app.min.css?<%= timestamp %>"/>
|
||||
/build -->
|
||||
|
@ -25,6 +26,7 @@
|
|||
<link rel="stylesheet" type="text/css" href="/css/pager.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/css/login-form.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/css/registration-form.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/css/post-upload.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/css/user-list.css"/>
|
||||
<link rel="stylesheet" type="text/css" href="/css/user.css"/>
|
||||
<!-- /build -->
|
||||
|
@ -59,6 +61,7 @@
|
|||
<script type="text/javascript" src="/js/Util.js"></script>
|
||||
<script type="text/javascript" src="/js/BrowsingSettings.js"></script>
|
||||
<script type="text/javascript" src="/js/Controls/FileDropper.js"></script>
|
||||
<script type="text/javascript" src="/js/Controls/TagInput.js"></script>
|
||||
<script type="text/javascript" src="/js/PresenterManager.js"></script>
|
||||
<script type="text/javascript" src="/js/Presenters/TopNavigationPresenter.js"></script>
|
||||
<script type="text/javascript" src="/js/Presenters/PagedCollectionPresenter.js"></script>
|
||||
|
|
140
public_html/js/Controls/TagInput.js
Normal file
140
public_html/js/Controls/TagInput.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
var App = App || {};
|
||||
App.Controls = App.Controls || {};
|
||||
|
||||
//todo: autocomplete
|
||||
|
||||
App.Controls.TagInput = function(
|
||||
$underlyingInput,
|
||||
_,
|
||||
jQuery) {
|
||||
|
||||
var KEY_RETURN = 13;
|
||||
var KEY_SPACE = 32;
|
||||
var KEY_BACKSPACE = 8;
|
||||
|
||||
var tags = [];
|
||||
var options = {
|
||||
beforeTagAdded: null,
|
||||
beforeTagRemoved: null,
|
||||
};
|
||||
|
||||
if ($underlyingInput.length !== 1) {
|
||||
throw new Error('Cannot set tag input to more than one elements at once');
|
||||
}
|
||||
if ($underlyingInput.attr('data-tagged')) {
|
||||
throw new Error('Tag input was already initialized for this element');
|
||||
}
|
||||
$underlyingInput.attr('data-tagged', true);
|
||||
$underlyingInput.hide();
|
||||
|
||||
var $wrapper = jQuery('<div class="tag-input">');
|
||||
var $tagList = jQuery('<ul class="tags">');
|
||||
var $input = jQuery('<input type="text"/>');
|
||||
$wrapper.append($tagList);
|
||||
$wrapper.append($input);
|
||||
$wrapper.insertAfter($underlyingInput);
|
||||
$wrapper.click(function(e) {
|
||||
e.preventDefault();
|
||||
$input.focus();
|
||||
});
|
||||
$input.attr('placeholder', $underlyingInput.attr('placeholder'));
|
||||
|
||||
$input.unbind('paste').bind('paste', function(e) {
|
||||
e.preventDefault();
|
||||
var pastedText;
|
||||
if (window.clipboardData) {
|
||||
pastedText = window.clipboardData.getData('Text');
|
||||
} else {
|
||||
pastedText = (e.originalEvent || e).clipboardData.getData('text/plain');
|
||||
}
|
||||
var patedTags = pastedText.split(/\s+/);
|
||||
_.each(patedTags, function(tag) {
|
||||
addTag(tag);
|
||||
});
|
||||
});
|
||||
|
||||
$input.unbind('keydown').bind('keydown', function(e) {
|
||||
if (e.which === KEY_RETURN || e.which === KEY_SPACE) {
|
||||
e.preventDefault();
|
||||
var tag = $input.val();
|
||||
addTag(tag);
|
||||
$input.val('');
|
||||
} else if (e.which === KEY_BACKSPACE && jQuery(this).val().length === 0) {
|
||||
e.preventDefault();
|
||||
removeLastTag();
|
||||
}
|
||||
});
|
||||
|
||||
function addTag(tag) {
|
||||
tag = tag.trim();
|
||||
if (tag.length === 0) {
|
||||
return;
|
||||
}
|
||||
var oldTags = getTags();
|
||||
if (_.contains(_.map(oldTags, function(tag) { return tag.toLowerCase(); }), tag.toLowerCase())) {
|
||||
flashTag(tag);
|
||||
} else {
|
||||
if (typeof(options.beforeTagAdded) === 'function') {
|
||||
options.beforeTagAdded(tag);
|
||||
}
|
||||
var newTags = oldTags.slice();
|
||||
newTags.push(tag);
|
||||
setTags(newTags);
|
||||
}
|
||||
}
|
||||
|
||||
function removeTag(tag) {
|
||||
var oldTags = getTags();
|
||||
var newTags = _.without(oldTags, tag);
|
||||
if (newTags.length !== oldTags.length) {
|
||||
if (typeof(options.beforeTagRemoved) === 'function') {
|
||||
options.beforeTagRemoved(tag);
|
||||
}
|
||||
setTags(newTags);
|
||||
}
|
||||
}
|
||||
|
||||
function removeLastTag() {
|
||||
removeTag(_.last(getTags()));
|
||||
}
|
||||
|
||||
function flashTag(tag) {
|
||||
var $elem = $tagList.find('li[data-tag="' + tag.toLowerCase() + '"]');
|
||||
$elem.css({backgroundColor: 'rgba(255, 200, 200, 1)'});
|
||||
}
|
||||
|
||||
function setTags(newTags) {
|
||||
tags = newTags.slice();
|
||||
$tagList.empty();
|
||||
$underlyingInput.val(newTags.join(' '));
|
||||
_.each(newTags, function(tag) {
|
||||
var $elem = jQuery('<li/>');
|
||||
$elem.text(tag);
|
||||
$elem.attr('data-tag', tag.toLowerCase());
|
||||
|
||||
var $deleteButton = jQuery('<a><i class="fa fa-remove"></i></a>');
|
||||
$deleteButton.bind('click', function(e) {
|
||||
e.preventDefault();
|
||||
removeTag(tag);
|
||||
$input.focus();
|
||||
});
|
||||
$elem.append($deleteButton);
|
||||
|
||||
$tagList.append($elem);
|
||||
});
|
||||
}
|
||||
|
||||
function getTags() {
|
||||
return tags;
|
||||
}
|
||||
|
||||
_.extend(options, {
|
||||
setTags: setTags,
|
||||
getTags: getTags,
|
||||
removeTag: removeTag,
|
||||
addTag: addTag,
|
||||
});
|
||||
return options;
|
||||
};
|
||||
|
||||
App.DI.register('tagInput', App.Controls.TagInput);
|
|
@ -2,19 +2,547 @@ var App = App || {};
|
|||
App.Presenters = App.Presenters || {};
|
||||
|
||||
App.Presenters.PostUploadPresenter = function(
|
||||
_,
|
||||
jQuery,
|
||||
topNavigationPresenter) {
|
||||
mousetrap,
|
||||
promise,
|
||||
util,
|
||||
api,
|
||||
router,
|
||||
topNavigationPresenter,
|
||||
messagePresenter) {
|
||||
|
||||
var $el = jQuery('#content');
|
||||
var $messages;
|
||||
var template;
|
||||
var allPosts = [];
|
||||
var tagInput;
|
||||
var interactionEnabled = true;
|
||||
|
||||
function init(args) {
|
||||
topNavigationPresenter.select('upload');
|
||||
topNavigationPresenter.changeTitle('Upload');
|
||||
render();
|
||||
|
||||
promise.wait(util.promiseTemplate('post-upload')).then(function(html) {
|
||||
template = _.template(html);
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
function render() {
|
||||
$el.html('Post upload placeholder');
|
||||
$el.html(template());
|
||||
$messages = $el.find('.messages');
|
||||
|
||||
new App.Controls.FileDropper($el.find('[name=post-content]'), fileHandlerChanged, jQuery);
|
||||
$el.find('.url-handler input').keydown(urlHandlerKeyPressed);
|
||||
$el.find('.url-handler button').click(urlHandlerButtonClicked);
|
||||
$el.find('thead th.checkbox').click(postTableSelectAllCheckboxClicked);
|
||||
|
||||
mousetrap.bind('a', function(e) {
|
||||
if (!e.altKey && !e.ctrlKey) {
|
||||
selectPrevPostInTable();
|
||||
}
|
||||
}, 'keyup');
|
||||
mousetrap.bind('d', function(e) {
|
||||
if (!e.altKey && !e.ctrlKey) {
|
||||
selectNextPostInTable();
|
||||
}
|
||||
}, 'keyup');
|
||||
|
||||
$el.find('.remove').click(removeButtonClicked);
|
||||
$el.find('.move-up').click(moveUpButtonClicked);
|
||||
$el.find('.move-down').click(moveDownButtonClicked);
|
||||
$el.find('.submit').click(submitButtonClicked);
|
||||
|
||||
tagInput = new App.Controls.TagInput($el.find('form [name=tags]'), _, jQuery);
|
||||
}
|
||||
|
||||
function getDefaultPost() {
|
||||
return {
|
||||
safety: 'safe',
|
||||
source: null,
|
||||
fileName: null,
|
||||
anonymous: false,
|
||||
tags: [],
|
||||
|
||||
content: null,
|
||||
url: null,
|
||||
thumbnail: null,
|
||||
$tableRow: null,
|
||||
};
|
||||
}
|
||||
|
||||
function urlHandlerKeyPressed(e) {
|
||||
if (e.which !== 13) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
$el.find('.url-handler button').trigger('click');
|
||||
}
|
||||
|
||||
function urlHandlerButtonClicked(e) {
|
||||
var $input = $el.find('.url-handler input');
|
||||
|
||||
var url = $input.val().trim();
|
||||
if (url === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
var protocol = /^(\w+):\/\//.exec(url);
|
||||
if (!protocol) {
|
||||
url = 'http://' + url;
|
||||
} else {
|
||||
protocol = protocol[1].toLowerCase();
|
||||
if (protocol !== 'http' && protocol !== 'https') {
|
||||
window.alert('Unsupported protocol: ' + protocol);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$input.val('');
|
||||
addPostFromUrl(url);
|
||||
}
|
||||
|
||||
function fileHandlerChanged(files) {
|
||||
for (var i = 0; i < files.length; i ++) {
|
||||
addPostFromFile(files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function addPostFromFile(file) {
|
||||
var post = _.extend({}, getDefaultPost(), {fileName: file.name});
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
post.content = e.target.result;
|
||||
if (file.type.match('image.*')) {
|
||||
post.thumbnail = e.target.result;
|
||||
postThumbnailLoaded(post);
|
||||
}
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
postAdded(post);
|
||||
}
|
||||
|
||||
function addPostFromUrl(url) {
|
||||
var post = _.extend({}, getDefaultPost(), {url: url, source: url, fileName: url});
|
||||
|
||||
var matches = url.match(/watch.*?=([a-zA-Z0-9_-]+)/);
|
||||
if (matches) {
|
||||
var youtubeThumbnailUrl = 'http://img.youtube.com/vi/' + matches[1] + '/mqdefault.jpg';
|
||||
post.thumbnail = youtubeThumbnailUrl;
|
||||
} else {
|
||||
post.thumbnail = url;
|
||||
}
|
||||
postAdded(post);
|
||||
postThumbnailLoaded(post);
|
||||
}
|
||||
|
||||
function postAdded(post) {
|
||||
var allPosts = getAllPosts();
|
||||
allPosts.push(post);
|
||||
setAllPosts(allPosts);
|
||||
|
||||
var $table = $el.find('table');
|
||||
var $row = $table.find('.template').clone(true);
|
||||
|
||||
post.$tableRow = $row;
|
||||
|
||||
$row.removeClass('template');
|
||||
$row.find('td:not(.checkbox)').click(postTableRowClicked);
|
||||
$row.find('td.checkbox').click(postTableCheckboxClicked);
|
||||
$row.find('img').mouseenter(postTableRowImageHovered);
|
||||
$row.find('img').mouseleave(postTableRowImageUnhovered);
|
||||
$row.data('post', post);
|
||||
$table.find('tbody').append($row);
|
||||
|
||||
postChanged(post);
|
||||
|
||||
selectPostInTable(post);
|
||||
showOrHidePostsTable();
|
||||
}
|
||||
|
||||
function postChanged(post) {
|
||||
var $row = post.$tableRow;
|
||||
$row.find('.tags').text(post.tags.join(', ') || '-');
|
||||
$row.find('.safety div').attr('class', 'safety-' + post.safety);
|
||||
}
|
||||
|
||||
function postThumbnailLoaded(post) {
|
||||
var selectedPosts = getSelectedPosts();
|
||||
if (selectedPosts.length === 1 && selectedPosts[0] === post && post.thumbnail !== null) {
|
||||
updatePostThumbnailInForm(post);
|
||||
}
|
||||
updatePostThumbnailInTable(post);
|
||||
}
|
||||
|
||||
function updatePostThumbnailInForm(post) {
|
||||
$el.find('.form-slider .thumbnail img')[0].setAttribute('src', post.thumbnail);
|
||||
}
|
||||
|
||||
function updatePostThumbnailInTable(post) {
|
||||
var $row = post.$tableRow;
|
||||
//huge speedup thanks to this condition
|
||||
if ($row.find('img').attr('src') !== post.thumbnail && post.thumbnail !== null) {
|
||||
$row.find('img')[0].setAttribute('src', post.thumbnail);
|
||||
}
|
||||
}
|
||||
|
||||
function postTableRowClicked(e) {
|
||||
e.preventDefault();
|
||||
var $allCheckboxes = jQuery(this).parents('table').find('tbody input[type=checkbox]');
|
||||
var $myCheckbox = jQuery(this).parents('tr').find('input[type=checkbox]');
|
||||
$allCheckboxes.prop('checked', false);
|
||||
$myCheckbox.prop('checked', true);
|
||||
postTableCheckboxesChanged(e);
|
||||
}
|
||||
|
||||
function postTableCheckboxClicked(e) {
|
||||
if (e.target.nodeName === 'TD') {
|
||||
var checkbox = jQuery(this).find('input[type=checkbox]');
|
||||
checkbox.prop('checked', !checkbox.prop('checked'));
|
||||
}
|
||||
postTableCheckboxesChanged(e);
|
||||
}
|
||||
|
||||
function postTableSelectAllCheckboxClicked(e) {
|
||||
var $checkbox = jQuery(this).find('input[type=checkbox]');
|
||||
if (e.target.nodeName === 'TH') {
|
||||
$checkbox.prop('checked', !$checkbox.prop('checked'));
|
||||
}
|
||||
$el.find('tbody input[type=checkbox]').prop('checked', $checkbox.prop('checked'));
|
||||
postTableCheckboxesChanged();
|
||||
}
|
||||
|
||||
function postTableCheckboxesChanged(e) {
|
||||
var $table = $el.find('table');
|
||||
if (!interactionEnabled) {
|
||||
if (typeof(e) !== 'undefined') {
|
||||
e.preventDefault();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$table.find('tbody tr').each(function(i, row) {
|
||||
var $row = jQuery(row);
|
||||
var checked = $row.find('input[type=checkbox]').prop('checked');
|
||||
$row.toggleClass('selected', checked);
|
||||
});
|
||||
|
||||
var allPosts = getAllPosts();
|
||||
var selectedPosts = getSelectedPosts();
|
||||
$table.find('[name=select-all]').prop('checked', allPosts.length === selectedPosts.length);
|
||||
postTableSelectionChanged(selectedPosts);
|
||||
}
|
||||
|
||||
function getAllPosts() {
|
||||
return allPosts;
|
||||
}
|
||||
|
||||
function setAllPosts(newPosts) {
|
||||
allPosts = newPosts;
|
||||
}
|
||||
|
||||
function setAllPostsFromTable() {
|
||||
var newPosts = _.map($el.find('tbody tr'), function(row) {
|
||||
var $row = jQuery(row);
|
||||
return $row.data('post');
|
||||
});
|
||||
setAllPosts(newPosts);
|
||||
}
|
||||
|
||||
function getSelectedPosts() {
|
||||
var selectedPosts = [];
|
||||
$el.find('tbody tr.selected').each(function(i, row) {
|
||||
var $row = jQuery(row);
|
||||
selectedPosts.push($row.data('post'));
|
||||
});
|
||||
return selectedPosts;
|
||||
}
|
||||
|
||||
function postTableSelectionChanged(selectedPosts) {
|
||||
if (selectedPosts.length === 0) {
|
||||
hidePostEditForm();
|
||||
} else {
|
||||
showPostEditForm(selectedPosts);
|
||||
}
|
||||
}
|
||||
|
||||
function hidePostEditForm() {
|
||||
var $postEditForm = $el.find('form');
|
||||
$postEditForm.parent('.form-slider').slideUp(function() {
|
||||
$postEditForm.find('.thumbnail').hide();
|
||||
});
|
||||
}
|
||||
|
||||
function showPostEditForm(selectedPosts) {
|
||||
var $postEditForm = $el.find('form');
|
||||
$postEditForm.parent('.form-slider').slideDown();
|
||||
if (selectedPosts.length !== 1) {
|
||||
$postEditForm.parent('.form-slider').find('.thumbnail').slideUp();
|
||||
$postEditForm.find('.file-name strong').text('Multiple posts selected');
|
||||
} else {
|
||||
var post = selectedPosts[0];
|
||||
$postEditForm.parent('.form-slider').find('.thumbnail').slideDown();
|
||||
$postEditForm.find('.file-name strong').text(post.fileName);
|
||||
updatePostThumbnailInForm(post);
|
||||
}
|
||||
|
||||
var combinedPost = getCombinedPost(selectedPosts);
|
||||
|
||||
$postEditForm.find('[name=source]').val(combinedPost.source);
|
||||
$postEditForm.find('[name=anonymous]').prop('checked', combinedPost.anonymous);
|
||||
$postEditForm.find('[name=safety]').prop('checked', false);
|
||||
if (combinedPost.safety !== null) {
|
||||
$postEditForm.find('[name=safety][value=' + combinedPost.safety + ']').prop('checked', true);
|
||||
}
|
||||
tagInput.setTags(combinedPost.tags);
|
||||
|
||||
$postEditForm.find('[name=source]').unbind('change').bind('change', function(e) {
|
||||
setPostsSource(selectedPosts, jQuery(this).val());
|
||||
});
|
||||
$postEditForm.find('[name=safety]').unbind('change').bind('change', function(e) {
|
||||
setPostsSafety(selectedPosts, jQuery(this).val());
|
||||
});
|
||||
$postEditForm.find('[name=anonymous]').unbind('change').bind('change', function(e) {
|
||||
setPostsAnonymity(selectedPosts, jQuery(this).is(':checked'));
|
||||
});
|
||||
tagInput.beforeTagAdded = function(tag) {
|
||||
addTagToPosts(selectedPosts, tag);
|
||||
};
|
||||
tagInput.beforeTagRemoved = function(tag) {
|
||||
removeTagFromPosts(selectedPosts, tag);
|
||||
};
|
||||
}
|
||||
|
||||
function getCombinedPost(posts) {
|
||||
var combinedPost = _.extend({}, getDefaultPost());
|
||||
if (posts.length === 0) {
|
||||
return combinedPost;
|
||||
}
|
||||
_.extend(combinedPost, posts[0]);
|
||||
|
||||
var tagFilter = function(post) {
|
||||
return function(tag) {
|
||||
return post.tags.indexOf(tag) !== -1;
|
||||
};
|
||||
};
|
||||
|
||||
for (var i = 1; i < posts.length; i ++) {
|
||||
if (posts[i].safety !== posts[0].safety) {
|
||||
combinedPost.safety = null;
|
||||
}
|
||||
if (posts[i].anonymous !== posts[0].anonymous) {
|
||||
combinedPost.anonymous = null;
|
||||
}
|
||||
if (posts[i].source !== posts[0].source) {
|
||||
combinedPost.source = null;
|
||||
}
|
||||
combinedPost.tags = combinedPost.tags.filter(tagFilter(posts[i]));
|
||||
}
|
||||
|
||||
return combinedPost;
|
||||
}
|
||||
|
||||
function setPostsSource(posts, newSource) {
|
||||
_.each(posts, function(post) {
|
||||
//todo: take care of max source length
|
||||
post.source = newSource;
|
||||
postChanged(post);
|
||||
});
|
||||
}
|
||||
|
||||
function setPostsSafety(posts, newSafety) {
|
||||
_.each(posts, function(post) {
|
||||
post.safety = newSafety;
|
||||
postChanged(post);
|
||||
});
|
||||
}
|
||||
|
||||
function setPostsAnonymity(posts, isAnonymous) {
|
||||
_.each(posts, function(post) {
|
||||
post.anonymous = isAnonymous;
|
||||
postChanged(post);
|
||||
});
|
||||
}
|
||||
|
||||
function addTagToPosts(posts, tag) {
|
||||
jQuery.each(posts, function(i, post) {
|
||||
var index = post.tags.indexOf(tag);
|
||||
if (index === -1) {
|
||||
post.tags.push(tag);
|
||||
}
|
||||
});
|
||||
jQuery.each(posts, function(i, post) {
|
||||
postChanged(post);
|
||||
});
|
||||
}
|
||||
|
||||
function removeTagFromPosts(posts, tag) {
|
||||
jQuery.each(posts, function(i, post) {
|
||||
var index = post.tags.indexOf(tag);
|
||||
if (index !== -1) {
|
||||
post.tags.splice(index, 1);
|
||||
}
|
||||
});
|
||||
jQuery.each(posts, function(i, post) {
|
||||
postChanged(post);
|
||||
});
|
||||
}
|
||||
|
||||
function postTableRowImageHovered(e) {
|
||||
var $img = jQuery(this);
|
||||
if (!$img.attr('src')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var $lightbox = jQuery('#lightbox');
|
||||
$lightbox.find('img').attr('src', $img.attr('src'));
|
||||
$lightbox
|
||||
.show()
|
||||
.css({
|
||||
left: ($img.position().left + $img.outerWidth()) + 'px',
|
||||
top: ($img.position().top + ($img.outerHeight() - $lightbox.outerHeight()) / 2) + 'px',
|
||||
});
|
||||
}
|
||||
|
||||
function postTableRowImageUnhovered(e) {
|
||||
var $lightbox = jQuery('#lightbox');
|
||||
$lightbox.hide();
|
||||
}
|
||||
|
||||
function selectPostInTable(post) {
|
||||
var $table = $el.find('table');
|
||||
$table.find('tbody input[type=checkbox]').prop('checked', false);
|
||||
$table.find('tbody tr').each(function(i, row) {
|
||||
var $row = jQuery(row);
|
||||
if (post === $row.data('post')) {
|
||||
$row.find('input[type=checkbox]').prop('checked', true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
postTableCheckboxesChanged();
|
||||
}
|
||||
|
||||
function selectPrevPostInTable() {
|
||||
var prevPost = $el.find('tbody tr.selected:eq(0)').prev().data('post');
|
||||
if (prevPost) {
|
||||
selectPostInTable(prevPost);
|
||||
}
|
||||
}
|
||||
|
||||
function selectNextPostInTable() {
|
||||
var nextPost = $el.find('tbody tr.selected:eq(0)').next().data('post');
|
||||
if (nextPost) {
|
||||
selectPostInTable(nextPost);
|
||||
}
|
||||
}
|
||||
|
||||
function showOrHidePostsTable() {
|
||||
if (getAllPosts().length === 0) {
|
||||
util.disableExitConfirmation();
|
||||
$el.find('#post-upload-step2').fadeOut();
|
||||
} else {
|
||||
util.enableExitConfirmation();
|
||||
$el.find('#post-upload-step2').fadeIn();
|
||||
}
|
||||
}
|
||||
|
||||
function removeButtonClicked(e) {
|
||||
e.preventDefault();
|
||||
removePosts(getSelectedPosts());
|
||||
}
|
||||
|
||||
function moveUpButtonClicked(e) {
|
||||
e.preventDefault();
|
||||
movePostsUp(getSelectedPosts());
|
||||
}
|
||||
|
||||
function moveDownButtonClicked(e) {
|
||||
e.preventDefault();
|
||||
movePostsDown(getSelectedPosts());
|
||||
}
|
||||
|
||||
function removePosts(posts) {
|
||||
_.each(posts, function(post) {
|
||||
post.$tableRow.remove();
|
||||
});
|
||||
setAllPostsFromTable();
|
||||
showOrHidePostsTable();
|
||||
postTableCheckboxesChanged();
|
||||
}
|
||||
|
||||
function movePostsUp(posts) {
|
||||
_.each(posts, function(post) {
|
||||
var $row = post.$tableRow;
|
||||
$row.insertBefore($row.prev('tr:not(.selected)'));
|
||||
});
|
||||
setAllPostsFromTable();
|
||||
}
|
||||
|
||||
function movePostsDown(posts) {
|
||||
_.each(posts, function(post) {
|
||||
var $row = post.$tableRow;
|
||||
$row.insertAfter($row.next('tr:not(.selected)'));
|
||||
});
|
||||
setAllPostsFromTable();
|
||||
}
|
||||
|
||||
function uploadNextPost() {
|
||||
messagePresenter.hideMessages($messages);
|
||||
|
||||
var posts = getAllPosts();
|
||||
if (posts.length === 0) {
|
||||
util.disableExitConfirmation();
|
||||
router.navigate('#/posts');
|
||||
return;
|
||||
}
|
||||
|
||||
var post = posts[0];
|
||||
var $row = post.$tableRow;
|
||||
|
||||
var formData = {};
|
||||
if (post.url) {
|
||||
formData.url = post.url;
|
||||
} else {
|
||||
formData.file = post.content;
|
||||
}
|
||||
formData.source = post.source;
|
||||
formData.safety = post.safety;
|
||||
formData.anonymous = post.anonymous;
|
||||
formData.tags = post.tags.join(', ');
|
||||
|
||||
if (post.tags.length === 0) {
|
||||
messagePresenter.showError($messages, 'No tags set.');
|
||||
interactionEnabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
promise.wait(api.post('/posts', formData)).then(function(response) {
|
||||
$row.slideUp(function(response) {
|
||||
$row.remove();
|
||||
uploadNextPost();
|
||||
});
|
||||
}).fail(function(response) {
|
||||
messagePresenter.showError($messages, response.json && response.json.error || response);
|
||||
interactionEnabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
function submitButtonClicked(e) {
|
||||
e.preventDefault();
|
||||
if (!interactionEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$el.find('tbody input[type=checkbox]').prop('checked', false);
|
||||
postTableCheckboxesChanged();
|
||||
|
||||
messagePresenter.showInfo($messages, 'Uploading in progress…');
|
||||
interactionEnabled = false;
|
||||
uploadNextPost();
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -24,4 +552,4 @@ App.Presenters.PostUploadPresenter = function(
|
|||
|
||||
};
|
||||
|
||||
App.DI.register('postUploadPresenter', ['jQuery', 'topNavigationPresenter'], App.Presenters.PostUploadPresenter);
|
||||
App.DI.register('postUploadPresenter', ['_', 'jQuery', 'mousetrap', 'promise', 'util', 'api', 'router', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.PostUploadPresenter);
|
||||
|
|
|
@ -4,6 +4,16 @@ App.Util = function(_, jQuery, promise) {
|
|||
|
||||
var templateCache = {};
|
||||
|
||||
function enableExitConfirmation() {
|
||||
jQuery(window).bind('beforeunload', function(e) {
|
||||
return 'There are unsaved changes.';
|
||||
});
|
||||
}
|
||||
|
||||
function disableExitConfirmation() {
|
||||
jQuery(window).unbind('beforeunload');
|
||||
}
|
||||
|
||||
function parseComplexRouteArgs(args) {
|
||||
var result = {};
|
||||
args = (args || '').split(/;/);
|
||||
|
@ -136,6 +146,8 @@ App.Util = function(_, jQuery, promise) {
|
|||
parseComplexRouteArgs: parseComplexRouteArgs,
|
||||
compileComplexRouteArgs: compileComplexRouteArgs,
|
||||
formatRelativeTime: formatRelativeTime,
|
||||
enableExitConfirmation: enableExitConfirmation,
|
||||
disableExitConfirmation: disableExitConfirmation,
|
||||
};
|
||||
|
||||
};
|
||||
|
|
134
public_html/templates/post-upload.tpl
Normal file
134
public_html/templates/post-upload.tpl
Normal file
|
@ -0,0 +1,134 @@
|
|||
<div id="post-upload-step1">
|
||||
<input name="post-content" multiple type="file"/>
|
||||
|
||||
<div class="url-handler">
|
||||
<div class="input-wrapper">
|
||||
<input type="text" placeholder="Alternatively, paste an URL here." name="url"/>
|
||||
</div>
|
||||
<button type="submit">Add URL</button>
|
||||
</div>
|
||||
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
|
||||
<div id="post-upload-step2">
|
||||
<hr>
|
||||
|
||||
<div class="hybrid-view">
|
||||
<div class="hybrid-window">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="checkbox">
|
||||
<input type="checkbox" name="select-all"/>
|
||||
</th>
|
||||
<th class="thumbnail"></th>
|
||||
<th class="tags">Tags</th>
|
||||
<th class="safety">Safety</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
</tbody>
|
||||
|
||||
<tfoot>
|
||||
<tr class="template">
|
||||
<td class="checkbox">
|
||||
<input type="checkbox"/>
|
||||
</td>
|
||||
<td class="thumbnail">
|
||||
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="Thumbnail"/>
|
||||
</td>
|
||||
<td class="tags"></td>
|
||||
<td class="safety"><div class="safety-template"></div></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
<ul class="operations"><!--
|
||||
--><li>
|
||||
<button class="remove"><i class="fa fa-remove"></i> Remove</button>
|
||||
</li><!--
|
||||
--><li>
|
||||
<button class="move-up"><i class="fa fa-chevron-up"></i> Move up</button>
|
||||
</li><!--
|
||||
--><li>
|
||||
<button class="move-down"><i class="fa fa-chevron-down"></i> Move down</button>
|
||||
</li><!--
|
||||
--><li class="right">
|
||||
<button class="submit highlight" type="submit"><i class="fa fa-upload"></i> Submit</button>
|
||||
</li><!--
|
||||
--></ul>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="hybrid-window">
|
||||
<div class="messages"></div>
|
||||
|
||||
<div class="form-slider">
|
||||
<div class="thumbnail">
|
||||
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="Thumbnail"/>
|
||||
</div>
|
||||
|
||||
<form class="form-wrapper">
|
||||
<div class="form-row file-name">
|
||||
<label class="form-label">File:</label>
|
||||
<div class="form-input">
|
||||
<strong>filename.jpg</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label">Safety:</label>
|
||||
<div class="form-input">
|
||||
<label for="post-safety-safe">
|
||||
<input type="radio" id="post-safety-safe" name="safety" value="safe"/>
|
||||
Safe
|
||||
</label>
|
||||
|
||||
<label for="post-safety-sketchy">
|
||||
<input type="radio" id="post-safety-sketchy" name="safety" value="sketchy"/>
|
||||
Sketchy
|
||||
</label>
|
||||
|
||||
<label for="post-safety-unsafe">
|
||||
<input type="radio" id="post-safety-unsafe" name="safety" value="unsafe"/>
|
||||
Unsafe
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="post-tags">Tags:</label>
|
||||
<div class="form-input">
|
||||
<input type="text" name="tags" id="post-tags" placeholder="Enter some tags…" value=""/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="post-source">Source:</label>
|
||||
<div class="form-input">
|
||||
<input maxlength="200" type="text" name="source" id="post-source" placeholder="Where did you get this? (optional)" value=""/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="post-anonymous">Anonymity:</label>
|
||||
<div class="form-input">
|
||||
<label for="post-anonymous">
|
||||
<input type="checkbox" id="post-anonymous" name="anonymous"/>
|
||||
Don't show my name in this post
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="lightbox">
|
||||
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="Preview">
|
||||
</div>
|
||||
|
||||
</div>
|
Loading…
Reference in a new issue