Changeset View
Changeset View
Standalone View
Standalone View
webroot/rsrc/js/application/diff/DiffChangesetList.js
| Show First 20 Lines • Show All 53 Lines • ▼ Show 20 Lines | JX.Stratcom.listen( | ||||
| onrangemove); | onrangemove); | ||||
| var onrangeup = JX.bind(this, this._ifawake, this._onrangeup); | var onrangeup = JX.bind(this, this._ifawake, this._onrangeup); | ||||
| JX.Stratcom.listen( | JX.Stratcom.listen( | ||||
| 'mouseup', | 'mouseup', | ||||
| null, | null, | ||||
| onrangeup); | onrangeup); | ||||
| var onrange = JX.bind(this, this._ifawake, this._onSelectRange); | |||||
| JX.enableDispatch(window, 'selectionchange'); | |||||
| JX.Stratcom.listen('selectionchange', null, onrange); | |||||
| this._setupInlineCommentListeners(); | this._setupInlineCommentListeners(); | ||||
| }, | }, | ||||
| properties: { | properties: { | ||||
| translations: null, | translations: null, | ||||
| inlineURI: null, | inlineURI: null, | ||||
| inlineListURI: null, | inlineListURI: null, | ||||
| isStandalone: false, | isStandalone: false, | ||||
| ▲ Show 20 Lines • Show All 119 Lines • ▼ Show 20 Lines | wake: function() { | ||||
| label = pht('Reply to selected inline comment or change.'); | label = pht('Reply to selected inline comment or change.'); | ||||
| this._installKey('r', 'inline', label, | this._installKey('r', 'inline', label, | ||||
| JX.bind(this, this._onkeyreply, false)); | JX.bind(this, this._onkeyreply, false)); | ||||
| label = pht('Reply and quote selected inline comment.'); | label = pht('Reply and quote selected inline comment.'); | ||||
| this._installKey('R', 'inline', label, | this._installKey('R', 'inline', label, | ||||
| JX.bind(this, this._onkeyreply, true)); | JX.bind(this, this._onkeyreply, true)); | ||||
| label = pht('Add new inline comment on selected source text.'); | |||||
| this._installKey('c', 'inline', label, | |||||
| JX.bind(this, this._onKeyCreate)); | |||||
| label = pht('Edit selected inline comment.'); | label = pht('Edit selected inline comment.'); | ||||
| this._installKey('e', 'inline', label, this._onkeyedit); | this._installKey('e', 'inline', label, this._onkeyedit); | ||||
| label = pht('Mark or unmark selected inline comment as done.'); | label = pht('Mark or unmark selected inline comment as done.'); | ||||
| this._installKey('w', 'inline', label, this._onkeydone); | this._installKey('w', 'inline', label, this._onkeydone); | ||||
| label = pht('Collapse or expand inline comment.'); | label = pht('Collapse or expand inline comment.'); | ||||
| this._installKey('q', 'diff-vis', label, this._onkeycollapse); | this._installKey('q', 'diff-vis', label, this._onkeycollapse); | ||||
| ▲ Show 20 Lines • Show All 207 Lines • ▼ Show 20 Lines | _onkeyedit: function() { | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| var pht = this.getTranslations(); | var pht = this.getTranslations(); | ||||
| this._warnUser(pht('You must select a comment to edit.')); | this._warnUser(pht('You must select a comment to edit.')); | ||||
| }, | }, | ||||
| _onKeyCreate: function() { | |||||
| var start = this._sourceSelectionStart; | |||||
| var end = this._sourceSelectionEnd; | |||||
| if (!this._sourceSelectionStart) { | |||||
| var pht = this.getTranslations(); | |||||
| this._warnUser( | |||||
| pht( | |||||
| 'You must select source text to create a new inline comment.')); | |||||
| return; | |||||
| } | |||||
| this._setSourceSelection(null, null); | |||||
| var changeset = start.changeset; | |||||
| changeset.newInlineForRange(start.targetNode, end.targetNode); | |||||
| }, | |||||
| _onkeydone: function() { | _onkeydone: function() { | ||||
| var cursor = this._cursorItem; | var cursor = this._cursorItem; | ||||
| if (cursor) { | if (cursor) { | ||||
| if (cursor.type == 'comment') { | if (cursor.type == 'comment') { | ||||
| var inline = cursor.target; | var inline = cursor.target; | ||||
| if (inline.canDone()) { | if (inline.canDone()) { | ||||
| this.setFocus(null); | this.setFocus(null); | ||||
| ▲ Show 20 Lines • Show All 1,764 Lines • ▼ Show 20 Lines | _onInlineEvent: function(action, e) { | ||||
| case 'draft': | case 'draft': | ||||
| inline.triggerDraft(); | inline.triggerDraft(); | ||||
| break; | break; | ||||
| case 'menu': | case 'menu': | ||||
| var node = e.getNode('inline-action-dropdown'); | var node = e.getNode('inline-action-dropdown'); | ||||
| inline.activateMenu(node, e); | inline.activateMenu(node, e); | ||||
| break; | break; | ||||
| } | } | ||||
| }, | |||||
| _onSelectRange: function(e) { | |||||
| this._updateSourceSelection(); | |||||
| }, | |||||
| _updateSourceSelection: function() { | |||||
| var ranges = this._getSelectedRanges(); | |||||
| // If we have zero or more than one range, don't do anything. | |||||
| if (ranges.length === 1) { | |||||
| for (var ii = 0; ii < ranges.length; ii++) { | |||||
| var range = ranges[ii]; | |||||
| var head = range.startContainer; | |||||
| var last = range.endContainer; | |||||
| var head_loc = this._getFragmentLocation(head); | |||||
| var last_loc = this._getFragmentLocation(last); | |||||
| if (head_loc === null || last_loc === null) { | |||||
| break; | |||||
| } | |||||
| if (head_loc.changesetID !== last_loc.changesetID) { | |||||
| break; | |||||
| } | |||||
| head_loc.offset += range.startOffset; | |||||
| last_loc.offset += range.endOffset; | |||||
| this._setSourceSelection(head_loc, last_loc); | |||||
| return; | |||||
| } | |||||
| } | |||||
| this._setSourceSelection(null, null); | |||||
| }, | |||||
| _setSourceSelection: function(start, end) { | |||||
| var start_updated = | |||||
| !this._isSameSourceSelection(this._sourceSelectionStart, start); | |||||
| var end_updated = | |||||
| !this._isSameSourceSelection(this._sourceSelectionEnd, end); | |||||
| if (!start_updated && !end_updated) { | |||||
| return; | |||||
| } | |||||
| this._sourceSelectionStart = start; | |||||
| this._sourceSelectionEnd = end; | |||||
| if (!start) { | |||||
| this._closeSourceSelectionMenu(); | |||||
| return; | |||||
| } | |||||
| var menu; | |||||
| if (this._sourceSelectionMenu) { | |||||
| menu = this._sourceSelectionMenu; | |||||
| } else { | |||||
| menu = this._newSourceSelectionMenu(); | |||||
| this._sourceSelectionMenu = menu; | |||||
| } | |||||
| var pos = JX.$V(start.node) | |||||
| .add(0, -menu.getMenuNodeDimensions().y) | |||||
| .add(0, -24); | |||||
| menu.setPosition(pos); | |||||
| menu.open(); | |||||
| }, | |||||
| _newSourceSelectionMenu: function() { | |||||
| var pht = this.getTranslations(); | |||||
| var menu = new JX.PHUIXDropdownMenu(null) | |||||
| .setWidth(240); | |||||
| // We need to disable autofocus for this menu, since it operates on the | |||||
| // text selection in the document. If we leave this enabled, opening the | |||||
| // menu immediately discards the selection. | |||||
| menu.setDisableAutofocus(true); | |||||
| var list = new JX.PHUIXActionListView(); | |||||
| menu.setContent(list.getNode()); | |||||
| var oncreate = JX.bind(this, this._onSourceSelectionMenuAction, 'create'); | |||||
| var comment_item = new JX.PHUIXActionView() | |||||
| .setIcon('fa-comment-o') | |||||
| .setName(pht('New Inline Comment')) | |||||
| .setKeyCommand('c') | |||||
| .setHandler(oncreate); | |||||
| list.addItem(comment_item); | |||||
| return menu; | |||||
| }, | |||||
| _onSourceSelectionMenuAction: function(action, e) { | |||||
| e.kill(); | |||||
| this._closeSourceSelectionMenu(); | |||||
| switch (action) { | |||||
| case 'create': | |||||
| this._onKeyCreate(); | |||||
| break; | |||||
| } | |||||
| }, | |||||
| _closeSourceSelectionMenu: function() { | |||||
| if (this._sourceSelectionMenu) { | |||||
| this._sourceSelectionMenu.close(); | |||||
| } | |||||
| }, | |||||
| _isSameSourceSelection: function(u, v) { | |||||
| if (u === null && v === null) { | |||||
| return true; | |||||
| } | |||||
| if (u === null && v !== null) { | |||||
| return false; | |||||
| } | |||||
| if (u !== null && v === null) { | |||||
| return false; | |||||
| } | |||||
| return ( | |||||
| (u.changesetID === v.changesetID) && | |||||
| (u.line === v.line) && | |||||
| (u.displayColumn === v.displayColumn) && | |||||
| (u.offset === v.offset) | |||||
| ); | |||||
| }, | |||||
| _getFragmentLocation: function(fragment) { | |||||
| // Find the changeset containing the fragment. | |||||
| var changeset = null; | |||||
| try { | |||||
| var node = JX.DOM.findAbove( | |||||
| fragment, | |||||
| 'div', | |||||
| 'differential-changeset'); | |||||
| changeset = this.getChangesetForNode(node); | |||||
| if (!changeset) { | |||||
| return null; | |||||
| } | |||||
| } catch (ex) { | |||||
| return null; | |||||
| } | |||||
| // Find the line number and display column for the fragment. | |||||
| var line = null; | |||||
| var column_count = -1; | |||||
| var offset = null; | |||||
| var target_node = null; | |||||
| var td; | |||||
| try { | |||||
| // NOTE: In Safari, you can carefully select an entire line and then | |||||
| // move your mouse down slightly, causing selection of an empty | |||||
| // document fragment which is an immediate child of the next "<tr />". | |||||
| // If the fragment is a direct child of a "<tr />" parent, assume the | |||||
| // user has done this and select the last child of the previous row | |||||
| // instead. It's possible there are other ways to do this, so this may | |||||
| // not always be the right rule. | |||||
| // Otherwise, select the containing "<td />". | |||||
| var is_end; | |||||
| if (JX.DOM.isType(fragment.parentNode, 'tr')) { | |||||
| // Assume this is Safari, and that the user has carefully selected a | |||||
| // row and then moved their mouse down a few pixels to select the | |||||
| // invisible fragment at the beginning of the next row. | |||||
| var cells = fragment.parentNode.previousSibling.childNodes; | |||||
| td = cells[cells.length - 1]; | |||||
| is_end = true; | |||||
| } else { | |||||
| td = JX.DOM.findAbove(fragment, 'td'); | |||||
| is_end = false; | |||||
| } | |||||
| var cursor = td; | |||||
| while (cursor) { | |||||
| if (cursor.getAttribute('data-copy-mode')) { | |||||
| column_count++; | |||||
| } | |||||
| var n = parseInt(cursor.getAttribute('data-n')); | |||||
| if (n) { | |||||
| if (line === null) { | |||||
| target_node = cursor; | |||||
| line = n; | |||||
| } | |||||
| } | |||||
| cursor = cursor.previousSibling; | |||||
| } | |||||
| if (!line) { | |||||
| return null; | |||||
| } | |||||
| if (column_count < 0) { | |||||
| return null; | |||||
| } | |||||
| var seen = 0; | |||||
| for (var ii = 0; ii < td.childNodes.length; ii++) { | |||||
| var child = td.childNodes[ii]; | |||||
| if (child === fragment) { | |||||
| offset = seen; | |||||
| break; | |||||
| } | |||||
| seen += child.textContent.length; | |||||
| } | } | ||||
| if (offset === null) { | |||||
| if (is_end) { | |||||
| offset = seen; | |||||
| } else { | |||||
| offset = 0; | |||||
| } | |||||
| } | |||||
| } catch (ex) { | |||||
| return null; | |||||
| } | |||||
| var changeset_id; | |||||
| if (column_count > 0) { | |||||
| changeset_id = changeset.getRightChangesetID(); | |||||
| } else { | |||||
| changeset_id = changeset.getLeftChangesetID(); | |||||
| } | |||||
| return { | |||||
| node: td, | |||||
| changeset: changeset, | |||||
| changesetID: changeset_id, | |||||
| line: line, | |||||
| displayColumn: column_count, | |||||
| offset: offset, | |||||
| targetNode: target_node | |||||
| }; | |||||
| }, | |||||
| _getSelectedRanges: function() { | |||||
| var ranges = []; | |||||
| if (!window.getSelection) { | |||||
| return ranges; | |||||
| } | |||||
| var selection = window.getSelection(); | |||||
| for (var ii = 0; ii < selection.rangeCount; ii++) { | |||||
| var range = selection.getRangeAt(ii); | |||||
| if (range.collapsed) { | |||||
| continue; | |||||
| } | |||||
| ranges.push(range); | |||||
| } | |||||
| return ranges; | |||||
| } | |||||
| } | } | ||||
| }); | }); | ||||