diff options
author | Adolfo Fitoria <adolfo.fitoria@gmail.com> | 2012-12-12 20:57:53 -0600 |
---|---|---|
committer | Adolfo Fitoria <adolfo.fitoria@gmail.com> | 2012-12-12 20:57:53 -0600 |
commit | 2d47db07d2c2c26707abd63f9ea4c7fd2037907f (patch) | |
tree | 0a196d0b48c7c0fb62932b88798031c31444ff88 | |
parent | 11a8154f88c7cd0d2dcc589ceaf6b734d442414e (diff) | |
parent | 60398b4fb0abca816c2ea67d61d31616dc4c4eb9 (diff) | |
download | askbot-2d47db07d2c2c26707abd63f9ea4c7fd2037907f.tar.gz askbot-2d47db07d2c2c26707abd63f9ea4c7fd2037907f.tar.bz2 askbot-2d47db07d2c2c26707abd63f9ea4c7fd2037907f.zip |
Merge branch 'master' of github.com:ASKBOT/askbot-devel
50 files changed, 2443 insertions, 715 deletions
diff --git a/askbot/__init__.py b/askbot/__init__.py index c77c33d1..a7f7738a 100644 --- a/askbot/__init__.py +++ b/askbot/__init__.py @@ -7,7 +7,7 @@ basic actions on behalf of the forum application import os import platform -VERSION = (0, 7, 44) +VERSION = (0, 7, 46) #keys are module names used by python imports, #values - the package qualifier to use for pip diff --git a/askbot/doc/source/changelog.rst b/askbot/doc/source/changelog.rst index ca776770..25f3c7a7 100644 --- a/askbot/doc/source/changelog.rst +++ b/askbot/doc/source/changelog.rst @@ -1,13 +1,20 @@ Changes in Askbot ================= -Development version -------------------- +0.7.46 (Dec 12, 2012) +--------------------- +* Bugfix release + +0.7.45 (Dec 12, 2012) +--------------------- * Feedback sender's email is added to the Reply-To header in the feedback form (Evgeny) +* Reimplemented search as drop-down (Evgeny) +* Basic design to work on smartphones (Evgeny) +* Allowed use of alternative form on the user signup page (Evgeny) 0.7.44 (Nov 11, 2012) -------------------- +--------------------- * Support for django 1.4 (Adolfo) * Added option to enable/disable rss feeds (Evgeny) * Added minimum reputation to insert links and hotlinked images (Evgeny) diff --git a/askbot/forms.py b/askbot/forms.py index ba52a891..5168e354 100644 --- a/askbot/forms.py +++ b/askbot/forms.py @@ -1157,11 +1157,7 @@ class RevisionForm(forms.Form): """ Lists revisions of a Question or Answer """ - revision = forms.ChoiceField( - widget=forms.Select( - attrs={'style': 'width:520px'} - ) - ) + revision = forms.ChoiceField(widget=forms.Select()) def __init__(self, post, latest_revision, *args, **kwargs): super(RevisionForm, self).__init__(*args, **kwargs) diff --git a/askbot/media/images/ajax-loader.gif b/askbot/media/images/ajax-loader.gif Binary files differnew file mode 100644 index 00000000..212a8197 --- /dev/null +++ b/askbot/media/images/ajax-loader.gif diff --git a/askbot/media/js/live_search.js b/askbot/media/js/live_search.js index f7d89c2a..a0c7c2de 100644 --- a/askbot/media/js/live_search.js +++ b/askbot/media/js/live_search.js @@ -1,3 +1,214 @@ +var SearchDropMenu = function() { + WrappedElement.call(this); + this._data = undefined; + this._selectedItemIndex = 0; + this._askButtonEnabled = true; +} +inherits(SearchDropMenu, WrappedElement); + +SearchDropMenu.prototype.setData = function(data) { + this._data = data; +}; + +SearchDropMenu.prototype.setAskHandler = function(handler) { + this._askHandler = handler; +}; + +SearchDropMenu.prototype.setAskButtonEnabled = function(isEnabled) { + this._askButtonEnabled = isEnabled; +}; + +/** + * assumes that data is already set + */ +SearchDropMenu.prototype.render = function() { + var list = this._resultsList; + list.empty(); + var me = this; + $.each(this._data, function(idx, item) { + var listItem = me.makeElement('li'); + var link = me.makeElement('a'); + link.attr('href', item['url']); + link.html(item['title']); + listItem.append(link); + list.append(listItem); + }); + if (this._data.length === 0) { + list.addClass('empty'); + this._element.addClass('empty'); + } else { + list.removeClass('empty'); + this._element.removeClass('empty'); + } +}; + +/** + * @param {number} idx position of item starting from 1 for the topmost + * Selects item inentified by position. + * Scrolls the list to make top of the item visible. + */ +SearchDropMenu.prototype.selectItem = function(idx) { + //idx is 1-based index + this._selectedItemIndex = idx; + var list = this._resultsList; + list.find('li').removeClass('selected'); + var item = this.getItem(idx); + if (item && idx > 0) { + item.addClass('selected'); + var itemTopY = item.position().top;//relative to visible area + var curScrollTop = list.scrollTop(); + + /* if item is clipped on top, scroll down */ + if (itemTopY < 0) { + list.scrollTop(curScrollTop + itemTopY); + return; + } + + var listHeight = list.outerHeight(); + /* pixels above the lower border of the list */ + var itemPeepHeight = listHeight - itemTopY; + /* pixels below the lower border */ + var itemSinkHeight = item.outerHeight() - itemPeepHeight; + if (itemSinkHeight > 0) { + list.scrollTop(curScrollTop + itemSinkHeight); + } + } + +}; + +SearchDropMenu.prototype.getItem = function(idx) { + return $(this._resultsList.find('li')[idx - 1]); +}; + +SearchDropMenu.prototype.getItemCount = function() { + return this._resultsList.find('li').length; +}; + +SearchDropMenu.prototype.getSelectedItemIndex = function() { + return this._selectedItemIndex; +}; + +SearchDropMenu.prototype.navigateToItem = function(idx) { + var item = this.getItem(idx); + if (item) { + window.location.href = item.find('a').attr('href'); + } +}; + +SearchDropMenu.prototype.makeKeyHandler = function() { + var me = this; + return function(e) { + var keyCode = getKeyCode(e); + if (keyCode === 27) {//escape + me.hide(); + return false; + } + if (keyCode !== 38 && keyCode !== 40 && keyCode !== 13) { + return; + } + var itemCount = me.getItemCount(); + if (itemCount > 0) { + //count is 0 with no title matches, curItem is 0 when none is selected + var curItem = me.getSelectedItemIndex(); + if (keyCode === 38) {//upArrow + if (curItem > 0) { + curItem = curItem - 1; + } + } else if (keyCode === 40) {//downArrow + if (curItem < itemCount) { + curItem = curItem + 1; + } + } else if (keyCode === 13) {//enter + if (curItem === 0) { + return true; + } else { + me.navigateToItem(curItem); + return false; + } + } + me.selectItem(curItem); + return false + } + }; +}; + +/** todo: change this to state management as >1 thing happens */ +SearchDropMenu.prototype.showWaitIcon = function() { + if (this._askButtonEnabled) { + this._waitIcon.show(); + this._footer.hide(); + this._element.addClass('empty'); + } +}; + +SearchDropMenu.prototype.hideWaitIcon = function() { + if (this._askButtonEnabled) { + this._waitIcon.hide(); + this._footer.show(); + this._element.removeClass('empty'); + } +}; + +SearchDropMenu.prototype.createDom = function() { + this._element = this.makeElement('div'); + this._element.addClass('search-drop-menu'); + this._element.hide(); + + this._resultsList = this.makeElement('ul'); + this._element.append(this._resultsList); + this._element.addClass('empty'); + + var waitIcon = new WaitIcon(); + waitIcon.hide(); + this._element.append(waitIcon.getElement()); + this._waitIcon = waitIcon; + + //add ask button, @todo: make into separate class? + var footer = this.makeElement('div'); + this._element.append(footer); + this._footer = footer; + + if (this._askButtonEnabled) { + footer.addClass('footer'); + var button = this.makeElement('button'); + button.addClass('submit'); + button.html(gettext('Ask Your Question')) + footer.append(button); + var handler = this._askHandler; + setupButtonEventHandlers(button, handler); + } + + $(document).keydown(this.makeKeyHandler()); +}; + +SearchDropMenu.prototype.isOpen = function() { + return this._element.is(':visible'); +}; + +SearchDropMenu.prototype.show = function() { + var searchBar = this._element.prev(); + var searchBarHeight = searchBar.outerHeight(); + var topOffset = searchBar.offset().top + searchBarHeight; + this._element.show();//show so that size calcs work + var footerHeight = this._footer.outerHeight(); + var windowHeight = $(window).height(); + this._resultsList.css( + 'max-height', + windowHeight - topOffset - footerHeight - 40 //what is this number? + ); +}; + +SearchDropMenu.prototype.hide = function() { + this._element.hide(); +}; + +SearchDropMenu.prototype.reset = function() { + this._data = undefined; + this._resultsList.empty(); + this._selectedItemIndex = 0; + this._element.hide(); +}; + var TagWarningBox = function(){ WrappedElement.call(this); this._tags = []; @@ -6,9 +217,8 @@ inherits(TagWarningBox, WrappedElement); TagWarningBox.prototype.createDom = function(){ this._element = this.makeElement('div'); - this._element - .css('display', 'block') - .css('margin', '0 0 13px 2px'); + this._element.css('display', 'block'); + this._element.css('margin', '0 0 13px 2px'); this._element.addClass('non-existing-tags'); this._warning = this.makeElement('p'); this._element.append(this._warning); @@ -51,355 +261,605 @@ TagWarningBox.prototype.showWarning = function(){ this._warning.show(); }; -var liveSearch = function(query_string) { - var query = $('input#keywords'); - var query_val = function () {return $.trim(query.val());}; - var prev_text = query_val(); - var running = false; - var q_list_sel = 'question-list';//id of question listing div - var search_url = askbot['urls']['questions']; - var x_button = $('input[name=reset_query]'); - var tag_warning_box = new TagWarningBox(); +/** + * @constructor + * tool tip to be shown on top of the search input + */ +var InputToolTip = function() { + WrappedElement.call(this); +}; +inherits(InputToolTip, WrappedElement); - //the tag search input is optional in askbot - $('#ab-tag-search').parent().before( - tag_warning_box.getElement() - ); +InputToolTip.prototype.show = function() { + this._element.removeClass('dimmed'); + this._element.show(); +}; - var run_tag_search = function(){ - var search_tags = $('#ab-tag-search').val().split(/\s+/); - if (search_tags.length === 0) { - return; - } - /** @todo: the questions/ might need translation... */ - query_string = '/questions/scope:all/sort:activity-desc/page:1/' - $.each(search_tags, function(idx, tag) { - query_string = QSutils.add_search_tag(query_string, search_tags); - }); - var url = search_url + query_string; - $.ajax({ - url: url, - dataType: 'json', - success: function(data, text_status, xhr){ - render_result(data, text_status, xhr); - $('#ab-tag-search').val(''); - }, - }); - updateHistory(url); - }; +InputToolTip.prototype.hide = function() { + this._element.removeClass('dimmed'); + this._element.hide(); +}; - var activate_tag_search_input = function(){ - //the autocomplete is set up in tag_selector.js - var button = $('#ab-tag-search-add'); - if (button.length === 0){//may be absent - return; - } - var ac = new AutoCompleter({ - url: askbot['urls']['get_tag_list'], - preloadData: true, - minChars: 1, - useCache: true, - matchInside: true, - maxCacheLength: 100, - maxItemsToShow: 20, - onItemSelect: run_tag_search, - delay: 10 - }); - ac.decorate($('#ab-tag-search')); - setupButtonEventHandlers(button, run_tag_search); - //var tag_search_input = $('#ab-tag-search'); - //tag_search_input.keydown( - // makeKeyHandler(13, run_tag_search) - //); - }; +InputToolTip.prototype.dim = function() { + this._element.addClass('dimmed'); +}; - var render_tag_warning = function(tag_list){ - if ( !tag_list ) { - return; - } - tag_warning_box.clear(); - $.each(tag_list, function(idx, tag_name){ - tag_warning_box.addTag(tag_name); - }); - tag_warning_box.showWarning(); - }; +InputToolTip.prototype.setClickHandler = function(handler) { + this._clickHandler = handler; +}; - var refresh_x_button = function(){ - if(query_val().length > 0){ - if (query.hasClass('searchInput')){ - query.attr('class', 'searchInputCancelable'); - x_button.show(); - } - } else { - x_button.hide(); - query.attr('class', 'searchInput'); - } - }; +InputToolTip.prototype.createDom = function() { + var element = this.makeElement('div'); + this._element = element; - var restart_query = function() { - sortMethod = 'activity-desc'; - query.val(''); - refresh_x_button(); - send_query(); - }; + element.html(gettext('search or ask your question')); + element.addClass('input-tool-tip'); - var eval_query = function(){ - cur_query = query_val(); - if (cur_query !== prev_text && running === false){ - if (cur_query.length >= minSearchWordLength){ - send_query(cur_query); - } else if (cur_query.length === 0){ - restart_query(); - } + var handler = this._clickHandler; + var me = this; + element.click(function() { + handler(); + me.dim(); + return false; + }); + $(document).click(function() { + if (element.css('display') === 'block') { + element.removeClass('dimmed'); } - }; + }); +}; + + +/** + * @constructor + * provides full text search functionality + * which re-draws contents of the main page + * in response to the search query + */ +var FullTextSearch = function() { + WrappedElement.call(this); + this._running = false; + this._baseUrl = askbot['urls']['questions']; + this._q_list_sel = 'question-list';//id of question listing div + /** @todo: the questions/ needs translation... */ + this._searchUrl = '/scope:all/sort:activity-desc/page:1/' + this._askButtonEnabled = true; +}; +inherits(FullTextSearch, WrappedElement); + +/** + * @param {{boolean=}} optional, if given then function is setter + * otherwise it is a getter + * isRunning returns `true` when search is running + */ +FullTextSearch.prototype.isRunning = function(val) { + if (val === undefined) { + return this._running; + } else { + this._running = val; + } +}; - var update_query_string = function(query_text){ - if(query_text === undefined) { // handle missing parameter - query_text = query_val(); - } - query_string = QSutils.patch_query_string( - query_string, - 'query:' + encodeURIComponent(query_text), - query_text === '' // remove if empty - ); - return query_text; - }; +FullTextSearch.prototype.setAskButtonEnabled = function(isEnabled) { + this._askButtonEnabled = isEnabled; +} - var send_query = function(query_text){ - running = true; - if(!prev_text && query_text && showSortByRelevance) { - // If there was no query but there is some query now - and we support relevance search - then switch to it */ - query_string = QSutils.patch_query_string(query_string, 'sort:relevance-desc'); - } - prev_text = update_query_string(query_text); - query_string = QSutils.patch_query_string(query_string, 'page:1'); /* if something has changed, then reset the page no. */ - var url = search_url + query_string; - $.ajax({ - url: url, - dataType: 'json', - success: render_result, - complete: function(){ - running = false; - eval_query(); - }, - cache: false - }); - updateHistory(url); - }; +/** + * @param {{string}} url for the page displaying search results + */ +FullTextSearch.prototype.setSearchUrl = function(url) { + this._searchUrl = url; +}; - var updateHistory = function(url) { - var context = { state:1, rand:Math.random() }; - History.pushState( context, "Questions", url ); - setTimeout(function (){ - /* HACK: For some weird reson, sometimes something overrides the above pushState so we re-aplly it - This might be caused by some other JS plugin. - The delay of 10msec allows the other plugin to override the URL. - */ - History.replaceState( context, "Questions", url ); - }, 10); - }; +FullTextSearch.prototype.getSearchUrl = function() { + return this._searchUrl; +}; - /* *********************************** */ +FullTextSearch.prototype.renderTagWarning = function(tag_list){ + if ( !tag_list ) { + return; + } + var tagWarningBox = this._tag_warning_box; + tagWarningBox.clear(); + $.each(tag_list, function(idx, tag_name){ + tagWarningBox.addTag(tag_name); + }); + tagWarningBox.showWarning(); +}; - var render_related_tags = function(tags, query_string){ - if (tags.length === 0) return; +FullTextSearch.prototype.runTagSearch = function() { + var search_tags = $('#ab-tag-search').val().split(/\s+/); + if (search_tags.length === 0) { + return; + } + var searchUrl = this.getSearchUrl(); + //add all tags to the url + searchUrl = QSutils.add_search_tag(searchUrl, search_tags); + var url = this._baseUrl + searchUrl; + var me = this; + $.ajax({ + url: url, + dataType: 'json', + success: function(data, text_status, xhr){ + me.renderFullTextResult(data, text_status, xhr); + $('#ab-tag-search').val(''); + }, + }); + this.updateHistory(url); +}; - var html_list = []; - for (var i=0; i<tags.length; i++){ - var tag = new Tag(); - tag.setName(tags[i]['name']); - tag.setDeletable(false); - tag.setLinkable(true); - tag.setUrlParams(query_string); - - html_list.push(tag.getElement().outerHTML()); - html_list.push('<span class="tag-number">× '); - html_list.push(tags[i]['used_count']); - html_list.push('</span>'); - html_list.push('<br />'); +FullTextSearch.prototype.updateHistory = function(url) { + var context = { state:1, rand:Math.random() }; + History.pushState( context, "Questions", url ); + setTimeout(function(){ + /* HACK: For some weird reson, sometimes + * overrides the above pushState so we re-aplly it + * This might be caused by some other JS plugin. + * The delay of 10msec allows the other plugin to override the URL. + */ + History.replaceState(context, "Questions", url); + }, + 10 + ); +}; + +FullTextSearch.prototype.activateTagSearchInput = function() { + //the autocomplete is set up in tag_selector.js + var button = $('#ab-tag-search-add'); + if (button.length === 0){//may be absent + return; + } + var me = this; + var ac = new AutoCompleter({ + url: askbot['urls']['get_tag_list'], + preloadData: true, + minChars: 1, + useCache: true, + matchInside: true, + maxCacheLength: 100, + maxItemsToShow: 20, + onItemSelect: function(){ this.runTagSearch(); }, + delay: 10 + }); + ac.decorate($('#ab-tag-search')); + setupButtonEventHandlers( + button, + function() { me.runTagSearch() } + ); +}; + +FullTextSearch.prototype.sendTitleSearchQuery = function(query_text) { + this.isRunning(true); + this._prevText = query_text; + var data = {query_text: query_text}; + var me = this; + $.ajax({ + url: askbot['urls']['titleSearch'], + data: data, + dataType: 'json', + success: function(data, text_status, xhr){ + me.renderTitleSearchResult(data, text_status, xhr); + }, + complete: function(){ + me.isRunning(false); + me.evalTitleSearchQuery(); + }, + cache: false + }); +}; + + +FullTextSearch.prototype.sendFullTextSearchQuery = function(query_text) { + this.isRunning(true); + var searchUrl = this.getSearchUrl(); + var prevText = this._prevText; + if(!prevText && query_text && askbot['settings']['showSortByRelevance']) { + /* If there was no query but there is some + * query now - and we support relevance search + * - then switch to it + */ + searchUrl = QSutils.patch_query_string( + searchUrl, 'sort:relevance-desc' + ); + } + this._prevText = this.updateQueryString(query_text); + + /* if something has changed, then reset the page no. */ + searchUrl = QSutils.patch_query_string(searchUrl, 'page:1'); + var url = this._baseUrl + searchUrl; + var me = this; + $.ajax({ + url: url, + dataType: 'json', + success: function(data, text_status, xhr){ + me.renderFullTextSearchResult(data, text_status, xhr); + }, + complete: function(){ + me.isRunning(false); + }, + cache: false + }); + this.updateHistory(url); +}; + +FullTextSearch.prototype.refresh = function() { + this.sendFullTextSearchQuery();/* used for tag search, maybe not necessary */ +}; + +FullTextSearch.prototype.getSearchQuery = function() { + return $.trim(this._query.val()); +}; + +/** + * renders title search result in the dropdown under the search input + */ +FullTextSearch.prototype.renderTitleSearchResult = function(data) { + var menu = this._dropMenu; + menu.hideWaitIcon(); + menu.setData(data); + menu.render(); + menu.show(); +}; + +FullTextSearch.prototype.renderFullTextSearchResult = function(data) { + if (data['questions'].length === 0) { + return; + } + + $('#pager').toggle(data['paginator'] !== '').html(data['paginator']); + $('#questionCount').html(data['question_counter']); + this.renderSearchTags(data['query_data']['tags'], data['query_string']); + if(data['faces'].length > 0) { + $('#contrib-users > a').remove(); + $('#contrib-users').append(data['faces'].join('')); + } + this.renderRelatedTags(data['related_tags'], data['query_string']); + this.renderRelevanceSortTab(data['query_string']); + this.renderTagWarning(data['non_existing_tags']); + this.setActiveSortTab( + data['query_data']['sort_order'], + data['query_string'] + ); + if(data['feed_url']){ + // Change RSS URL + $("#ContentLeft a.rss:first").attr("href", data['feed_url']); + } + + // Patch scope selectors + var baseUrl = this._baseUrl; + $('#scopeWrapper > a.scope-selector').each(function(index) { + var old_qs = $(this).attr('href').replace(baseUrl, ''); + var scope = QSutils.get_query_string_selector_value(old_qs, 'scope'); + qs = QSutils.patch_query_string(data['query_string'], 'scope:' + scope); + $(this).attr('href', baseUrl + qs); + }); + + // Patch "Ask your question" + var askButton = $('#askButton'); + var askHrefBase = askButton.attr('href').split('?')[0]; + askButton.attr( + 'href', + askHrefBase + data['query_data']['ask_query_string'] + ); /* INFO: ask_query_string should already be URL-encoded! */ + + this._query.focus(); + + var old_list = $('#' + this._q_list_sel); + var new_list = $('<div></div>').hide().html(data['questions']); + new_list.find('.timeago').timeago(); + + var q_list_sel = this._q_list_sel; + old_list.stop(true).after(new_list).fadeOut(200, function() { + //show new div with a fadeIn effect + old_list.remove(); + new_list.attr('id', q_list_sel); + new_list.fadeIn(400); + }); +}; + +FullTextSearch.prototype.evalTitleSearchQuery = function() { + var cur_query = this.getSearchQuery(); + var prevText = this._prevText; + if (cur_query !== prevText && this.isRunning() === false){ + if (cur_query.length >= askbot['settings']['minSearchWordLength']){ + this.sendTitleSearchQuery(cur_query); + } else if (cur_query.length === 0){ + this.reset(); } - $('#related-tags').html(html_list.join('')); - }; + } +}; - var render_search_tags = function(tags, query_string){ - var search_tags = $('#searchTags'); - search_tags.empty(); - if (tags.length === 0){ - $('#listSearchTags').hide(); - $('#search-tips').hide();//wrong - if there are search users - } else { - $('#listSearchTags').show(); - $('#search-tips').show(); - $.each(tags, function(idx, tag_name){ - var tag = new Tag(); - tag.setName(tag_name); - tag.setLinkable(false); - tag.setDeletable(true); - tag.setDeleteHandler( - function(){ - remove_search_tag(tag_name, query_string); - } - ); - search_tags.append(tag.getElement()); - }); +FullTextSearch.prototype.reset = function() { + this._prevText = ''; + this._dropMenu.reset(); + this._element.val(''); + this._element.focus(); + this._xButton.hide(); + this._toolTip.show(); +}; + +FullTextSearch.prototype.refreshXButton = function() { + if(this.getSearchQuery().length > 0){ + if (this._query.hasClass('searchInput')){ + $('#searchBar').attr('class', 'cancelable'); + this._xButton.show(); } - }; + } else { + this._xButton.hide(); + $('#searchBar').removeClass('cancelable'); + } +}; - var create_relevance_tab = function(query_string){ - relevance_tab = $('<a></a>'); - href = search_url + QSutils.patch_query_string(query_string, 'sort:relevance-desc'); - relevance_tab.attr('href', href); - relevance_tab.attr('id', 'by_relevance'); - relevance_tab.html('<span>' + sortButtonData['relevance']['label'] + '</span>'); - return relevance_tab; - }; +FullTextSearch.prototype.updateQueryString = function(query_text) { + if (query_text === undefined) { // handle missing parameter + query_text = this.getSearchQuery(); + } + var newUrl = QSutils.patch_query_string( + this._searchUrl, + 'query:' + encodeURIComponent(query_text), + query_text === '' // remove if empty + ); + this.setSearchUrl(newUrl); + return query_text; +}; - /* *************************************** */ +FullTextSearch.prototype.renderRelatedTags = function(tags, query_string){ + if (tags.length === 0) return; - var remove_search_tag = function(tag){ - query_string = QSutils.remove_search_tag(query_string, tag); - send_query(); - }; + var html_list = []; + for (var i=0; i<tags.length; i++){ + var tag = new Tag(); + tag.setName(tags[i]['name']); + tag.setDeletable(false); + tag.setLinkable(true); + tag.setUrlParams(query_string); + + html_list.push(tag.getElement().outerHTML()); + html_list.push('<span class="tag-number">× '); + html_list.push(tags[i]['used_count']); + html_list.push('</span>'); + html_list.push('<br />'); + } + $('#related-tags').html(html_list.join('')); +}; - var set_active_sort_tab = function(sort_method, query_string){ - var tabs = $('#sort_tabs > a'); - tabs.attr('class', 'off'); - tabs.each(function(index, element){ - var tab = $(element); - if ( tab.attr('id') ) { - var tab_name = tab.attr('id').replace(/^by_/,''); - if (tab_name in sortButtonData){ - href = search_url + QSutils.patch_query_string( - query_string, - 'sort:' + tab_name + '-desc' - ); - tab.attr('href', href); - tab.attr('title', sortButtonData[tab_name]['desc_tooltip']); - tab.html(sortButtonData[tab_name]['label']); +FullTextSearch.prototype.renderSearchTags = function(tags, query_string){ + var search_tags = $('#searchTags'); + search_tags.empty(); + var me = this; + if (tags.length === 0){ + $('#listSearchTags').hide(); + $('#search-tips').hide();//wrong - if there are search users + } else { + $('#listSearchTags').show(); + $('#search-tips').show(); + $.each(tags, function(idx, tag_name){ + var tag = new Tag(); + tag.setName(tag_name); + tag.setLinkable(false); + tag.setDeletable(true); + tag.setDeleteHandler( + function(){ + this.removeSearchTag(tag_name, query_string); } - } + ); + search_tags.append(tag.getElement()); }); - var bits = sort_method.split('-', 2); - var name = bits[0]; - var sense = bits[1];//sense of sort - var antisense = (sense == 'asc' ? 'desc':'asc'); - var arrow = (sense == 'asc' ? ' ▲':' ▼'); - var active_tab = $('#by_' + name); - active_tab.attr('class', 'on'); - active_tab.attr('title', sortButtonData[name][antisense + '_tooltip']); - active_tab.html(sortButtonData[name]['label'] + arrow); - }; + } +}; - var render_relevance_sort_tab = function(query_string){ - if (showSortByRelevance === false){ - return; - } - var relevance_tab = $('#by_relevance'); - if (prev_text && prev_text.length > 0){ - if (relevance_tab.length == 0){ - relevance_tab = create_relevance_tab(query_string); - $('#sort_tabs>span').after(relevance_tab); +FullTextSearch.prototype.createRelevanceTab = function(query_string){ + var relevance_tab = $('<a></a>'); + href = this._baseUrl + QSutils.patch_query_string(query_string, 'sort:relevance-desc'); + relevance_tab.attr('href', href); + relevance_tab.attr('id', 'by_relevance'); + relevance_tab.html( + '<span>' + askbot['data']['sortButtonData']['relevance']['label'] + '</span>' + ); + return relevance_tab; +}; + +FullTextSearch.prototype.removeSearchTag = function(tag) { + var searchUrl = this.getSearchUrl() + searchUrl = QSutils.remove_search_tag(searchUrl, tag); + this.setSearchUrl(searchUrl); + this.sendFullTextSearchQuery(); +}; + +FullTextSearch.prototype.setActiveSortTab = function(sort_method, query_string){ + var tabs = $('#sort_tabs > a'); + tabs.attr('class', 'off'); + var baseUrl = this._baseUrl; + tabs.each(function(index, element){ + var tab = $(element); + if ( tab.attr('id') ) { + var tab_name = tab.attr('id').replace(/^by_/,''); + if (tab_name in askbot['data']['sortButtonData']){ + href = baseUrl + QSutils.patch_query_string( + query_string, + 'sort:' + tab_name + '-desc' + ); + tab.attr('href', href); + tab.attr( + 'title', + askbot['data']['sortButtonData'][tab_name]['desc_tooltip'] + ); + tab.html( + askbot['data']['sortButtonData'][tab_name]['label'] + ); } } - else { - if (relevance_tab.length > 0){ - relevance_tab.remove(); - } + }); + var bits = sort_method.split('-', 2); + var name = bits[0]; + var sense = bits[1];//sense of sort + var antisense = (sense == 'asc' ? 'desc':'asc'); + var arrow = (sense == 'asc' ? ' ▲':' ▼'); + var active_tab = $('#by_' + name); + active_tab.attr('class', 'on'); + active_tab.attr( + 'title', + askbot['data']['sortButtonData'][name][antisense + '_tooltip'] + ); + active_tab.html( + askbot['data']['sortButtonData'][name]['label'] + arrow + ); +}; + +FullTextSearch.prototype.renderRelevanceSortTab = function(query_string) { + if (askbot['settings']['showSortByRelevance'] === false){ + return; + } + var relevance_tab = $('#by_relevance'); + var prevText = this._prevText; + if (prevText && prevText.length > 0){ + if (relevance_tab.length == 0){ + relevance_tab = this.createRelevanceTab(query_string); + $('#sort_tabs>span').after(relevance_tab); + } + } else { + if (relevance_tab.length > 0){ + relevance_tab.remove(); } + } +}; + +FullTextSearch.prototype.makeAskHandler = function() { + var me = this; + return function() { + var query = me.getSearchQuery(); + window.location.href = askbot['urls']['ask'] + '?title=' + query; + return false; }; +}; - var render_result = function(data, text_status, xhr){ - if (data['questions'].length > 0){ - $('#pager').toggle(data['paginator'] !== '').html(data['paginator']); - $('#questionCount').html(data['question_counter']); - render_search_tags(data['query_data']['tags'], data['query_string']); - if(data['faces'].length > 0) { - $('#contrib-users > a').remove(); - $('#contrib-users').append(data['faces'].join('')); - } - render_related_tags(data['related_tags'], data['query_string']); - render_relevance_sort_tab(data['query_string']); - render_tag_warning(data['non_existing_tags']); - set_active_sort_tab(data['query_data']['sort_order'], data['query_string']); - if(data['feed_url']){ - // Change RSS URL - $("#ContentLeft a.rss:first").attr("href", data['feed_url']); +FullTextSearch.prototype.updateToolTip = function() { + var query = this.getSearchQuery(); + if (query === '') { + this._toolTip.show(); + } else { + this._toolTip.hide(); + } +}; + +/** + * keydown handler operates on the tooltip and the X button + * keyup is not good enough, because in that case + * tooltip will be displayed with the input box simultaneously + */ +FullTextSearch.prototype.makeKeyDownHandler = function() { + var me = this; + var toolTip = this._toolTip; + var xButton = this._xButton; + var dropMenu = this._dropMenu; + return function(e) {//don't like the keyup delay to + var keyCode = getKeyCode(e); + + if (keyCode === 27) {//escape key + if (dropMenu.isOpen() === false) { + me.reset(); + return false; } + } - // Patch scope selectors - $('#scopeWrapper > a.scope-selector').each(function(index) { - var old_qs = $(this).attr('href').replace(search_url, ''); - var scope = QSutils.get_query_string_selector_value(old_qs, 'scope'); - qs = QSutils.patch_query_string(data['query_string'], 'scope:' + scope); - $(this).attr('href', search_url + qs); - }); - - // Patch "Ask your question" - var askButton = $('#askButton'); - var askHrefBase = askButton.attr('href').split('?')[0]; - askButton.attr('href', askHrefBase + data['query_data']['ask_query_string']); /* INFO: ask_query_string should already be URL-encoded! */ - - query.focus(); - - var old_list = $('#' + q_list_sel); - var new_list = $('<div></div>').hide().html(data['questions']); - new_list.find('.timeago').timeago(); - old_list.stop(true).after(new_list).fadeOut(200, function() { - //show new div with a fadeIn effect - old_list.remove(); - new_list.attr('id', q_list_sel); - new_list.fadeIn(400); - }); + var query = me.getSearchQuery(); + if (query.length === 0) { + if (keyCode !== 8 && keyCode !== 48) {//del and backspace + toolTip.hide(); + dropMenu.show(); + dropMenu.showWaitIcon(); + //xButton.show();//causes a jump of search input... + } + } else { + me.updateToolTip(); + me.refreshXButton(); } }; +}; + +FullTextSearch.prototype.makeFormSubmitHandler = function() { + var me = this; + var baseUrl = me._baseUrl; + return function(evt) { + // if user clicks the button the s(h)e probably wants page reload, + // so provide that experience but first update the query string + me.updateQueryString(); + var searchUrl = me.getSearchUrl(); + evt.preventDefault(); + window.location.href = baseUrl + searchUrl; + }; +}; + +FullTextSearch.prototype.decorate = function(element) { + this._element = element;/* this is a bit artificial we don't use _element */ + this._query = element; + this._xButton = $('input[name=reset_query]'); + this._prevText = this.getSearchQuery(); + this._tag_warning_box = new TagWarningBox(); + + var toolTip = new InputToolTip(); + toolTip.setClickHandler(function() { + element.focus(); + }); + this._element.after(toolTip.getElement()); + //below is called after getElement, b/c element must be defined + if (this._prevText !== '') { + toolTip.hide();//hide if search query is not empty + } + this._toolTip = toolTip; + + var dropMenu = new SearchDropMenu(); + dropMenu.setAskHandler(this.makeAskHandler()); + dropMenu.setAskButtonEnabled(this._askButtonEnabled); + this._dropMenu = dropMenu; + element.parent().after(this._dropMenu.getElement()); - /* *********************************** */ + $(element).click(function(e) { return false }); + $(document).click(function() { dropMenu.reset(); }); - // Wire search tags + //the tag search input is optional in askbot + $('#ab-tag-search').parent().before( + this._tag_warning_box.getElement() + ); + + // make search tags functional var search_tags = $('#searchTags .tag-left'); + var searchUrl = this.getSearchUrl(); + var me = this; $.each(search_tags, function(idx, element){ var tag = new Tag(); tag.decorate($(element)); //todo: setDeleteHandler and setHandler //must work after decorate & must have getName tag.setDeleteHandler( - function(){ - remove_search_tag(tag.getName(), query_string); - } + function(){ + me.removeSearchTag(tag.getName(), searchUrl); + } ); }); - - // Wire X button - x_button.click(function () { - restart_query(); /* wrapped in closure because it's not yet defined at this point */ + // enable x button (search reset) + this._xButton.click(function () { + /* wrapped in closure because it's not yet defined at this point */ + me.reset(); }); - refresh_x_button(); + this.refreshXButton(); - // Wire query box + // enable query box var main_page_eval_handle; - query.keyup(function(e){ - refresh_x_button(); - if (running === false){ + this._query.keydown(this.makeKeyDownHandler()); + this._query.keyup(function(e){ + me.updateToolTip(); + me.refreshXButton(); + if (me.isRunning() === false){ clearTimeout(main_page_eval_handle); - main_page_eval_handle = setTimeout(eval_query, 400); + main_page_eval_handle = setTimeout( + function() { me.evalTitleSearchQuery() }, + 400 + ); } }); - activate_tag_search_input(); + this.activateTagSearchInput(); - $("form#searchForm").submit(function(event) { - // if user clicks the button the s(h)e probably wants page reload, - // so provide that experience but first update the query string - event.preventDefault(); - update_query_string(); - window.location.href = search_url + query_string; - }); - - /* *********************************** */ - - // Hook for tag_selector.js - liveSearch.refresh = function () { - send_query(); - }; + $("form#searchForm").submit(me.makeFormSubmitHandler()); }; diff --git a/askbot/media/js/live_search_new_thread.js b/askbot/media/js/live_search_new_thread.js index eedd5fe8..d17951eb 100644 --- a/askbot/media/js/live_search_new_thread.js +++ b/askbot/media/js/live_search_new_thread.js @@ -2,7 +2,7 @@ var liveSearchNewThreadInit = function(auto_focus_out) { var query = $('input#id_title.questionTitleInput'); var prev_text = $.trim(query.val()); - var search_url = askbot['urls']['api_get_questions']; + var search_url = askbot['urls']['titleSearch']; var running = false; var q_list_sel = 'question-list'; //id of question listing div @@ -32,7 +32,7 @@ var liveSearchNewThreadInit = function(auto_focus_out) { var eval_query = function(){ cur_text = $.trim(query.val()); if (cur_text !== prev_text && running === false){ - if (cur_text.length >= minSearchWordLength){ + if (cur_text.length >= askbot['settings']['minSearchWordLength']){ send_query(cur_text); } else if (cur_text.length === 0){ restart_query(); diff --git a/askbot/media/js/post.js b/askbot/media/js/post.js index 05e49387..6bc37600 100644 --- a/askbot/media/js/post.js +++ b/askbot/media/js/post.js @@ -23,6 +23,7 @@ var lanai = { } }; +//todo: clean-up now there is utils:WaitIcon function appendLoader(element) { loading = gettext('loading...') element.append('<img class="ajax-loader" ' + diff --git a/askbot/media/js/tag_selector.js b/askbot/media/js/tag_selector.js index 790acce2..f6e698f3 100644 --- a/askbot/media/js/tag_selector.js +++ b/askbot/media/js/tag_selector.js @@ -150,7 +150,7 @@ function pickedTags(){ 'remove', function(){ deleteTagLocally(); - liveSearch.refresh(); + askbot['controllers']['fullTextSearch'].refresh(); } ); } @@ -293,7 +293,7 @@ function pickedTags(){ to_tag_container ); $(input_sel).val(''); - liveSearch.refresh(); + askbot['controllers']['fullTextSearch'].refresh(); } ); } @@ -348,7 +348,7 @@ function pickedTags(){ filter_value: $(this).val() }, success: function(){ - liveSearch.refresh(); + askbot['controllers']['fullTextSearch'].refresh(); } }); }); diff --git a/askbot/media/js/utils.js b/askbot/media/js/utils.js index 3c2a478b..e9ae4dd2 100644 --- a/askbot/media/js/utils.js +++ b/askbot/media/js/utils.js @@ -97,6 +97,18 @@ var showMessage = function(element, msg, where) { }; })(jQuery); +/** + * @return {number} key code of the event or `undefined` + */ +var getKeyCode = function(e) { + if (e.which) { + return e.which; + } else if (e.keyCode) { + return e.keyCode; + } + return undefined; +}; + var makeKeyHandler = function(key, callback){ return function(e){ if ((e.which && e.which == key) || (e.keyCode && e.keyCode == key)){ @@ -116,15 +128,16 @@ var setupButtonEventHandlers = function(button, callback){ var putCursorAtEnd = function(element){ - var el = element.get()[0]; + var el = $(element).get()[0]; + var jEl = $(el); if (el.setSelectionRange){ - var len = element.val().length * 2; + var len = jEl.val().length * 2; el.setSelectionRange(len, len); } else{ - element.val(element.val()); + jEl.val(jEl.val()); } - element.scrollTop = 999999; + jEl.scrollTop(999999); }; var setCheckBoxesIn = function(selector, value){ @@ -201,7 +214,8 @@ QSutils.patch_query_string = function (query_string, patch, remove) { var mapping = {}; if(!remove) { - mapping[patch_split[0]] = patch_split[1]; // prepopulate the patched selector if it's not meant to be removed + // prepopulate the patched selector if it's not meant to be removed + mapping[patch_split[0]] = patch_split[1]; } for (var i = 0; i < params.length; i++) { @@ -368,6 +382,45 @@ WrappedElement.prototype.dispose = function(){ this._in_document = false; }; +/** + * @constructor + * a loader + */ +var WaitIcon = function() { + WrappedElement.call(this); + this._isVisible = false; +}; +inherits(WaitIcon, WrappedElement); + +WaitIcon.prototype.setVisible = function(isVisible) { + this._isVisible = isVisible; + if (this._element) { + if (this._isVisible === true) { + this._element.show(); + } else { + this._element.hide(); + } + } +}; + +WaitIcon.prototype.hide = function() { + this.setVisible(false); +}; + +WaitIcon.prototype.show = function() { + this.setVisible(true); +}; + +WaitIcon.prototype.createDom = function() { + var box = this.makeElement('div'); + box.addClass('wait-icon-box'); + this._element = box; + var img = this.makeElement('img'); + img.attr('src', mediaUrl('media/images/ajax-loader.gif')); + box.append(img); + this.setVisible(this._isVisible); +}; + /** * @contsructor * a form helper that disables submit button @@ -411,6 +464,30 @@ OneShotForm.prototype.decorate = function(element) { /** * @constructor + * a simple link + */ +var Link = function() { + WrappedElement.call(this); +}; +inherits(Link, WrappedElement); + +Link.prototype.setUrl = function(url) { + this._url = url; +}; + +Link.prototype.setText = function(text) { + this._text = text; +}; + +Link.prototype.createDom = function() { + var link = this.makeElement('a'); + this._element = link; + link.attr('href', this._url); + link.html(this._text); +}; + +/** + * @constructor * Widget is a Wrapped element with state */ var Widget = function() { @@ -1538,10 +1615,14 @@ var SelectBox = function(){ this._items = []; this._select_handler = function(){};//empty default this._is_editable = false; - this._item_class = SelectBoxItem; + this._item_class = this.setItemClass(SelectBoxItem); }; inherits(SelectBox, Widget); +SelectBox.prototype.setItemClass = function(itemClass) { + this._item_class = itemClass; +}; + SelectBox.prototype.setEditable = function(is_editable) { this._is_editable = is_editable; }; @@ -1572,7 +1653,21 @@ SelectBox.prototype.getItemByIndex = function(idx) { return this._items[idx]; }; -//why do we have these two almost identical methods? +/** + * removes all items + */ +SelectBox.prototype.empty = function() { + var items = this._items; + $.each(items, function(idx, item){ + item.dispose(); + }); + this._items = []; +}; + +/* + * why do we have these two almost identical methods? + * the difference seems to be remove/vs fade out + */ SelectBox.prototype.removeItem = function(id){ var item = this.getItem(id); item.getElement().fadeOut(); @@ -1697,6 +1792,12 @@ SelectBox.prototype.decorate = function(element){ }); }; +SelectBox.prototype.createDom = function() { + var element = this.makeElement('ul'); + this._element = element; + element.addClass('select-box'); +}; + /** * This is a dropdown list elment */ diff --git a/askbot/media/style/style.css b/askbot/media/style/style.css index 1f5aabc5..3ca80614 100644 --- a/askbot/media/style/style.css +++ b/askbot/media/style/style.css @@ -163,6 +163,9 @@ html { a:hover { text-decoration: underline; } +.avatar-box { + text-decoration: none; +} .badge-context-toggle.active { cursor: pointer; text-decoration: underline; @@ -208,6 +211,10 @@ body.user-messages { font-size: 16px; color: #424242; } +.wait-icon-box { + text-align: center; + margin-bottom: 8px; +} #closeNotify { position: absolute; right: 5px; @@ -380,7 +387,7 @@ body.user-messages { margin-bottom: 10px; font-family: 'Open Sans Condensed', Arial, sans-serif; } -#secondaryHeader #homeButton { +#homeButton { border-right: #afaf9e 1px solid; background: -6px -36px url(../images/sprites.png) no-repeat; height: 55px; @@ -388,28 +395,22 @@ body.user-messages { display: block; float: left; } -#secondaryHeader #homeButton:hover { +#homeButton:hover { background: -51px -36px url(../images/sprites.png) no-repeat; } -#secondaryHeader #scopeWrapper { - width: 688px; - float: left; -} -#secondaryHeader #scopeWrapper a { +.scope-selector { display: block; float: left; -} -#secondaryHeader #scopeWrapper .scope-selector { font-size: 20px; color: #7a7a6b; height: 55px; line-height: 55px; margin-left: 16px; } -#secondaryHeader #scopeWrapper .on { +.scope-selector.on { background: url(../images/scopearrow.png) no-repeat center bottom; } -#secondaryHeader #scopeWrapper .ask-message { +.scope-selector.ask-message { font-size: 24px; } .validate-email-page label { @@ -431,78 +432,137 @@ body.user-messages { #searchBar { /* Main search form , check widgets/search_bar.html */ - display: inline-block; + display: block; background-color: #fff; - width: 400px; border: 1px solid #c9c9b5; - float: right; - height: 42px; - margin: 6px 0px 0px 15px; + height: 41px; + z-index: 10000; + position: relative; } -#searchBar .searchInput, -#searchBar .searchInputCancelable { - font-size: 26px; - height: 39px; - line-height: 39px; +#searchBar input.searchInput { + font-size: 22px; + height: 26px; + line-height: 26px; font-weight: 300; background: #FFF; border: 0px; color: #484848; - padding-left: 10px; - padding-top: 1px; font-family: Arial; - vertical-align: top; + width: 100%; + margin: 8px 0 6px 0; + padding: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; } -#searchBar .searchInput { - width: 340px; +#searchBar div.input-tool-tip { + margin-top: -40px; + padding-top: 10px; + height: 30px; + line-height: 20px; + font-size: 20px; + font-style: italic; } -#searchBar .searchInputCancelable { - width: 305px; +.search-drop-menu { + box-sizing: border-box; + background: whitesmoke; + border: 1px solid #c9c9b5; + border-top: none; + margin: 0; + position: relative; + top: 0; + z-index: 10000; } -#searchBar .logoutsearch { - width: 337px; +.search-drop-menu ul { + list-style: none; + overflow: auto; + padding: 0 0 10px 0; + margin: 0 0 20px 0; + position: relative; } -#searchBar .searchBtn { +.search-drop-menu ul li { + padding: 5px 10px; + position: relative; +} +.search-drop-menu ul li a { + text-decoration: none; +} +.search-drop-menu ul li.selected { + background: #08c; +} +.search-drop-menu ul li.selected a { + color: whitesmoke; +} +.search-drop-menu ul.empty { + margin-bottom: 0; +} +.search-drop-menu .footer { + text-align: center; + padding-bottom: 10px; +} +.search-drop-menu.empty ul { + padding: 5px; + margin: 0; +} +.input-tool-tip { + color: #999; +} +.input-tool-tip.dimmed { + color: #ccc; +} +input[type="submit"].searchBtn { font-size: 10px; color: #666; background-color: #eee; - height: 42px; + height: 41px; border: #FFF 1px solid; line-height: 22px; text-align: center; float: right; - margin: 0px; + margin: 7px 28px 0 0; width: 48px; background: -98px -36px url(../images/sprites.png) no-repeat; cursor: pointer; + position: relative; + z-index: 10001; +} +.ask-page input[type="submit"].searchBtn { + display: none; +} +.ask-page .input-tool-tip { + color: white; + height: 0; + z-index: 0; +} +.ask-page .search-drop-menu.empty { + border: none; + padding: 0; } -#searchBar .searchBtn:hover { +.ask-page .search-drop-menu.empty ul { + padding: 0; +} +.searchBtn:hover { background: -146px -36px url(../images/sprites.png) no-repeat; } -#searchBar .cancelSearchBtn { +.cancelSearchBtn { font-size: 30px; color: #ce8888; background: #fff; - height: 42px; + height: 41px; line-height: 42px; border: 0px; border-left: #deded0 1px solid; text-align: center; width: 35px; cursor: pointer; + float: right; + margin-top: 7px; + position: relative; + z-index: 10001; } -#searchBar .cancelSearchBtn:hover { +.cancelSearchBtn:hover { color: #d84040; } -body.anon #searchBar { - width: 500px; -} -body.anon #searchBar .searchInput { - width: 435px; -} -body.anon #searchBar .searchInputCancelable { - width: 405px; -} #askButton { /* check blocks/secondary_header.html and widgets/ask_button.html*/ @@ -555,6 +615,35 @@ body.anon #searchBar .searchInputCancelable { -moz-text-shadow: 0px 1px 0px #c6d9dd; -webkit-text-shadow: 0px 1px 0px #c6d9dd; } +/* + Put the secondary navigation together: + 1) raise the search bar by 55px + 2) add padding to fit the buttons +*/ +#searchBar { + margin: 0 228px 0 327px; + width: auto; + margin-top: -49px; + padding: 0 49px 0 8px; +} +/* line up drop menu the same way as the search bar */ +.search-drop-menu { + margin: 0 228px 0 327px; + width: auto; +} +.ask-page .search-drop-menu, +body.anon.ask-page .search-drop-menu { + margin: 0; +} +body.anon #searchBar, +body.anon .search-drop-menu { + margin-left: 227px; + /* we don't have the "followed" scope */ + +} +#searchBar.cancelable { + padding-right: 82px; +} /* ----- Content layout, check two_column_body.html or one_column_body.html ----- */ #ContentLeft { width: 730px; @@ -1425,8 +1514,9 @@ ul#related-tags li { } #askFormBar { display: inline-block; - padding: 4px 7px 0px 0px; + padding: 4px 0 0 0; margin-top: 0px; + width: 100%; } #askFormBar p { margin: 0 0 5px 0; @@ -1438,10 +1528,14 @@ ul#related-tags li { font-size: 24px; height: 36px; line-height: 36px; - margin: 0px; - padding: 0px 0 0 5px; + margin: 0; + padding: 0; + /*-left: 5px;*/ + border: #cce6ec 3px solid; - width: 719px; + width: 100%; + /*719px;*/ + } .ask-page div#question-list, .edit-question-page div#question-list { @@ -1487,11 +1581,13 @@ ul#related-tags li { } .ask-page #id_tags, .edit-question-page #id_tags { + box-sizing: border-box; border: #cce6ec 3px solid; - height: 25px; + height: 31px; padding-left: 5px; font-size: 14px; - width: 395px; + width: 100%; + max-width: 395px; } .ask-page #id_post_author_username, .question-page #id_post_author_username, @@ -1762,12 +1858,17 @@ ul#related-tags li { .edit-question-page .wmd-container, .edit-answer-page .wmd-container { width: 723px; + width: 100%; } .ask-page #editor, .question-page #editor, .edit-question-page #editor, .edit-answer-page #editor { - width: 710px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + height: 100%; padding: 6px; } .ask-page .retagger-buttons button, @@ -1891,6 +1992,7 @@ ul#related-tags li { font-size: 14px; margin-top: 5px; margin-bottom: 5px; + width: 100%; } .edit-question-page #id_title, #fmedit #id_title, @@ -1901,7 +2003,7 @@ ul#related-tags li { margin: 0px; padding: 0px 0 0 5px; border: #cce6ec 3px solid; - width: 719px; + width: 100%; margin-bottom: 10px; } .edit-question-page #id_summary, @@ -1980,6 +2082,7 @@ ul#related-tags li { margin-top: 10px; font-family: Arial; color: #4b4b4b; + word-wrap: break-word; } .question-page .question-body p, .question-page .answer-body p { @@ -3922,22 +4025,12 @@ pre.prettyprint { } /* language-specific fixes */ body.lang-es #searchBar { - width: 398px; -} -body.lang-es #searchBar .searchInput { - width: 337px; -} -body.lang-es #searchBar .searchInputCancelable { - width: 302px; + /* need special left padding */ + } body.anon.lang-es #searchBar { - width: 485px; -} -body.anon.lang-es #searchBar .searchInput { - width: 425px; -} -body.anon.lang-es #searchBar .searchInputCancelable { - width: 390px; + /* need special left padding */ + } /* user groups */ #user-groups ul { @@ -4145,6 +4238,12 @@ textarea.tipped-input { /* modifications for small screens */ @media screen and (max-width: 960px) { /* content margins touch viewport */ + #homeButton { + background: 1px -36px url(../images/sprites.png) no-repeat; + } + #homeButton:hover { + background: -44px -36px url(../images/sprites.png) no-repeat; + } } @media screen and (max-width: 480px) { .content-wrapper { @@ -4156,24 +4255,148 @@ textarea.tipped-input { #ContentLeft { width: 100%; } - #secondaryHeader #scopeWrapper { + .main-page h1, + #askButton, + #metaNav #navBadges, + .user-info, + .copyright, + .counts .views, + .counts .votes, + .help, + .rss, + .scope-selector, + .settings, + .tabBar, + .tags, + .userinfo, + .widgets { display: none; } - #askButton { - display: block; + .ask-page input[type="submit"].searchBtn, + .edit-question-page input[type="submit"].searchBtn { + display: none; + } + .ask-page .preview-toggle, + .edit-answer-page .preview-toggle, + .edit-question-page .preview-toggle, + .ask-page .proxy-user-info, + .edit-answer-page .proxy-user-info, + .edit-question-page .proxy-user-info, + .ask-page .answer-options, + .edit-answer-page .answer-options, + .edit-question-page .answer-options, + .ask-page .question-options, + .edit-answer-page .question-options, + .edit-question-page .question-options, + .ask-page .revision-comment, + .edit-answer-page .revision-comment, + .edit-question-page .revision-comment, + .ask-page .wmd-preview, + .edit-answer-page .wmd-preview, + .edit-question-page .wmd-preview, + .ask-page #wmd-hr-button, + .edit-answer-page #wmd-hr-button, + .edit-question-page #wmd-hr-button, + .ask-page #wmd-heading-button, + .edit-answer-page #wmd-heading-button, + .edit-question-page #wmd-heading-button { + display: none; + } + .edit-answer-page label[for="id_title"], + .edit-question-page label[for="id_title"], + .edit-answer-page label[for="id_revision"], + .edit-question-page label[for="id_revision"], + .edit-answer-page #id_revision, + .edit-question-page #id_revision { + display: none; + } + .edit-answer-page #fmedit #id_title, + .edit-question-page #fmedit #id_title { + margin: 15px 0 0 0; + } + .question-page .comment-votes { + display: none; + } + .question-page .comments form.post-comments { + margin: 0 10px 0 0; + } + .question-page .comments .comment .comment-body { + margin-left: 5px; + } + .question-page .post-update-info-container { float: none; - margin: auto; - margin-top: 5px; + width: 100%; } - .main-page h1, - .main-page .counts .views, - .main-page .counts .votes, - .main-page .rss, - .main-page .tabBar, - .main-page .tags, - .main-page .userinfo { + .question-page .post-update-info { + float: none; + margin-left: 0; + width: 95%; + } + .question-page .post-update-info br, + .question-page .post-update-info .badge1, + .question-page .post-update-info .badge2, + .question-page .post-update-info .badge3, + .question-page .post-update-info .gravatar, + .question-page .post-update-info .reputation-score, + .question-page .post-update-info .badge-count { display: none; } + .question-page .question-content, + .question-page ul.post-retag input { + width: 88%; + } + .question-page .answer-table, + .question-page #question-table { + width: 85%; + } + .users-page .userList td { + display: block; + width: 100%; + } + .users-page .userList td .user { + width: 100%; + } + .user-profile-page td { + display: block; + } + .footer-links, + .powered-link { + text-align: center; + width: 100%; + } + #userToolsNav { + margin-left: 10px; + } + #metaNav { + float: left; + } + #metaNav a#navUsers, + #metaNav a#navTags, + #metaNav a#navGroups { + background: none; + color: #d0e296; + font-size: 16px; + text-decoration: underline; + margin-left: 20px; + padding-left: 0; + } + .powered-link { + margin-bottom: 15px; + } + .short-summary:first-child { + padding-top: 0; + } + #searchBar, + body.anon #searchBar { + margin: -49px 8px 0 52px; + } + .search-drop-menu, + body.anon .search-drop-menu { + margin: 0 8px 0 52px; + } + input[type="submit"].searchBtn { + margin-right: 8px; + } .short-summary { width: 100%; } diff --git a/askbot/media/style/style.less b/askbot/media/style/style.less index 2ad46f23..dd854876 100644 --- a/askbot/media/style/style.less +++ b/askbot/media/style/style.less @@ -152,6 +152,10 @@ a:hover { text-decoration: underline; } +.avatar-box { + text-decoration: none; +} + .badge-context-toggle.active { cursor: pointer; text-decoration: underline; @@ -201,6 +205,11 @@ body.user-messages { } } +.wait-icon-box { + text-align: center; + margin-bottom: 8px; +} + #closeNotify { position: absolute; right: 5px; @@ -395,45 +404,37 @@ body.user-messages { border-top:#fcfcfc 1px solid; margin-bottom:10px; font-family:@main-font; +} - #homeButton{ - border-right:#afaf9e 1px solid; - .sprites(-6px,-36px); - height:55px; - width:43px; - display:block; - float:left; - } +#homeButton{ + border-right: #afaf9e 1px solid; + .sprites(-6px,-36px); + height:55px; + width:43px; + display:block; + float:left; +} - #homeButton:hover{ - .sprites(-6px-45,-36px); - } - - #scopeWrapper{ - width:688px; - float:left; - - a{ - display:block; - float:left; - } +#homeButton:hover{ + .sprites(-51px,-36px); +} - .scope-selector{ - font-size:20px; - color:#7a7a6b; - height:55px; - line-height:55px; - margin-left:16px - } +.scope-selector { + display:block; + float:left; + font-size:20px; + color:#7a7a6b; + height:55px; + line-height:55px; + margin-left:16px +} - .on{ - background:url(../images/scopearrow.png) no-repeat center bottom; - } +.scope-selector.on { + background:url(../images/scopearrow.png) no-repeat center bottom; +} - .ask-message{ - font-size:24px; - } - } +.scope-selector.ask-message { + font-size:24px; } .validate-email-page { @@ -456,90 +457,153 @@ body.user-messages { } #searchBar { /* Main search form , check widgets/search_bar.html */ - display: inline-block; + display: block; background-color: #fff; - width: 400px; border: 1px solid #c9c9b5; - float:right; - height:42px; - margin:6px 0px 0px 15px; + height: 41px; + z-index: 10000; + position: relative; - .searchInput, .searchInputCancelable{ - font-size: 26px; - height: 39px; - line-height: 39px; + input.searchInput { + font-size: 22px; + height: 26px; + line-height: 26px; font-weight:300; background:#FFF; border:0px; color:#484848; - padding-left:10px; - padding-top: 1px; font-family:@body-font; - vertical-align: top; - } - - .searchInput,{ - width: 340px; + width: 100%; + margin: 8px 0 6px 0; + padding: 0; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; } - .searchInputCancelable { - width: 305px; + div.input-tool-tip { + margin-top: -40px; + padding-top: 10px; + height: 30px; + line-height: 20px; + font-size: 20px; + font-style: italic; } +} - .logoutsearch { - width: 337px; - } +.search-drop-menu { + box-sizing: border-box; + background: whitesmoke; + border: 1px solid #c9c9b5; + border-top: none; + margin: 0; + position: relative; + top: 0; + z-index: 10000; - .searchBtn { - font-size: 10px; - color: #666; - background-color: #eee; - height: 42px; - border:#FFF 1px solid; - line-height: 22px; - text-align: center; - float:right; - margin: 0px; - width:48px; - .sprites(-98px,-36px); - cursor:pointer; + ul { + list-style: none; + overflow: auto; + padding: 0 0 10px 0; + margin: 0 0 20px 0; + position: relative; + li { + padding: 5px 10px; + position: relative; + a { + text-decoration: none; + } + } + li.selected { + background: #08c; + a { + color: whitesmoke; + } + } } - .searchBtn:hover { - .sprites(-98px-48,-36px); + ul.empty { + margin-bottom: 0; } - .cancelSearchBtn { - font-size: 30px; - color: #ce8888; - background:#fff; - height: 42px; - line-height: 42px; - border:0px; - border-left:#deded0 1px solid; + .footer { text-align: center; - width: 35px; - cursor:pointer; + padding-bottom: 10px; } +} - .cancelSearchBtn:hover { - color: #d84040; +.search-drop-menu.empty { + ul { + padding: 5px; + margin: 0; } } -body.anon { - #searchBar { - width: 500px; - .searchInput { - width: 435px; - } +.input-tool-tip { + color: #999; +} +.input-tool-tip.dimmed { + color: #ccc; +} - .searchInputCancelable { - width: 405px; +input[type="submit"].searchBtn { + font-size: 10px; + color: #666; + background-color: #eee; + height: 41px; + border:#FFF 1px solid; + line-height: 22px; + text-align: center; + float:right; + margin: 7px 28px 0 0; + width: 48px; + .sprites(-98px,-36px); + cursor:pointer; + position: relative; + z-index: 10001; +} +.ask-page { + input[type="submit"].searchBtn { + display: none; + } + .input-tool-tip { + color: white; + height: 0; + z-index: 0; + } + .search-drop-menu.empty { + border: none; + padding: 0; + ul { + padding: 0; } } } +.searchBtn:hover { + .sprites(-98px-48,-36px); +} + +.cancelSearchBtn { + font-size: 30px; + color: #ce8888; + background:#fff; + height: 41px; + line-height: 42px; + border:0px; + border-left:#deded0 1px solid; + text-align: center; + width: 35px; + cursor:pointer; + float: right; + margin-top: 7px; + position: relative; + z-index: 10001; +} + +.cancelSearchBtn:hover { + color: #d84040; +} #askButton{ /* check blocks/secondary_header.html and widgets/ask_button.html*/ line-height:44px; @@ -554,6 +618,37 @@ body.anon { .button-style-hover; } +/* + Put the secondary navigation together: + 1) raise the search bar by 55px + 2) add padding to fit the buttons +*/ +#searchBar { + margin: 0 228px 0 327px; + width: auto; + margin-top: -49px; + padding: 0 49px 0 8px; +} +/* line up drop menu the same way as the search bar */ +.search-drop-menu { + margin: 0 228px 0 327px; + width: auto; +} +.ask-page .search-drop-menu, +body.anon.ask-page .search-drop-menu { + margin: 0; +} +body.anon { + #searchBar, + .search-drop-menu { + margin-left: 227px;/* we don't have the "followed" scope */ + } +} +#searchBar.cancelable { + padding-right: 82px; +} + + /* ----- Content layout, check two_column_body.html or one_column_body.html ----- */ #ContentLeft { @@ -1423,10 +1518,11 @@ ul#related-tags li { #askFormBar { display:inline-block; - padding: 4px 7px 0px 0px; + padding: 4px 0 0 0; margin-top:0px; + width: 100%; - p{ + p { margin:0 0 5px 0; font-size:14px; color:@info-text-dark; @@ -1436,10 +1532,10 @@ ul#related-tags li { font-size: 24px; height: 36px; line-height: 36px; - margin: 0px; - padding: 0px 0 0 5px; + margin: 0; + padding: 0;/*-left: 5px;*/ border:#cce6ec 3px solid; - width:719px; + width: 100%;/*719px;*/ } } @@ -1483,11 +1579,13 @@ ul#related-tags li { } #id_tags { - border:#cce6ec 3px solid; - height:25px; - padding-left:5px; - font-size:14px; - width:395px; + box-sizing: border-box; + border: #cce6ec 3px solid; + height: 31px; + padding-left: 5px; + font-size: 14px; + width: 100%; + max-width: 395px; } } @@ -1646,9 +1744,14 @@ ul#related-tags li { .edit-answer-page { .wmd-container { width: 723px; + width: 100%; } #editor { - width: 710px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + height: 100%; padding: 6px; } .retagger-buttons { @@ -1784,6 +1887,7 @@ ul#related-tags li { font-size:14px; margin-top:5px; margin-bottom:5px; + width: 100%; } #id_title{ font-size: 24px; @@ -1792,7 +1896,7 @@ ul#related-tags li { margin: 0px; padding: 0px 0 0 5px; border:#cce6ec 3px solid; - width: 719px; + width: 100%; margin-bottom:10px; } #id_summary{ @@ -1838,7 +1942,7 @@ ul#related-tags li { vertical-align: top; } - .question-content{ + .question-content { float:right; width:682px; margin-bottom:10px; @@ -2512,7 +2616,10 @@ ul#related-tags li { form{ margin-bottom:15px; } - input[type="text"],input[type="password"],select{ + + input[type="text"], + input[type="password"], + select{ border:#cce6ec 3px solid; height:25px; line-height: 25px; @@ -3757,24 +3864,12 @@ pre.prettyprint { clear:both;padding: 3px; border: 0px solid #888; } /* language-specific fixes */ body.lang-es { #searchBar { - width: 398px; - .searchInput { - width: 337px; - } - .searchInputCancelable { - width: 302px; - } + /* need special left padding */ } } body.anon.lang-es { #searchBar { - width: 485px; - .searchInput { - width: 425px; - } - .searchInputCancelable { - width: 390px; - } + /* need special left padding */ } } @@ -4008,6 +4103,12 @@ textarea.tipped-input { /* modifications for small screens */ @media screen and (max-width: 960px) {/* content margins touch viewport */ + #homeButton { + .sprites(1px,-36px); + } + #homeButton:hover { + .sprites(-44px,-36px); + } } @media screen and (max-width: 480px) { .content-wrapper { @@ -4019,27 +4120,154 @@ textarea.tipped-input { #ContentLeft { width: 100%; } - #secondaryHeader #scopeWrapper { + .main-page h1, + #askButton, + #metaNav #navBadges, + .user-info, + .copyright, + .counts .views, + .counts .votes, + .help, + .rss, + .scope-selector, + .settings, + .tabBar, + .tags, + .userinfo, + .widgets { display: none; } - #askButton { - display: block; - float: none; - margin: auto; - margin-top: 5px; + + .ask-page, + .edit-question-page { + input[type="submit"].searchBtn { + display: none; + } } - #homeButton { + + .ask-page, + .edit-answer-page, + .edit-question-page { + .preview-toggle, + .proxy-user-info, + .answer-options, + .question-options, + .revision-comment, + .wmd-preview, + #wmd-hr-button, + #wmd-heading-button { + display: none; + } } - .main-page { - h1, - .counts .views, - .counts .votes, - .rss, - .tabBar, - .tags, - .userinfo { + + .edit-answer-page, + .edit-question-page { + label[for="id_title"], + label[for="id_revision"], + #id_revision { display: none; } + #fmedit #id_title { + margin: 15px 0 0 0; + } + } + + .question-page { + .comment-votes { + display: none; + } + .comments { + form.post-comments { + margin: 0 10px 0 0; + } + .comment .comment-body { + margin-left: 5px; + } + } + .post-update-info-container { + float: none; + width: 100%; + } + .post-update-info { + float: none; + margin-left: 0; + width: 100%-5; + + br, + .badge1, + .badge2, + .badge3, + .gravatar, + .reputation-score, + .badge-count { + display: none; + } + } + .question-content, + ul.post-retag input { + width: 100%-12; + } + .answer-table, + #question-table { + width: 100%-15; + } + } + + .users-page { + .userList td { + display: block; + width: 100%; + .user { + width: 100%; + } + } + } + + .user-profile-page { + td { + display: block; + } + } + + .footer-links, + .powered-link { + text-align: center; + width: 100%; + } + + #userToolsNav { + margin-left: 10px; + } + + #metaNav { + float: left; + a#navUsers, + a#navTags, + a#navGroups { + background: none; + color: #d0e296; + font-size: 16px; + text-decoration: underline; + margin-left: 20px; + padding-left: 0; + } + } + .powered-link { + margin-bottom: 15px; + } + .short-summary:first-child { + padding-top: 0; + } + #searchBar, + body.anon #searchBar { + margin: -49px 8px 0 52px; + } + .search-drop-menu, + body.anon .search-drop-menu { + margin: 0 8px 0 52px; + } + input[type="submit"].searchBtn { + margin-right: 8px; } .short-summary { width: 100%; diff --git a/askbot/migrations/0158_add_title_search_indices_for_postgresql_and_mysql.py b/askbot/migrations/0158_add_title_search_indices_for_postgresql_and_mysql.py new file mode 100644 index 00000000..f2351a02 --- /dev/null +++ b/askbot/migrations/0158_add_title_search_indices_for_postgresql_and_mysql.py @@ -0,0 +1,405 @@ +# -*- coding: utf-8 -*- +import askbot +import datetime +import os +from south.db import db +from south.v2 import DataMigration +from django.db import models +from askbot.migrations_api import mysql_table_supports_full_text_search +from askbot.migrations_api import get_drop_index_sql +from askbot.migrations_api import get_create_full_text_index_sql +from askbot.search import postgresql + +INDEX_NAME = 'askbot_thread_search_index' + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + # Note: Remember to use orm['appname.ModelName'] rather than "from appname.models..." + if db.backend_name == 'mysql': + table_name = orm['askbot.Thread']._meta.db_table + if mysql_table_supports_full_text_search(table_name): + columns = ('title', 'tagnames') + sql = get_create_full_text_index_sql(INDEX_NAME, table_name, columns) + db.execute(sql) + elif db.backend_name == 'postgres': + script_path = os.path.join( + askbot.get_install_directory(), + 'search', + 'postgresql', + 'thread_and_post_models_27112012.plsql' + ) + postgresql.setup_full_text_search(script_path) + + def backwards(self, orm): + "Write your backwards methods here." + if db.backend_name == 'mysql': + table_name = orm['askbot.Thread']._meta.db_table + if mysql_table_supports_full_text_search(table_name): + sql = get_drop_index_sql(INDEX_NAME, table_name) + db.execute(sql) + + models = { + 'askbot.activity': { + 'Meta': {'object_name': 'Activity', 'db_table': "u'activity'"}, + 'active_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'activity_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_auditted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True'}), + 'receiving_users': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'received_activity'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'recipients': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'incoming_activity'", 'symmetrical': 'False', 'through': "orm['askbot.ActivityAuditStatus']", 'to': "orm['auth.User']"}), + 'summary': ('django.db.models.fields.TextField', [], {'default': "''"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.activityauditstatus': { + 'Meta': {'unique_together': "(('user', 'activity'),)", 'object_name': 'ActivityAuditStatus'}, + 'activity': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Activity']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.anonymousanswer': { + 'Meta': {'object_name': 'AnonymousAnswer'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'anonymous_answers'", 'to': "orm['askbot.Post']"}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'askbot.anonymousquestion': { + 'Meta': {'object_name': 'AnonymousQuestion'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_addr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '180'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}) + }, + 'askbot.askwidget': { + 'Meta': {'object_name': 'AskWidget'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'include_text_field': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'inner_style': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'outer_style': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Tag']", 'null': 'True', 'blank': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'askbot.award': { + 'Meta': {'object_name': 'Award', 'db_table': "u'award'"}, + 'awarded_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'badge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_badge'", 'to': "orm['askbot.BadgeData']"}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'notified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'award_user'", 'to': "orm['auth.User']"}) + }, + 'askbot.badgedata': { + 'Meta': {'ordering': "('slug',)", 'object_name': 'BadgeData'}, + 'awarded_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'awarded_to': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'badges'", 'symmetrical': 'False', 'through': "orm['askbot.Award']", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}) + }, + 'askbot.draftanswer': { + 'Meta': {'object_name': 'DraftAnswer'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'draft_answers'", 'to': "orm['askbot.Thread']"}) + }, + 'askbot.draftquestion': { + 'Meta': {'object_name': 'DraftQuestion'}, + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125', 'null': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300', 'null': 'True'}) + }, + 'askbot.emailfeedsetting': { + 'Meta': {'unique_together': "(('subscriber', 'feed_type'),)", 'object_name': 'EmailFeedSetting'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'feed_type': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'frequency': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '8'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reported_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), + 'subscriber': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'notification_subscriptions'", 'to': "orm['auth.User']"}) + }, + 'askbot.favoritequestion': { + 'Meta': {'object_name': 'FavoriteQuestion', 'db_table': "u'favorite_question'"}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_favorite_questions'", 'to': "orm['auth.User']"}) + }, + 'askbot.group': { + 'Meta': {'object_name': 'Group', '_ormbases': ['auth.Group']}, + 'description': ('django.db.models.fields.related.OneToOneField', [], {'blank': 'True', 'related_name': "'described_group'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}), + 'group_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.Group']", 'unique': 'True', 'primary_key': 'True'}), + 'is_vip': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}), + 'moderate_answers_to_enquirers': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'moderate_email': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'openness': ('django.db.models.fields.SmallIntegerField', [], {'default': '2'}), + 'preapproved_email_domains': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}), + 'preapproved_emails': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}) + }, + 'askbot.groupmembership': { + 'Meta': {'object_name': 'GroupMembership', '_ormbases': ['auth.AuthUserGroups']}, + 'authusergroups_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.AuthUserGroups']", 'unique': 'True', 'primary_key': 'True'}), + 'level': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}) + }, + 'askbot.markedtag': { + 'Meta': {'object_name': 'MarkedTag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'reason': ('django.db.models.fields.CharField', [], {'max_length': '16'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'user_selections'", 'to': "orm['askbot.Tag']"}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tag_selections'", 'to': "orm['auth.User']"}) + }, + 'askbot.post': { + 'Meta': {'object_name': 'Post'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'posts'", 'to': "orm['auth.User']"}), + 'comment_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_posts'", 'symmetrical': 'False', 'through': "orm['askbot.PostToGroup']", 'to': "orm['askbot.Group']"}), + 'html': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_edited_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'last_edited_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'last_edited_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'locked': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'locked_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'locked_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'locked_posts'", 'null': 'True', 'to': "orm['auth.User']"}), + 'offensive_flag_count': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'old_answer_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'old_comment_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'old_question_id': ('django.db.models.fields.PositiveIntegerField', [], {'default': 'None', 'unique': 'True', 'null': 'True', 'blank': 'True'}), + 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comments'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'points': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_column': "'score'"}), + 'post_type': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}), + 'summary': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {'null': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'related_name': "'posts'", 'null': 'True', 'blank': 'True', 'to': "orm['askbot.Thread']"}), + 'vote_down_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'vote_up_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'wiki': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'wikified_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}) + }, + 'askbot.postflagreason': { + 'Meta': {'object_name': 'PostFlagReason'}, + 'added_at': ('django.db.models.fields.DateTimeField', [], {}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}), + 'details': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'post_reject_reasons'", 'to': "orm['askbot.Post']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '128'}) + }, + 'askbot.postrevision': { + 'Meta': {'ordering': "('-revision',)", 'unique_together': "(('post', 'revision'),)", 'object_name': 'PostRevision'}, + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}), + 'approved_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'approved_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'postrevisions'", 'to': "orm['auth.User']"}), + 'by_email': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'revisions'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'revised_at': ('django.db.models.fields.DateTimeField', [], {}), + 'revision': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'summary': ('django.db.models.fields.CharField', [], {'max_length': '300', 'blank': 'True'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '125', 'blank': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}), + 'title': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '300', 'blank': 'True'}) + }, + 'askbot.posttogroup': { + 'Meta': {'unique_together': "(('post', 'group'),)", 'object_name': 'PostToGroup', 'db_table': "'askbot_post_groups'"}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']"}) + }, + 'askbot.questionview': { + 'Meta': {'object_name': 'QuestionView'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'viewed'", 'to': "orm['askbot.Post']"}), + 'when': ('django.db.models.fields.DateTimeField', [], {}), + 'who': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'question_views'", 'to': "orm['auth.User']"}) + }, + 'askbot.questionwidget': { + 'Meta': {'object_name': 'QuestionWidget'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'order_by': ('django.db.models.fields.CharField', [], {'default': "'-added_at'", 'max_length': '18'}), + 'question_number': ('django.db.models.fields.PositiveIntegerField', [], {'default': '7'}), + 'search_query': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'style': ('django.db.models.fields.TextField', [], {'default': '"\\n@import url(\'http://fonts.googleapis.com/css?family=Yanone+Kaffeesatz:300,400,700\');\\nbody {\\n overflow: hidden;\\n}\\n\\n#container {\\n width: 200px;\\n height: 350px;\\n}\\nul {\\n list-style: none;\\n padding: 5px;\\n margin: 5px;\\n}\\nli {\\n border-bottom: #CCC 1px solid;\\n padding-bottom: 5px;\\n padding-top: 5px;\\n}\\nli:last-child {\\n border: none;\\n}\\na {\\n text-decoration: none;\\n color: #464646;\\n font-family: \'Yanone Kaffeesatz\', sans-serif;\\n font-size: 15px;\\n}\\n"', 'blank': 'True'}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'askbot.replyaddress': { + 'Meta': {'object_name': 'ReplyAddress'}, + 'address': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '25'}), + 'allowed_from_email': ('django.db.models.fields.EmailField', [], {'max_length': '150'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'reply_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'reply_action': ('django.db.models.fields.CharField', [], {'default': "'auto_answer_or_comment'", 'max_length': '32'}), + 'response_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'edit_addresses'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'used_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.repute': { + 'Meta': {'object_name': 'Repute', 'db_table': "u'repute'"}, + 'comment': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'negative': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'positive': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'question': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Post']", 'null': 'True', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'reputation_type': ('django.db.models.fields.SmallIntegerField', [], {}), + 'reputed_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'askbot.tag': { + 'Meta': {'ordering': "('-used_count', 'name')", 'object_name': 'Tag', 'db_table': "u'tag'"}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created_tags'", 'to': "orm['auth.User']"}), + 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'deleted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'deleted_by': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'deleted_tags'", 'null': 'True', 'to': "orm['auth.User']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'suggested_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'suggested_tags'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'tag_wiki': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'described_tag'", 'unique': 'True', 'null': 'True', 'to': "orm['askbot.Post']"}), + 'used_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.thread': { + 'Meta': {'object_name': 'Thread'}, + 'accepted_answer': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'to': "orm['askbot.Post']"}), + 'added_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'answer_accepted_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'answer_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}), + 'close_reason': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True', 'blank': 'True'}), + 'closed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'closed_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'closed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}), + 'favorited_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'unused_favorite_threads'", 'symmetrical': 'False', 'through': "orm['askbot.FavoriteQuestion']", 'to': "orm['auth.User']"}), + 'favourite_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'followed_by': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'followed_threads'", 'symmetrical': 'False', 'to': "orm['auth.User']"}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'group_threads'", 'symmetrical': 'False', 'through': "orm['askbot.ThreadToGroup']", 'to': "orm['askbot.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_activity_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_activity_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'unused_last_active_in_threads'", 'to': "orm['auth.User']"}), + 'points': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_column': "'score'"}), + 'tagnames': ('django.db.models.fields.CharField', [], {'max_length': '125'}), + 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'threads'", 'symmetrical': 'False', 'to': "orm['askbot.Tag']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '300'}), + 'view_count': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}) + }, + 'askbot.threadtogroup': { + 'Meta': {'unique_together': "(('thread', 'group'),)", 'object_name': 'ThreadToGroup', 'db_table': "'askbot_thread_groups'"}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'thread': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['askbot.Thread']"}), + 'visibility': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}) + }, + 'askbot.vote': { + 'Meta': {'unique_together': "(('user', 'voted_post'),)", 'object_name': 'Vote', 'db_table': "u'vote'"}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['auth.User']"}), + 'vote': ('django.db.models.fields.SmallIntegerField', [], {}), + 'voted_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'voted_post': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'votes'", 'to': "orm['askbot.Post']"}) + }, + 'auth.authusergroups': { + 'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'AuthUserGroups', 'db_table': "'auth_user_groups'", 'managed': 'False'}, + 'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'about': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'avatar_type': ('django.db.models.fields.CharField', [], {'default': "'n'", 'max_length': '1'}), + 'bronze': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'consecutive_days_visit_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'country': ('django_countries.fields.CountryField', [], {'max_length': '2', 'blank': 'True'}), + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'date_of_birth': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}), + 'display_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'email_isvalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'email_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}), + 'email_signature': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'email_tag_filter_strategy': ('django.db.models.fields.SmallIntegerField', [], {'default': '1'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'gold': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'gravatar': ('django.db.models.fields.CharField', [], {'max_length': '32'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ignored_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'interesting_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_fake': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'location': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'new_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'questions_per_page': ('django.db.models.fields.SmallIntegerField', [], {'default': '10'}), + 'real_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'reputation': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'seen_response_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}), + 'show_country': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'show_marked_tags': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'silver': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}), + 'status': ('django.db.models.fields.CharField', [], {'default': "'w'", 'max_length': '2'}), + 'subscribed_tags': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), + 'website': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['askbot'] + symmetrical = True diff --git a/askbot/migrations_api/__init__.py b/askbot/migrations_api/__init__.py index 586c02d9..0260f51b 100644 --- a/askbot/migrations_api/__init__.py +++ b/askbot/migrations_api/__init__.py @@ -5,6 +5,7 @@ since models change with time, this api is implemented in different versions. Different versions do not need to have all the same functions. """ from south.db import db +from django.db import connection def safe_add_column(table, column, column_data, keep_default = False): """when user calls syncdb with askbot the first time @@ -22,6 +23,25 @@ def safe_add_column(table, column, column_data, keep_default = False): return False +def mysql_table_supports_full_text_search(table_name): + """true, if engine is MyISAM""" + cursor = connection.cursor() + cursor.execute("SHOW CREATE TABLE %s" % table_name) + data = cursor.fetchone() + return 'ENGINE=MyISAM' in data[1] + + +def get_drop_index_sql(index_name, table_name): + """returns sql for dropping index by name on table""" + return 'ALTER TABLE %s DROP INDEX %s' % (table_name, index_name) + + +def get_create_full_text_index_sql(index_name, table_name, column_list): + column_sql = '(%s)' % ','.join(column_list) + query_template = 'CREATE FULLTEXT INDEX %s on %s %s' + return query_template % (index_name, table_name, column_sql) + + class BaseAPI(object): def __init__(self, orm): self.orm = orm diff --git a/askbot/models/__init__.py b/askbot/models/__init__.py index 63259c53..e7c85afa 100644 --- a/askbot/models/__init__.py +++ b/askbot/models/__init__.py @@ -112,7 +112,7 @@ def get_users_by_text_query(search_query, users_query_set = None): users_query_set = User.objects.all() if 'postgresql_psycopg2' in askbot.get_database_engine_name(): from askbot.search import postgresql - return postgresql.run_full_text_search(users_query_set, search_query) + return postgresql.run_thread_search(users_query_set, search_query) else: return users_query_set.filter( models.Q(username__icontains=search_query) | @@ -228,7 +228,7 @@ User.add_to_class('new_response_count', models.IntegerField(default=0)) User.add_to_class('seen_response_count', models.IntegerField(default=0)) User.add_to_class('consecutive_days_visit_count', models.IntegerField(default = 0)) -GRAVATAR_TEMPLATE = "http://www.gravatar.com/avatar/%(gravatar)s?" + \ +GRAVATAR_TEMPLATE = "//www.gravatar.com/avatar/%(gravatar)s?" + \ "s=%(size)d&d=%(type)s&r=PG" def user_get_gravatar_url(self, size): diff --git a/askbot/models/question.py b/askbot/models/question.py index 5c0d5732..361ea8ad 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -42,6 +42,23 @@ class ThreadQuerySet(models.query.QuerySet): groups = [Group.objects.get_global_group()] return self.filter(groups__in=groups).distinct() + def get_for_title_query(self, search_query): + """returns threads matching title query + todo: possibly add tags + todo: implement full text search on relevant fields + """ + db_engine_name = askbot.get_database_engine_name() + if 'postgresql_psycopg2' in db_engine_name: + from askbot.search import postgresql + return postgresql.run_title_search( + self, search_query + ).order_by('-relevance') + elif 'mysql' in db_engine_name and mysql.supports_full_text_search(): + return self.filter(title__search=search_query) + else: + return self.filter(title__icontains=search_query) + + class ThreadManager(BaseQuerySetManager): def get_query_set(self): @@ -174,6 +191,7 @@ class ThreadManager(BaseQuerySetManager): def get_for_query(self, search_query, qs=None): """returns a query set of questions, matching the full text query + todo: move to query set """ if getattr(django_settings, 'ENABLE_HAYSTACK_SEARCH', False): from askbot.search.haystack import AskbotSearchQuerySet @@ -195,7 +213,7 @@ class ThreadManager(BaseQuerySetManager): ) elif 'postgresql_psycopg2' in askbot.get_database_engine_name(): from askbot.search import postgresql - return postgresql.run_full_text_search(qs, search_query) + return postgresql.run_thread_search(qs, search_query) else: return qs.filter( models.Q(title__icontains=search_query) | diff --git a/askbot/search/mysql.py b/askbot/search/mysql.py index 055b30ca..e6d2e21e 100644 --- a/askbot/search/mysql.py +++ b/askbot/search/mysql.py @@ -26,14 +26,3 @@ def supports_full_text_search(): else: SUPPORTS_FTS = False return SUPPORTS_FTS - -def get_create_full_text_index_sql(index_name, table_name, column_list): - cursor = connection.cursor() - column_sql = '(%s)' % ','.join(column_list) - sql = 'CREATE FULLTEXT INDEX %s on %s %s' % (index_name, table_name, column_sql) - cursor.execute(sql) - return sql - -def get_drop_index_sql(index_name, table_name): - return 'ALTER TABLE %s DROP INDEX %s' % (table_name, index_name) - diff --git a/askbot/search/postgresql/__init__.py b/askbot/search/postgresql/__init__.py index 5b893129..42e10c5f 100644 --- a/askbot/search/postgresql/__init__.py +++ b/askbot/search/postgresql/__init__.py @@ -20,7 +20,7 @@ def setup_full_text_search(script_path): finally: cursor.close() -def run_full_text_search(query_set, query_text): +def run_full_text_search(query_set, query_text, text_search_vector_name): """runs full text search against the query set and the search text. All words in the query text are added to the search with the & operator - i.e. @@ -29,14 +29,17 @@ def run_full_text_search(query_set, query_text): It is also assumed that we ar searching in the same table as the query set was built against, also it is assumed that the table has text search vector - stored in the column called `text_search_vector`. + stored in the column called with value of`text_search_vector_name`. """ table_name = query_set.model._meta.db_table - + rank_clause = 'ts_rank(' + table_name + \ - '.text_search_vector, plainto_tsquery(%s))' - - where_clause = table_name + '.text_search_vector @@ plainto_tsquery(%s)' + '.' + text_search_vector_name + \ + ', plainto_tsquery(%s))' + + where_clause = table_name + '.' + \ + text_search_vector_name + \ + ' @@ plainto_tsquery(%s)' search_query = '&'.join(query_text.split())#apply "AND" operator extra_params = (search_query,) @@ -46,5 +49,12 @@ def run_full_text_search(query_set, query_text): 'params': extra_params, 'select_params': extra_params, } - return query_set.extra(**extra_kwargs) + +def run_thread_search(query_set, query): + """runs search for full thread content""" + return run_full_text_search(query_set, query, 'text_search_vector'); + +def run_title_search(query_set, query): + """runs search for title and tags""" + return run_full_text_search(query_set, query, 'title_search_vector') diff --git a/askbot/search/postgresql/thread_and_post_models_27112012.plsql b/askbot/search/postgresql/thread_and_post_models_27112012.plsql new file mode 100644 index 00000000..9e90522d --- /dev/null +++ b/askbot/search/postgresql/thread_and_post_models_27112012.plsql @@ -0,0 +1,231 @@ +/* function testing for existence of a column in a table + if table does not exists, function will return "false" */ +CREATE OR REPLACE FUNCTION column_exists(colname text, tablename text) +RETURNS boolean AS +$$ +DECLARE + q text; + onerow record; +BEGIN + + q = 'SELECT attname FROM pg_attribute WHERE attrelid = ( SELECT oid FROM pg_class WHERE relname = '''||tablename||''') AND attname = '''||colname||''''; + + FOR onerow IN EXECUTE q LOOP + RETURN true; + END LOOP; + + RETURN false; +END; +$$ LANGUAGE plpgsql; + +/* function adding tsvector column to table if it does not exists */ +CREATE OR REPLACE FUNCTION add_tsvector_column(colname text, tablename text) +RETURNS boolean AS +$$ +DECLARE + q text; +BEGIN + IF NOT column_exists(colname, tablename) THEN + q = 'ALTER TABLE ' || tablename || ' ADD COLUMN ' || colname || ' tsvector'; + EXECUTE q; + RETURN true; + ELSE + q = 'UPDATE ' || tablename || ' SET ' || colname || '=NULL'; + EXECUTE q; + RETURN false; + END IF; +END; +$$ LANGUAGE plpgsql; + +/* aggregate function that concatenates tsvectors */ +CREATE OR REPLACE FUNCTION tsv_add(tsv1 tsvector, tsv2 tsvector) +RETURNS tsvector AS +$$ +BEGIN + RETURN tsv1 || tsv2; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION setup_aggregates() RETURNS boolean AS +$$ +DECLARE + onerow record; +BEGIN + FOR onerow IN SELECT * FROM pg_proc WHERE proname = 'concat_tsvectors' AND proisagg LOOP + DROP AGGREGATE concat_tsvectors(tsvector); + END LOOP; + CREATE AGGREGATE concat_tsvectors ( + BASETYPE = tsvector, + SFUNC = tsv_add, + STYPE = tsvector, + INITCOND = '' + ); + RETURN true; +END; +$$ LANGUAGE plpgsql; + +SELECT setup_aggregates(); + +/* calculates text search vector for the individual thread row +DOES not include question body post, answers or comments */ +CREATE OR REPLACE FUNCTION get_thread_tsv(title text, tagnames text) +RETURNS tsvector AS +$$ +BEGIN + /* todo add weight depending on votes */ + RETURN setweight(to_tsvector('english', coalesce(title, '')), 'A') || + setweight(to_tsvector('english', coalesce(tagnames, '')), 'A'); +END; +$$ LANGUAGE plpgsql; + +/* calculates text seanch vector for the individual question row */ +CREATE OR REPLACE FUNCTION get_post_tsv(text text, post_type text) +RETURNS tsvector AS +$$ +BEGIN + /* todo adjust weights to reflect votes */ + IF post_type='question' THEN + RETURN setweight(to_tsvector('english', coalesce(text, '')), 'B'); + ELSIF post_type='answer' THEN + /* todo reflect whether the answer acepted or has many points */ + RETURN setweight(to_tsvector('english', coalesce(text, '')), 'C'); + ELSIF post_type='comment' THEN + RETURN setweight(to_tsvector('english', coalesce(text, '')), 'D'); + ELSE + RETURN to_tsvector(''); + END IF; +END; +$$ LANGUAGE plpgsql; + +/* calculates text search vector for the question body part by thread id +here we extract question title and the text by thread_id and then +calculate the text search vector. In the future question +title will be moved to the askbot_thread table and this function +will be simpler. +*/ +CREATE OR REPLACE FUNCTION get_thread_question_tsv(thread_id integer) +RETURNS tsvector AS +$$ +DECLARE + query text; + onerow record; +BEGIN + query = 'SELECT text FROM askbot_post WHERE thread_id=' || thread_id || + ' AND post_type=''question'' AND deleted=false'; + FOR onerow in EXECUTE query LOOP + RETURN get_post_tsv(onerow.text, 'question'); + END LOOP; + RETURN to_tsvector(''); +END; +$$ LANGUAGE plpgsql; + +DROP FUNCTION IF EXISTS get_dependent_comments_tsv(object_id integer, tablename text); +CREATE OR REPLACE FUNCTION get_dependent_comments_tsv(parent_id integer) +RETURNS tsvector AS +$$ +DECLARE + query text; + onerow record; +BEGIN + query = 'SELECT concat_tsvectors(text_search_vector) FROM askbot_post' || + ' WHERE parent_id=' || parent_id || + ' AND post_type=''comment'' AND deleted=false'; + FOR onerow IN EXECUTE query LOOP + RETURN onerow.concat_tsvectors; + END LOOP; + RETURN to_tsvector(''); +END; +$$ LANGUAGE plpgsql; + +DROP FUNCTION IF EXISTS get_dependent_answers_tsv(question_id integer); +CREATE OR REPLACE FUNCTION get_dependent_answers_tsv(thread_id integer) +RETURNS tsvector AS +$$ +DECLARE + query text; + onerow record; +BEGIN + query = 'SELECT concat_tsvectors(text_search_vector) ' || + 'FROM askbot_post WHERE thread_id = ' || thread_id || + ' AND deleted=false'; + FOR onerow IN EXECUTE query LOOP + RETURN onerow.concat_tsvectors; + END LOOP; + RETURN to_tsvector(''); +END; +$$ LANGUAGE plpgsql; + +/* create tsvector columns in the content tables */ +SELECT add_tsvector_column('text_search_vector', 'askbot_thread'); +SELECT add_tsvector_column('text_search_vector', 'askbot_post'); +SELECT add_tsvector_column('title_search_vector', 'askbot_thread'); + +/* populate tsvectors with data */ +-- post tsvectors +UPDATE askbot_post set text_search_vector = get_post_tsv(text, 'comment') WHERE post_type='comment'; +UPDATE askbot_post SET text_search_vector = get_post_tsv(text, 'answer') WHERE post_type='answer'; +UPDATE askbot_post SET text_search_vector = get_post_tsv(text, 'question') WHERE post_type='question'; +UPDATE askbot_post as q SET text_search_vector = text_search_vector || + get_dependent_comments_tsv(q.id) WHERE post_type IN ('question', 'answer'); + +--thread tsvector +UPDATE askbot_thread SET text_search_vector = get_thread_tsv(title, tagnames); +UPDATE askbot_thread as t SET text_search_vector = text_search_vector || + get_dependent_answers_tsv(t.id) || + get_thread_question_tsv(t.id); +UPDATE askbot_thread SET title_search_vector = get_thread_tsv(title, tagnames); + +/* one trigger per table for tsv updates */ + +/* set up update triggers */ +CREATE OR REPLACE FUNCTION thread_update_trigger() RETURNS trigger AS +$$ +DECLARE + title_tsv tsvector; +BEGIN + title_tsv = get_thread_tsv(new.title, new.tagnames); + new.title_search_vector = title_tsv; + new.text_search_vector = title_tsv || + get_thread_question_tsv(new.id) || + get_dependent_answers_tsv(new.id); + RETURN new; +END; +$$ LANGUAGE plpgsql; +DROP TRIGGER IF EXISTS thread_search_vector_update_trigger on askbot_thread; +CREATE TRIGGER thread_search_vector_update_trigger +BEFORE UPDATE ON askbot_thread FOR EACH ROW EXECUTE PROCEDURE thread_update_trigger(); + +CREATE OR REPLACE FUNCTION thread_insert_trigger() RETURNS trigger AS +$$ +BEGIN + new.text_search_vector = get_thread_tsv(new.title, new.tagnames); + RETURN new; +END; +$$ LANGUAGE plpgsql; +DROP TRIGGER IF EXISTS thread_search_vector_insert_trigger on askbot_thread; +CREATE TRIGGER thread_search_vector_insert_trigger +BEFORE INSERT ON askbot_thread FOR EACH ROW EXECUTE PROCEDURE thread_insert_trigger(); + +/* post trigger */ +CREATE OR REPLACE FUNCTION post_trigger() RETURNS trigger AS +$$ +BEGIN + IF new.post_type = 'question' THEN + new.text_search_vector = get_post_tsv(new.text, 'question') || + get_dependent_comments_tsv(new.id); + ELSIF new.post_type = 'answer' THEN + new.text_search_vector = get_post_tsv(new.text, 'answer') || + get_dependent_comments_tsv(new.id); + ELSIF new.post_type = 'comment' THEN + new.text_search_vector = get_post_tsv(new.text, 'comment'); + END IF; + UPDATE askbot_thread SET id=new.thread_id WHERE id=new.thread_id; + return new; +END; +$$ LANGUAGE plpgsql; +DROP TRIGGER IF EXISTS post_search_vector_update_trigger on askbot_post; +CREATE TRIGGER post_search_vector_update_trigger +BEFORE INSERT OR UPDATE ON askbot_post FOR EACH ROW EXECUTE PROCEDURE post_trigger(); + +DROP INDEX IF EXISTS askbot_search_idx; +CREATE INDEX askbot_search_idx ON askbot_thread USING gin(text_search_vector); diff --git a/askbot/search/state_manager.py b/askbot/search/state_manager.py index a02f1577..7b6b746c 100644 --- a/askbot/search/state_manager.py +++ b/askbot/search/state_manager.py @@ -170,10 +170,16 @@ class SearchState(object): SAFE_CHARS = const.TAG_SEP + '_+.-' def query_string(self): + """returns part of the url to the main page, + responsible to display the full text search results, + taking into account sort method, selected scope + and search tags""" + lst = [ 'scope:' + self.scope, 'sort:' + self.sort ] + if self.query: lst.append('query:' + urllib.quote(smart_str(self.query), safe=self.SAFE_CHARS)) if self.tags: diff --git a/askbot/skins/utils.py b/askbot/skins/utils.py index e2a815b7..b04f9bda 100644 --- a/askbot/skins/utils.py +++ b/askbot/skins/utils.py @@ -169,7 +169,7 @@ def get_media_url(url, ignore_missing = False): #print after - before return url -def update_media_revision(skin = None): +def update_media_revision(skin=None): """update skin media revision number based on the contents of the skin media directory""" from askbot.conf import settings as askbot_settings diff --git a/askbot/templates/answer_edit.html b/askbot/templates/answer_edit.html index 20c0684d..887714cb 100644 --- a/askbot/templates/answer_edit.html +++ b/askbot/templates/answer_edit.html @@ -25,6 +25,7 @@ editor_type = settings.EDITOR_TYPE ) }} + <div class="answer-options"> {% if settings.WIKI_ON and answer.wiki == False %} {{ macros.checkbox_in_div(form.wiki) }} {% endif %} @@ -34,6 +35,7 @@ %} {{ macros.checkbox_in_div(form.post_privately) }} {% endif %} + </div> <div class="after-editor"> <input id="edit_post_form_submit_button" type="submit" value="{% trans %}Save edit{% endtrans %}" class="submit" /> <input type="button" value="{% trans %}Cancel{% endtrans %}" class="submit" onclick="history.back(-1);" /> diff --git a/askbot/templates/ask.html b/askbot/templates/ask.html index 27434f83..5e54ad6f 100644 --- a/askbot/templates/ask.html +++ b/askbot/templates/ask.html @@ -20,11 +20,6 @@ <script type='text/javascript' src='{{"/js/wmd/showdown.js"|media}}'></script> <script type='text/javascript' src='{{"/js/wmd/wmd.js"|media}}'></script> {% endif %} - <script type='text/javascript'> - var sortMethod = undefined;//need for live_search - var minSearchWordLength = {{settings.MIN_SEARCH_WORD_LENGTH}}; - </script> - <script type='text/javascript' src='{{"/js/live_search_new_thread.js"|media}}'></script> {% include "meta/editor_data.html" %} {% if mandatory_tags %} {% include "meta/mandatory_tags_js.html" %} @@ -33,7 +28,6 @@ {% include "meta/category_tree_js.html" %} {% endif %} <script type='text/javascript'> - askbot['urls']['api_get_questions'] = '{% url api_get_questions %}'; askbot['urls']['saveDraftQuestion'] = '{% url save_draft_question %}'; {% if settings.ENABLE_MATHJAX or settings.MARKUP_CODE_FRIENDLY %} var codeFriendlyMarkdown = true; @@ -41,7 +35,6 @@ var codeFriendlyMarkdown = false; {% endif %} $().ready(function(){ - liveSearchNewThreadInit(); //set current module button style $('#editor').TextAreaResizer(); //highlight code synctax when editor has new text diff --git a/askbot/templates/authopenid/logout.html b/askbot/templates/authopenid/logout.html index 1ac6705c..e3e1363f 100644 --- a/askbot/templates/authopenid/logout.html +++ b/askbot/templates/authopenid/logout.html @@ -7,7 +7,7 @@ <p>{% trans %}However, you still may be logged in to your OpenID provider. Please logout of your provider if you wish to do so.{% endtrans %}</p> {% if settings.FACEBOOK_KEY and settings.FACEBOOK_SECRET %} <div id="fb-root"></div> - <script src="http://connect.facebook.net/en_US/all.js"></script> + <script src="//connect.facebook.net/en_US/all.js"></script> <script> FB.init({appId: '{{settings.FACEBOOK_KEY}}', status: true, cookie: true, xfbml: true}); </script> diff --git a/askbot/templates/authopenid/providers_javascript.html b/askbot/templates/authopenid/providers_javascript.html index cd9f56b6..b0ee0ae2 100644 --- a/askbot/templates/authopenid/providers_javascript.html +++ b/askbot/templates/authopenid/providers_javascript.html @@ -39,7 +39,7 @@ </script> {% if settings.FACEBOOK_KEY and settings.FACEBOOK_SECRET %} <div id="fb-root"></div> -<script src="http://connect.facebook.net/en_US/all.js"></script> +<script src="//connect.facebook.net/en_US/all.js"></script> <script> $(document).ready(function(){ if (typeof FB != 'undefined'){ diff --git a/askbot/templates/badges.html b/askbot/templates/badges.html index ce76e76b..e669b7d4 100644 --- a/askbot/templates/badges.html +++ b/askbot/templates/badges.html @@ -5,21 +5,16 @@ <h1 class="section-title">{% trans %}Badges{% endtrans %}</h1> <p> {% trans %}Community gives you awards for your questions, answers and votes.{% endtrans %}<br/> -{% trans %}Below is the list of available badges and number - of times each type of badge has been awarded. Have ideas about fun -badges? Please, give us your <a href='{{feedback_faq_url}}'>feedback</a> -{% endtrans %} +{% trans %}Below is the list of available badges and number of times each type of badge has been awarded.{% endtrans %} </p> <div id="medalList"> {% for badge in badges %} <div style="clear:both;line-height:30px"> - <div style="float:left;min-width:30px;text-align:right;height:30px"> - {% for a in mybadges %} - {% if a.badge_id == badge.id %} - <span style="font-size:175%; padding-right:5px; color:#5B9058;">✔</span> - {% endif %} - {% endfor %} - </div> + {% if badge.id in my_badge_ids %} + <div style="float:left;min-width:30px;text-align:right;height:30px"> + <span style="font-size:175%; padding-right:5px; color:#5B9058;">✔</span> + </div> + {% endif %} <div style="float:left;width:230px;"> <a href="{{badge.get_absolute_url()}}" title="{{badge.get_type_display()}} : {{badge.description}}" diff --git a/askbot/templates/base.html b/askbot/templates/base.html index 63d7115f..4646a3a0 100644 --- a/askbot/templates/base.html +++ b/askbot/templates/base.html @@ -10,9 +10,11 @@ {% if settings.GOOGLE_SITEMAP_CODE %} <meta name="google-site-verification" content="{{settings.GOOGLE_SITEMAP_CODE}}" /> {% endif %} + <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> <link rel="shortcut icon" href="{{ settings.SITE_FAVICON|media }}" /> {% block before_css %}{% endblock %} {% include "meta/html_head_stylesheets.html" %} + {% include "meta/fonts.html" %} {% block forestyle %}{% endblock %} {% include "meta/html_head_javascript.html" %} {% block forejs %}{% endblock %} diff --git a/askbot/templates/embed/ask_by_widget.html b/askbot/templates/embed/ask_by_widget.html index 4cec5f6d..dc3db806 100644 --- a/askbot/templates/embed/ask_by_widget.html +++ b/askbot/templates/embed/ask_by_widget.html @@ -6,11 +6,7 @@ {%else %} <link href="{{'/bootstrap/css/bootstrap.css'|media}}" rel="stylesheet" type="text/css" /> {%endif%} - {% if settings.USE_LOCAL_FONTS %} - {% include "meta/fonts.html" %} - {% else %} - <link href='http://fonts.googleapis.com/css?family=Open+Sans+Condensed:400,700&subset=latin,cyrillic-ext,latin-ext' rel='stylesheet' type='text/css' /> - {% endif %} + {% include "meta/fonts.html" %} <style type="text/css" media="screen"> body{ font-family: Verdana, Arial, Helvetica, sans-serif; @@ -213,13 +209,15 @@ <script type="text/javascript" src='{{"/js/live_search_new_thread.js"|media}}'></script> <script type="text/javascript" charset="utf-8"> - var minSearchWordLength = {{settings.MIN_SEARCH_WORD_LENGTH}}; - askbot['urls']['api_get_questions'] = '{% url api_get_questions %}'; + askbot['settings']['minSearchWordLength'] = {{settings.MIN_SEARCH_WORD_LENGTH}}; + askbot['urls']['titleSearch'] = '{% url title_search %}'; askbot['urls']['upload'] = '{% url upload %}'; $(document).ready(function(){ - $("#id_title").focus(); - $("#id_title").addClass('questionTitleInput'); - liveSearchNewThreadInit(true); + var searchInput = $('#id_title'); + searchInput.addClass('questionTitleInput'); + var search = new FullTextSearch(); + search.decorate(searchInput); + searchInput.focus(); }); </script> {% endblock %} diff --git a/askbot/templates/embed/askbot_widget.js b/askbot/templates/embed/askbot_widget.js index 8c358855..300d3dd8 100755 --- a/askbot/templates/embed/askbot_widget.js +++ b/askbot/templates/embed/askbot_widget.js @@ -10,8 +10,10 @@ var {{variable_name}} = { toHtml: function() { var html = {{variable_name}}.createButton(); var link = document.createElement('link'); + var protocol = document.location.protocol; + link.setAttribute("rel", "stylesheet"); - link.setAttribute("href", 'http://{{host}}{%url render_ask_widget_css widget.id%}'); + link.setAttribute("href", protocol + '//{{host}}{%url render_ask_widget_css widget.id%}'); //creating the div var motherDiv = document.createElement('div'); @@ -36,7 +38,7 @@ var {{variable_name}} = { containerDiv.appendChild(closeButton); var iframe = document.createElement('iframe'); - iframe.setAttribute('src', 'http://{{host}}{% url ask_by_widget widget.id %}'); + iframe.setAttribute('src', protocol + '//{{host}}{% url ask_by_widget widget.id %}'); containerDiv.appendChild(iframe); diff --git a/askbot/templates/embed/list_widgets.html b/askbot/templates/embed/list_widgets.html index 83de5871..cdbcd219 100644 --- a/askbot/templates/embed/list_widgets.html +++ b/askbot/templates/embed/list_widgets.html @@ -10,19 +10,24 @@ <th>Code</th> <th>Actions</th> </tr> + {% if request.is_secure() %} + {% set protocol='https' %} + {% else %} + {% set protocol='http' %} + {% endif %} {% if widget_name == 'ask' %} - {%for widget in widgets%} - <tr> - <td>{{widget.title}}</td> - <td> <script type="text/javascript" src="http://{{request.get_host()}}{% url render_ask_widget widget.id%}" ></script></td> - <td><a href="{% url edit_widget widget_name, widget.id %}">Edit</a> | <a href="{% url delete_widget widget_name, widget.id %}">Delete</a></td> - </tr> - {%endfor%} + {%for widget in widgets%} + <tr> + <td>{{widget.title}}</td> + <td> <script type="text/javascript" src="{{ protocol }}://{{request.get_host()}}{% url render_ask_widget widget.id%}" ></script></td> + <td><a href="{% url edit_widget widget_name, widget.id %}">Edit</a> | <a href="{% url delete_widget widget_name, widget.id %}">Delete</a></td> + </tr> + {%endfor%} {%else%} {%for widget in widgets%} <tr> <td>{{widget.title}}</td> - <td> <iframe src="http://{{request.get_host()}}{% url question_widget widget.id%}" > </iframe></td> + <td> <iframe src="{{ protocol }}://{{request.get_host()}}{% url question_widget widget.id%}" > </iframe></td> <td><a href="{% url edit_widget widget_name, widget.id %}">Edit</a> | <a href="{% url delete_widget widget_name, widget.id %}">Delete</a></td> </tr> {%endfor%} diff --git a/askbot/templates/macros.html b/askbot/templates/macros.html index 55113377..02a97cc7 100644 --- a/askbot/templates/macros.html +++ b/askbot/templates/macros.html @@ -498,11 +498,11 @@ for the purposes of the AJAX comment editor #} title="{{desc_tooltip}}"><span>{{label}}</span></a> {% endif %} <script type="text/javascript">{# need to pass on text translations to js #} - var sortButtonData = sortButtonData || {}; - sortButtonData["{{key_name}}"] = { - label: "{{label}}", - asc_tooltip: "{{asc_tooltip}}", - desc_tooltip: "{{desc_tooltip}}" + askbot['data']['sortButtonData'] = askbot['data']['sortButtonData'] || {}; + askbot['data']['sortButtonData']['{{key_name}}'] = { + label: '{{label}}', + asc_tooltip: '{{asc_tooltip}}', + desc_tooltip: '{{desc_tooltip}}' }; </script> {%- endmacro %} @@ -631,7 +631,7 @@ answer {% if answer.accepted() %}accepted-answer{% endif %} {% if answer.author_ {#todo: rename this to avatar #} {%- macro gravatar(user, size) -%} {% spaceless %} -<a style="text-decoration:none" +<a class="avatar-box" href="{{ user.get_absolute_url() }}" ><img class="gravatar" width="{{size}}" height="{{size}}" diff --git a/askbot/templates/main_page/javascript.html b/askbot/templates/main_page/javascript.html index f1e0fb44..7ad02e29 100644 --- a/askbot/templates/main_page/javascript.html +++ b/askbot/templates/main_page/javascript.html @@ -1,12 +1,9 @@ <script type="text/javascript"> /*<![CDATA[*/ - var sortMethod = '{{sort}}'; - var showSortByRelevance = {% if show_sort_by_relevance %}true{% else %}false{% endif %}; - var minSearchWordLength = {{settings.MIN_SEARCH_WORD_LENGTH}}; + askbot['settings']['showSortByRelevance'] = {% if show_sort_by_relevance %}true{% else %}false{% endif %}; $(document).ready(function(){ /*var on_tab = '#nav_questions'; $(on_tab).attr('className','on');*/ - liveSearch('{{ search_state.query_string()|escapejs }}'); Hilite.exact = false; Hilite.elementid = "question-list"; Hilite.debug_referrer = location.href; @@ -21,7 +18,6 @@ askbot['urls']['mark_subscribed_tag'] = '{% url mark_subscribed_tag %}'; askbot['urls']['unmark_tag'] = '{% url unmark_tag %}'; askbot['urls']['set_tag_filter_strategy'] = '{% url "set_tag_filter_strategy" %}'; - askbot['urls']['questions'] = '{% url "questions" %}'; askbot['urls']['question_url_template'] = scriptUrl + '{{'question/'|transurl}}{{ "{{QuestionID}}/" }}'; if (Modernizr.history) { @@ -39,7 +35,7 @@ } if (hash !== '' && hash !== undefined && url !== undefined){ {# was this causing strange redirects in IE??? #} - window.location = 'http://' + window.location.host + url; + window.location = document.location.protocol + '//' + window.location.host + url; } } /*]]>*/ @@ -48,4 +44,3 @@ {% if request.user.is_authenticated() %} <script type='text/javascript' src='{{"/js/tag_selector.js"|media}}'></script> {% endif %} -<script type="text/javascript" src='{{"/js/live_search.js"|media}}'></script> diff --git a/askbot/templates/main_page/tab_bar.html b/askbot/templates/main_page/tab_bar.html index 37f63012..c0b37955 100644 --- a/askbot/templates/main_page/tab_bar.html +++ b/askbot/templates/main_page/tab_bar.html @@ -33,8 +33,8 @@ </a> {% endif %} <script type="text/javascript"> - var sortButtonData = sortButtonData || {}; - sortButtonData['relevance'] = { + askbot['data']['sortButtonData'] = askbot['data']['sortButtonData'] || {}; + askbot['data']['sortButtonData']['relevance'] = { asc_tooltip: "{{asc_relevance_tooltip}}", desc_tooltip: "{{desc_relevance_tooltip}}", label: "{{relevance_label}}" diff --git a/askbot/templates/meta/bottom_scripts.html b/askbot/templates/meta/bottom_scripts.html index 9b754caa..4b2bb6f8 100644 --- a/askbot/templates/meta/bottom_scripts.html +++ b/askbot/templates/meta/bottom_scripts.html @@ -24,8 +24,12 @@ askbot['urls']['follow_user'] = '/followit/follow/user/{{'{{'}}userId{{'}}'}}/'; askbot['urls']['unfollow_user'] = '/followit/unfollow/user/{{'{{'}}userId{{'}}'}}/'; askbot['urls']['user_signin'] = '{{ settings.LOGIN_URL }}'; - askbot['settings']['static_url'] = '{{ settings.STATIC_URL }}'; askbot['urls']['getEditor'] = '{% url "get_editor" %}'; + askbot['urls']['titleSearch'] = '{% url "title_search" %}'; + askbot['urls']['ask'] = '{% url "ask" %}'; + askbot['urls']['questions'] = '{% url "questions" %}'; + askbot['settings']['static_url'] = '{{ settings.STATIC_URL }}'; + askbot['settings']['minSearchWordLength'] = {{settings.MIN_SEARCH_WORD_LENGTH}}; </script> <script type="text/javascript" @@ -39,6 +43,7 @@ <!-- History.js --> <script type='text/javascript' src="{{"/js/jquery.history.js"|media }}"></script> <script type='text/javascript' src="{{"/js/utils.js"|media }}"></script> +<script type="text/javascript" src="{{'/js/live_search.js'|media}}"></script> {% if settings.ENABLE_MATHJAX %} <script type='text/javascript' src="{{settings.MATHJAX_BASE_URL}}/MathJax.js"> MathJax.Hub.Config({ @@ -52,13 +57,37 @@ /*<![CDATA[*/ $(document).ready(function(){ // focus input on the search bar endcomment - {% if active_tab in ('users', 'questions', 'tags') %} - $('#keywords').focus(); + {% if active_tab in ('users', 'questions', 'tags', 'badges') %} + var searchInput = $('#keywords'); {% elif active_tab == 'ask' %} - $('#id_title').focus(); + var searchInput = $('#id_title'); {% else %} + var searchInput = undefined; animateHashes(); {% endif %} + + if (searchInput) { + searchInput.focus(); + putCursorAtEnd(searchInput); + } + + {% if active_tab in ('questions', 'badges', 'ask') %} + if (searchInput) { + var search = new FullTextSearch(); + askbot['controllers'] = askbot['controllers'] || {}; + askbot['controllers']['fullTextSearch'] = search; + {% if search_state %} + search.setSearchUrl('{{ search_state.query_string()|escapejs }}'); + {% else %} + search.setSearchUrl(''); + {% endif %} + {% if active_tab == 'ask' %} + search.setAskButtonEnabled(false); + {% endif %} + search.decorate(searchInput); + } + {% endif %} + if (askbot['data']['userIsAdminOrMod']) { $('body').addClass('admin'); } diff --git a/askbot/templates/meta/fonts.html b/askbot/templates/meta/fonts.html index e8e54a8f..1e0fe707 100644 --- a/askbot/templates/meta/fonts.html +++ b/askbot/templates/meta/fonts.html @@ -1,8 +1,15 @@ -<style type="text/css"> -@font-face { - font-family: 'Open Sans Condensed'; - font-style: normal; - font-weight: 700; - src: url('{{"/images/OpenSans-CondBold.ttf"|media}}'); -} -</style> +{% if settings.USE_LOCAL_FONTS %} + {# this version is for serving fonts locally - e.g. for intranet sites #} + <style type="text/css"> + @font-face { + font-family: 'Open Sans Condensed'; + font-style: normal; + font-weight: 700; + src: url('{{"/images/OpenSans-CondBold.ttf"|media}}'); + } + </style> +{% else %} + {# note: for IE8 we ask for fonts separately #} + <link href='//fonts.googleapis.com/css?family=Open+Sans+Condensed:700&subset=latin-ext' rel='stylesheet' type='text/css'> + <link href='//fonts.googleapis.com/css?family=Open+Sans+Condensed:700&subset=cyrillic-ext' rel='stylesheet' type='text/css'> +{% endif %} diff --git a/askbot/templates/meta/html_head_stylesheets.html b/askbot/templates/meta/html_head_stylesheets.html index 85bb489c..23750239 100644 --- a/askbot/templates/meta/html_head_stylesheets.html +++ b/askbot/templates/meta/html_head_stylesheets.html @@ -9,13 +9,6 @@ <script type="text/javascript" src="{{"/js/less.min.js"|media}}"></script> {% endif %} <link href="{{'/bootstrap/css/bootstrap.css'|media}}" rel="stylesheet" type="text/css" /> -{% if settings.USE_LOCAL_FONTS %} - {% include "meta/fonts.html" %} -{% else %} - {# note: IE8 fix - a combined font link wont work so we have two #} - <link href='http://fonts.googleapis.com/css?family=Open+Sans+Condensed:700&subset=latin-ext' rel='stylesheet' type='text/css'> - <link href='http://fonts.googleapis.com/css?family=Open+Sans+Condensed:700&subset=cyrillic-ext' rel='stylesheet' type='text/css'> -{% endif %} {{ skin.get_extra_css_link() }} {% if settings.USE_CUSTOM_CSS %} <link diff --git a/askbot/templates/question/javascript.html b/askbot/templates/question/javascript.html index 5dca2522..5871ee5f 100644 --- a/askbot/templates/question/javascript.html +++ b/askbot/templates/question/javascript.html @@ -55,7 +55,7 @@ if (window.location.hash === 'fmanswer'){ $('#fmanswer textarea').focus(); } - {% if settings.ENABLE_SHARING_GOOGLE %}$.getScript("http://apis.google.com/js/plusone.js"){% endif %} + {% if settings.ENABLE_SHARING_GOOGLE %}$.getScript("//apis.google.com/js/plusone.js"){% endif %} {% if request.user.id == question.author_id %} $("#fmanswer_button").click(function() { diff --git a/askbot/templates/user_profile/user_stats.html b/askbot/templates/user_profile/user_stats.html index dc3d97e0..aa5aa07e 100644 --- a/askbot/templates/user_profile/user_stats.html +++ b/askbot/templates/user_profile/user_stats.html @@ -158,8 +158,5 @@ }); </script> <script type='text/javascript' src='{{"/js/tag_selector.js"|media}}'></script> - <script type="text/javascript"> - askbot['urls']['questions'] = '{% url "questions" %}'; - </script> {% endblock %} <!-- end user_stats.html --> diff --git a/askbot/templates/widgets/ask_form.html b/askbot/templates/widgets/ask_form.html index 0f851fee..87f3c4a6 100644 --- a/askbot/templates/widgets/ask_form.html +++ b/askbot/templates/widgets/ask_form.html @@ -1,16 +1,18 @@ {% import "macros.html" as macros %} <form id="fmask" action="" method="post" >{% csrf_token %} <div class="form-item"> - <div id="askFormBar"> - {% if not request.user.is_authenticated() %} + {% if not request.user.is_authenticated() %} <p>{% trans %}<span class=\"strong big\">You are welcome to start submitting your question anonymously</span>. When you submit the post, you will be redirected to the login/signup page. Your question will be saved in the current session and will be published after you log in. Login/signup process is very simple. Login takes about 30 seconds, initial signup takes a minute or less.{% endtrans %}</p> - {% else %} - {% if settings.EMAIL_VALIDATION %} - {% if not request.user.email_isvalid %} - {% trans email=request.user.email %}<span class='strong big'>Looks like your email address, %(email)s has not yet been validated.</span> To post messages you must verify your email, please see <a href='%(email_validation_faq_url)s'>more details here</a>.<br>You can submit your question now and validate email after that. Your question will saved as pending meanwhile.{% endtrans %} - {% endif %} + {% else %} + {% if settings.EMAIL_VALIDATION %} + {% if not request.user.email_isvalid %} + <p>{% trans email=request.user.email %}<span class='strong big'>Looks like your email address, %(email)s has not yet been validated.</span> To post messages you must verify your email, please see <a href='%(email_validation_faq_url)s'>more details here</a>.<br>You can submit your question now and validate email after that. Your question will saved as pending meanwhile.{% endtrans %}</p> {% endif %} {% endif %} + {% endif %} + </div> + <div class="form-item ask-form-bar"> + <div id="askFormBar"> <input id="id_title" class="questionTitleInput" name="title" autocomplete="off" value="{% if form.initial.title %}{{form.initial.title|escape}}{% endif %}"/> <span class="form-error">{{ form.title.errors }}</span> @@ -19,7 +21,6 @@ {{ form.title.help_text }} </div> </div> - <div id='question-list'></div> {{ macros.edit_post( form, diff --git a/askbot/templates/widgets/edit_post.html b/askbot/templates/widgets/edit_post.html index b9bfa1e3..57770570 100644 --- a/askbot/templates/widgets/edit_post.html +++ b/askbot/templates/widgets/edit_post.html @@ -64,7 +64,7 @@ </div> {% endif %} {% if 'summary' in post_form['fields'] %} - <div class="form-item"> + <div class="form-item revision-comment"> <strong>{{ post_form.summary.label_tag() }}</strong> <br/> {{ post_form.summary }} <div class="title-desc"> diff --git a/askbot/templates/widgets/markdown_help.html b/askbot/templates/widgets/markdown_help.html index 9816fe26..cea8847b 100644 --- a/askbot/templates/widgets/markdown_help.html +++ b/askbot/templates/widgets/markdown_help.html @@ -17,7 +17,7 @@ </li> {% endif %} <li> - <b>{% trans %}link{% endtrans %}</b>:[{% trans %}text{% endtrans %}](http://url.com/ "{% trans %}title{% endtrans %}") + <b>{% trans %}link{% endtrans %}</b>:[{% trans %}text{% endtrans %}](http://example.com/ "{% trans %}title{% endtrans %}") </li> <li> diff --git a/askbot/templates/widgets/scope_nav.html b/askbot/templates/widgets/scope_nav.html index a6bda630..b68d899c 100644 --- a/askbot/templates/widgets/scope_nav.html +++ b/askbot/templates/widgets/scope_nav.html @@ -1,3 +1,4 @@ +<div id="scopeNav"> {% if active_tab != "ask" %} {% if not search_state %} {# get empty SearchState() if there's none #} {% set search_state=search_state|get_empty_search_state %} @@ -13,3 +14,4 @@ {% else %} <div class="scope-selector ask-message">{% trans %}Please ask your question here{% endtrans %}</div> {% endif %} +</div> diff --git a/askbot/templates/widgets/search_bar.html b/askbot/templates/widgets/search_bar.html index 59c4fd58..8c485c73 100644 --- a/askbot/templates/widgets/search_bar.html +++ b/askbot/templates/widgets/search_bar.html @@ -1,17 +1,7 @@ {% if active_tab != "ask" %} {% spaceless %} -<div id="searchBar"> +<div id="searchBar" {% if query %}class="cancelable"{% endif %}> {# url action depends on which tab is active #} - <form - {% if active_tab == "tags" %} - action="{% url tags %}" - {% elif active_tab == "users" %} - action="{% url users %}" - {% else %} - action="{% url questions %}" id="searchForm" - {% endif %} - method="get"> - <input type="submit" value="" name="search" class="searchBtn" /> {% if active_tab == "tags" %} <input type="hidden" name="t" value="tag"/> {% else %} @@ -21,26 +11,13 @@ {% endif %} {# class was searchInput #} <input - {% if query %} - class="searchInputCancelable" - {% else %} class="searchInput" - {% endif %} type="text" autocomplete="off" value="{{ query|default_if_none('') }}" name="query" id="keywords" /> - <input type="button" - value="X" - name="reset_query" - class="cancelSearchBtn" - {% if not query %}{# query is only defined by questions view (active_tab) #} - style="display: none;" - {% endif %} - /> - </form> </div> {% endspaceless %} {% endif %} diff --git a/askbot/templates/widgets/secondary_header.html b/askbot/templates/widgets/secondary_header.html index caf190bc..f0aca706 100644 --- a/askbot/templates/widgets/secondary_header.html +++ b/askbot/templates/widgets/secondary_header.html @@ -1,12 +1,45 @@ <!-- template secondary_header.html --> <div id="secondaryHeader"> <div class="content-wrapper"> - <a id="homeButton" href="{% url questions %}"></a> - <div id="scopeWrapper"> - {% include "widgets/scope_nav.html" %} + {# form is wrapping search buttons and the search bar inputs #} + <form + {% if active_tab == "tags" %} + action="{% url tags %}" + {% elif active_tab == "users" %} + action="{% url users %}" + {% else %} + action="{% url questions %}" id="searchForm" + {% endif %} + method="get"> + <div> + {# + Some or all contents of this div may be dropped + over the search bar via negative margins, + to make sure that the search bar can occupy 100% + of the content width. + Search bar may have padding on the left and right + to accomodate the buttons. + #} + <a id="homeButton" href="{% url questions %}"></a> + {% include "widgets/scope_nav.html" %} + {# + three buttons below are in the opposite order because + they are floated at the right + #} + {% include "widgets/ask_button.html" %} + <input type="submit" value="" name="search" class="searchBtn" /> + <input type="button" + value="X" + name="reset_query" + class="cancelSearchBtn" + {% if not query %}{# query is only defined by questions view (active_tab) #} + style="display: none;" + {% endif %} + /> + {# clears button floats #} + <div class="clearfix"></div> + </div> {% include "widgets/search_bar.html" %} {# include search form widget #} - </div> - {% include "widgets/ask_button.html" %} - <div class="clean"></div> + </form> </div> </div> diff --git a/askbot/templates/widgets/user_navigation.html b/askbot/templates/widgets/user_navigation.html index 06b0cdb9..4cb6314a 100644 --- a/askbot/templates/widgets/user_navigation.html +++ b/askbot/templates/widgets/user_navigation.html @@ -20,7 +20,7 @@ <a href="{{ settings.LOGIN_URL }}?next={{request.path|clean_login_url}}">{% trans %}Hi there! Please sign in{% endtrans %}</a> {% endif %} {% if request.user.is_authenticated() and request.user.is_administrator() %} - <a href="{% url site_settings %}">{% trans %}settings{% endtrans %}</a> - <a href="{% url widgets %}">{% trans %}widgets{% endtrans %}</a> + <a class="settings" href="{% url site_settings %}">{% trans %}settings{% endtrans %}</a> + <a class="widgets" href="{% url widgets %}">{% trans %}widgets{% endtrans %}</a> {% endif %} - <a href="{% url "help" %}" title="{% trans %}help{% endtrans %}">{% trans %}help{% endtrans %}</a> + <a class="help" href="{% url "help" %}" title="{% trans %}help{% endtrans %}">{% trans %}help{% endtrans %}</a> diff --git a/askbot/tests/page_load_tests.py b/askbot/tests/page_load_tests.py index 1e5ce903..91b8015d 100644 --- a/askbot/tests/page_load_tests.py +++ b/askbot/tests/page_load_tests.py @@ -153,14 +153,14 @@ class PageLoadTestCase(AskbotTestCase): self.proto_test_ask_page(True, 200) @with_settings(GROUPS_ENABLED=False) - def test_api_get_questions_groups_disabled(self): - data = {'query': 'Question'} - response = self.client.get(reverse('api_get_questions'), data) + def test_title_search_groups_disabled(self): + data = {'query_text': 'Question'} + response = self.client.get(reverse('title_search'), data) data = simplejson.loads(response.content) self.assertTrue(len(data) > 1) @with_settings(GROUPS_ENABLED=True) - def test_api_get_questions_groups_enabled(self): + def test_title_search_groups_enabled(self): group = models.Group(name='secret group', openness=models.Group.OPEN) group.save() @@ -169,14 +169,14 @@ class PageLoadTestCase(AskbotTestCase): question = self.post_question(user=user, title='alibaba', group_id=group.id) #ask for data anonymously - should get nothing - query_data = {'query': 'alibaba'} - response = self.client.get(reverse('api_get_questions'), query_data) + query_data = {'query_text': 'alibaba'} + response = self.client.get(reverse('title_search'), query_data) response_data = simplejson.loads(response.content) self.assertEqual(len(response_data), 0) #log in - should get the question self.client.login(method='force', user_id=user.id) - response = self.client.get(reverse('api_get_questions'), query_data) + response = self.client.get(reverse('title_search'), query_data) response_data = simplejson.loads(response.content) self.assertEqual(len(response_data), 1) diff --git a/askbot/urls.py b/askbot/urls.py index 4694b38c..9bb00406 100644 --- a/askbot/urls.py +++ b/askbot/urls.py @@ -13,7 +13,7 @@ from askbot.sitemap import QuestionsSitemap from askbot.skins.utils import update_media_revision admin.autodiscover() -update_media_revision()#needs to be run once, so put it here +#update_media_revision()#needs to be run once, so put it here if getattr(settings, "ASKBOT_TRANSLATE_URL", False): from django.utils.translation import ugettext as _ @@ -74,9 +74,9 @@ urlpatterns = patterns('', # END main page urls url( - r'^api/get_questions/', - views.commands.api_get_questions, - name='api_get_questions' + r'^api/title_search/', + views.commands.title_search, + name='title_search' ), url( r'^get-thread-shared-users/', diff --git a/askbot/views/commands.py b/askbot/views/commands.py index 2f60c88b..1663a049 100644 --- a/askbot/views/commands.py +++ b/askbot/views/commands.py @@ -696,31 +696,35 @@ def subscribe_for_tags(request): @decorators.get_only -def api_get_questions(request): - """json api for retrieving questions""" - query = request.GET.get('query', '').strip() - if not query: +def title_search(request): + """json api for retrieving questions by title match""" + query = request.GET.get('query_text') + + if query is None: return HttpResponseBadRequest('Invalid query') + query = query.strip() + if askbot_settings.GROUPS_ENABLED: threads = models.Thread.objects.get_visible(user=request.user) else: threads = models.Thread.objects.all() - threads = models.Thread.objects.get_for_query( - search_query=query, - qs=threads - ) - - if conf.should_show_sort_by_relevance(): - threads = threads.extra(order_by = ['-relevance']) + threads = threads.get_for_title_query(query) #todo: filter out deleted threads, for now there is no way threads = threads.distinct()[:30] - thread_list = [{ - 'title': escape(thread.title), - 'url': thread.get_absolute_url(), - 'answer_count': thread.get_answer_count(request.user) - } for thread in threads] + + thread_list = list() + for thread in threads:#todo: this is a temp hack until thread model is fixed + try: + thread_list.append({ + 'title': escape(thread.title), + 'url': thread.get_absolute_url(), + 'answer_count': thread.get_answer_count(request.user) + }) + except: + continue + json_data = simplejson.dumps(thread_list) return HttpResponse(json_data, mimetype = "application/json") diff --git a/askbot/views/meta.py b/askbot/views/meta.py index d0c21965..092004e7 100644 --- a/askbot/views/meta.py +++ b/askbot/views/meta.py @@ -143,21 +143,19 @@ def badges(request):#user status/reputation system raise Http404 known_badges = badge_data.BADGES.keys() badges = BadgeData.objects.filter(slug__in = known_badges).order_by('slug') - my_badges = [] + my_badge_ids = list() if request.user.is_authenticated(): - my_badges = Award.objects.filter( + my_badge_ids = Award.objects.filter( user=request.user - ).values( - 'badge_id' + ).values_list( + 'badge_id', flat=True ).distinct() - #my_badges.query.group_by = ['badge_id'] data = { 'active_tab': 'badges', 'badges' : badges, 'page_class': 'meta', - 'mybadges' : my_badges, - 'feedback_faq_url' : reverse('feedback'), + 'my_badge_ids' : my_badge_ids } return render(request, 'badges.html', data) diff --git a/askbot_requirements.txt b/askbot_requirements.txt index 2be12b8e..60f76d52 100644 --- a/askbot_requirements.txt +++ b/askbot_requirements.txt @@ -1,5 +1,5 @@ akismet -django==1.3.1 +django>=1.3.1 Jinja2 Coffin>=0.3 South>=0.7.1 diff --git a/askbot_requirements_dev.txt b/askbot_requirements_dev.txt index b960c76e..4a90315f 100644 --- a/askbot_requirements_dev.txt +++ b/askbot_requirements_dev.txt @@ -1,5 +1,5 @@ akismet -django==1.3.1 +django==1.4.2 Jinja2 Coffin>=0.3 South>=0.7.1 |