16 Commits
1.0.1 ... 1.0.2

Author SHA1 Message Date
43334b33e1 Fixed suggestions not hiding in the upload 2014-12-14 20:49:42 +01:00
839e97b1e2 Fixed history for posts uploaded anonymously
Until now posts uploaded anonymously were not so anonymous - the author
was clearly visible in post history (and tag history if the upload has
led to creation of any new tags). Both of these are fixed now.
2014-12-14 09:30:22 +01:00
8a33b9581d Tweaked font size for <small/> tag 2014-12-07 13:43:15 +01:00
7067d8e13d Added support for [small]text[/small] 2014-12-07 13:43:04 +01:00
5769034223 Fixed tag input behavior for initial text 2014-11-30 20:30:06 +01:00
c0a5c800e0 Fixed autocomplete staying even after hiding input 2014-11-30 13:05:31 +01:00
b70231bd7e Changed used tags in autocomplete to be grayed-out 2014-11-30 12:49:48 +01:00
bc757dd883 Fixed reused implied tags marked as duplicates 2014-11-30 12:49:48 +01:00
924592675c Fixed tag suggestions for implied tags 2014-11-30 12:40:22 +01:00
a3aea27a13 Fixed tag edits not triggering tag list updates 2014-11-30 12:40:22 +01:00
3c54671aeb Widened post edit form 2014-11-30 11:58:39 +01:00
d8df51f0c0 Moved suggestions before siblings 2014-11-30 11:55:24 +01:00
b693a5f4b3 Added tag siblings suggestion synchronization 2014-11-30 11:54:38 +01:00
303f91e15c Refactored tag suggestions 2014-11-30 11:45:49 +01:00
c350c47195 Added showing tag suggestions on click 2014-11-30 11:31:40 +01:00
24ce67b4ff Added auto completion to tag list presenter 2014-11-30 00:22:50 +01:00
10 changed files with 172 additions and 97 deletions

View File

@ -24,6 +24,10 @@ h3 {
font-size: 20px; font-size: 20px;
} }
small {
font-size: 13px;
}
#middle { #middle {
padding: 0 2em; padding: 0 2em;
position: relative; position: relative;

View File

@ -18,10 +18,12 @@
#home .post .left { #home .post .left {
display: inline-block; display: inline-block;
float: left; float: left;
margin-right: 0.5em;
} }
#home .post .right { #home .post .right {
display: inline-block; display: inline-block;
float: right; float: right;
margin-left: 0.5em;
} }
#home .post-footer, #home .post-footer,
@ -35,5 +37,7 @@
#home .version { #home .version {
opacity: .4; opacity: .4;
font-size: 12px; }
#home .subheader, #home .post-footer {
font-size: 85%;
} }

View File

@ -157,12 +157,17 @@
#post-view #post-edit-target { #post-view #post-edit-target {
padding: 1em; padding: 1em;
width: 50%;
min-width: 30em;
position: absolute; position: absolute;
background: rgba(255, 255, 255, 0.8); background: rgba(255, 255, 255, 0.8);
box-shadow: 0 0 1em 0.5em rgba(255, 255, 255, 0.8); box-shadow: 0 0 1em 0.5em rgba(255, 255, 255, 0.8);
z-index: 2; z-index: 2;
display: none; display: none;
} }
#post-edit-target .form-wrapper {
min-width: 100%;
}
#post-view>* { #post-view>* {
z-index: -1; z-index: -1;
} }
@ -224,6 +229,7 @@
position: absolute; position: absolute;
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.3);
border: 1px solid rgba(0, 0, 0, 0.3); border: 1px solid rgba(0, 0, 0, 0.3);
font-size: 12pt;
} }
.post-note .text-wrapper { .post-note .text-wrapper {
position: absolute; position: absolute;

View File

@ -17,12 +17,14 @@ App.Controls.AutoCompleteInput = function($input) {
maxResults: 15, maxResults: 15,
minLengthToArbitrarySearch: 3, minLengthToArbitrarySearch: 3,
onApply: null, onApply: null,
onRender: null,
additionalFilter: null, additionalFilter: null,
}; };
var showTimeout = null; var showTimeout = null;
var cachedSource = null; var cachedSource = null;
var results = []; var results = [];
var activeResult = -1; var activeResult = -1;
var monitorInputHidingInterval = null;
if ($input.length === 0) { if ($input.length === 0) {
throw new Error('Input element was not found'); throw new Error('Input element was not found');
@ -133,6 +135,7 @@ App.Controls.AutoCompleteInput = function($input) {
function hide() { function hide() {
$div.hide(); $div.hide();
window.clearInterval(monitorInputHidingInterval);
} }
function selectPrevious() { function selectPrevious() {
@ -222,12 +225,16 @@ App.Controls.AutoCompleteInput = function($input) {
}); });
$list.append($listItem); $list.append($listItem);
}); });
if (options.onRender) {
options.onRender($list);
}
refreshActiveResult(); refreshActiveResult();
$div.css({ $div.css({
left: ($input.offset().left) + 'px', left: ($input.offset().left) + 'px',
top: ($input.offset().top + $input.outerHeight() - 2) + 'px', top: ($input.offset().top + $input.outerHeight() - 2) + 'px',
}); });
$div.show(); $div.show();
monitorInputHiding();
} }
function refreshActiveResult() { function refreshActiveResult() {
@ -237,5 +244,13 @@ App.Controls.AutoCompleteInput = function($input) {
} }
} }
function monitorInputHiding() {
monitorInputHidingInterval = window.setInterval(function() {
if (!$input.is(':visible')) {
hide();
}
}, 100);
}
return options; return options;
}; };

