diff options
22 files changed, 684 insertions, 289 deletions
diff --git a/askbot/deps/django_authopenid/backends.py b/askbot/deps/django_authopenid/backends.py index 5ff49c1b..48fcc45e 100644 --- a/askbot/deps/django_authopenid/backends.py +++ b/askbot/deps/django_authopenid/backends.py @@ -127,10 +127,12 @@ def ldap_authenticate(username, password): common_name = user_information[common_name_field][0] first_name, last_name = split_name(common_name, common_name_format) + #here we have an opportunity to copy password in the auth_user table + #but we don't do it for security reasons try: user = User.objects.get(username__exact=exact_username) # always update user profile to synchronize with ldap server - user.set_password(password) + user.set_unusable_password() #user.first_name = first_name #user.last_name = last_name user.email = email @@ -139,7 +141,7 @@ def ldap_authenticate(username, password): # create new user in local db user = User() user.username = exact_username - user.set_password(password)#copy password from LDAP locally + user.set_unusable_password() #user.first_name = first_name #user.last_name = last_name user.email = email diff --git a/askbot/deps/django_authopenid/views.py b/askbot/deps/django_authopenid/views.py index a3ecbbab..e63988fb 100644 --- a/askbot/deps/django_authopenid/views.py +++ b/askbot/deps/django_authopenid/views.py @@ -322,7 +322,7 @@ def signin(request): login(request, user) return HttpResponseRedirect(next_url) else: - user.message_set.create(_('incorrect user name or password')) + request.user.message_set.create(_('Incorrect user name or password')) return HttpResponseRedirect(request.path) else: if password_action == 'login': diff --git a/askbot/doc/source/optional-modules.rst b/askbot/doc/source/optional-modules.rst index 54043c1e..d56cbe42 100644 --- a/askbot/doc/source/optional-modules.rst +++ b/askbot/doc/source/optional-modules.rst @@ -103,7 +103,16 @@ The parameters are (note that some have pre-set defaults that might work for you * user id field name (``LDAP_USERID_FIELD``) * email field name (``LDAP_EMAIL_FIELD``) * user name filter template (``LDAP_USERNAME_FILTER_TEMPLATE``) -* user name filter template - must have two string placeholders. + must have two string placeholders. +* given (first) name field (``LDAP_GIVEN_NAME_FIELD``) +* surname (last name) field (``LDAP_SURNAME_FIELD``) +* common name field (``LDAP_COMMON_NAME_FIELD``) + either given and surname should be used or common name. + All three are not necessary - either first two or common. + These fields are used to extract users first and last names. +* Format of common name (``LDAP_COMMON_NAME_FIELD_FORMAT``) + values can be only 'first,last' or 'last,first' - used to + extract last and first names from common name There are three more optional parameters that must go to the ``settings.py`` file:: @@ -124,6 +133,10 @@ you might need to :ref:`debug <debugging>` the settings. The function to look at is `askbot.deps.django_authopenid.backends.ldap_authenticate`. If you have problems with LDAP please contact us at support@askbot.com. +The easiest way to debug - insert ``import pdb; pdb.set_trace()`` line into function +`askbot.deps.django_authopenid.backends.ldap_authenticate`, +start the ``runserver`` and step through. + Uploaded avatars ================ diff --git a/askbot/skins/common/media/js/editor.js b/askbot/skins/common/media/js/editor.js index ae4f5aea..c6f1c873 100644 --- a/askbot/skins/common/media/js/editor.js +++ b/askbot/skins/common/media/js/editor.js @@ -16,65 +16,66 @@ Ajax upload */jQuery.extend({createUploadIframe:function(d,b){var a="jUploadFrame"+d;if(window.ActiveXObject){var c=document.createElement('<iframe id="'+a+'" name="'+a+'" />');if(typeof b=="boolean"){c.src="javascript:false"}else{if(typeof b=="string"){c.src=b}}}else{var c=document.createElement("iframe");c.id=a;c.name=a}c.style.position="absolute";c.style.top="-1000px";c.style.left="-1000px";document.body.appendChild(c);return c},createUploadForm:function(g,b){var e="jUploadForm"+g;var a="jUploadFile"+g;var d=$('<form action="" method="POST" name="'+e+'" id="'+e+'" enctype="multipart/form-data"></form>');var c=$("#"+b);var f=$(c).clone();$(c).attr("id",a);$(c).before(f);$(c).appendTo(d);$(d).css("position","absolute");$(d).css("top","-1200px");$(d).css("left","-1200px");$(d).appendTo("body");return d},ajaxFileUpload:function(k){k=jQuery.extend({},jQuery.ajaxSettings,k);var a=new Date().getTime();var b=jQuery.createUploadForm(a,k.fileElementId);var i=jQuery.createUploadIframe(a,k.secureuri);var h="jUploadFrame"+a;var j="jUploadForm"+a;if(k.global&&!jQuery.active++){jQuery.event.trigger("ajaxStart")}var c=false;var f={};if(k.global){jQuery.event.trigger("ajaxSend",[f,k])}var d=function(l){var p=document.getElementById(h);try{if(p.contentWindow){f.responseText=p.contentWindow.document.body?p.contentWindow.document.body.innerText:null;f.responseXML=p.contentWindow.document.XMLDocument?p.contentWindow.document.XMLDocument:p.contentWindow.document}else{if(p.contentDocument){f.responseText=p.contentDocument.document.body?p.contentDocument.document.body.textContent||document.body.innerText:null;f.responseXML=p.contentDocument.document.XMLDocument?p.contentDocument.document.XMLDocument:p.contentDocument.document}}}catch(o){jQuery.handleError(k,f,null,o)}if(f||l=="timeout"){c=true;var m;try{m=l!="timeout"?"success":"error";if(m!="error"){var n=jQuery.uploadHttpData(f,k.dataType);if(k.success){k.success(n,m)}if(k.global){jQuery.event.trigger("ajaxSuccess",[f,k])}}else{jQuery.handleError(k,f,m)}}catch(o){m="error";jQuery.handleError(k,f,m,o)}if(k.global){jQuery.event.trigger("ajaxComplete",[f,k])}if(k.global&&!--jQuery.active){jQuery.event.trigger("ajaxStop")}if(k.complete){k.complete(f,m)}jQuery(p).unbind();setTimeout(function(){try{$(p).remove();$(b).remove()}catch(q){jQuery.handleError(k,f,null,q)}},100);f=null}};if(k.timeout>0){setTimeout(function(){if(!c){d("timeout")}},k.timeout)}try{var b=$("#"+j);$(b).attr("action",k.url);$(b).attr("method","POST");$(b).attr("target",h);if(b.encoding){b.encoding="multipart/form-data"}else{b.enctype="multipart/form-data"}$(b).submit()}catch(g){jQuery.handleError(k,f,null,g)}if(window.attachEvent){document.getElementById(h).attachEvent("onload",d)}else{document.getElementById(h).addEventListener("load",d,false)}return{abort:function(){}}},uploadHttpData:function(r,type){var data=!type;data=type=="xml"||data?r.responseXML:r.responseText;if(type=="script"){jQuery.globalEval(data)}if(type=="json"){eval("data = "+data)}if(type=="html"){jQuery("<div>").html(data).evalScripts()}return data}}); /** * Upload call. Used only once in the wmd file upload - * this is "tightly coupled" with the wmd file uploader - * @param {Object} jquery object imageUrl - where the - * uploaded url must be inserted on successful upload - * @param {Function} handler that is run upon change - * of the file upload field + * this is used in the wmd file uploader and the + * askbots image and attachment upload plugins + * @todo refactor this code to "new style" */ -function ajaxFileUpload(imageUrl, startUploadHandler) -{ - $("#loading").ajaxStart(function(){ - $(this).show(); - }).ajaxComplete(function(){ - $(this).hide(); - }); +function ajaxFileUpload(options) { - $("#upload").ajaxStart(function(){ - $(this).hide(); - }).ajaxComplete(function(){ - $(this).show(); - }); + var spinner = options['spinner']; + var uploadInputId = options['uploadInputId']; + var urlInput = $(options['urlInput']); + var startUploadHandler = options['startUploadHandler']; - $.ajaxFileUpload - ( - { - url: askbot['urls']['upload'], - secureuri:false, - fileElementId:'file-upload', - dataType: 'xml', - success: function (data, status) - { - var fileURL = $(data).find('file_url').text(); - /* - * hopefully a fix for the "fakepath" issue - * https://www.mediawiki.org/wiki/Special:Code/MediaWiki/83225 - */ - fileURL = fileURL.replace(/\w:.*\\(.*)$/,'$1'); - var error = $(data).find('error').text(); - if(error != ''){ - alert(error); - if (startUploadHandler){ - /* re-install this as the upload extension - * will remove the handler to prevent double uploading */ - $('#file-upload').change(startUploadHandler); - } - }else{ - imageUrl.attr('value', fileURL); - } + spinner.ajaxStart(function(){ + $(this).show(); + }).ajaxComplete(function(){ + $(this).hide(); + }); - }, - error: function (data, status, e) - { - alert(e); - if (startUploadHandler){ - /* re-install this as the upload extension - * will remove the handler to prevent double uploading */ - $('#file-upload').change(startUploadHandler); - } - } - } - ) + /* important!!! upload input must be loaded by id + * because ajaxFileUpload monkey-patches the upload form */ + $('#' + uploadInputId).ajaxStart(function(){ + $(this).hide(); + }).ajaxComplete(function(){ + $(this).show(); + }); + //var localFilePath = upload_input.val(); + $.ajaxFileUpload({ + url: askbot['urls']['upload'], + secureuri: false, + fileElementId: uploadInputId, + dataType: 'xml', + success: function (data, status) { + + var fileURL = $(data).find('file_url').text(); + /* + * hopefully a fix for the "fakepath" issue + * https://www.mediawiki.org/wiki/Special:Code/MediaWiki/83225 + */ + fileURL = fileURL.replace(/\w:.*\\(.*)$/,'$1'); + var error = $(data).find('error').text(); + if(error != ''){ + alert(error); + } else { + urlInput.attr('value', fileURL); + } + + /* re-install this as the upload extension + * will remove the handler to prevent double uploading + * this hack is a manipulation around the + * ajaxFileUpload jQuery plugin. */ + $('#' + uploadInputId).unbind('change').change(startUploadHandler); + }, + error: function (data, status, e) { + alert(e); + if (startUploadHandler){ + /* re-install this as the upload extension + * will remove the handler to prevent double uploading */ + $('#' + uploadInputId).unbind('change').change(startUploadHandler); + } + } + }); return false; }; diff --git a/askbot/skins/common/media/js/jquery.ajaxfileupload.js b/askbot/skins/common/media/js/jquery.ajaxfileupload.js index 75292776..23759c2e 100644 --- a/askbot/skins/common/media/js/jquery.ajaxfileupload.js +++ b/askbot/skins/common/media/js/jquery.ajaxfileupload.js @@ -23,8 +23,7 @@ jQuery.extend({ document.body.appendChild(io); return io; }, - createUploadForm: function(id, fileElementId) - { + createUploadForm: function(id, fileElementId) { //create form var formId = 'jUploadForm' + id; var fileId = 'jUploadFile' + id; @@ -62,8 +61,7 @@ jQuery.extend({ if ( s.global ) jQuery.event.trigger("ajaxSend", [xml, s]); // Wait for a response to come back - var uploadCallback = function(isTimeout) - { + var uploadCallback = function(isTimeout) { var io = document.getElementById(frameId); try { if(io.contentWindow){ @@ -81,12 +79,10 @@ jQuery.extend({ io.contentDocument.document.XMLDocument : io.contentDocument.document; } } - catch(e) - { + catch(e) { jQuery.handleError(s, xml, null, e); } - if ( xml || isTimeout == "timeout") - { + if ( xml || isTimeout == "timeout") { requestDone = true; var status; try { @@ -125,16 +121,15 @@ jQuery.extend({ jQuery(io).unbind(); - setTimeout(function() - { try - { + setTimeout(function() { + try { $(io).remove(); $(form).remove(); - } catch(e) { - jQuery.handleError(s, xml, null, e); - } - }, 100) + } catch(e) { + jQuery.handleError(s, xml, null, e); + } + }, 100); xml = null; } } @@ -145,25 +140,21 @@ jQuery.extend({ if( !requestDone ) uploadCallback( "timeout" ); }, s.timeout); } - try - { + try { // var io = $('#' + frameId); var form = $('#' + formId); $(form).attr('action', s.url); $(form).attr('method', 'POST'); $(form).attr('target', frameId); - if(form.encoding) - { + if(form.encoding) { form.encoding = 'multipart/form-data'; } - else - { + else { form.enctype = 'multipart/form-data'; } $(form).submit(); - } catch(e) - { + } catch(e) { jQuery.handleError(s, xml, null, e); } if(window.attachEvent){ diff --git a/askbot/skins/common/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js b/askbot/skins/common/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js new file mode 100644 index 00000000..341904b5 --- /dev/null +++ b/askbot/skins/common/media/js/tinymce/plugins/askbot_attachment/editor_plugin.js @@ -0,0 +1,112 @@ +/**
+ * askbot_attachment.js
+ *
+ * Copyright 2012, Askbot SpA
+ * Released under License.
+ *
+ * License: http://tinymce.moxiecode.com/license
+ * Contributing: http://tinymce.moxiecode.com/contributing
+ */
+
+(function() {
+ var insertIntoDom = function(url, description) {
+ var sel = tinyMCE.activeEditor.selection;
+
+ var content = '<a href="' + url;
+ if (description) {
+ content = content + '" title="' + description;
+ }
+ content = content + '">see attachment</a>';
+
+ sel.setContent(content);
+ };
+
+ var modalMenuHeadline = gettext('Insert a file');
+
+ var createDialog = function() {
+ var dialog = new FileUploadDialog();
+ dialog.setHeadingText(modalMenuHeadline);
+ dialog.setPostUploadHandler(insertIntoDom);
+ debugger;
+ dialog.setInputId('askbot_attachment_input');
+ dialog.setUrlInputTooltip(gettext('Or paste file url here'));
+ $(document).append(dialog.getElement());
+ return dialog;
+ };
+
+ var dialog = undefined;
+
+ var getDialog = function() {
+ if (dialog === undefined) {
+ dialog = createDialog();
+ }
+ return dialog;
+ };
+
+ // Load plugin specific language pack
+ tinymce.PluginManager.requireLangPack('askbot_attachment');
+
+ tinymce.create('tinymce.plugins.AskbotAttachmentPlugin', {
+ /**
+ * Initializes the plugin, this will be executed after the plugin has been created.
+ * This call is done before the editor instance has finished it's initialization so use the onInit event
+ * of the editor instance to intercept that event.
+ *
+ * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.
+ * @param {string} url Absolute URL to where the plugin is located.
+ */
+ init : function(ed, url) {
+ // Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('mceAskbotAttachment');
+ ed.addCommand('mceAskbotAttachment', function() {
+ //start file uploader modal menu
+ var dialog = getDialog();
+ dialog.show();
+ });
+
+ // Register askbot_attachment button
+ ed.addButton('askbot_attachment', {
+ title : gettext('Insert a file'),
+ cmd : 'mceAskbotAttachment'
+ //image : url + '/img/askbot_leuploader.gif'
+ });
+
+ // Add a node change handler, selects the button in the UI when a image is selected
+ ed.onNodeChange.add(function(ed, cm, n) {
+ cm.setActive('askbot_attachment', n.nodeName == 'IMG');
+ });
+ },
+
+ /**
+ * Creates control instances based in the incomming name. This method is normally not
+ * needed since the addButton method of the tinymce.Editor class is a more easy way of adding buttons
+ * but you sometimes need to create more complex controls like listboxes, split buttons etc then this
+ * method can be used to create those.
+ *
+ * @param {String} n Name of the control to create.
+ * @param {tinymce.ControlManager} cm Control manager to use inorder to create new control.
+ * @return {tinymce.ui.Control} New control instance or null if no control was created.
+ */
+ createControl : function(n, cm) {
+ return null;
+ },
+
+ /**
+ * Returns information about the plugin as a name/value array.
+ * The current keys are longname, author, authorurl, infourl and version.
+ *
+ * @return {Object} Name/value array containing information about the plugin.
+ */
+ getInfo : function() {
+ return {
+ longname : 'AskbotAttachment plugin',
+ author : 'Askbot SpA, Chile',
+ authorurl : 'http://askbot.com',
+ infourl : 'http://github.com/ASKBOT/askbot-devel/',
+ version : '0.1'
+ };
+ }
+ });
+
+ // Register plugin
+ tinymce.PluginManager.add('askbot_attachment', tinymce.plugins.AskbotAttachmentPlugin);
+})();
diff --git a/askbot/skins/common/media/js/tinymce/plugins/askbot_attachment/langs/en.js b/askbot/skins/common/media/js/tinymce/plugins/askbot_attachment/langs/en.js new file mode 100644 index 00000000..d38ad8ea --- /dev/null +++ b/askbot/skins/common/media/js/tinymce/plugins/askbot_attachment/langs/en.js @@ -0,0 +1,3 @@ +tinyMCE.addI18n('en.askbot_imageupload',{
+ desc : 'Upload an image'
+});
diff --git a/askbot/skins/common/media/js/tinymce/plugins/askbot_attachment/langs/en_dlg.js b/askbot/skins/common/media/js/tinymce/plugins/askbot_attachment/langs/en_dlg.js new file mode 100644 index 00000000..5cd400c1 --- /dev/null +++ b/askbot/skins/common/media/js/tinymce/plugins/askbot_attachment/langs/en_dlg.js @@ -0,0 +1,3 @@ +tinyMCE.addI18n('en.askbot_imageupload_dlg',{
+ title : 'Upload an image'
+});
diff --git a/askbot/skins/common/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js b/askbot/skins/common/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js new file mode 100644 index 00000000..0903feb4 --- /dev/null +++ b/askbot/skins/common/media/js/tinymce/plugins/askbot_imageuploader/editor_plugin.js @@ -0,0 +1,112 @@ +/**
+ * askbot_imageuploader.js
+ *
+ * Copyright 2012, Askbot SpA
+ * Released under License.
+ *
+ * License: http://tinymce.moxiecode.com/license
+ * Contributing: http://tinymce.moxiecode.com/contributing
+ */
+
+(function() {
+ var insertIntoDom = function(url, description) {
+ var sel = tinyMCE.activeEditor.selection;
+
+ var content = '<img src="' + url;
+ if (description) {
+ content = content + '" alt="' + description;
+ }
+ content = content + '"/>';
+
+ sel.setContent(content);
+ };
+
+ var modalMenuHeadline = gettext('Upload an image');
+
+ var createDialog = function() {
+ var dialog = new FileUploadDialog();
+ dialog.setHeadingText(modalMenuHeadline);
+ dialog.setPostUploadHandler(insertIntoDom);
+ dialog.setUrlInputTooltip('Or paste image url here');
+ dialog.setInputId('askbot_imageuploader_input');
+ $(document).append(dialog.getElement());
+ return dialog;
+ };
+
+ var dialog = undefined;
+
+ var getDialog = function() {
+ if (dialog === undefined) {
+ dialog = createDialog();
+ }
+ return dialog;
+ };
+
+ // Load plugin specific language pack
+ tinymce.PluginManager.requireLangPack('askbot_imageuploader');
+
+ tinymce.create('tinymce.plugins.AskbotImageUploaderPlugin', {
+ /**
+ * Initializes the plugin, this will be executed after the plugin has been created.
+ * This call is done before the editor instance has finished it's initialization so use the onInit event
+ * of the editor instance to intercept that event.
+ *
+ * @param {tinymce.Editor} ed Editor instance that the plugin is initialized in.
+ * @param {string} url Absolute URL to where the plugin is located.
+ */
+ init : function(ed, url) {
+ // Register the command so that it can be invoked by using tinyMCE.activeEditor.execCommand('mceAskbotImageUploader');
+ ed.addCommand('mceAskbotImageUploader', function() {
+ //start file uploader modal menu
+ var dialog = getDialog();
+ debugger;
+ dialog.show();
+ });
+
+ // Register askbot_imageuploader button
+ ed.addButton('askbot_imageuploader', {
+ title : gettext('Insert image'),
+ cmd : 'mceAskbotImageUploader'
+ //image : url + '/img/askbot_leuploader.gif'
+ });
+
+ // Add a node change handler, selects the button in the UI when a image is selected
+ ed.onNodeChange.add(function(ed, cm, n) {
+ cm.setActive('askbot_imageuploader', n.nodeName == 'IMG');
+ });
+ },
+
+ /**
+ * Creates control instances based in the incomming name. This method is normally not
+ * needed since the addButton method of the tinymce.Editor class is a more easy way of adding buttons
+ * but you sometimes need to create more complex controls like listboxes, split buttons etc then this
+ * method can be used to create those.
+ *
+ * @param {String} n Name of the control to create.
+ * @param {tinymce.ControlManager} cm Control manager to use inorder to create new control.
+ * @return {tinymce.ui.Control} New control instance or null if no control was created.
+ */
+ createControl : function(n, cm) {
+ return null;
+ },
+
+ /**
+ * Returns information about the plugin as a name/value array.
+ * The current keys are longname, author, authorurl, infourl and version.
+ *
+ * @return {Object} Name/value array containing information about the plugin.
+ */
+ getInfo : function() {
+ return {
+ longname : 'AskbotImageUploader plugin',
+ author : 'Askbot SpA, Chile',
+ authorurl : 'http://askbot.com',
+ infourl : 'http://github.com/ASKBOT/askbot-devel/',
+ version : '0.1'
+ };
+ }
+ });
+
+ // Register plugin
+ tinymce.PluginManager.add('askbot_imageuploader', tinymce.plugins.AskbotImageUploaderPlugin);
+})();
diff --git a/askbot/skins/common/media/js/tinymce/plugins/askbot_imageuploader/langs/en.js b/askbot/skins/common/media/js/tinymce/plugins/askbot_imageuploader/langs/en.js new file mode 100644 index 00000000..d38ad8ea --- /dev/null +++ b/askbot/skins/common/media/js/tinymce/plugins/askbot_imageuploader/langs/en.js @@ -0,0 +1,3 @@ +tinyMCE.addI18n('en.askbot_imageupload',{
+ desc : 'Upload an image'
+});
diff --git a/askbot/skins/common/media/js/tinymce/plugins/askbot_imageuploader/langs/en_dlg.js b/askbot/skins/common/media/js/tinymce/plugins/askbot_imageuploader/langs/en_dlg.js new file mode 100644 index 00000000..5cd400c1 --- /dev/null +++ b/askbot/skins/common/media/js/tinymce/plugins/askbot_imageuploader/langs/en_dlg.js @@ -0,0 +1,3 @@ +tinyMCE.addI18n('en.askbot_imageupload_dlg',{
+ title : 'Upload an image'
+});
diff --git a/askbot/skins/common/media/js/utils.js b/askbot/skins/common/media/js/utils.js index 43316da7..ad4963fc 100644 --- a/askbot/skins/common/media/js/utils.js +++ b/askbot/skins/common/media/js/utils.js @@ -308,6 +308,7 @@ Widget.prototype.makeButton = function(label, handler) { var TippedInput = function(){ WrappedElement.call(this); this._instruction = null; + this._attrs = {}; }; inherits(TippedInput, WrappedElement); @@ -316,6 +317,14 @@ TippedInput.prototype.reset = function(){ $(this._element).addClass('blank'); }; +TippedInput.prototype.setInstruction = function(text) { + this._instruction = text; +}; + +TippedInput.prototype.setAttr = function(key, value) { + this._attrs[key] = value; +}; + TippedInput.prototype.isBlank = function(){ return this.getVal() === this._instruction; }; @@ -335,8 +344,17 @@ TippedInput.prototype.setVal = function(value){ } }; +TippedInput.prototype.createDom = function() { + this._element = this.makeElement('input'); + var element = this._element; + element.val(this._instruction); + this.decorate(element); +}; + TippedInput.prototype.decorate = function(element){ this._element = element; + element.attr(this._attrs); + var instruction_text = this.getVal(); this._instruction = instruction_text; var me = this; @@ -552,8 +570,272 @@ DeleteIcon.prototype.setContent = function(content){ } /** + * @contstructor + * Simple modal dialog with Ok/Cancel buttons by default + */ +var ModalDialog = function() { + WrappedElement.call(this); + this._accept_button_text = gettext('Ok'); + this._reject_button_text = gettext('Cancel'); + this._heading_text = 'Add heading by setHeadingText()'; + this._initial_content = undefined; + this._accept_handler = function(){}; + var me = this; + this._reject_handler = function() { me.hide(); }; + this._content_element = undefined; +}; +inherits(ModalDialog, WrappedElement); + +ModalDialog.prototype.show = function() { + this._element.modal('show'); +}; + +ModalDialog.prototype.hide = function() { + this._element.modal('hide'); +}; + +ModalDialog.prototype.setContent = function(content) { + this._initial_content = content; +}; + +ModalDialog.prototype.prependContent = function(content) { + this._content_element.prepend(content); +}; + +ModalDialog.prototype.setHeadingText = function(text) { + this._heading_text = text; +}; + +ModalDialog.prototype.setAcceptButtonText = function(text) { + this._accept_button_text = text; +}; + +ModalDialog.prototype.setRejectButtonText = function(text) { + this._reject_button_text = text; +}; + +ModalDialog.prototype.setAcceptHandler = function(handler) { + this._accept_handler = handler; +}; + +ModalDialog.prototype.setRejectHandler = function(handler) { + this._reject_handler = handler; +}; + +ModalDialog.prototype.clearMessages = function() { + this._element.find('.alert').remove(); +}; + +ModalDialog.prototype.setMessage = function(text, message_type) { + var box = new AlertBox(); + box.setText(text); + if (message_type === 'error') { + box.setError(true); + } + this.prependContent(box.getElement()); +}; + +ModalDialog.prototype.createDom = function() { + this._element = this.makeElement('div') + var element = this._element; + + element.addClass('modal'); + + //1) create header + var header = this.makeElement('div') + header.addClass('modal-header'); + element.append(header); + + var close_link = this.makeElement('div'); + close_link.addClass('close'); + close_link.attr('data-dismiss', 'modal'); + close_link.html('x'); + header.append(close_link); + + var title = this.makeElement('h3'); + title.html(this._heading_text); + header.append(title); + + //2) create content + var body = this.makeElement('div') + body.addClass('modal-body'); + element.append(body); + this._content_element = body; + if (this._initial_content) { + this._content_element.append(this._initial_content); + } + + //3) create footer with accept and reject buttons (ok/cancel). + var footer = this.makeElement('div'); + footer.addClass('modal-footer'); + element.append(footer); + + var accept_btn = this.makeElement('button'); + accept_btn.addClass('btn btn-primary'); + accept_btn.html(this._accept_button_text); + footer.append(accept_btn); + + var reject_btn = this.makeElement('button'); + reject_btn.addClass('btn cancel'); + reject_btn.html(this._reject_button_text); + footer.append(reject_btn); + + //4) attach event handlers to the buttons + setupButtonEventHandlers(accept_btn, this._accept_handler); + setupButtonEventHandlers(reject_btn, this._reject_handler); + setupButtonEventHandlers(close_link, this._reject_handler); + + this.hide(); +}; + +/** + * @constructor + */ +var FileUploadDialog = function() { + ModalDialog.call(this); + self._post_upload_handler = undefined; +}; +inherits(FileUploadDialog, ModalDialog); + +FileUploadDialog.prototype.setPostUploadHandler = function(handler) { + this._post_upload_handler = handler; +}; + +FileUploadDialog.prototype.runPostUploadHandler = function(url, descr) { + this._post_upload_handler(url, descr); +}; + +FileUploadDialog.prototype.setInputId = function(id) { + this._input_id = id; +}; + +FileUploadDialog.prototype.getInputId = function() { + return this._input_id; +}; + +FileUploadDialog.prototype.setUrlInputTooltip = function(text) { + this._url_input_tooltip = text; +}; + +FileUploadDialog.prototype.getUrl = function() { + var url_input = this._url_input; + if (url_input.isBlank() === false) { + return url_input.getVal(); + } + return ''; +}; + +//disable description for now +//FileUploadDialog.prototype.getDescription = function() { +// return this._description_input.getVal(); +//}; + +FileUploadDialog.prototype.resetInputs = function() { + this._url_input.reset(); + //this._description_input.reset(); + this._upload_input.val(''); +}; + +FileUploadDialog.prototype.show = function() { + //hack around the ajaxFileUpload plugin + FileUploadDialog.superClass_.show.call(this); + var upload_input = this._upload_input; + upload_input.unbind('change'); + //todo: fix this - make event handler reinstall work + upload_input.change(this.getStartUploadHandler()); +}; + +FileUploadDialog.prototype.getStartUploadHandler = function(){ + /* startUploadHandler is passed in to re-install the event handler + * which is removed by the ajaxFileUpload jQuery extension + */ + var spinner = this._spinner; + var uploadInputId = this.getInputId(); + var urlInput = this._url_input; + var handler = function() { + var options = { + 'spinner': spinner, + 'uploadInputId': uploadInputId, + 'urlInput': urlInput.getElement(), + 'startUploadHandler': handler//pass in itself + }; + return ajaxFileUpload(options); + }; + return handler; +}; + +FileUploadDialog.prototype.createDom = function() { + + var superClass = FileUploadDialog.superClass_; + + var me = this; + superClass.setAcceptHandler.call(this, function(){ + var url = $.trim(me.getUrl()); + //var description = me.getDescription(); + //@todo: have url cleaning code here + if (url.length > 0) { + me.runPostUploadHandler(url);//, description); + me.resetInputs(); + } + me.hide(); + }); + superClass.setRejectHandler.call(this, function(){ + me.resetInputs(); + me.hide(); + }); + superClass.createDom.call(this); + + var form = this.makeElement('form'); + form.css('margin-bottom', 0); + this.prependContent(form); + + // File upload button + var upload_input = this.makeElement('input'); + upload_input.attr({ + id: this._input_id, + type: 'file', + name: 'file-upload', + //size: 26??? + }); + form.append(upload_input); + this._upload_input = upload_input; + form.append($('<br/>')); + + // The url input text box + var url_input = new TippedInput(); + url_input.setInstruction(this._url_input_tooltip || gettext('Or paste file url here')); + var url_input_element = url_input.getElement(); + url_input_element.css({ + 'width': '200px', + }); + form.append(url_input_element); + form.append($('<br/>')); + this._url_input = url_input; + + /* //Description input box + var descr_input = new TippedInput(); + descr_input.setInstruction(gettext('Describe the image here')); + this.makeElement('input'); + form.append(descr_input.getElement()); + form.append($('<br/>')); + this._description_input = descr_input; + */ + var spinner = this.makeElement('img'); + spinner.attr('src', mediaUrl('media/images/indicator.gif')); + spinner.css('display', 'none'); + form.append(spinner); + this._spinner = spinner; + + upload_input.change(this.getStartUploadHandler()); +}; + +/** * attaches a modal menu with a text editor * to a link. The modal menu is from bootstrap.js + * todo: this should probably be a subclass of ModalDialog, + * triggered by a link click, then a whole bunch of methods + * would be simply inherited from the modal dialog: + * clearMessages, etc. */ var TextPropertyEditor = function(){ WrappedElement.call(this); @@ -577,90 +859,48 @@ TextPropertyEditor.prototype.makeEditor = function(){ if (this._editor) { return this._editor; } - var editor = this.makeElement('div') - .addClass('modal'); + var editor = new ModalDialog(); this._editor = editor; + editor.setHeadingText(this.getWidgetData()['editor_heading']); - var header = this.makeElement('div') - .addClass('modal-header'); - editor.append(header); - - var close_link = this.makeElement('div') - .addClass('close') - .attr('data-dismiss', 'modal') - .html('x'); - header.append(close_link); - - var title = this.makeElement('h3') - .html(this.getWidgetData()['editor_heading']); - header.append(title); - - var body = this.makeElement('div') - .addClass('modal-body'); - editor.append(body); - - var textarea = this.makeElement('textarea') - .addClass('tipped-input blank') - .val(this.getWidgetData()['help_text']); - body.append(textarea); + //create main content for the editor + var textarea = this.makeElement('textarea'); + textarea.addClass('tipped-input blank'); + textarea.val(this.getWidgetData()['help_text']); var tipped_input = new TippedInput(); tipped_input.decorate(textarea); this._text_input = tipped_input; - var footer = this.makeElement('div') - .addClass('modal-footer'); - editor.append(footer); + editor.setContent(textarea); + //body.append(textarea); - var save_btn = this.makeElement('button') - .addClass('btn btn-primary') - .html(gettext('Save')); - footer.append(save_btn); - - var cancel_btn = this.makeElement('button') - .addClass('btn cancel') - .html(gettext('Cancel')); - footer.append(cancel_btn); + editor.setAcceptButtonText(gettext('Save')); + editor.setRejectButtonText(gettext('Cancel')); var me = this; - setupButtonEventHandlers(save_btn, function(){ + editor.setAcceptHandler(function(){ me.saveData(); }); - setupButtonEventHandlers(cancel_btn, function(){ - editor.modal('hide'); - }); - editor.modal('hide'); - $(document).append(editor); + $(document).append(editor.getElement()); return editor; }; TextPropertyEditor.prototype.openEditor = function(){ - this._editor.modal('show'); + this._editor.show(); }; TextPropertyEditor.prototype.clearMessages = function(){ - this._editor.find('.alert').remove(); -}; - -TextPropertyEditor.prototype.getAlert = function(){ - var box = new AlertBox(); - var modal_body = this._editor.find('.modal-body'); - modal_body.prepend(box.getElement()); - return box; + this._editor.clearMessages() }; TextPropertyEditor.prototype.showAlert = function(text){ - this.clearMessages(); - var box = this.getAlert(); - box.setText(text); - return box; + this._editor.setMessage(text, 'alert'); }; TextPropertyEditor.prototype.showError = function(text){ - var box = this.showAlert(text); - box.setError(true); - return box; + this._editor.setMessage(text, 'error'); }; TextPropertyEditor.prototype.setText = function(text){ @@ -672,7 +912,7 @@ TextPropertyEditor.prototype.getText = function(){ }; TextPropertyEditor.prototype.hideDialog = function(){ - this._editor.modal('hide'); + this._editor.hide(); }; TextPropertyEditor.prototype.startOpeningEditor = function(){ diff --git a/askbot/skins/common/media/js/wmd/wmd.js b/askbot/skins/common/media/js/wmd/wmd.js index 98af264f..60d6d868 100644 --- a/askbot/skins/common/media/js/wmd/wmd.js +++ b/askbot/skins/common/media/js/wmd/wmd.js @@ -357,9 +357,26 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){ upload_input.attr('id', 'file-upload'); upload_input.attr('size', 26); + var spinner = $('<img />'); + spinner.attr('id', 'loading'); + spinner.attr('src', mediaUrl("media/images/indicator.gif")); + spinner.css('display', 'none'); + var startUploadHandler = function(){ - localUploadFileName = $(this).val(); - return ajaxFileUpload($('#image-url'), startUploadHandler); + localUploadFileName = $(this).val();//this is a local var + /* + * startUploadHandler is passed into the ajaxFileUpload + * in order to re-install the onchange handler + * because the jquery extension ajaxFileUpload removes the handler + */ + var options = { + spinner: spinner, + uploadInputId: 'file-upload', + urlInput: $(input), + startUploadHandler: startUploadHandler + }; + return ajaxFileUpload(options); + //$('#image-url'), startUploadHandler); }; upload_input.change(startUploadHandler); @@ -367,12 +384,8 @@ util.prompt = function(text, defaultInputText, makeLinkMarkdown, dialogType){ upload_container.append(upload_input); upload_container.append($('<br/>')); - var spinner = $('<img />'); - spinner.attr('id', 'loading'); - spinner.attr('src', mediaUrl("media/images/indicator.gif")); - spinner.css('display', 'none'); - upload_container.append(spinner); + upload_container.css('padding', '5px'); $(form).append(upload_container); } diff --git a/askbot/skins/default/media/bootstrap/css/bootstrap.css b/askbot/skins/default/media/bootstrap/css/bootstrap.css index 3e829732..ff80a3e1 100644 --- a/askbot/skins/default/media/bootstrap/css/bootstrap.css +++ b/askbot/skins/default/media/bootstrap/css/bootstrap.css @@ -1,5 +1,9 @@ /*! * Bootstrap v2.0.2 + * This file was modified for Askbot + * some styles were deleted, others added at the bottom + * of this file. Also some fixes to bootstrap are added + * at the bottom of askbot's style.less. * * Copyright 2012 Twitter, Inc * Licensed under the Apache License v2.0 @@ -92,14 +96,6 @@ img { vertical-align: middle; } button, -input, -select, -textarea { - margin: 0; - font-size: 100%; - vertical-align: middle; -} -button, input { *overflow: visible; line-height: normal; @@ -130,14 +126,6 @@ textarea { overflow: auto; vertical-align: top; } -body { - margin: 0; - font-family: Arial, sans-serif; - font-size: 13px; - line-height: 18px; - color: #333333; - background-color: #ffffff; -} a { color: #0088cc; text-decoration: none; @@ -894,25 +882,10 @@ legend small { font-size: 13.5px; color: #999999; } -label, -input, -button, -select, -textarea { - font-size: 13px; - font-weight: normal; - line-height: 18px; -} -input, -button, -select, -textarea { - font-family: Arial, sans-serif; -} label { - display: block; + /*display: block; margin-bottom: 5px; - color: #333333; + color: #333333;*/ } input, textarea, @@ -928,25 +901,6 @@ label textarea, label select { display: block; } -input[type="image"], -input[type="checkbox"], -input[type="radio"] { - width: auto; - height: auto; - padding: 0; - margin: 3px 0; - *margin-top: 0; - /* IE7 */ - - line-height: normal; - cursor: pointer; - -webkit-border-radius: 0; - -moz-border-radius: 0; - border-radius: 0; - border: 0 \9; - /* IE9 and down */ - -} input[type="image"] { border: 0; } @@ -999,26 +953,6 @@ textarea { input[type="hidden"] { display: none; } -.radio, -.checkbox { - padding-left: 18px; -} -.radio input[type="radio"], -.checkbox input[type="checkbox"] { - float: left; - margin-left: -18px; -} -.controls > .radio:first-child, -.controls > .checkbox:first-child { - padding-top: 5px; -} -.radio.inline, -.checkbox.inline { - display: inline-block; - padding-top: 5px; - margin-bottom: 0; - vertical-align: middle; -} .radio.inline + .radio.inline, .checkbox.inline + .checkbox.inline { margin-left: 10px; @@ -1074,18 +1008,6 @@ select:focus { .input-xxlarge { width: 530px; } -input[class*="span"], -select[class*="span"], -textarea[class*="span"], -.uneditable-input { - float: none; - margin-left: 0; -} -input, -textarea, -.uneditable-input { - margin-left: 0; -} input.span12, textarea.span12, .uneditable-input.span12 { width: 930px; } @@ -1237,15 +1159,6 @@ select:focus:required:invalid:focus { .form-actions:after { clear: both; } -.uneditable-input { - display: block; - background-color: #ffffff; - border-color: #eee; - -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025); - cursor: not-allowed; -} :-moz-placeholder { color: #999999; } @@ -1273,17 +1186,6 @@ select:focus:required:invalid:focus { .input-append { margin-bottom: 5px; } -.input-prepend input, -.input-append input, -.input-prepend select, -.input-append select, -.input-prepend .uneditable-input, -.input-append .uneditable-input { - *margin-left: 0; - -webkit-border-radius: 0 3px 3px 0; - -moz-border-radius: 0 3px 3px 0; - border-radius: 0 3px 3px 0; -} .input-prepend input:focus, .input-append input:focus, .input-prepend select:focus, @@ -4280,11 +4182,6 @@ a.thumbnail:hover { .row-fluid > .span1 { width: 5.801104972%; } - input, - textarea, - .uneditable-input { - margin-left: 0; - } input.span12, textarea.span12, .uneditable-input.span12 { width: 714px; } @@ -4633,3 +4530,24 @@ a.thumbnail:hover { margin-left: 30px; } } + +/* Modifications for askbot */ +.caret { + margin-bottom: 7px; +} +.btn-group { + text-align: left; +} +.btn-toolbar { + margin: 0; +} +.modal-footer { + text-align: left; +} +.modal p { + font-size: 14px; +} +.modal-body > textarea { + width: 515px; + margin-bottom: 0px; +} diff --git a/askbot/skins/default/media/style/style.less b/askbot/skins/default/media/style/style.less index bfb567bb..e4c1d714 100644 --- a/askbot/skins/default/media/style/style.less +++ b/askbot/skins/default/media/style/style.less @@ -3649,24 +3649,3 @@ textarea.tipped-input { margin-top: 9px; } } - -/* fixes for bootstrap */ -.caret { - margin-bottom: 7px; -} -.btn-group { - text-align: left; -} -.btn-toolbar { - margin: 0; -} -.modal-footer { - text-align: left; -} -.modal p { - font-size: 14px; -} -.modal-body > textarea { - width: 515px; - margin-bottom: 0px; -} diff --git a/askbot/skins/default/templates/ask.html b/askbot/skins/default/templates/ask.html index f0dae0ce..e9e53338 100644 --- a/askbot/skins/default/templates/ask.html +++ b/askbot/skins/default/templates/ask.html @@ -20,6 +20,7 @@ <script type='text/javascript' src='{{"/js/wmd/showdown.js"|media}}'></script> <script type='text/javascript' src='{{"/js/wmd/wmd.js"|media}}'></script> {% else %} + <script type='text/javascript' src='{{"/js/wmd/showdown.js"|media}}'></script> {% include "meta/tinymce.html" %} {% endif %} <script type='text/javascript'> diff --git a/askbot/skins/default/templates/meta/bottom_scripts.html b/askbot/skins/default/templates/meta/bottom_scripts.html index 093283f8..76992383 100644 --- a/askbot/skins/default/templates/meta/bottom_scripts.html +++ b/askbot/skins/default/templates/meta/bottom_scripts.html @@ -28,6 +28,7 @@ src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" {% endif %} ></script> +<script type="text/javascript" src='{{"/bootstrap/js/bootstrap.js"|media}}'></script> <!-- History.js --> <script type='text/javascript' src="{{"/js/jquery.history.js"|media }}"></script> <script type="text/javascript" src="{% url django.views.i18n.javascript_catalog %}"></script> diff --git a/askbot/skins/default/templates/meta/html_head_stylesheets.html b/askbot/skins/default/templates/meta/html_head_stylesheets.html index 0d2ba463..bb90e3f8 100644 --- a/askbot/skins/default/templates/meta/html_head_stylesheets.html +++ b/askbot/skins/default/templates/meta/html_head_stylesheets.html @@ -4,6 +4,7 @@ <link href="{{"/style/style.less"|media }}" rel="stylesheet/less" type="text/css" /> <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 %} diff --git a/askbot/skins/default/templates/meta/tinymce.html b/askbot/skins/default/templates/meta/tinymce.html index 0f12d960..6f8a6018 100644 --- a/askbot/skins/default/templates/meta/tinymce.html +++ b/askbot/skins/default/templates/meta/tinymce.html @@ -10,10 +10,11 @@ oninit: function(){ tinyMCE.activeEditor.setContent(askbot['data']['editorContent'] || ''); }, + plugins: 'askbot_imageuploader,askbot_attachment', theme : 'advanced', theme_advanced_toolbar_location : 'top', theme_advanced_toolbar_align: 'left', - theme_advanced_buttons1 : 'bold,italic,underline,|,bullist,numlist,|,undo,redo,|,link,unlink,image', + theme_advanced_buttons1 : 'bold,italic,underline,|,bullist,numlist,|,undo,redo,|,link,unlink,askbot_imageuploader,askbot_attachment', theme_advanced_buttons2 : '', theme_advanced_buttons3 : '', theme_advanced_path: false, @@ -38,4 +39,10 @@ height: 5px; background: #fff; } + .defaultSkin span.mce_askbot_imageuploader { + background-position: -380px 0px; + } + .defaultSkin span.mce_askbot_attachment { + background-position: -520px 0px; + } </style> diff --git a/askbot/skins/default/templates/user_profile/user_inbox.html b/askbot/skins/default/templates/user_profile/user_inbox.html index cda45027..62dd3e24 100644 --- a/askbot/skins/default/templates/user_profile/user_inbox.html +++ b/askbot/skins/default/templates/user_profile/user_inbox.html @@ -94,6 +94,5 @@ inbox_section - forum|flags setup_inbox(); }); </script> - <script type="text/javascript" src="{{'/bootstrap/js/bootstrap.js'|media}}" /> <!-- end user_responses.html --> {% endblock %} diff --git a/askbot/skins/default/templates/users.html b/askbot/skins/default/templates/users.html index 96598b1f..4797f67e 100644 --- a/askbot/skins/default/templates/users.html +++ b/askbot/skins/default/templates/users.html @@ -2,11 +2,6 @@ {% import "macros.html" as macros %} <!-- users.html --> {% block title %}{% spaceless %}{% trans %}Users{% endtrans %}{% endspaceless %}{% endblock %} -{% block before_css %} - {% if group and request.user.is_authenticated() and request.user.is_administrator() %} - <link href="{{'/bootstrap/css/bootstrap.css'|media}}" rel="stylesheet" type="text/css" /> - {% endif %} -{% endblock %} {% block forestyle %} <link rel="stylesheet" type="text/css" href="{{"/js/wmd/wmd.css"|media}}" /> {% endblock %} @@ -84,7 +79,6 @@ askbot['urls']['delete_group_logo_url'] = '{% url delete_group_logo %}'; askbot['urls']['join_or_leave_group'] = '{% url join_or_leave_group %}'; </script> - <script type="text/javascript" src='{{"/bootstrap/js/bootstrap.js"|media}}'></script> <script type='text/javascript' src='{{"/js/editor.js"|media}}'></script> <script type='text/javascript' src='{{"/js/wmd/showdown.js"|media}}'></script> <script type='text/javascript' src='{{"/js/wmd/wmd.js"|media}}'></script> diff --git a/askbot/views/writers.py b/askbot/views/writers.py index 58584b94..29ad229b 100644 --- a/askbot/views/writers.py +++ b/askbot/views/writers.py @@ -68,9 +68,8 @@ def upload(request):#ajax upload file to a question or answer file_name_prefix = request.POST.get('file_name_prefix', '') if file_name_prefix not in ('', 'group_logo_'): raise exceptions.PermissionDenied('invalid upload file name prefix') - - # check file type - f = request.FILES['file-upload'] + + f = request.FILES['file-upload']#take first file #todo: extension checking should be replaced with mimetype checking #and this must be part of the form validation file_extension = os.path.splitext(f.name)[1].lower() |