Index: trunk/extensions/LiquidThreads/i18n/Lqt.i18n.php |
— | — | @@ -60,6 +60,7 @@ |
61 | 61 | 'lqt_hist_merged_to' => '[[$1|Reply]] moved from another thread', |
62 | 62 | 'lqt_hist_split_from' => 'Split to a new thread', |
63 | 63 | 'lqt_hist_root_blanked' => 'Removed comment text', |
| 64 | + 'lqt_hist_adjusted_sortkey' => 'Adjusted thread position', |
64 | 65 | |
65 | 66 | 'lqt_revision_as_of' => "Revision as of $2 at $3.", |
66 | 67 | |
— | — | @@ -260,6 +261,17 @@ |
261 | 262 | 'lqt-save-subject-error-unknown' => 'An unknown error occurred when attempting '. |
262 | 263 | 'to set the subject of this thread. Please try to do this by clicking "edit" on the top post.', |
263 | 264 | 'lqt-cancel-subject-edit' => 'Cancel', |
| 265 | + 'lqt-drag-activate' => 'Drag to new location', |
| 266 | + 'lqt-drag-drop-zone' => 'Drop here', |
| 267 | + 'lqt-drag-confirm' => 'To complete the following actions, please fill in a reason '. |
| 268 | + 'and click "Confirm".', |
| 269 | + 'lqt-drag-reparent' => "Move post to underneath a new parent.", |
| 270 | + 'lqt-drag-split' => 'Move post to its own thread', |
| 271 | + 'lqt-drag-setsortkey' => "Adjust post's position on the page", |
| 272 | + 'lqt-drag-bump' => 'Move post to top of discussion page', |
| 273 | + 'lqt-drag-save' => 'Confirm', |
| 274 | + 'lqt-drag-reason' => 'Reason for change: ', |
| 275 | + 'lqt-drag-subject' => 'Subject for new thread: ', |
264 | 276 | |
265 | 277 | // Feeds |
266 | 278 | 'lqt-feed-title-all' => '{{SITENAME}} — New posts', |
Index: trunk/extensions/LiquidThreads/jquery/js2.combined.js |
— | — | @@ -5659,7 +5659,291 @@ |
5660 | 5660 | }); |
5661 | 5661 | |
5662 | 5662 | })(jQuery); |
| 5663 | + |
5663 | 5664 | /* |
| 5665 | + * jQuery UI Droppable 1.7.2 |
| 5666 | + * |
| 5667 | + * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) |
| 5668 | + * Dual licensed under the MIT (MIT-LICENSE.txt) |
| 5669 | + * and GPL (GPL-LICENSE.txt) licenses. |
| 5670 | + * |
| 5671 | + * http://docs.jquery.com/UI/Droppables |
| 5672 | + * |
| 5673 | + * Depends: |
| 5674 | + * ui.core.js |
| 5675 | + * ui.draggable.js |
| 5676 | + */ |
| 5677 | +(function($) { |
| 5678 | + |
| 5679 | +$.widget("ui.droppable", { |
| 5680 | + |
| 5681 | + _init: function() { |
| 5682 | + |
| 5683 | + var o = this.options, accept = o.accept; |
| 5684 | + this.isover = 0; this.isout = 1; |
| 5685 | + |
| 5686 | + this.options.accept = this.options.accept && $.isFunction(this.options.accept) ? this.options.accept : function(d) { |
| 5687 | + return d.is(accept); |
| 5688 | + }; |
| 5689 | + |
| 5690 | + //Store the droppable's proportions |
| 5691 | + this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight }; |
| 5692 | + |
| 5693 | + // Add the reference and positions to the manager |
| 5694 | + $.ui.ddmanager.droppables[this.options.scope] = $.ui.ddmanager.droppables[this.options.scope] || []; |
| 5695 | + $.ui.ddmanager.droppables[this.options.scope].push(this); |
| 5696 | + |
| 5697 | + (this.options.addClasses && this.element.addClass("ui-droppable")); |
| 5698 | + |
| 5699 | + }, |
| 5700 | + |
| 5701 | + destroy: function() { |
| 5702 | + var drop = $.ui.ddmanager.droppables[this.options.scope]; |
| 5703 | + for ( var i = 0; i < drop.length; i++ ) |
| 5704 | + if ( drop[i] == this ) |
| 5705 | + drop.splice(i, 1); |
| 5706 | + |
| 5707 | + this.element |
| 5708 | + .removeClass("ui-droppable ui-droppable-disabled") |
| 5709 | + .removeData("droppable") |
| 5710 | + .unbind(".droppable"); |
| 5711 | + }, |
| 5712 | + |
| 5713 | + _setData: function(key, value) { |
| 5714 | + |
| 5715 | + if(key == 'accept') { |
| 5716 | + this.options.accept = value && $.isFunction(value) ? value : function(d) { |
| 5717 | + return d.is(value); |
| 5718 | + }; |
| 5719 | + } else { |
| 5720 | + $.widget.prototype._setData.apply(this, arguments); |
| 5721 | + } |
| 5722 | + |
| 5723 | + }, |
| 5724 | + |
| 5725 | + _activate: function(event) { |
| 5726 | + var draggable = $.ui.ddmanager.current; |
| 5727 | + if(this.options.activeClass) this.element.addClass(this.options.activeClass); |
| 5728 | + (draggable && this._trigger('activate', event, this.ui(draggable))); |
| 5729 | + }, |
| 5730 | + |
| 5731 | + _deactivate: function(event) { |
| 5732 | + var draggable = $.ui.ddmanager.current; |
| 5733 | + if(this.options.activeClass) this.element.removeClass(this.options.activeClass); |
| 5734 | + (draggable && this._trigger('deactivate', event, this.ui(draggable))); |
| 5735 | + }, |
| 5736 | + |
| 5737 | + _over: function(event) { |
| 5738 | + |
| 5739 | + var draggable = $.ui.ddmanager.current; |
| 5740 | + if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element |
| 5741 | + |
| 5742 | + if (this.options.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { |
| 5743 | + if(this.options.hoverClass) this.element.addClass(this.options.hoverClass); |
| 5744 | + this._trigger('over', event, this.ui(draggable)); |
| 5745 | + } |
| 5746 | + |
| 5747 | + }, |
| 5748 | + |
| 5749 | + _out: function(event) { |
| 5750 | + |
| 5751 | + var draggable = $.ui.ddmanager.current; |
| 5752 | + if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return; // Bail if draggable and droppable are same element |
| 5753 | + |
| 5754 | + if (this.options.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { |
| 5755 | + if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass); |
| 5756 | + this._trigger('out', event, this.ui(draggable)); |
| 5757 | + } |
| 5758 | + |
| 5759 | + }, |
| 5760 | + |
| 5761 | + _drop: function(event,custom) { |
| 5762 | + |
| 5763 | + var draggable = custom || $.ui.ddmanager.current; |
| 5764 | + if (!draggable || (draggable.currentItem || draggable.element)[0] == this.element[0]) return false; // Bail if draggable and droppable are same element |
| 5765 | + |
| 5766 | + var childrenIntersection = false; |
| 5767 | + this.element.find(":data(droppable)").not(".ui-draggable-dragging").each(function() { |
| 5768 | + var inst = $.data(this, 'droppable'); |
| 5769 | + if(inst.options.greedy && $.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)) { |
| 5770 | + childrenIntersection = true; return false; |
| 5771 | + } |
| 5772 | + }); |
| 5773 | + if(childrenIntersection) return false; |
| 5774 | + |
| 5775 | + if(this.options.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { |
| 5776 | + if(this.options.activeClass) this.element.removeClass(this.options.activeClass); |
| 5777 | + if(this.options.hoverClass) this.element.removeClass(this.options.hoverClass); |
| 5778 | + this._trigger('drop', event, this.ui(draggable)); |
| 5779 | + return this.element; |
| 5780 | + } |
| 5781 | + |
| 5782 | + return false; |
| 5783 | + |
| 5784 | + }, |
| 5785 | + |
| 5786 | + ui: function(c) { |
| 5787 | + return { |
| 5788 | + draggable: (c.currentItem || c.element), |
| 5789 | + helper: c.helper, |
| 5790 | + position: c.position, |
| 5791 | + absolutePosition: c.positionAbs, //deprecated |
| 5792 | + offset: c.positionAbs |
| 5793 | + }; |
| 5794 | + } |
| 5795 | + |
| 5796 | +}); |
| 5797 | + |
| 5798 | +$.extend($.ui.droppable, { |
| 5799 | + version: "1.7.2", |
| 5800 | + eventPrefix: 'drop', |
| 5801 | + defaults: { |
| 5802 | + accept: '*', |
| 5803 | + activeClass: false, |
| 5804 | + addClasses: true, |
| 5805 | + greedy: false, |
| 5806 | + hoverClass: false, |
| 5807 | + scope: 'default', |
| 5808 | + tolerance: 'intersect' |
| 5809 | + } |
| 5810 | +}); |
| 5811 | + |
| 5812 | +$.ui.intersect = function(draggable, droppable, toleranceMode) { |
| 5813 | + |
| 5814 | + if (!droppable.offset) return false; |
| 5815 | + |
| 5816 | + var x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width, |
| 5817 | + y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height; |
| 5818 | + var l = droppable.offset.left, r = l + droppable.proportions.width, |
| 5819 | + t = droppable.offset.top, b = t + droppable.proportions.height; |
| 5820 | + |
| 5821 | + switch (toleranceMode) { |
| 5822 | + case 'fit': |
| 5823 | + return (l < x1 && x2 < r |
| 5824 | + && t < y1 && y2 < b); |
| 5825 | + break; |
| 5826 | + case 'intersect': |
| 5827 | + return (l < x1 + (draggable.helperProportions.width / 2) // Right Half |
| 5828 | + && x2 - (draggable.helperProportions.width / 2) < r // Left Half |
| 5829 | + && t < y1 + (draggable.helperProportions.height / 2) // Bottom Half |
| 5830 | + && y2 - (draggable.helperProportions.height / 2) < b ); // Top Half |
| 5831 | + break; |
| 5832 | + case 'pointer': |
| 5833 | + var draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left), |
| 5834 | + draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top), |
| 5835 | + isOver = $.ui.isOver(draggableTop, draggableLeft, t, l, droppable.proportions.height, droppable.proportions.width); |
| 5836 | + return isOver; |
| 5837 | + break; |
| 5838 | + case 'touch': |
| 5839 | + return ( |
| 5840 | + (y1 >= t && y1 <= b) || // Top edge touching |
| 5841 | + (y2 >= t && y2 <= b) || // Bottom edge touching |
| 5842 | + (y1 < t && y2 > b) // Surrounded vertically |
| 5843 | + ) && ( |
| 5844 | + (x1 >= l && x1 <= r) || // Left edge touching |
| 5845 | + (x2 >= l && x2 <= r) || // Right edge touching |
| 5846 | + (x1 < l && x2 > r) // Surrounded horizontally |
| 5847 | + ); |
| 5848 | + break; |
| 5849 | + default: |
| 5850 | + return false; |
| 5851 | + break; |
| 5852 | + } |
| 5853 | + |
| 5854 | +}; |
| 5855 | + |
| 5856 | +/* |
| 5857 | + This manager tracks offsets of draggables and droppables |
| 5858 | +*/ |
| 5859 | +$.ui.ddmanager = { |
| 5860 | + current: null, |
| 5861 | + droppables: { 'default': [] }, |
| 5862 | + prepareOffsets: function(t, event) { |
| 5863 | + |
| 5864 | + var m = $.ui.ddmanager.droppables[t.options.scope]; |
| 5865 | + var type = event ? event.type : null; // workaround for #2317 |
| 5866 | + var list = (t.currentItem || t.element).find(":data(droppable)").andSelf(); |
| 5867 | + |
| 5868 | + droppablesLoop: for (var i = 0; i < m.length; i++) { |
| 5869 | + |
| 5870 | + if(m[i].options.disabled || (t && !m[i].options.accept.call(m[i].element[0],(t.currentItem || t.element)))) continue; //No disabled and non-accepted |
| 5871 | + for (var j=0; j < list.length; j++) { if(list[j] == m[i].element[0]) { m[i].proportions.height = 0; continue droppablesLoop; } }; //Filter out elements in the current dragged item |
| 5872 | + m[i].visible = m[i].element.css("display") != "none"; if(!m[i].visible) continue; //If the element is not visible, continue |
| 5873 | + |
| 5874 | + m[i].offset = m[i].element.offset(); |
| 5875 | + m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight }; |
| 5876 | + |
| 5877 | + if(type == "mousedown") m[i]._activate.call(m[i], event); //Activate the droppable if used directly from draggables |
| 5878 | + |
| 5879 | + } |
| 5880 | + |
| 5881 | + }, |
| 5882 | + drop: function(draggable, event) { |
| 5883 | + |
| 5884 | + var dropped = false; |
| 5885 | + $.each($.ui.ddmanager.droppables[draggable.options.scope], function() { |
| 5886 | + |
| 5887 | + if(!this.options) return; |
| 5888 | + if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) |
| 5889 | + dropped = this._drop.call(this, event); |
| 5890 | + |
| 5891 | + if (!this.options.disabled && this.visible && this.options.accept.call(this.element[0],(draggable.currentItem || draggable.element))) { |
| 5892 | + this.isout = 1; this.isover = 0; |
| 5893 | + this._deactivate.call(this, event); |
| 5894 | + } |
| 5895 | + |
| 5896 | + }); |
| 5897 | + return dropped; |
| 5898 | + |
| 5899 | + }, |
| 5900 | + drag: function(draggable, event) { |
| 5901 | + |
| 5902 | + //If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse. |
| 5903 | + if(draggable.options.refreshPositions) $.ui.ddmanager.prepareOffsets(draggable, event); |
| 5904 | + |
| 5905 | + //Run through all droppables and check their positions based on specific tolerance options |
| 5906 | + |
| 5907 | + $.each($.ui.ddmanager.droppables[draggable.options.scope], function() { |
| 5908 | + |
| 5909 | + if(this.options.disabled || this.greedyChild || !this.visible) return; |
| 5910 | + var intersects = $.ui.intersect(draggable, this, this.options.tolerance); |
| 5911 | + |
| 5912 | + var c = !intersects && this.isover == 1 ? 'isout' : (intersects && this.isover == 0 ? 'isover' : null); |
| 5913 | + if(!c) return; |
| 5914 | + |
| 5915 | + var parentInstance; |
| 5916 | + if (this.options.greedy) { |
| 5917 | + var parent = this.element.parents(':data(droppable):eq(0)'); |
| 5918 | + if (parent.length) { |
| 5919 | + parentInstance = $.data(parent[0], 'droppable'); |
| 5920 | + parentInstance.greedyChild = (c == 'isover' ? 1 : 0); |
| 5921 | + } |
| 5922 | + } |
| 5923 | + |
| 5924 | + // we just moved into a greedy child |
| 5925 | + if (parentInstance && c == 'isover') { |
| 5926 | + parentInstance['isover'] = 0; |
| 5927 | + parentInstance['isout'] = 1; |
| 5928 | + parentInstance._out.call(parentInstance, event); |
| 5929 | + } |
| 5930 | + |
| 5931 | + this[c] = 1; this[c == 'isout' ? 'isover' : 'isout'] = 0; |
| 5932 | + this[c == "isover" ? "_over" : "_out"].call(this, event); |
| 5933 | + |
| 5934 | + // we just moved out of a greedy child |
| 5935 | + if (parentInstance && c == 'isout') { |
| 5936 | + parentInstance['isout'] = 0; |
| 5937 | + parentInstance['isover'] = 1; |
| 5938 | + parentInstance._over.call(parentInstance, event); |
| 5939 | + } |
| 5940 | + }); |
| 5941 | + |
| 5942 | + } |
| 5943 | +}; |
| 5944 | + |
| 5945 | +})(jQuery); |
| 5946 | + |
| 5947 | +/* |
5664 | 5948 | * jQuery UI Resizable 1.7.2 |
5665 | 5949 | * |
5666 | 5950 | * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about) |
Index: trunk/extensions/LiquidThreads/lqt.css |
— | — | @@ -469,3 +469,22 @@ |
470 | 470 | .lqt-reply-form { |
471 | 471 | padding: 0.5em 1em; |
472 | 472 | } |
| 473 | + |
| 474 | +.lqt-drop-zone { |
| 475 | + margin-top: 1em; |
| 476 | + margin-bottom: 1em; |
| 477 | + padding: 0.5em 1em; |
| 478 | + border: 1px solid #999999; |
| 479 | + text-align: center center; |
| 480 | + color: #999999; |
| 481 | +} |
| 482 | + |
| 483 | +.lqt-drop-zone.lqt-drop-zone-active { |
| 484 | + border: 1px solid #3333ff !important; |
| 485 | +} |
| 486 | + |
| 487 | +.lqt-drop-zone.lqt-drop-zone-hover { |
| 488 | + border: 2px solid #3333ff !important; |
| 489 | + color: #3333ff !important; |
| 490 | + font-weight: bold !important; |
| 491 | +} |
Index: trunk/extensions/LiquidThreads/LiquidThreads.php |
— | — | @@ -204,10 +204,11 @@ |
205 | 205 | $wgLqtEnotif = true; |
206 | 206 | |
207 | 207 | /* Thread actions which do *not* cause threads to be "bumped" to the top */ |
208 | | -/* Using numbers because the change type constants are defined in Thread.php, don't |
| 208 | +/* Using numbers because the change type constants are defined in Threads.php, don't |
209 | 209 | want to have to parse it on every page view */ |
210 | 210 | $wgThreadActionsNoBump = array( 3 /* Edited summary */, 10 /* Merged from */, |
211 | | - 12 /* Split from */, 2 /* Edited root */, ); |
| 211 | + 12 /* Split from */, 2 /* Edited root */, |
| 212 | + 14 /* Adjusted sortkey */ ); |
212 | 213 | |
213 | 214 | /** Switch this on if you've migrated from a version before around May 2009 */ |
214 | 215 | $wgLiquidThreadsMigrate = false; |
Index: trunk/extensions/LiquidThreads/classes/Threads.php |
— | — | @@ -23,6 +23,7 @@ |
24 | 24 | const CHANGE_MERGED_TO = 11; |
25 | 25 | const CHANGE_SPLIT_FROM = 12; |
26 | 26 | const CHANGE_ROOT_BLANKED = 13; |
| 27 | + const CHANGE_ADJUSTED_SORTKEY = 14; |
27 | 28 | |
28 | 29 | static $VALID_CHANGE_TYPES = array( |
29 | 30 | self::CHANGE_EDITED_SUMMARY, |
— | — | @@ -39,6 +40,7 @@ |
40 | 41 | self::CHANGE_MERGED_TO, |
41 | 42 | self::CHANGE_SPLIT_FROM, |
42 | 43 | self::CHANGE_ROOT_BLANKED, |
| 44 | + self::CHANGE_ADJUSTED_SORTKEY, |
43 | 45 | ); |
44 | 46 | |
45 | 47 | // Possible values of Thread->editedness. |
Index: trunk/extensions/LiquidThreads/classes/View.php |
— | — | @@ -855,6 +855,16 @@ |
856 | 856 | 'lqt-ajax-invalid-subject', |
857 | 857 | 'lqt-save-subject-error-unknown', |
858 | 858 | 'lqt-cancel-subject-edit', |
| 859 | + 'lqt-drag-activate', |
| 860 | + 'lqt-drag-drop-zone', |
| 861 | + 'lqt-drag-confirm', |
| 862 | + 'lqt-drag-reparent', |
| 863 | + 'lqt-drag-split', |
| 864 | + 'lqt-drag-setsortkey', |
| 865 | + 'lqt-drag-bump', |
| 866 | + 'lqt-drag-save', |
| 867 | + 'lqt-drag-reason', |
| 868 | + 'lqt-drag-subject', |
859 | 869 | ); |
860 | 870 | |
861 | 871 | $data = array(); |
— | — | @@ -1454,6 +1464,10 @@ |
1455 | 1465 | wfTimestamp( TS_MW, $thread->modified() ), |
1456 | 1466 | array( 'id' => 'lqt-thread-modified-' . $thread->id(), |
1457 | 1467 | 'class' => 'lqt-thread-modified' ) ); |
| 1468 | + $html .= Xml::hidden( 'lqt-thread-sortkey', |
| 1469 | + $thread->sortkey(), |
| 1470 | + array( 'id' => 'lqt-thread-sortkey-' . $thread->id() ) |
| 1471 | + ); |
1458 | 1472 | } |
1459 | 1473 | |
1460 | 1474 | // Add the thread's title |
Index: trunk/extensions/LiquidThreads/classes/Thread.php |
— | — | @@ -150,7 +150,7 @@ |
151 | 151 | $bump = !in_array( $change_type, $wgThreadActionsNoBump ); |
152 | 152 | } |
153 | 153 | if ( $bump ) { |
154 | | - $this->sortkey = wfTimestampNow( TS_DB ); |
| 154 | + $this->sortkey = wfTimestamp( TS_MW ); |
155 | 155 | } |
156 | 156 | |
157 | 157 | $this->modified = wfTimestampNow(); |
— | — | @@ -159,7 +159,7 @@ |
160 | 160 | |
161 | 161 | $topmost = $this->topmostThread(); |
162 | 162 | $topmost->modified = wfTimestampNow(); |
163 | | - if ( $bump ) $topmost->setSortkey( wfTimestampNow( TS_DB ) ); |
| 163 | + if ( $bump ) $topmost->setSortkey( wfTimestamp( TS_MW ) ); |
164 | 164 | $topmost->save(); |
165 | 165 | |
166 | 166 | ThreadRevision::create( $this, $change_type, $change_object, $reason ); |
— | — | @@ -415,7 +415,7 @@ |
416 | 416 | $dbr = wfGetDB( DB_SLAVE ); |
417 | 417 | $this->modified = $dbr->timestamp( wfTimestampNow() ); |
418 | 418 | $this->created = $dbr->timestamp( wfTimestampNow() ); |
419 | | - $this->sortkey = wfTimestampNow( TS_DB ); |
| 419 | + $this->sortkey = wfTimestamp( TS_MW ); |
420 | 420 | $this->editedness = Threads::EDITED_NEVER; |
421 | 421 | $this->replyCount = 0; |
422 | 422 | return; |
— | — | @@ -868,12 +868,15 @@ |
869 | 869 | foreach( $replies as $reply ) { |
870 | 870 | if ( ! $reply->hasSuperthread() ) { |
871 | 871 | throw new MWException( "Post ".$this->id(). |
872 | | - " has contaminated reply ".$reply->id()."\n" ); |
| 872 | + " has contaminated reply ".$reply->id(). |
| 873 | + ". Found no superthread."); |
873 | 874 | } |
874 | 875 | |
875 | 876 | if ( $reply->superthread()->id() != $this->id() ) { |
876 | 877 | throw new MWException( "Post ". $this->id() . |
877 | | - " has contaminated reply ".$reply->id()."\n" ); |
| 878 | + " has contaminated reply ".$reply->id(). |
| 879 | + ". Expected ".$this->id().", got ". |
| 880 | + $reply->superthread()->id() ); |
878 | 881 | } |
879 | 882 | } |
880 | 883 | } |
— | — | @@ -1292,7 +1295,7 @@ |
1293 | 1296 | function setSortKey( $k = null ) { |
1294 | 1297 | if ( is_null( $k ) ) { |
1295 | 1298 | $dbr = wfGetDB( DB_SLAVE ); |
1296 | | - $k = wfTimestampNow( TS_DB ); |
| 1299 | + $k = wfTimestamp( TS_MW ); |
1297 | 1300 | } |
1298 | 1301 | |
1299 | 1302 | $this->sortkey = $k; |
— | — | @@ -1325,7 +1328,7 @@ |
1326 | 1329 | } |
1327 | 1330 | } |
1328 | 1331 | |
1329 | | - public function split( $newSubject, $reason = '' ) { |
| 1332 | + public function split( $newSubject, $reason = '', $newSortkey = null ) { |
1330 | 1333 | $oldTopThread = $this->topmostThread(); |
1331 | 1334 | $oldParent = $this->superthread(); |
1332 | 1335 | |
— | — | @@ -1333,15 +1336,21 @@ |
1334 | 1337 | |
1335 | 1338 | $oldParent->removeReply( $this ); |
1336 | 1339 | |
| 1340 | + $bump = null; |
| 1341 | + if ( !is_null($newSortkey) ) { |
| 1342 | + $this->setSortkey( $newSortkey ); |
| 1343 | + $bump = false; |
| 1344 | + } |
| 1345 | + |
1337 | 1346 | $oldTopThread->commitRevision( Threads::CHANGE_SPLIT_FROM, $this, $reason ); |
1338 | | - $this->commitRevision( Threads::CHANGE_SPLIT, null, $reason ); |
| 1347 | + $this->commitRevision( Threads::CHANGE_SPLIT, null, $reason, $bump ); |
1339 | 1348 | } |
1340 | 1349 | |
1341 | 1350 | public function moveToParent( $newParent, $reason = '' ) { |
1342 | 1351 | $newSubject = $newParent->subject(); |
1343 | 1352 | |
1344 | 1353 | $oldTopThread = $newParent->topmostThread(); |
1345 | | - $oldParent = $newParent->superthread(); |
| 1354 | + $oldParent = $this->superthread(); |
1346 | 1355 | |
1347 | 1356 | Thread::recursiveSet( $this, $newSubject, $newParent, $newParent ); |
1348 | 1357 | |
Index: trunk/extensions/LiquidThreads/classes/ThreadHistoryPager.php |
— | — | @@ -25,6 +25,7 @@ |
26 | 26 | Threads::CHANGE_MERGED_TO => wfMsgNoTrans( 'lqt_hist_merged_to' ), |
27 | 27 | Threads::CHANGE_SPLIT_FROM => wfMsgNoTrans( 'lqt_hist_split_from' ), |
28 | 28 | Threads::CHANGE_ROOT_BLANKED => wfMsgNoTrans( 'lqt_hist_root_blanked' ), |
| 29 | + Threads::CHANGE_ADJUSTED_SORTKEY => wfMsgNoTrans( 'lqt_hist_adjusted_sortkey' ), |
29 | 30 | ); |
30 | 31 | } |
31 | 32 | |
Index: trunk/extensions/LiquidThreads/api/ApiThreadAction.php |
— | — | @@ -12,9 +12,10 @@ |
13 | 13 | 'markunread' => 'actionMarkUnread', |
14 | 14 | 'split' => 'actionSplit', |
15 | 15 | 'merge' => 'actionMerge', |
16 | | - 'reply' => 'actionReply', // Not implemented |
| 16 | + 'reply' => 'actionReply', |
17 | 17 | 'newthread' => 'actionNewThread', |
18 | 18 | 'setsubject' => 'actionSetSubject', |
| 19 | + 'setsortkey' => 'actionSetSortkey', |
19 | 20 | ); |
20 | 21 | } |
21 | 22 | |
— | — | @@ -35,6 +36,9 @@ |
36 | 37 | "timestamp. If false, does not set it. Default depends on ". |
37 | 38 | "the action being taken. Presently only works for newthread ". |
38 | 39 | "and reply actions.", |
| 40 | + 'sortkey' => "Specifies the timestamp to which to set a thread's ". |
| 41 | + "sort key. Must be in the form YYYYMMddhhmmss, ". |
| 42 | + "a unix timestamp or 'now'.", |
39 | 43 | ); |
40 | 44 | } |
41 | 45 | |
— | — | @@ -60,6 +64,7 @@ |
61 | 65 | 'text' => null, |
62 | 66 | 'render' => null, |
63 | 67 | 'bump' => null, |
| 68 | + 'sortkey' => null, |
64 | 69 | ); |
65 | 70 | } |
66 | 71 | |
— | — | @@ -192,8 +197,17 @@ |
193 | 198 | $reason = $params['reason']; |
194 | 199 | } |
195 | 200 | |
| 201 | + // Check if they specified a sortkey |
| 202 | + $sortkey = null; |
| 203 | + if ( !empty($params['sortkey']) ) { |
| 204 | + $ts = $params['sortkey']; |
| 205 | + $ts = wfTimestamp( TS_MW, $ts ); |
| 206 | + |
| 207 | + $sortkey = $ts; |
| 208 | + } |
| 209 | + |
196 | 210 | // Do the split |
197 | | - $thread->split( $subject, $reason ); |
| 211 | + $thread->split( $subject, $reason, $sortkey ); |
198 | 212 | |
199 | 213 | $result = array(); |
200 | 214 | $result[] = |
— | — | @@ -576,7 +590,7 @@ |
577 | 591 | 'action' => 'setsubject', |
578 | 592 | 'result' => 'success', |
579 | 593 | 'thread-id' => $thread->id(), |
580 | | - 'thread-title' => $thread->title(), |
| 594 | + 'thread-title' => $thread->title()->getPrefixedText(), |
581 | 595 | 'new-subject' => $subject, |
582 | 596 | ); |
583 | 597 | |
— | — | @@ -585,6 +599,58 @@ |
586 | 600 | $this->getResult()->addValue( null, $this->getModuleName(), $result ); |
587 | 601 | } |
588 | 602 | |
| 603 | + public function actionSetSortkey( $threads, $params ) { |
| 604 | + // First check for threads |
| 605 | + if ( !count($threads) ) { |
| 606 | + $this->dieUsage( 'You must specify a thread to set the sortkey of', |
| 607 | + 'no-specified-threads' ); |
| 608 | + return; |
| 609 | + } |
| 610 | + |
| 611 | + // Validate timestamp |
| 612 | + if ( empty( $params['sortkey'] ) ) { |
| 613 | + $this->dieUsage( 'You must specify a valid timestamp for the sortkey'. |
| 614 | + 'parameter. It should be in the form YYYYMMddhhmmss, a '. |
| 615 | + 'unix timestamp or "now".', 'invalid-sortkey' ); |
| 616 | + return; |
| 617 | + } |
| 618 | + |
| 619 | + $ts = $params['sortkey']; |
| 620 | + |
| 621 | + if ($ts == 'now') $ts = wfTimestampNow(); |
| 622 | + |
| 623 | + $ts = wfTimestamp( TS_MW, $ts ); |
| 624 | + |
| 625 | + if ( !$ts ) { |
| 626 | + $this->dieUsage( 'You must specify a valid timestamp for the sortkey'. |
| 627 | + 'parameter. It should be in the form YYYYMMddhhmmss, a '. |
| 628 | + 'unix timestamp or "now".', 'invalid-sortkey' ); |
| 629 | + return; |
| 630 | + } |
| 631 | + |
| 632 | + $reason = null; |
| 633 | + |
| 634 | + if ( isset( $params['reason'] ) ) { |
| 635 | + $reason = $params['reason']; |
| 636 | + } |
| 637 | + |
| 638 | + $thread = array_pop($threads); |
| 639 | + $thread->setSortkey( $ts ); |
| 640 | + $thread->commitRevision( Threads::CHANGE_ADJUSTED_SORTKEY, null, $reason ); |
| 641 | + |
| 642 | + $result = array( |
| 643 | + 'action' => 'setsortkey', |
| 644 | + 'result' => 'success', |
| 645 | + 'thread-id' => $thread->id(), |
| 646 | + 'thread-title' => $thread->title()->getPrefixedText(), |
| 647 | + 'new-sortkey' => $ts, |
| 648 | + ); |
| 649 | + |
| 650 | + $result = array( 'thread' => $result ); |
| 651 | + |
| 652 | + $this->getResult()->addValue( null, $this->getModuleName(), $result ); |
| 653 | + } |
| 654 | + |
589 | 655 | public function getVersion() { |
590 | 656 | return __CLASS__ . ': $Id: $'; |
591 | 657 | } |
Index: trunk/extensions/LiquidThreads/lqt.js |
— | — | @@ -35,14 +35,7 @@ |
36 | 36 | |
37 | 37 | // Try to find a place for it |
38 | 38 | if ( !repliesElement.length ) { |
39 | | - repliesElement = $j('<div class="lqt-thread-replies"/>' ); |
40 | | - |
41 | | - var finishDiv = $j('<div class="lqt-replies-finish"/>'); |
42 | | - finishDiv.append($j('<div class="lqt-replies-finish-corner"/>')); |
43 | | - finishDiv.contents().html(' '); |
44 | | - repliesElement.append(finishDiv); |
45 | | - |
46 | | - $j(container).append(repliesElement); |
| 39 | + repliesElement = liquidThreads.getRepliesElement( $j(container) ); |
47 | 40 | } |
48 | 41 | |
49 | 42 | repliesElement.find('.lqt-replies-finish').before( replyDiv ); |
— | — | @@ -55,6 +48,38 @@ |
56 | 49 | liquidThreads.currentReplyThread = thread_id; |
57 | 50 | }, |
58 | 51 | |
| 52 | + 'getRepliesElement' : function(thread /* a .lqt_thread */ ) { |
| 53 | + var repliesElement = thread.contents().filter('.lqt-thread-replies'); |
| 54 | + |
| 55 | + if ( !repliesElement.length ) { |
| 56 | + repliesElement = $j('<div class="lqt-thread-replies"/>' ); |
| 57 | + |
| 58 | + var finishDiv = $j('<div class="lqt-replies-finish"/>'); |
| 59 | + finishDiv.append($j('<div class="lqt-replies-finish-corner"/>')); |
| 60 | + finishDiv.contents().html(' '); |
| 61 | + repliesElement.append(finishDiv); |
| 62 | + |
| 63 | + var repliesFinishElement = thread.contents().filter('.lqt-replies-finish'); |
| 64 | + if ( repliesFinishElement.length ) { |
| 65 | + repliesFinishElement.before(repliesElement); |
| 66 | + } else { |
| 67 | + thread.append(repliesElement); |
| 68 | + } |
| 69 | + } |
| 70 | + |
| 71 | + return repliesElement; |
| 72 | + }, |
| 73 | + |
| 74 | + 'checkEmptyReplies' : function( element ) { |
| 75 | + var contents = element.contents(); |
| 76 | + |
| 77 | + contents = contents.not('.lqt-replies-finish,.lqt-post-sep,.lqt-edit-form'); |
| 78 | + |
| 79 | + if ( !contents.length ) { |
| 80 | + element.remove(); |
| 81 | + } |
| 82 | + }, |
| 83 | + |
59 | 84 | 'handleNewLink' : function(e) { |
60 | 85 | e.preventDefault(); |
61 | 86 | |
— | — | @@ -264,23 +289,18 @@ |
265 | 290 | }, |
266 | 291 | |
267 | 292 | 'cancelEdit' : function( e ) { |
268 | | - if (e.preventDefault) { |
| 293 | + if ( typeof e != 'undefined' && typeof e.preventDefault == 'function' ) { |
269 | 294 | e.preventDefault(); |
270 | 295 | } |
271 | 296 | |
272 | 297 | $j('.lqt-edit-form').not(e).each( |
273 | 298 | function() { |
274 | 299 | var repliesElement = $j(this).closest('.lqt-thread-replies'); |
275 | | - var emptyAll = repliesElement.length && |
276 | | - !( repliesElement.contents().not('.lqt-replies-finish') |
277 | | - .not('.lqt-edit-form').length ); |
278 | 300 | $j(this).fadeOut('slow', |
279 | 301 | function() { |
280 | 302 | $j(this).empty(); |
281 | 303 | |
282 | | - if (emptyAll) { |
283 | | - repliesElement.remove(); |
284 | | - } |
| 304 | + liquidThreads.checkEmptyReplies( repliesElement ); |
285 | 305 | } ) |
286 | 306 | } ); |
287 | 307 | |
— | — | @@ -305,6 +325,18 @@ |
306 | 326 | var replyLink = menu.find('.lqt-command-reply > a'); |
307 | 327 | replyLink.data( 'thread-id', threadID ); |
308 | 328 | replyLink.click( liquidThreads.handleReplyLink ); |
| 329 | + |
| 330 | + // Add "Drag to new location" to menu |
| 331 | + var dragLI = $j('<li class="lqt-command-drag" />' ); |
| 332 | + var dragLink = $j('<a/>').text( wgLqtMessages['lqt-drag-activate'] ); |
| 333 | + dragLink.attr('href','#'); |
| 334 | + dragLI.append(dragLink); |
| 335 | + dragLink.click( liquidThreads.activateDragDrop ); |
| 336 | + |
| 337 | + menu.append(dragLI); |
| 338 | + |
| 339 | + // Remove split and merge |
| 340 | + menu.contents().filter('.lqt-command-split,.lqt-command-merge').remove(); |
309 | 341 | |
310 | 342 | var trigger = menuContainer.find( '.lqt-thread-actions-trigger' ) |
311 | 343 | |
— | — | @@ -536,7 +568,7 @@ |
537 | 569 | 'doReloadThread' : function( thread /* The .lqt_thread */ ) { |
538 | 570 | var post = thread.find('div.lqt-post-wrapper')[0]; |
539 | 571 | post = $j(post); |
540 | | - var threadId = post.data('thread-id'); |
| 572 | + var threadId = thread.data('thread-id'); |
541 | 573 | var loader = $j('<div class="mw-ajax-loader"/>'); |
542 | 574 | var header = $j('#lqt-header-'+threadId); |
543 | 575 | |
— | — | @@ -559,14 +591,20 @@ |
560 | 592 | header.hide(); |
561 | 593 | |
562 | 594 | // Replace post content |
563 | | - var newThread = newContent.filter('div.lqt_thread')[0]; |
564 | | - var newThreadContent = $j(newThread).contents(); |
| 595 | + var newThread = newContent.filter('div.lqt_thread'); |
| 596 | + var newThreadContent = newThread.contents(); |
565 | 597 | thread.append( newThreadContent ); |
| 598 | + thread.attr( 'class', newThread.attr('class') ); |
566 | 599 | |
567 | 600 | // Replace header content |
568 | 601 | var newHeader = newContent.filter('#lqt-header-'+threadId); |
569 | | - var newHeaderContent = $j(newHeader).contents(); |
570 | | - header.append( newHeaderContent ); |
| 602 | + if ( header.length ) { |
| 603 | + var newHeaderContent = $j(newHeader).contents(); |
| 604 | + header.append( newHeaderContent ); |
| 605 | + } else { |
| 606 | + // No existing header, add one before the thread |
| 607 | + thread.before(newHeader); |
| 608 | + } |
571 | 609 | |
572 | 610 | // Set up thread. |
573 | 611 | thread.find('.lqt-post-wrapper').each( function() { |
— | — | @@ -591,6 +629,8 @@ |
592 | 630 | var threadId = threadWrapper.id.substring( prefixLength ); |
593 | 631 | |
594 | 632 | $j(threadContainer).data( 'thread-id', threadId ); |
| 633 | + $j(threadWrapper).data( 'thread-id', threadId ); |
| 634 | + console.log( "Set up thread "+threadId ); |
595 | 635 | |
596 | 636 | // Set up reply link |
597 | 637 | var replyLinks = $j(threadWrapper).find('.lqt-add-reply'); |
— | — | @@ -1041,6 +1081,389 @@ |
1042 | 1082 | } |
1043 | 1083 | }, 'json' ); |
1044 | 1084 | } ); |
| 1085 | + }, |
| 1086 | + |
| 1087 | + 'activateDragDrop' : function(e) { |
| 1088 | + e.preventDefault(); |
| 1089 | + |
| 1090 | + // Set up draggability. |
| 1091 | + var thread = $j(this).closest('.lqt_thread'); |
| 1092 | + var threadID = thread.find('.lqt-post-wrapper').data('thread-id'); |
| 1093 | + |
| 1094 | + var helperFunc; |
| 1095 | + if ( thread.hasClass( 'lqt-thread-topmost' ) ) { |
| 1096 | + var header = $j('#lqt-header-'+threadID); |
| 1097 | + var headline = header.contents().filter('.mw-headline').clone(); |
| 1098 | + var helper = $j('<h2/>').append(headline); |
| 1099 | + helperFunc = function() { return helper; }; |
| 1100 | + } else { |
| 1101 | + helperFunc = |
| 1102 | + function() { |
| 1103 | + var helper = thread.clone(); |
| 1104 | + helper.find('.lqt-thread-replies').remove(); |
| 1105 | + return helper; |
| 1106 | + }; |
| 1107 | + } |
| 1108 | + |
| 1109 | + var draggableOptions = |
| 1110 | + { |
| 1111 | + 'axis' : 'y', |
| 1112 | + 'opacity' : '0.70', |
| 1113 | + 'revert' : 'invalid', |
| 1114 | + 'helper' : helperFunc |
| 1115 | + }; |
| 1116 | + thread.draggable( draggableOptions ); |
| 1117 | + |
| 1118 | + // Kill all existing drop zones |
| 1119 | + $j('.lqt-drop-zone').remove(); |
| 1120 | + |
| 1121 | + // Set up some dropping targets. Add one before the first thread, after every |
| 1122 | + // other thread, and as a subthread of every post. |
| 1123 | + var createDropZone = function( ) { |
| 1124 | + var element = $j('<div class="lqt-drop-zone" />'); |
| 1125 | + element.text( wgLqtMessages['lqt-drag-drop-zone'] ); |
| 1126 | + return element; |
| 1127 | + }; |
| 1128 | + |
| 1129 | + // First drop zone |
| 1130 | + var firstDropZone = createDropZone(); |
| 1131 | + firstDropZone.data( 'sortkey', 'now' ); |
| 1132 | + firstDropZone.data( 'parent', 'top' ); |
| 1133 | + var firstThread = $j('.lqt-thread-topmost.lqt-thread-first'); |
| 1134 | + var firstThreadID = firstThread.find('.lqt-post-wrapper').data('thread-id'); |
| 1135 | + var firstHeading = $j('#lqt-header-'+firstThreadID); |
| 1136 | + firstHeading.before(firstDropZone); |
| 1137 | + |
| 1138 | + // Now one after every thread |
| 1139 | + $j('.lqt-thread-topmost').each( function() { |
| 1140 | + var sortkeySelector = 'input[name=lqt-thread-sortkey]'; |
| 1141 | + var sortkeyField = $j(this).contents().filter(sortkeySelector); |
| 1142 | + var sortkey = parseInt(sortkeyField.val()); |
| 1143 | + |
| 1144 | + var dropZone = createDropZone(); |
| 1145 | + dropZone.data( 'sortkey', sortkey - 1 ); |
| 1146 | + dropZone.data( 'parent', 'top' ); |
| 1147 | + $j(this).after(dropZone); |
| 1148 | + } ); |
| 1149 | + |
| 1150 | + // Now one underneath every thread |
| 1151 | + $j('.lqt_thread').each( function() { |
| 1152 | + var thread = $j(this); |
| 1153 | + var repliesElement = liquidThreads.getRepliesElement( thread ); |
| 1154 | + var dropZone = createDropZone(); |
| 1155 | + var threadId = thread.data('thread-id'); |
| 1156 | + |
| 1157 | + dropZone.data( 'sortkey', 'now' ); |
| 1158 | + dropZone.data( 'parent', threadId ); |
| 1159 | + |
| 1160 | + repliesElement.contents().filter('.lqt-replies-finish').before(dropZone); |
| 1161 | + |
| 1162 | + } ); |
| 1163 | + |
| 1164 | + var droppableOptions = |
| 1165 | + { |
| 1166 | + 'activeClass' : 'lqt-drop-zone-active', |
| 1167 | + 'hoverClass' : 'lqt-drop-zone-hover', |
| 1168 | + 'drop' : liquidThreads.completeDragDrop, |
| 1169 | + 'tolerance' : 'intersect' |
| 1170 | + }; |
| 1171 | + |
| 1172 | + $j('.lqt-drop-zone').droppable( droppableOptions ); |
| 1173 | + }, |
| 1174 | + |
| 1175 | + 'completeDragDrop' : function( e, ui ) { |
| 1176 | + var thread = $j(ui.draggable); |
| 1177 | + |
| 1178 | + // Determine parameters |
| 1179 | + var params = { |
| 1180 | + 'sortkey' : $j(this).data('sortkey'), |
| 1181 | + 'parent' : $j(this).data('parent') |
| 1182 | + }; |
| 1183 | + |
| 1184 | + // Figure out an insertion point |
| 1185 | + if ( $j(this).prev().length ) { |
| 1186 | + params.insertAfter = $j(this).prev(); |
| 1187 | + } else if ( $j(this).next().length ) { |
| 1188 | + params.insertBefore = $j(this).next(); |
| 1189 | + } else { |
| 1190 | + params.insertUnder = $j(this).parent(); |
| 1191 | + } |
| 1192 | + |
| 1193 | + // Kill the helper. |
| 1194 | + ui.helper.remove(); |
| 1195 | + |
| 1196 | + setTimeout( function() { thread.draggable('destroy'); }, 1 ); |
| 1197 | + |
| 1198 | + // Now, let's do our updates |
| 1199 | + liquidThreads.confirmDragDrop( thread, params ); |
| 1200 | + |
| 1201 | + $j('.lqt-drop-zone').each( function() { |
| 1202 | + var repliesHolder = $j(this).closest('.lqt-thread-replies'); |
| 1203 | + |
| 1204 | + $j(this).remove(); |
| 1205 | + |
| 1206 | + if (repliesHolder.length) { |
| 1207 | + liquidThreads.checkEmptyReplies(repliesHolder); |
| 1208 | + } |
| 1209 | + } ); |
| 1210 | + }, |
| 1211 | + |
| 1212 | + 'confirmDragDrop' : function( thread, params ) { |
| 1213 | + var confirmDialog = $j('<div class="lqt-drag-confirm" />'); |
| 1214 | + |
| 1215 | + // Add an intro |
| 1216 | + var intro = $j('<p/>').text( wgLqtMessages['lqt-drag-confirm'] ); |
| 1217 | + confirmDialog.append( intro ); |
| 1218 | + |
| 1219 | + // Summarize changes to be made |
| 1220 | + var actionSummary = $j('<ul/>'); |
| 1221 | + |
| 1222 | + var addAction = function(msg) { |
| 1223 | + var li = $j('<li/>'); |
| 1224 | + li.text( wgLqtMessages[msg] ); |
| 1225 | + actionSummary.append(li); |
| 1226 | + }; |
| 1227 | + |
| 1228 | + var bump = (params.sortkey == 'now'); |
| 1229 | + var topLevel = (params.parent == 'top'); |
| 1230 | + var wasTopLevel = thread.hasClass( 'lqt-thread-topmost' ); |
| 1231 | + |
| 1232 | + if ( params.sortkey == 'now' && wasTopLevel && topLevel ) { |
| 1233 | + addAction( 'lqt-drag-bump' ); |
| 1234 | + } else if ( topLevel && params.sortkey != 'now' ) { |
| 1235 | + addAction( 'lqt-drag-setsortkey' ); |
| 1236 | + } |
| 1237 | + |
| 1238 | + if ( !wasTopLevel && topLevel ) { |
| 1239 | + addAction( 'lqt-drag-split' ); |
| 1240 | + } else if ( !topLevel ) { |
| 1241 | + addAction( 'lqt-drag-reparent' ); |
| 1242 | + } |
| 1243 | + |
| 1244 | + confirmDialog.append(actionSummary); |
| 1245 | + |
| 1246 | + // Summary prompt |
| 1247 | + var summaryPrompt = $j('<p/>').text( wgLqtMessages['lqt-drag-reason'] ); |
| 1248 | + var summaryField = $j('<input type="text" size="45"/>'); |
| 1249 | + summaryField.addClass( 'lqt-drag-confirm-reason' ).attr('name', 'reason'); |
| 1250 | + summaryPrompt.append( summaryField ); |
| 1251 | + confirmDialog.append( summaryPrompt ); |
| 1252 | + |
| 1253 | + if ( typeof params.reason != 'undefined' ) { |
| 1254 | + summaryField.val(params.reason); |
| 1255 | + } |
| 1256 | + |
| 1257 | + // New subject prompt, if appropriate |
| 1258 | + if ( !wasTopLevel && topLevel ) { |
| 1259 | + var subjectPrompt = $j('<p/>').text( wgLqtMessages['lqt-drag-subject'] ); |
| 1260 | + var subjectField = $j('<input type="text" size="45"/>'); |
| 1261 | + subjectField.addClass( 'lqt-drag-confirm-subject' ) |
| 1262 | + .attr( 'name', 'subject' ); |
| 1263 | + subjectPrompt.append( subjectField ); |
| 1264 | + confirmDialog.append( subjectPrompt ); |
| 1265 | + } |
| 1266 | + |
| 1267 | + // Now dialogify it. |
| 1268 | + $j('body').append(confirmDialog); |
| 1269 | + |
| 1270 | + var spinner; |
| 1271 | + var successCallback = function() { |
| 1272 | + confirmDialog.dialog('close'); |
| 1273 | + confirmDialog.remove(); |
| 1274 | + spinner.remove(); |
| 1275 | + liquidThreads.reloadTOC(); |
| 1276 | + }; |
| 1277 | + |
| 1278 | + var buttonLabel = wgLqtMessages['lqt-drag-save'] |
| 1279 | + var buttons = {}; |
| 1280 | + buttons[buttonLabel] = |
| 1281 | + function() { |
| 1282 | + // Load data |
| 1283 | + params.reason = $j(this).find('input[name=reason]').val(); |
| 1284 | + |
| 1285 | + if ( !wasTopLevel && topLevel ) { |
| 1286 | + params.subject = |
| 1287 | + $j(this).find('input[name=subject]').val(); |
| 1288 | + } |
| 1289 | + |
| 1290 | + // Add spinners |
| 1291 | + spinner = $j('<div class="mw-ajax-loader" />'); |
| 1292 | + thread.before(spinner) |
| 1293 | + |
| 1294 | + if ( typeof params.insertAfter != 'undefined' ) { |
| 1295 | + params.insertAfter.after(spinner); |
| 1296 | + } |
| 1297 | + |
| 1298 | + $j(this).dialog('close'); |
| 1299 | + |
| 1300 | + liquidThreads.submitDragDrop( thread, params, |
| 1301 | + successCallback ); |
| 1302 | + }; |
| 1303 | + confirmDialog.dialog( { 'AutoOpen' : true, 'buttons' : buttons, |
| 1304 | + 'modal' : true } ); |
| 1305 | + }, |
| 1306 | + |
| 1307 | + 'submitDragDrop' : function( thread, params, callback ) { |
| 1308 | + var newSortkey = params.sortkey; |
| 1309 | + var newParent = params.parent; |
| 1310 | + var threadId = thread.find('.lqt-post-wrapper').data('thread-id'); |
| 1311 | + |
| 1312 | + var bump = (params.sortkey == 'now'); |
| 1313 | + var topLevel = (newParent == 'top'); |
| 1314 | + var wasTopLevel = thread.hasClass( 'lqt-thread-topmost' ); |
| 1315 | + |
| 1316 | + var doneCallback = |
| 1317 | + function(data) { |
| 1318 | + // TODO error handling |
| 1319 | + var result; |
| 1320 | + result = 'success'; |
| 1321 | + |
| 1322 | + if (typeof data == 'undefined' || !data || |
| 1323 | + typeof data.threadaction == 'undefined' ) { |
| 1324 | + result = 'failure'; |
| 1325 | + } |
| 1326 | + |
| 1327 | + if (typeof data.error != 'undefined') { |
| 1328 | + result = data.error.code+': '+data.error.description; |
| 1329 | + } |
| 1330 | + |
| 1331 | + if (result != 'success') { |
| 1332 | + alert( "Error: "+result ); |
| 1333 | + return; |
| 1334 | + } |
| 1335 | + |
| 1336 | + var payload; |
| 1337 | + if ( typeof data.threadaction.thread != 'undefined' ) { |
| 1338 | + payload = data.threadaction.thread; |
| 1339 | + } else if (typeof data.threadaction[0] != 'undefined') { |
| 1340 | + payload = data.threadaction[0]; |
| 1341 | + } |
| 1342 | + |
| 1343 | + var oldParent = undefined; |
| 1344 | + if (!wasTopLevel) { |
| 1345 | + oldParent = thread.closest('.lqt-thread-topmost'); |
| 1346 | + } |
| 1347 | + |
| 1348 | + // Do the actual physical movement |
| 1349 | + var threadId = thread.find('.lqt-post-wrapper') |
| 1350 | + .data('thread-id'); |
| 1351 | + var topmost = thread.hasClass('lqt-thread-topmost'); |
| 1352 | + |
| 1353 | + if ( topmost ) { |
| 1354 | + var heading = $j('#lqt-header-'+threadId); |
| 1355 | + } |
| 1356 | + |
| 1357 | + |
| 1358 | + // Assorted ways of returning a thread to its proper place. |
| 1359 | + if ( typeof params.insertAfter != 'undefined' ) { |
| 1360 | + // Move the heading |
| 1361 | + if ( topmost ) { |
| 1362 | + heading.remove(); |
| 1363 | + params.insertAfter.after(heading); |
| 1364 | + thread.remove(); |
| 1365 | + heading.after( thread ); |
| 1366 | + } else { |
| 1367 | + thread.remove(); |
| 1368 | + params.insertAfter.after(thread); |
| 1369 | + } |
| 1370 | + } else if ( typeof params.insertBefore != 'undefined' ) { |
| 1371 | + if ( topmost ) { |
| 1372 | + heading.remove(); |
| 1373 | + params.insertBefore.before(heading); |
| 1374 | + thread.remove(); |
| 1375 | + heading.after( thread ); |
| 1376 | + } else { |
| 1377 | + thread.remove(); |
| 1378 | + params.insertBefore.before( thread ); |
| 1379 | + } |
| 1380 | + } else if ( typeof params.insertUnder != 'undefined' ) { |
| 1381 | + if ( topmost ) { |
| 1382 | + heading.remove(); |
| 1383 | + params.insertUnder.prepend(heading); |
| 1384 | + thread.remove(); |
| 1385 | + heading.after(thread); |
| 1386 | + } else { |
| 1387 | + thread.remove(); |
| 1388 | + params.insertUnder.prepend(thread); |
| 1389 | + } |
| 1390 | + } |
| 1391 | + |
| 1392 | + thread.data('thread-id', threadId); |
| 1393 | + thread.find('.lqt-post-wrapper').data('thread-id', threadId); |
| 1394 | + |
| 1395 | + if ( typeof payload['new-sortkey'] |
| 1396 | + != 'undefined') { |
| 1397 | + newSortKey = payload['new-sortkey']; |
| 1398 | + thread.find('.lqt-thread-modified').val( newSortKey ); |
| 1399 | + thread.find('input[name=lqt-thread-sortkey]').val(newSortKey); |
| 1400 | + } else { |
| 1401 | + // Force an update on the top-level thread |
| 1402 | + var reloadThread = thread; |
| 1403 | + |
| 1404 | + if ( ! topLevel && typeof payload['new-ancestor-id'] |
| 1405 | + != 'undefined' ) { |
| 1406 | + var ancestorId = payload['new-ancestor-id']; |
| 1407 | + reloadThread = |
| 1408 | + $j('#lqt_thread_id_'+ancestorId); |
| 1409 | + } |
| 1410 | + |
| 1411 | + liquidThreads.doReloadThread( reloadThread ); |
| 1412 | + } |
| 1413 | + |
| 1414 | + // Kill the heading, if there isn't one. |
| 1415 | + if ( !topLevel && wasTopLevel && heading.length ) { |
| 1416 | + heading.remove(); |
| 1417 | + } |
| 1418 | + |
| 1419 | + if ( !wasTopLevel && typeof oldParent != 'undefined' ) { |
| 1420 | + liquidThreads.doReloadThread( oldParent ); |
| 1421 | + } |
| 1422 | + |
| 1423 | + // Call callback |
| 1424 | + if ( typeof callback == 'function' ) { |
| 1425 | + callback(); |
| 1426 | + } |
| 1427 | + } |
| 1428 | + |
| 1429 | + if ( !topLevel || !wasTopLevel ) { |
| 1430 | + |
| 1431 | + // Is it a split or a merge |
| 1432 | + var apiRequest = |
| 1433 | + { |
| 1434 | + 'action' : 'threadaction', |
| 1435 | + 'thread' : threadId, |
| 1436 | + 'format' : 'json', |
| 1437 | + 'reason' : params.reason |
| 1438 | + } |
| 1439 | + |
| 1440 | + if (topLevel) { |
| 1441 | + apiRequest.threadaction = 'split'; |
| 1442 | + apiRequest.subject = params.subject; |
| 1443 | + } else { |
| 1444 | + apiRequest.threadaction = 'merge'; |
| 1445 | + apiRequest.newparent = newParent; |
| 1446 | + } |
| 1447 | + |
| 1448 | + if ( newSortkey != 'none' ) { |
| 1449 | + apiRequest.sortkey = newSortkey; |
| 1450 | + } |
| 1451 | + |
| 1452 | + liquidThreads.apiRequest( apiRequest, doneCallback ); |
| 1453 | + |
| 1454 | + |
| 1455 | + } else if (newSortkey != 'none' ) { |
| 1456 | + var apiRequest = |
| 1457 | + { |
| 1458 | + 'action' : 'threadaction', |
| 1459 | + 'threadaction' : 'setsortkey', |
| 1460 | + 'thread' : threadId, |
| 1461 | + 'sortkey' : newSortkey, |
| 1462 | + 'format' : 'json', |
| 1463 | + 'reason' : params.reason |
| 1464 | + }; |
| 1465 | + |
| 1466 | + liquidThreads.apiRequest( apiRequest, doneCallback ); |
| 1467 | + } |
1045 | 1468 | } |
1046 | 1469 | } |
1047 | 1470 | |