FEATURE: search highlighting within topic
BUGFIX: fixed hiding of the search dialog when navigating within a topic
This commit is contained in:
parent
96fc5addc4
commit
e7991cb803
|
@ -8,6 +8,10 @@
|
|||
**/
|
||||
export default Em.ArrayController.extend(Discourse.Presence, {
|
||||
|
||||
contextChanged: function(){
|
||||
this.setProperties({ term: "", content: [], resultCount: 0, urls: [] });
|
||||
}.observes("searchContext"),
|
||||
|
||||
// If we need to perform another search
|
||||
newSearchNeeded: function() {
|
||||
this.set('noResults', false);
|
||||
|
|
|
@ -8,13 +8,31 @@
|
|||
**/
|
||||
Discourse.TopicController = Discourse.ObjectController.extend(Discourse.SelectedPostsCount, {
|
||||
multiSelect: false,
|
||||
needs: ['header', 'modal', 'composer', 'quote-button'],
|
||||
needs: ['header', 'modal', 'composer', 'quote-button', 'search'],
|
||||
allPostsSelected: false,
|
||||
editingTopic: false,
|
||||
selectedPosts: null,
|
||||
selectedReplies: null,
|
||||
queryParams: ['filter', 'username_filters'],
|
||||
|
||||
contextChanged: function(){
|
||||
this.set('controllers.search.searchContext', this.get('model.searchContext'));
|
||||
}.observes('topic'),
|
||||
|
||||
termChanged: function(){
|
||||
var dropdown = this.get('controllers.header.visibleDropdown');
|
||||
var term = this.get('controllers.search.term');
|
||||
|
||||
if(dropdown === 'search-dropdown' && term){
|
||||
this.set('searchHighlight', term);
|
||||
} else {
|
||||
if(this.get('searchHighlight')){
|
||||
this.set('searchHighlight', null);
|
||||
}
|
||||
}
|
||||
|
||||
}.observes('controllers.search.term', 'controllers.header.visibleDropdown'),
|
||||
|
||||
filter: function(key, value) {
|
||||
if (arguments.length > 1) {
|
||||
this.set('postStream.summary', value === "summary");
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
// forked cause we may want to amend the logic a bit
|
||||
/*
|
||||
* jQuery Highlight plugin
|
||||
*
|
||||
* Based on highlight v3 by Johann Burkard
|
||||
* http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
|
||||
*
|
||||
* Code a little bit refactored and cleaned (in my humble opinion).
|
||||
* Most important changes:
|
||||
* - has an option to highlight only entire words (wordsOnly - false by default),
|
||||
* - has an option to be case sensitive (caseSensitive - false by default)
|
||||
* - highlight element tag and class names can be specified in options
|
||||
*
|
||||
* Usage:
|
||||
* // wrap every occurrance of text 'lorem' in content
|
||||
* // with <span class='highlight'> (default options)
|
||||
* $('#content').highlight('lorem');
|
||||
*
|
||||
* // search for and highlight more terms at once
|
||||
* // so you can save some time on traversing DOM
|
||||
* $('#content').highlight(['lorem', 'ipsum']);
|
||||
* $('#content').highlight('lorem ipsum');
|
||||
*
|
||||
* // search only for entire word 'lorem'
|
||||
* $('#content').highlight('lorem', { wordsOnly: true });
|
||||
*
|
||||
* // don't ignore case during search of term 'lorem'
|
||||
* $('#content').highlight('lorem', { caseSensitive: true });
|
||||
*
|
||||
* // wrap every occurrance of term 'ipsum' in content
|
||||
* // with <em class='important'>
|
||||
* $('#content').highlight('ipsum', { element: 'em', className: 'important' });
|
||||
*
|
||||
* // remove default highlight
|
||||
* $('#content').unhighlight();
|
||||
*
|
||||
* // remove custom highlight
|
||||
* $('#content').unhighlight({ element: 'em', className: 'important' });
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2009 Bartek Szopka
|
||||
*
|
||||
* Licensed under MIT license.
|
||||
*
|
||||
*/
|
||||
|
||||
jQuery.extend({
|
||||
highlight: function (node, re, nodeName, className) {
|
||||
if (node.nodeType === 3) {
|
||||
var match = node.data.match(re);
|
||||
if (match) {
|
||||
var highlight = document.createElement(nodeName || 'span');
|
||||
highlight.className = className || 'highlight';
|
||||
var wordNode = node.splitText(match.index);
|
||||
wordNode.splitText(match[0].length);
|
||||
var wordClone = wordNode.cloneNode(true);
|
||||
highlight.appendChild(wordClone);
|
||||
wordNode.parentNode.replaceChild(highlight, wordNode);
|
||||
return 1; //skip added node in parent
|
||||
}
|
||||
} else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
|
||||
!/(script|style)/i.test(node.tagName) && // ignore script and style nodes
|
||||
!(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
|
||||
for (var i = 0; i < node.childNodes.length; i++) {
|
||||
i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
jQuery.fn.unhighlight = function (options) {
|
||||
var settings = { className: 'highlight', element: 'span' };
|
||||
jQuery.extend(settings, options);
|
||||
|
||||
return this.find(settings.element + "." + settings.className).each(function () {
|
||||
var parent = this.parentNode;
|
||||
parent.replaceChild(this.firstChild, this);
|
||||
parent.normalize();
|
||||
}).end();
|
||||
};
|
||||
|
||||
jQuery.fn.highlight = function (words, options) {
|
||||
var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false };
|
||||
jQuery.extend(settings, options);
|
||||
|
||||
if (words.constructor === String) {
|
||||
words = [words];
|
||||
}
|
||||
words = jQuery.grep(words, function(word){
|
||||
return word !== '';
|
||||
});
|
||||
words = jQuery.map(words, function(word) {
|
||||
return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
||||
});
|
||||
if (words.length === 0) { return this; }
|
||||
|
||||
var flag = settings.caseSensitive ? "" : "i";
|
||||
var pattern = "(" + words.join("|") + ")";
|
||||
if (settings.wordsOnly) {
|
||||
pattern = "\\b" + pattern + "\\b";
|
||||
}
|
||||
var re = new RegExp(pattern, flag);
|
||||
|
||||
return this.each(function () {
|
||||
jQuery.highlight(this, re, settings.element, settings.className);
|
||||
});
|
||||
};
|
||||
|
|
@ -86,8 +86,6 @@ Discourse.URL = Em.Object.createWithMixins({
|
|||
path = path.replace(rootURL, '');
|
||||
}
|
||||
|
||||
// Schedule a DOM cleanup event
|
||||
Em.run.scheduleOnce('afterRender', Discourse.Route, 'cleanDOM');
|
||||
|
||||
// Rewrite /my/* urls
|
||||
if (path.indexOf('/my/') === 0) {
|
||||
|
@ -100,9 +98,12 @@ Discourse.URL = Em.Object.createWithMixins({
|
|||
}
|
||||
}
|
||||
|
||||
if (this.navigatedToPost(oldPath, path)) { return; }
|
||||
// Schedule a DOM cleanup event
|
||||
Em.run.scheduleOnce('afterRender', Discourse.Route, 'cleanDOM');
|
||||
|
||||
// TODO: Extract into rules we can inject into the URL handler
|
||||
if (this.navigatedToHome(oldPath, path)) { return; }
|
||||
if (this.navigatedToPost(oldPath, path)) { return; }
|
||||
|
||||
if (path.match(/^\/?users\/[^\/]+$/)) {
|
||||
path += "/activity";
|
||||
|
|
|
@ -11,15 +11,16 @@ Discourse.HeaderView = Discourse.View.extend({
|
|||
classNames: ['d-header', 'clearfix'],
|
||||
classNameBindings: ['editingTopic'],
|
||||
templateName: 'header',
|
||||
topicBinding: 'Discourse.router.topicController.content',
|
||||
|
||||
showDropdown: function($target) {
|
||||
var elementId = $target.data('dropdown') || $target.data('notifications'),
|
||||
$dropdown = $("#" + elementId),
|
||||
$li = $target.closest('li'),
|
||||
$ul = $target.closest('ul'),
|
||||
$html = $('html');
|
||||
$html = $('html'),
|
||||
self = this;
|
||||
|
||||
self.set('controller.visibleDropdown', elementId);
|
||||
// we need to ensure we are rendered,
|
||||
// this optimises the speed of the initial render
|
||||
var render = $target.data('render');
|
||||
|
@ -27,7 +28,7 @@ Discourse.HeaderView = Discourse.View.extend({
|
|||
if(!this.get(render)){
|
||||
this.set(render, true);
|
||||
Em.run.next(this, function(){
|
||||
this.showDropdown($target);
|
||||
this.showDropdown.apply(self, [$target]);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@ -37,6 +38,7 @@ Discourse.HeaderView = Discourse.View.extend({
|
|||
$dropdown.fadeOut('fast');
|
||||
$li.removeClass('active');
|
||||
$html.data('hide-dropdown', null);
|
||||
self.set('controller.visibleDropdown', null);
|
||||
return $html.off('click.d-dropdown');
|
||||
};
|
||||
|
||||
|
@ -53,7 +55,7 @@ Discourse.HeaderView = Discourse.View.extend({
|
|||
$dropdown.find('input[type=text]').focus().select();
|
||||
|
||||
$html.on('click.d-dropdown', function(e) {
|
||||
return $(e.target).closest('.d-dropdown').length > 0 ? true : hideDropdown();
|
||||
return $(e.target).closest('.d-dropdown').length > 0 ? true : hideDropdown.apply(self);
|
||||
});
|
||||
|
||||
$html.data('hide-dropdown', hideDropdown);
|
||||
|
@ -107,36 +109,37 @@ Discourse.HeaderView = Discourse.View.extend({
|
|||
|
||||
didInsertElement: function() {
|
||||
|
||||
var headerView = this;
|
||||
var self = this;
|
||||
|
||||
this.$('a[data-dropdown]').on('click.dropdown', function(e) {
|
||||
headerView.showDropdown($(e.currentTarget));
|
||||
self.showDropdown.apply(self, [$(e.currentTarget)]);
|
||||
return false;
|
||||
});
|
||||
this.$('a.unread-private-messages, a.unread-notifications, a[data-notifications]').on('click.notifications', function(e) {
|
||||
headerView.showNotifications(e);
|
||||
self.showNotifications(e);
|
||||
return false;
|
||||
});
|
||||
$(window).bind('scroll.discourse-dock', function() {
|
||||
headerView.examineDockHeader();
|
||||
self.examineDockHeader();
|
||||
});
|
||||
$(document).bind('touchmove.discourse-dock', function() {
|
||||
headerView.examineDockHeader();
|
||||
self.examineDockHeader();
|
||||
});
|
||||
this.examineDockHeader();
|
||||
self.examineDockHeader();
|
||||
|
||||
// Delegate ESC to the composer
|
||||
$('body').on('keydown.header', function(e) {
|
||||
// Hide dropdowns
|
||||
if (e.which === 27) {
|
||||
headerView.$('li').removeClass('active');
|
||||
headerView.$('.d-dropdown').fadeOut('fast');
|
||||
self.$('li').removeClass('active');
|
||||
self.$('.d-dropdown').fadeOut('fast');
|
||||
}
|
||||
if (headerView.get('editingTopic')) {
|
||||
if (self.get('editingTopic')) {
|
||||
if (e.which === 13) {
|
||||
headerView.finishedEdit();
|
||||
self.finishedEdit();
|
||||
}
|
||||
if (e.which === 27) {
|
||||
return headerView.cancelEdit();
|
||||
return self.cancelEdit();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -257,5 +257,26 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
|||
|
||||
// Find all the quotes
|
||||
Em.run.scheduleOnce('afterRender', this, 'insertQuoteControls');
|
||||
|
||||
this.applySearchHighlight();
|
||||
},
|
||||
|
||||
applySearchHighlight: function(){
|
||||
var highlight = this.get('controller.searchHighlight');
|
||||
var cooked = this.$('.cooked');
|
||||
|
||||
if(!cooked){ return; }
|
||||
|
||||
if(highlight && highlight.length > 2){
|
||||
if(this._highlighted){
|
||||
cooked.unhighlight();
|
||||
}
|
||||
cooked.highlight(highlight);
|
||||
this._highlighted = true;
|
||||
|
||||
} else if(this._highlighted){
|
||||
cooked.unhighlight();
|
||||
this._highlighted = false;
|
||||
}
|
||||
}.observes('controller.searchHighlight', 'cooked')
|
||||
});
|
||||
|
|
|
@ -19,3 +19,9 @@
|
|||
color: scale-color($primary, $lightness: 50%);
|
||||
}
|
||||
}
|
||||
|
||||
.cooked .highlight{
|
||||
background-color: scale-color($highlight, $lightness: 40%);
|
||||
padding: 2px;
|
||||
margin: -2px;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue