/** * LiquidThreads core javascript library. * * Exposes global object `liquidThreads`. * Exposes static method `jQuery.getCSS`. * * FIXME: This module uses deprecated jQuery.browser. */ /*global liquidThreads, alert */ ( function ( mw, $ ) { window.wgWikiEditorIconVersion = 0; $.getCSS = function ( url, media ) { $( '' ).attr( { href: url, media: media || 'screen', type: 'text/css', rel: 'stylesheet' } ).appendTo( 'head' ); }; window.liquidThreads = { currentReplyThread : null, currentToolbar : null, 'handleReplyLink' : function ( e ) { if ( e.preventDefault ) { e.preventDefault(); } var target = this; if ( !this.className && e.target) { target = $( e.target ); } var container = $( target ).closest( '.lqt_thread' )[0]; var thread_id = $( this ).data( 'thread-id' ); // hide the form for this thread if it's currently being shown if ( thread_id === liquidThreads.currentReplyThread && $( '#wpTextbox1' ).is( ':visible' ) ) { liquidThreads.cancelEdit( {} ); return; } var params = { 'method' : 'reply', 'thread' : thread_id }; var repliesElement = $( container ).contents().filter( '.lqt-thread-replies' ); var replyDiv = repliesElement.contents().filter( '.lqt-reply-form' ); replyDiv = replyDiv.add( $( container ).contents().filter( '.lqt-reply-form' ) ); if ( !replyDiv.length ) { // Create a div for it replyDiv = $( '
' ); // Try to find a place for it if ( !repliesElement.length ) { repliesElement = liquidThreads.getRepliesElement( $( container ) ); } repliesElement.find( '.lqt-replies-finish' ).before( replyDiv ); } replyDiv.show(); replyDiv = replyDiv[0]; liquidThreads.injectEditForm( params, replyDiv, e.preload ); liquidThreads.currentReplyThread = thread_id; }, 'getRepliesElement' : function ( thread /* a .lqt_thread */ ) { var repliesElement = thread.contents().filter( '.lqt-thread-replies' ); if ( !repliesElement.length ) { repliesElement = $( '' ); var finishDiv = $( '' ); finishDiv.append( $( '' ) ); finishDiv.contents().html( ' ' ); repliesElement.append( finishDiv ); var repliesFinishElement = thread.contents().filter( '.lqt-replies-finish' ); if ( repliesFinishElement.length ) { repliesFinishElement.before( repliesElement ); } else { thread.append( repliesElement ); } } return repliesElement; }, 'checkEmptyReplies' : function ( element, action ) { var contents = element.contents(); contents = contents.not( '.lqt-replies-finish,.lqt-post-sep,.lqt-edit-form' ); if ( !contents.length ) { if ( action === undefined || action === 'remove' ) { element.remove(); } else { element.hide(); } } }, 'handleNewLink' : function ( e ) { e.preventDefault(); var talkpage = $( this ).attr( 'lqt_talkpage' ); var params = { 'talkpage' : talkpage, 'method' : 'talkpage_new_thread' }; var container = $( '.lqt-new-thread' ); container.data( 'lqt-talkpage', talkpage ); liquidThreads.injectEditForm( params, container ); liquidThreads.currentReplyThread = 0; }, 'handleEditLink' : function ( e ) { e.preventDefault(); // Grab the container. var parent = $( this ).closest( '.lqt-post-wrapper' ); var container = $( '' ).addClass( 'lqt-edit-form' ); parent.contents().fadeOut(); parent.append( container ); var params = { 'method' : 'edit', 'thread' : parent.data( 'thread-id' ) }; liquidThreads.injectEditForm( params, container ); }, 'injectEditForm' : function ( params, container, preload ) { var page = $( container ).closest( '.lqt-thread-topmost' ) .find( '.lqt-thread-talkpage-metadata' ).val(); if ( !page ) { page = $( container ).data( 'lqt-talkpage' ); } liquidThreads.cancelEdit( container ); var isIE7 = $.client.test( { 'msie': [['>=', 7], ['<', 8]] }, $.client.profile(), true ); var loadSpinner = $( '' ); $( container ).before( loadSpinner ); var finishShow = function () { // Scroll to the textbox var targetOffset = $( container ).offset().top; var windowHeight = $( window ).height(); var editBoxHeight = $( container ).height(); var scrollOffset; if ( windowHeight < editBoxHeight ) { scrollOffset = targetOffset; } else { scrollOffset = targetOffset - windowHeight + editBoxHeight; } $( 'html, body' ).animate( { scrollTop: scrollOffset }, 'slow' ); // Auto-focus and set to auto-grow as well $( container ).find( '#wpTextbox1' ).focus();//.autogrow(); // Focus the subject field if there is one. Overrides previous line. $( container ).find( '#lqt_subject_field' ).focus(); // Update signature editor $( container ).find( 'input[name=wpLqtSignature]' ).hide(); $( container ).find( '.lqt-signature-preview' ).show(); var editLink = $( '' ); editLink.text( mw.msg( 'lqt-edit-signature' ) ); editLink.click( liquidThreads.handleEditSignature ); editLink.attr( 'href', '#' ); $( container ).find( '.lqt-signature-preview' ).after( editLink ); editLink.before( ' ' ); }; var finishSetup = function () { // Kill the loader. $( '.lqt-loader' ).remove(); if ( preload ) { $( 'textarea', container )[0].value = preload; } if ( isIE7 ) { setTimeout( finishShow, 500 ); } else { $( container ).slideDown( 'slow', finishShow ); } var cancelButton = $( container ).find( '#mw-editform-cancel' ); cancelButton.click( liquidThreads.cancelEdit ); $( container ).find( '#wpTextbox1' ).attr( 'rows', 12 ); $( container ).find( '#wpDiff' ).hide(); if ( $.fn.wikiEditor && $.wikiEditor.isSupported( $.wikiEditor.modules.toolbar ) ) { // Add wikiEditor toolbar $( '#wpTextbox1' ).wikiEditor( 'addModule', $.wikiEditor.modules.toolbar.config.getDefaultConfig() ); // Add wikiEditor dialogs if ( mw.user.options.get( 'usebetatoolbar-cgd' ) && $.wikiEditor.isSupported( $.wikiEditor.modules.dialogs ) ) { $( '#wpTextbox1' ).addClass( 'toolbar-dialogs' ); $.wikiEditor.modules.dialogs.config.replaceIcons( $( '#wpTextbox1' ) ); $( '#wpTextbox1' ).wikiEditor( 'addModule', $.wikiEditor.modules.dialogs.config.getDefaultConfig() ); } // cleanup unnecessary things from the old toolbar $( '#editpage-specialchars' ).remove(); $( '#wpTextbox1' ).wikiEditor( 'removeFromToolbar', { 'section': 'main', 'group': 'insert', 'tool': 'signature' } ); $( '#wpTextbox1' ).focus(); } var currentFocused = $( container ).find( '#wpTextbox1' ); mw.hook( 'ext.lqt.textareaCreated' ).fire( currentFocused ); $( container ).find( '#wpTextbox1,#wpSummary' ).focus( function () { currentFocused = this; } ); }; mw.loader.using( ['mediawiki.action.edit'], function () { if ( isIE7 ) { $( container ).empty().show(); } liquidThreads.loadInlineEditForm( params, container, function () { var dependencies = [ 'ext.wikiEditor', 'user.options', 'jquery.wikiEditor.toolbar', 'jquery.wikiEditor.toolbar.config', 'jquery.async', 'jquery.cookie' ]; if ( mw.user.options.get( 'usebetatoolbar-cgd' ) ) { dependencies.push( 'jquery.wikiEditor.dialogs', 'jquery.wikiEditor.dialogs.config' ); } mw.loader.using( dependencies, finishSetup ); } ); } ); }, 'loadInlineEditForm' : function ( params, container, callback ) { params.action = 'threadaction'; params.threadaction = 'inlineeditform'; params.token = mw.user.tokens.get( 'editToken' ); ( new mw.Api() ).post( params ).done( function ( result ) { $( container ).empty().append( $( result.threadaction.inlineeditform.html ).contents() ); callback(); } ); }, //From http://clipmarks.com/clipmark/CEFC94CB-94D6-4495-A7AA-791B7355E284/ 'insertAtCursor' : function ( myField, myValue ) { //IE support if ( document.selection ) { myField.focus(); var sel = document.selection.createRange(); sel.text = myValue; } //MOZILLA/NETSCAPE support else if ( myField.selectionStart || myField.selectionStart === '0' ) { var startPos = myField.selectionStart; var endPos = myField.selectionEnd; myField.value = myField.value.substring( 0, startPos ) + myValue + myField.value.substring( endPos, myField.value.length ); } else { myField.value += myValue; } }, 'getSelection' : function () { if ( window.getSelection ) { return window.getSelection().toString(); } else if ( document.selection ) { return document.selection.createRange().text; } else if ( document.getSelection ) { return document.getSelection(); } else { return ''; } }, 'cancelEdit' : function ( e ) { if ( e !== undefined && e.preventDefault ) { e.preventDefault(); } // XXX: Should this be e.target instead of e? $( '.lqt-edit-form' ).not( e ).each( function () { var repliesElement = $( this ).closest( '.lqt-thread-replies' ); $( this ).fadeOut( 'slow', function () { $( this ).empty(); if ( $( this ).parent().is( '.lqt-post-wrapper' ) ) { $( this ).parent().contents().fadeIn(); $( this ).remove(); } liquidThreads.checkEmptyReplies( repliesElement ); } ); } ); liquidThreads.currentReplyThread = null; }, 'setupMenus' : function () { var post = $( this ); var toolbar = post.contents().filter( '.lqt-thread-toolbar' ); var threadID = post.data( 'thread-id' ); var menu = post.find( '.lqt-thread-toolbar-command-list' ); var menuContainer = post.find( '.lqt-thread-toolbar-menu' ); menu.remove().appendTo( menuContainer ); menuContainer.find( '.lqt-thread-toolbar-command-list' ).hide(); // Add handler for reply link var replyLink = toolbar.find( '.lqt-command-reply > a' ); replyLink.data( 'thread-id', threadID ); replyLink.click( liquidThreads.handleReplyLink ); // Add "Drag to new location" to menu var dragLI = $( '' ); var dragLink = $( '' ).text( mw.msg( 'lqt-drag-activate' ) ); dragLink.attr( 'href', '#' ); dragLI.append( dragLink ); dragLink.click( liquidThreads.activateDragDrop ); menu.append( dragLI ); // Remove split and merge menu.contents().filter( '.lqt-command-split,.lqt-command-merge' ).remove(); var trigger = menuContainer.find( '.lqt-thread-actions-trigger' ); trigger.show(); menu.hide(); // FIXME: After a drag-and-drop, this stops working on the thread and its replies trigger.click( function ( e ) { e.stopImmediatePropagation(); e.preventDefault(); // Hide the other menus $( '.lqt-thread-toolbar-command-list' ).not( menu ).hide( 'fast' ); menu.toggle( 'fast' ); var windowHeight = $( window ).height(); var toolbarOffset = toolbar.offset().top; var scrollPos = $( window ).scrollTop(); var menuBottom = ( toolbarOffset + 150 - scrollPos ); if ( menuBottom > windowHeight ) { // Switch to an upwards menu. menu.css( 'bottom', toolbar.height() ); } else { menu.css( 'bottom', 'auto' ); } } ); }, 'setupThreadMenu' : function ( menu, id ) { if ( menu.find( '.lqt-command-edit-subject' ).length || menu.closest( '.lqt_thread' ).is( '.lqt-thread-uneditable' ) ) { return; } var editSubjectField = $( '' ); var editSubjectLink = $( '' ); editSubjectLink.text( mw.msg( 'lqt-change-subject' ) ); editSubjectField.append( editSubjectLink ); editSubjectField.click( liquidThreads.handleChangeSubject ); editSubjectField.data( 'thread-id', id ); editSubjectField.addClass( 'lqt-command-edit-subject' ); // appending a space first to prevent cursive script character joining across elements menu.append( ' ', editSubjectField ); }, 'handleChangeSubject' : function ( e ) { e.preventDefault(); $( this ).closest( '.lqt-command-edit-subject' ).hide(); // Grab the h2 var threadId = $( this ).data( 'thread-id' ); var header = $( '#lqt-header-' + threadId ); var headerText = header.find( "input[name='raw-header']" ).val(); var textbox = $( '' ).val( headerText ); textbox.attr( 'id', 'lqt-subject-input-' + threadId ); textbox.attr( 'size', '75' ); textbox.val( headerText ); var saveText = mw.msg( 'lqt-save-subject' ); var saveButton = $( '' ); saveButton.val( saveText ); saveButton.click( liquidThreads.handleSubjectSave ); var cancelButton = $( '' ); cancelButton.val( mw.msg( 'lqt-cancel-subject-edit' ) ); cancelButton.click( function () { var form = $( this ).closest( '.mw-subject-editor' ); var header = form.closest( '.lqt_header' ); header.contents().filter( '.mw-headline' ).show(); header.next().find( '.lqt-command-edit-subject' ).show(); form.remove(); } ); header.contents().filter( 'span.mw-headline' ).hide(); var subjectForm = $( '' ); subjectForm.append( textbox ); subjectForm.append( ' ' ); subjectForm.append( saveButton ); subjectForm.append( ' ' ); subjectForm.append( cancelButton ); subjectForm.data( 'thread-id', threadId ); header.append( subjectForm ); }, handleSubjectSave: function () { var button = $( this ); var subjectForm = button.closest( '.mw-subject-editor' ); var header = subjectForm.closest( '.lqt_header' ); var threadId = subjectForm.data( 'thread-id' ); var textbox = $( '#lqt-subject-input-'+threadId ); var newSubject = $.trim( textbox.val() ); if ( !newSubject ) { alert( mw.msg( 'lqt-ajax-no-subject' ) ); return; } // Add a spinner var spinner = $( '' ); header.append( spinner ); subjectForm.hide(); var request = { action: 'threadaction', threadaction: 'setsubject', subject: $.trim( newSubject ), thread: threadId, token: mw.user.tokens.get( 'editToken' ) }; // Set new subject through API. ( new mw.Api() ).post( request ).done( function ( reply ) { var result; try { result = reply.threadaction.thread.result; } catch ( err ) { result = 'error'; } if ( result === 'success' ) { spinner.remove(); header.next().find( '.lqt-command-edit-subject' ).show(); liquidThreads.doReloadThread( $( '#lqt_thread_id_' + threadId ) ); } else { var code, description; try { code = reply.error.code; description = reply.error.info; if ( code === 'invalid-subject' ) { alert( mediaWiki.msg( 'lqt-ajax-invalid-subject' ) ); } subjectForm.show(); spinner.remove(); } catch ( err ) { alert( mediaWiki.msg( 'lqt-save-subject-error-unknown' ) ); subjectForm.remove(); spinner.remove(); header.contents().filter( '.mw-headline' ).show(); header.next().find( '.lqt-command-edit-subject' ).show(); } } } ); }, handleDocumentClick: function () { // Collapse all menus $( '.lqt-thread-toolbar-command-list' ).hide( 'fast' ); }, 'checkForUpdates' : function () { var threadModifiedTS = {}; var threads = []; $( '.lqt-thread-topmost' ).each( function () { var tsField = $( this ).find( '.lqt-thread-modified' ); if ( tsField.length ) { var oldTS = tsField.val(); // Prefix is lqt-thread-modified- var threadID = tsField.attr( 'id' ).substr( 'lqt-thread-modified-'.length ); threadModifiedTS[threadID] = oldTS; threads.push( threadID ); } } ); // Optimisation: if no threads are to be checked, do not check. if ( !threads.length ) { return; } ( new mw.Api() ).get( { 'action': 'query', 'list' : 'threads', 'thid' : threads.join( '|' ), 'thprop': 'id|subject|parent|modified' } ).done ( function ( data ) { var threads = data.query.threads; $.each( threads, function ( i, thread ) { var threadID = thread.id; var threadModified = thread.modified; if ( threadModified !== threadModifiedTS[threadID] ) { liquidThreads.showUpdated( threadID ); } } ); } ); }, 'showUpdated' : function ( id ) { // Check if there's already an updated marker here var threadObject = $( '#lqt_thread_id_' + id ); if ( threadObject.find( '.lqt-updated-notification' ).length ) { return; } var notifier = $( '' ); notifier.text( mw.msg( 'lqt-ajax-updated' ) + ' ' ); notifier.addClass( 'lqt-updated-notification' ); var updateButton = $( '' ); updateButton.text( mw.msg( 'lqt-ajax-update-link' ) ); updateButton.addClass( 'lqt-update-link' ); updateButton.click( liquidThreads.updateThread ); notifier.append( updateButton ); threadObject.prepend( notifier ); }, 'updateThread' : function ( e ) { e.preventDefault(); var thread = $( this ).closest( '.lqt_thread' ); liquidThreads.doReloadThread( thread ); }, 'doReloadThread' : function ( thread /* The .lqt_thread */ ) { var post = thread.find( 'div.lqt-post-wrapper' )[0]; post = $( post ); var threadId = thread.data( 'thread-id' ); var loader = $( '' ); var header = $( '#lqt-header-' + threadId ); thread.prepend( loader ); // Build an AJAX request ( new mw.Api() ).get( { action : 'query', list : 'threads', thid : threadId, thrender: 1 } ).done( function ( data ) { // Load data from JSON var html = data.query.threads[threadId].content; var newContent = $( html ); // Clear old post and header. thread.empty(); thread.hide(); header.empty(); header.hide(); // Replace post content var newThread = newContent.filter( 'div.lqt_thread' ); var newThreadContent = newThread.contents(); thread.append( newThreadContent ); thread.attr( 'class', newThread.attr( 'class' ) ); // Set up thread. thread.find( '.lqt-post-wrapper' ).each( function () { liquidThreads.setupThread( $( this ) ); } ); header.fadeIn(); thread.fadeIn(); // Scroll to the updated thread. var targetOffset = $( thread ).offset().top; $( 'html, body' ).animate( { scrollTop: targetOffset }, 'slow' ); } ); }, 'setupThread' : function ( threadContainer ) { var prefixLength = 'lqt_thread_id_'.length; // Add the interruption class if it needs it // FIXME: misses a lot of cases var $parentWrapper = $( threadContainer ) .closest( '.lqt-thread-wrapper' ).parent().closest( '.lqt-thread-wrapper' ); if ( $parentWrapper.next( '.lqt-thread-wrapper' ).length > 0 ) { $parentWrapper .find( '.lqt-thread-replies' ) .addClass( 'lqt-thread-replies-interruption' ); } // Update reply links var threadWrapper = $( threadContainer ).closest( '.lqt_thread' )[0]; var threadId = threadWrapper.id.substring( prefixLength ); $( threadContainer ).data( 'thread-id', threadId ); $( threadWrapper ).data( 'thread-id', threadId ); // Set up reply link var replyLinks = $( threadWrapper ).find( '.lqt-add-reply' ); replyLinks.click( liquidThreads.handleReplyLink ); replyLinks.data( 'thread-id', threadId ); // Hide edit forms $( threadContainer ).find( 'div.lqt-edit-form' ).each( function () { if ( $( this ).find( '#wpTextbox1' ).length ) { return; } this.style.display = 'none'; } ); // Update menus $( threadContainer ).each( liquidThreads.setupMenus ); // Update thread-level menu, if appropriate if ( $( threadWrapper ).hasClass( 'lqt-thread-topmost' ) ) { // To perform better, check the 3 elements before the top-level thread container before // scanning the whole document var menu, threadLevelCommandSelector = '#lqt-threadlevel-commands-'+threadId, traverseElement = $( threadWrapper ); for ( var i = 0; i < 3 && menu === undefined; ++i ) { traverseElement = traverseElement.prev(); if ( traverseElement.is( threadLevelCommandSelector ) ) { menu = traverseElement; } } if ( typeof menu === 'undefined' ) { menu = $( threadLevelCommandSelector ); } liquidThreads.setupThreadMenu( menu, threadId ); } }, 'showReplies' : function ( e ) { e.preventDefault(); // Grab the closest thread var thread = $( this ).closest( '.lqt_thread' ).find( 'div.lqt-post-wrapper' )[0]; thread = $( thread ); var threadId = thread.data( 'thread-id' ); var replies = thread.parent().find( '.lqt-thread-replies' ); var loader = $( '' ); var sep = $( '