closes #1556
added tag input box to composer when creating a topic added new routes for viewing tags 'tags' and 'tags/:tagname' respectively post_bar.tpl shows the tags of the topic can edit the main post to remove or add new tags added a new menu item to header to go to the tags pagev1.18.x
parent
1d7c293197
commit
df73ceaeb7
@ -0,0 +1,5 @@
|
||||
{
|
||||
"no_tag_topics": "There are no topics with this tag.",
|
||||
"tags": "Tags",
|
||||
"enter_tags_here": "Enter tags here..."
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
'use strict';
|
||||
|
||||
/* globals define, app, socket */
|
||||
|
||||
define(['forum/recent', 'forum/infinitescroll'], function(recent, infinitescroll) {
|
||||
var Tag = {};
|
||||
|
||||
Tag.init = function() {
|
||||
app.enterRoom('tags');
|
||||
|
||||
if ($('body').height() <= $(window).height() && $('#topics-container').children().length >= 20) {
|
||||
$('#load-more-btn').show();
|
||||
}
|
||||
|
||||
$('#load-more-btn').on('click', function() {
|
||||
loadMoreTopics();
|
||||
});
|
||||
|
||||
infinitescroll.init(loadMoreTopics);
|
||||
|
||||
function loadMoreTopics(direction) {
|
||||
if(direction < 0 || !$('#topics-container').length) {
|
||||
return;
|
||||
}
|
||||
|
||||
infinitescroll.loadMore('topics.loadMoreFromSet', {
|
||||
set: 'tag:' + ajaxify.variables.get('tag') + ':topics',
|
||||
after: $('#topics-container').attr('data-nextstart')
|
||||
}, function(data) {
|
||||
if (data.topics && data.topics.length) {
|
||||
recent.onTopicsLoaded('tag', data.topics, false);
|
||||
$('#topics-container').attr('data-nextstart', data.nextStart);
|
||||
} else {
|
||||
$('#load-more-btn').hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return Tag;
|
||||
});
|
@ -0,0 +1,60 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
/*globals define*/
|
||||
|
||||
define(function() {
|
||||
var tags = {};
|
||||
|
||||
tags.init = function(postContainer, postData) {
|
||||
var tagEl = postContainer.find('.tags');
|
||||
if (!tagEl.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
tagEl.tagsinput();
|
||||
addTags(postData.tags, tagEl);
|
||||
|
||||
var input = postContainer.find('.bootstrap-tagsinput input');
|
||||
input.autocomplete({
|
||||
delay: 100,
|
||||
source: function(request, response) {
|
||||
socket.emit('topics.searchTags', request.term, function(err, tags) {
|
||||
if (err) {
|
||||
return app.alertError(err.message)
|
||||
}
|
||||
if (tags) {
|
||||
response(tags);
|
||||
$('.ui-autocomplete a').attr('href', '#');
|
||||
}
|
||||
});
|
||||
},
|
||||
select: function(event, ui) {
|
||||
// when autocomplete is selected from the dropdown simulate a enter key down to turn it into a tag
|
||||
// http://stackoverflow.com/a/3276819/583363
|
||||
var e = jQuery.Event('keydown');
|
||||
e.which = 13;
|
||||
e.keyCode = 13;
|
||||
setTimeout(function() {
|
||||
input.trigger(e);
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
input.attr('tabIndex', tagEl.attr('tabIndex'));
|
||||
};
|
||||
|
||||
function addTags(tags, tagEl) {
|
||||
if (tags && tags.length) {
|
||||
for(var i=0; i<tags.length; ++i) {
|
||||
tagEl.tagsinput('add', tags[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tags.getTags = function(post_uuid) {
|
||||
return $('#cmp-uuid-' + post_uuid + ' .tags').tagsinput('items');
|
||||
};
|
||||
|
||||
return tags;
|
||||
});
|
@ -0,0 +1,45 @@
|
||||
.bootstrap-tagsinput {
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
display: inline-block;
|
||||
padding: 4px 6px;
|
||||
margin-bottom: 10px;
|
||||
color: #555;
|
||||
vertical-align: middle;
|
||||
border-radius: 4px;
|
||||
max-width: 100%;
|
||||
line-height: 22px;
|
||||
}
|
||||
.bootstrap-tagsinput input {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: auto !important;
|
||||
max-width: inherit;
|
||||
}
|
||||
.bootstrap-tagsinput input:focus {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
.bootstrap-tagsinput .tag {
|
||||
margin-right: 2px;
|
||||
color: white;
|
||||
}
|
||||
.bootstrap-tagsinput .tag [data-role="remove"] {
|
||||
margin-left: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.bootstrap-tagsinput .tag [data-role="remove"]:after {
|
||||
content: "x";
|
||||
padding: 0px 2px;
|
||||
}
|
||||
.bootstrap-tagsinput .tag [data-role="remove"]:hover {
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.bootstrap-tagsinput .tag [data-role="remove"]:hover:active {
|
||||
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
|
||||
var tagsController = {},
|
||||
async = require('async'),
|
||||
topics = require('./../topics');
|
||||
|
||||
tagsController.getTag = function(req, res, next) {
|
||||
var tag = req.params.tag;
|
||||
var uid = req.user ? req.user.uid : 0;
|
||||
|
||||
topics.getTagTids(tag, 0, 19, function(err, tids) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
topics.getTopics('tag:' + tag + ':topics', uid, tids, function(err, data) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
data.tag = tag;
|
||||
res.render('tag', data);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
tagsController.getTags = function(req, res, next) {
|
||||
topics.getTagsObjects(function(err, tags) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
async.map(tags, function(tag, next) {
|
||||
topics.getTagTopicCount(tag.name, function(err, count) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
tag.topicCount = count;
|
||||
next(null, tag);
|
||||
});
|
||||
}, function(err, tags) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
tags = tags.sort(function(a, b) {
|
||||
return parseInt(b.topicCount, 10) - parseInt(a.topicCount, 10);
|
||||
});
|
||||
res.render('tags', {tags: tags});
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
module.exports = tagsController;
|
@ -0,0 +1,115 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
db = require('../database');
|
||||
|
||||
module.exports = function(Topics) {
|
||||
|
||||
Topics.createTags = function(tags, tid, timestamp) {
|
||||
if(Array.isArray(tags)) {
|
||||
for (var i=0; i<tags.length; ++i) {
|
||||
tags[i] = tags[i].trim().toLowerCase();
|
||||
db.sortedSetAdd('tag:' + tags[i] + ':topics', timestamp, tid);
|
||||
db.setAdd('topic:' + tid + ':tags', tags[i]);
|
||||
db.setAdd('tags', tags[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Topics.getTagTids = function(tag, start, end, callback) {
|
||||
db.getSortedSetRevRange('tag:' + tag + ':topics', start, end, callback);
|
||||
};
|
||||
|
||||
Topics.getTagTopicCount = function(tag, callback) {
|
||||
db.sortedSetCard('tag:' + tag + ':topics', callback);
|
||||
};
|
||||
|
||||
Topics.getTags = function(callback) {
|
||||
db.getSetMembers('tags', callback);
|
||||
};
|
||||
|
||||
//returns tags as objects cuz templates.js cant do arrays yet >_>
|
||||
Topics.getTagsObjects = function(callback) {
|
||||
Topics.getTags(function(err, tags) {
|
||||
callback(err, mapToObject(tags));
|
||||
});
|
||||
};
|
||||
|
||||
Topics.getTopicTags = function(tid, callback) {
|
||||
db.getSetMembers('topic:' + tid + ':tags', callback);
|
||||
};
|
||||
|
||||
//returns tags as objects cuz templates.js cant do arrays yet >_>
|
||||
Topics.getTopicTagsObjects = function(tid, callback) {
|
||||
Topics.getTopicTags(tid, function(err, tags) {
|
||||
callback(err, mapToObject(tags));
|
||||
});
|
||||
};
|
||||
|
||||
function mapToObject(tags) {
|
||||
if (!tags) {
|
||||
return tags;
|
||||
}
|
||||
return tags.map(function(tag) {
|
||||
return {name: tag};
|
||||
});
|
||||
}
|
||||
|
||||
Topics.updateTags = function(tid, tags) {
|
||||
async.parallel({
|
||||
timestamp: function(next) {
|
||||
Topics.getTopicField(tid, 'timestamp', next);
|
||||
},
|
||||
currentTags: function(next) {
|
||||
Topics.getTopicTags(tid, next);
|
||||
}
|
||||
}, function(err, results) {
|
||||
removeTopicTags(tid, results.currentTags, function(err) {
|
||||
if (!err) {
|
||||
Topics.createTags(tags, tid, results.timestamp);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function removeTopicTags(tid, tags, callback) {
|
||||
async.parallel([
|
||||
function(next) {
|
||||
db.delete('topic:' + tid + ':tags', next);
|
||||
},
|
||||
function(next) {
|
||||
async.each(tags, function(tag, next) {
|
||||
db.sortedSetRemove('tag:' + tag + ':topics', tid, next);
|
||||
}, next);
|
||||
}
|
||||
], callback);
|
||||
}
|
||||
|
||||
Topics.searchTags = function(query, callback) {
|
||||
if (!query || query.length === 0) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
db.getSetMembers('tags', function(err, tags) {
|
||||
if (err) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
query = query.toLowerCase();
|
||||
var matches = [];
|
||||
for(var i=0; i<tags.length; ++i) {
|
||||
if (tags[i].toLowerCase().indexOf(query) === 0) {
|
||||
matches.push(tags[i]);
|
||||
}
|
||||
}
|
||||
|
||||
matches = matches.slice(0, 10).sort(function(a, b) {
|
||||
return a > b;
|
||||
});
|
||||
|
||||
callback(null, matches);
|
||||
});
|
||||
};
|
||||
|
||||
};
|
Loading…
Reference in New Issue