View File

@ -14,6 +14,14 @@ App.Controls.TagInput = function($underlyingInput) {
var tagConfirmKeys = [KEY_RETURN, KEY_SPACE]; var tagConfirmKeys = [KEY_RETURN, KEY_SPACE];
var inputConfirmKeys = [KEY_RETURN]; var inputConfirmKeys = [KEY_RETURN];
var SOURCE_INITIAL_TEXT = 1;
var SOURCE_AUTOCOMPLETION = 2;
var SOURCE_PASTE = 3;
var SOURCE_IMPLICATIONS = 4;
var SOURCE_INPUT_BLUR = 5;
var SOURCE_INPUT_ENTER = 6;
var SOURCE_SUGGESTIONS = 7;
var tags = []; var tags = [];
var options = { var options = {
beforeTagAdded: null, beforeTagAdded: null,
@ -56,10 +64,10 @@ App.Controls.TagInput = function($underlyingInput) {
$input.focus(); $input.focus();
}); });
$input.attr('placeholder', $underlyingInput.attr('placeholder')); $input.attr('placeholder', $underlyingInput.attr('placeholder'));
$suggestions.insertAfter($wrapper);
$siblings.insertAfter($wrapper); $siblings.insertAfter($wrapper);
$suggestions.insertAfter($wrapper);
processText($underlyingInput.val(), addTagDirectly); processText($underlyingInput.val(), SOURCE_INITIAL_TEXT);
$underlyingInput.val(''); $underlyingInput.val('');
} }
@ -67,7 +75,7 @@ App.Controls.TagInput = function($underlyingInput) {
function initAutoComplete() { function initAutoComplete() {
var autoComplete = new App.Controls.AutoCompleteInput($input); var autoComplete = new App.Controls.AutoCompleteInput($input);
autoComplete.onApply = function(text) { autoComplete.onApply = function(text) {
processText(text, addTag); processText(text, SOURCE_AUTOCOMPLETION);
$input.val(''); $input.val('');
}; };
autoComplete.additionalFilter = function(results) { autoComplete.additionalFilter = function(results) {
@ -75,6 +83,14 @@ App.Controls.TagInput = function($underlyingInput) {
return !_.contains(getTags(), resultItem[0]); return !_.contains(getTags(), resultItem[0]);
}); });
}; };
autoComplete.onRender = function($list) {
$list.find('li').each(function() {
var $li = jQuery(this);
if (isTaggedWith($li.attr('data-key'))) {
$li.css('opacity', '0.5');
}
});
};
} }
$input.bind('focus', function(e) { $input.bind('focus', function(e) {
@ -83,7 +99,7 @@ App.Controls.TagInput = function($underlyingInput) {
$input.bind('blur', function(e) { $input.bind('blur', function(e) {
$wrapper.removeClass('focused'); $wrapper.removeClass('focused');
var tagName = $input.val(); var tagName = $input.val();
addTag(tagName); addTag(tagName, SOURCE_INPUT_BLUR);
$input.val(''); $input.val('');
}); });
@ -101,7 +117,7 @@ App.Controls.TagInput = function($underlyingInput) {
return; return;
} }
processTextWithoutLast(pastedText, addTag); processTextWithoutLast(pastedText, SOURCE_PASTE);
}); });
$input.bind('keydown', function(e) { $input.bind('keydown', function(e) {
@ -114,7 +130,7 @@ App.Controls.TagInput = function($underlyingInput) {
var tagName = $input.val(); var tagName = $input.val();
e.preventDefault(); e.preventDefault();
$input.val(''); $input.val('');
addTag(tagName); addTag(tagName, SOURCE_INPUT_ENTER);
} else if (e.which === KEY_BACKSPACE && jQuery(this).val().length === 0) { } else if (e.which === KEY_BACKSPACE && jQuery(this).val().length === 0) {
e.preventDefault(); e.preventDefault();
removeLastTag(); removeLastTag();
@ -127,19 +143,19 @@ App.Controls.TagInput = function($underlyingInput) {
}); });
} }
function processText(text, callback) { function processText(text, source) {
var tagNamesToAdd = explodeText(text); var tagNamesToAdd = explodeText(text);
_.map(tagNamesToAdd, function(tagName) { callback(tagName); }); _.map(tagNamesToAdd, function(tagName) { addTag(tagName, source); });
} }
function processTextWithoutLast(text, callback) { function processTextWithoutLast(text, source) {
var tagNamesToAdd = explodeText(text); var tagNamesToAdd = explodeText(text);
var lastTagName = tagNamesToAdd.pop(); var lastTagName = tagNamesToAdd.pop();
_.map(tagNamesToAdd, function(tagName) { callback(tagName); }); _.map(tagNamesToAdd, function(tagName) { addTag(tagName, source); });
$input.val(lastTagName); $input.val(lastTagName);
} }
function addTag(tagName) { function addTag(tagName, source) {
tagName = tagName.trim(); tagName = tagName.trim();
if (tagName.length === 0) { if (tagName.length === 0) {
return; return;
@ -157,39 +173,53 @@ App.Controls.TagInput = function($underlyingInput) {
if (isTaggedWith(tagName)) { if (isTaggedWith(tagName)) {
flashTagRed(tagName); flashTagRed(tagName);
} else { } else {
beforeTagAdded(tagName); beforeTagAdded(tagName, source);
var exportedTag = getExportedTag(tagName); var exportedTag = getExportedTag(tagName);
if (!exportedTag || !exportedTag.banned) { if (!exportedTag || !exportedTag.banned) {
addTagDirectly(tagName); tags.push(tagName);
var $elem = createListElement(tagName);
$tagList.append($elem);
} }
afterTagAdded(tagName); afterTagAdded(tagName, source);
} }
} }
function addTagDirectly(tagName) { function beforeTagRemoved(tagName) {
tags.push(tagName); if (typeof(options.beforeTagRemoved) === 'function') {
var $elem = createListElement(tagName); options.beforeTagRemoved(tagName);
$tagList.append($elem); }
} }
function beforeTagAdded(tagName) { function afterTagRemoved(tagName) {
refreshShownSiblings();
}
function beforeTagAdded(tagName, source) {
if (typeof(options.beforeTagAdded) === 'function') { if (typeof(options.beforeTagAdded) === 'function') {
options.beforeTagAdded(tagName); options.beforeTagAdded(tagName);
} }
} }
function afterTagAdded(tagName) { function afterTagAdded(tagName, source) {
var tag = getExportedTag(tagName); if (source === SOURCE_IMPLICATIONS) {
if (tag) { flashTagYellow(tagName);
_.each(tag.implications, function(impliedTagName) { } else if (source !== SOURCE_INITIAL_TEXT) {
addTag(impliedTagName); var tag = getExportedTag(tagName);
flashTagYellow(impliedTagName); if (tag) {
}); _.each(tag.implications, function(impliedTagName) {
showOrHideSuggestions(tag.suggestions); if (!isTaggedWith(impliedTagName)) {
} else { addTag(impliedTagName, SOURCE_IMPLICATIONS);
flashTagGreen(tagName); }
});
if (source !== SOURCE_IMPLICATIONS && source !== SOURCE_SUGGESTIONS) {
showOrHideSuggestions(tagName);
refreshShownSiblings();
}
} else {
flashTagGreen(tagName);
}
} }
} }
@ -205,10 +235,9 @@ App.Controls.TagInput = function($underlyingInput) {
var oldTagNames = getTags(); var oldTagNames = getTags();
var newTagNames = _.without(oldTagNames, tagName); var newTagNames = _.without(oldTagNames, tagName);
if (newTagNames.length !== oldTagNames.length) { if (newTagNames.length !== oldTagNames.length) {
if (typeof(options.beforeTagRemoved) === 'function') { beforeTagRemoved(tagName);
options.beforeTagRemoved(tagName);
}
setTags(newTagNames); setTags(newTagNames);
afterTagRemoved(tagName);
} }
} }
@ -262,7 +291,8 @@ App.Controls.TagInput = function($underlyingInput) {
$tagLink.text(tagName); $tagLink.text(tagName);
$tagLink.click(function(e) { $tagLink.click(function(e) {
e.preventDefault(); e.preventDefault();
showOrHideTagSiblings(tagName); showOrHideSiblings(tagName);
showOrHideSuggestions(tagName);
}); });
$elem.append($tagLink); $elem.append($tagLink);
@ -276,19 +306,13 @@ App.Controls.TagInput = function($underlyingInput) {
return $elem; return $elem;
} }
function showOrHideSuggestions(suggestedTagNames) { function showOrHideSuggestions(tagName) {
if (_.size(suggestedTagNames) === 0) { var tag = getExportedTag(tagName);
return; var suggestions = tag ? tag.suggestions : [];
} updateSuggestions($suggestions, suggestions);
var suggestions = filterSuggestions(suggestedTagNames);
if (suggestions.length > 0) {
attachTagsToSuggestionList($suggestions.find('ul'), suggestions);
$suggestions.slideDown('fast');
}
} }
function showOrHideTagSiblings(tagName) { function showOrHideSiblings(tagName) {
if ($siblings.data('lastTag') === tagName && $siblings.is(':visible')) { if ($siblings.data('lastTag') === tagName && $siblings.is(':visible')) {
$siblings.slideUp('fast'); $siblings.slideUp('fast');
$siblings.data('lastTag', null); $siblings.data('lastTag', null);
@ -298,48 +322,58 @@ App.Controls.TagInput = function($underlyingInput) {
promise.wait(getSiblings(tagName), promise.make(function(resolve, reject) { promise.wait(getSiblings(tagName), promise.make(function(resolve, reject) {
$siblings.slideUp('fast', resolve); $siblings.slideUp('fast', resolve);
})).then(function(siblings) { })).then(function(siblings) {
siblings = _.pluck(siblings, 'name');
$siblings.data('lastTag', tagName); $siblings.data('lastTag', tagName);
$siblings.data('siblings', siblings);
if (!_.size(siblings)) { updateSuggestions($siblings, siblings);
return;
}
var suggestions = filterSuggestions(_.pluck(siblings, 'name'));
if (suggestions.length > 0) {
attachTagsToSuggestionList($siblings.find('ul'), suggestions);
$siblings.slideDown('fast');
}
}).fail(function() { }).fail(function() {
}); });
} }
function filterSuggestions(sourceTagNames) { function refreshShownSiblings() {
var tagNames = _.filter(sourceTagNames.slice(), function(tagName) { updateSuggestions($siblings, $siblings.data('siblings'));
return !isTaggedWith(tagName);
});
tagNames = tagNames.slice(0, 20);
return tagNames;
} }
function attachTagsToSuggestionList($list, tagNames) { function updateSuggestions($target, suggestedTagNames) {
$list.empty(); function filterSuggestions(sourceTagNames) {
_.each(tagNames, function(tagName) { if (!sourceTagNames) {
var $li = jQuery('<li>'); return [];
var $a = jQuery('<a href="#/posts/query=' + tagName + '">'); }
$a.text(tagName); var tagNames = _.filter(sourceTagNames.slice(), function(tagName) {
$a.click(function(e) { return !isTaggedWith(tagName);
e.preventDefault();
addTag(tagName);
$li.fadeOut('fast', function() {
$li.remove();
if ($list.children().length === 0) {
$list.parent('div').slideUp('fast');
}
});
}); });
$li.append($a); tagNames = tagNames.slice(0, 20);
$list.append($li); return tagNames;
}); }
function attachTagsToSuggestionList($list, tagNames) {
$list.empty();
_.each(tagNames, function(tagName) {
var $li = jQuery('<li>');
var $a = jQuery('<a href="#/posts/query=' + tagName + '">');
$a.text(tagName);
$a.click(function(e) {
e.preventDefault();
addTag(tagName, SOURCE_SUGGESTIONS);
$li.fadeOut('fast', function() {
$li.remove();
if ($list.children().length === 0) {
$list.parent('div').slideUp('fast');
}
});
});
$li.append($a);
$list.append($li);
});
}
var suggestions = filterSuggestions(suggestedTagNames);
if (suggestions.length > 0) {
attachTagsToSuggestionList($target.find('ul'), suggestions);
$target.slideDown('fast');
} else {
$target.slideUp('fast');
}
} }
function getSiblings(tagName) { function getSiblings(tagName) {
@ -364,6 +398,7 @@ App.Controls.TagInput = function($underlyingInput) {
function hideSuggestions() { function hideSuggestions() {
$siblings.hide(); $siblings.hide();
$suggestions.hide(); $suggestions.hide();
$siblings.data('siblings', []);
} }
_.extend(options, { _.extend(options, {

View File

@ -10,8 +10,6 @@ App.Presenters.TagListPresenter = function(
pagerPresenter, pagerPresenter,
topNavigationPresenter) { topNavigationPresenter) {
var KEY_RETURN = 13;
var $el = jQuery('#content'); var $el = jQuery('#content');
var $searchInput; var $searchInput;
var templates = {}; var templates = {};
@ -78,8 +76,8 @@ App.Presenters.TagListPresenter = function(
function render() { function render() {
$el.html(templates.list()); $el.html(templates.list());
$searchInput = $el.find('input[name=query]'); $searchInput = $el.find('input[name=query]');
$searchInput.keydown(searchInputKeyPressed);
$el.find('form').submit(searchFormSubmitted); $el.find('form').submit(searchFormSubmitted);
App.Controls.AutoCompleteInput($searchInput);
softRender(); softRender();
} }
@ -88,13 +86,6 @@ App.Presenters.TagListPresenter = function(
} }
function searchInputKeyPressed(e) {
if (e.which !== KEY_RETURN) {
return;
}
updateSearch();
}
function searchFormSubmitted(e) { function searchFormSubmitted(e) {
e.preventDefault(); e.preventDefault();
updateSearch(); updateSearch();

View File

@ -128,6 +128,7 @@ App.Presenters.TagPresenter = function(
promise.wait(api.put('/tags/' + tag.name, formData)) promise.wait(api.put('/tags/' + tag.name, formData))
.then(function(response) { .then(function(response) {
router.navigateInplace('#/tag/' + response.json.name); router.navigateInplace('#/tag/' + response.json.name);
tagList.refreshTags();
}).fail(function(response) { }).fail(function(response) {
window.alert(response.json && response.json.error || 'An error occured.'); window.alert(response.json && response.json.error || 'An error occured.');
}); });
@ -140,6 +141,7 @@ App.Presenters.TagPresenter = function(
promise.wait(api.delete('/tags/' + tag.name)) promise.wait(api.delete('/tags/' + tag.name))
.then(function(response) { .then(function(response) {
router.navigate('#/tags'); router.navigate('#/tags');
tagList.refreshTags();
}).fail(function(response) { }).fail(function(response) {
window.alert(response.json && response.json.error || 'An error occured.'); window.alert(response.json && response.json.error || 'An error occured.');
}); });
@ -151,6 +153,7 @@ App.Presenters.TagPresenter = function(
promise.wait(api.put('/tags/' + tag.name + '/merge', {targetTag: targetTag})) promise.wait(api.put('/tags/' + tag.name + '/merge', {targetTag: targetTag}))
.then(function(response) { .then(function(response) {
router.navigate('#/tags'); router.navigate('#/tags');
tagList.refreshTags();
}).fail(function(response) { }).fail(function(response) {
window.alert(response.json && response.json.error || 'An error occured.'); window.alert(response.json && response.json.error || 'An error occured.');
}); });
@ -183,4 +186,16 @@ App.Presenters.TagPresenter = function(
}; };
App.DI.register('tagPresenter', ['_', 'jQuery', 'util', 'promise', 'auth', 'api', 'tagList', 'router', 'keyboard', 'topNavigationPresenter', 'messagePresenter'], App.Presenters.TagPresenter); App.DI.register('tagPresenter', [
'_',
'jQuery',
'util',
'promise',
'auth',
'api',
'tagList',
'router',
'keyboard',
'topNavigationPresenter',
'messagePresenter'],
App.Presenters.TagPresenter);

View File

@ -210,6 +210,8 @@ App.Util.Misc = function(_, jQuery, marked, promise) {
text = text.replace(/\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/ig, '<a href="#/posts/query=$1"><code>$1</code></a>'); text = text.replace(/\[search\]((?:[^\[]|\[(?!\/?search\]))+)\[\/search\]/ig, '<a href="#/posts/query=$1"><code>$1</code></a>');
//spoilers //spoilers
text = text.replace(/\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/ig, '<span class="spoiler">$1</span>'); text = text.replace(/\[spoiler\]((?:[^\[]|\[(?!\/?spoiler\]))+)\[\/spoiler\]/ig, '<span class="spoiler">$1</span>');
//[small]
text = text.replace(/\[small\]((?:[^\[]|\[(?!\/?small\]))+)\[\/small\]/ig, '<small>$1</small>');
//strike-through //strike-through
text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, '$1<del>$3</del>'); text = text.replace(/(^|[^\\])(~~|~)([^~]+)\2/g, '$1<del>$3</del>');
text = text.replace(/\\~/g, '~'); text = text.replace(/\\~/g, '~');

View File

@ -1,7 +1,7 @@
<div id="home"> <div id="home">
<h1><%= title %></h1> <h1><%= title %></h1>
<p> <p class="subheader">
<small>Serving <%= globals.postCount || 0 %> posts (<%= formatFileSize(globals.postSize || 0) %>)</small> Serving <%= globals.postCount || 0 %> posts (<%= formatFileSize(globals.postSize || 0) %>)
</p> </p>
<% if (post && post.id) { %> <% if (post && post.id) { %>
@ -11,7 +11,7 @@
<div class="post-footer"> <div class="post-footer">
<small class="left"> <span class="left">
<% var showLink = canViewPosts %> <% var showLink = canViewPosts %>
<% if (showLink) { %> <% if (showLink) { %>
@ -26,9 +26,9 @@
uploaded uploaded
<%= formatRelativeTime(post.uploadTime) %> <%= formatRelativeTime(post.uploadTime) %>
</small> </span>
<small class="right"> <span class="right">
featured featured
<%= formatRelativeTime(post.lastFeatureTime) %> <%= formatRelativeTime(post.lastFeatureTime) %>
by by
@ -48,7 +48,7 @@
<% if (showLink) { %> <% if (showLink) { %>
</a> </a>
<% } %> <% } %>
</small> </span>
</div> </div>
</div> </div>

View File

@ -109,10 +109,13 @@ class PostService
{ {
$formData->validate($this->validator); $formData->validate($this->validator);
if ($formData->anonymous)
$this->authService->loginAnonymous();
$post = new Post(); $post = new Post();
$post->setUploadTime($this->timeService->getCurrentTime()); $post->setUploadTime($this->timeService->getCurrentTime());
$post->setLastEditTime($this->timeService->getCurrentTime()); $post->setLastEditTime($this->timeService->getCurrentTime());
$post->setUser($formData->anonymous ? null : $this->authService->getLoggedInUser()); $post->setUser($this->authService->getLoggedInUser());
$post->setOriginalFileName($formData->contentFileName); $post->setOriginalFileName($formData->contentFileName);
$post->setName($this->getUniqueRandomPostName()); $post->setName($this->getUniqueRandomPostName());