Page MenuHomePhabricator

No OneTemporary

diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js
index 27b539a311..b5fde20329 100644
--- a/webroot/rsrc/js/application/diff/DiffChangesetList.js
+++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js
@@ -1,675 +1,711 @@
/**
* @provides phabricator-diff-changeset-list
* @requires javelin-install
* @javelin
*/
JX.install('DiffChangesetList', {
construct: function() {
this._changesets = [];
var onload = JX.bind(this, this._ifawake, this._onload);
JX.Stratcom.listen('click', 'differential-load', onload);
var onmore = JX.bind(this, this._ifawake, this._onmore);
JX.Stratcom.listen('click', 'show-more', onmore);
var onmenu = JX.bind(this, this._ifawake, this._onmenu);
JX.Stratcom.listen('click', 'differential-view-options', onmenu);
var onhide = JX.bind(this, this._ifawake, this._onhide);
JX.Stratcom.listen('click', 'hide-inline', onhide);
var onreveal = JX.bind(this, this._ifawake, this._onreveal);
JX.Stratcom.listen('click', 'reveal-inline', onreveal);
var onedit = JX.bind(this, this._ifawake, this._onaction, 'edit');
JX.Stratcom.listen(
'click',
['differential-inline-comment', 'differential-inline-edit'],
onedit);
var ondone = JX.bind(this, this._ifawake, this._onaction, 'done');
JX.Stratcom.listen(
'click',
['differential-inline-comment', 'differential-inline-done'],
ondone);
var ondelete = JX.bind(this, this._ifawake, this._onaction, 'delete');
JX.Stratcom.listen(
'click',
['differential-inline-comment', 'differential-inline-delete'],
ondelete);
var onreply = JX.bind(this, this._ifawake, this._onaction, 'reply');
JX.Stratcom.listen(
'click',
['differential-inline-comment', 'differential-inline-reply'],
onreply);
},
properties: {
translations: null,
inlineURI: null
},
members: {
_initialized: false,
_asleep: true,
_changesets: null,
_cursorItem: null,
- _lastKeyboardManager: null,
+
+ _focusNode: null,
+ _focusStart: null,
+ _focusEnd: null,
sleep: function() {
this._asleep = true;
},
wake: function() {
this._asleep = false;
if (this._initialized) {
return;
}
this._initialized = true;
var pht = this.getTranslations();
var label;
label = pht('Jump to next change.');
this._installJumpKey('j', label, 1);
label = pht('Jump to previous change.');
this._installJumpKey('k', label, -1);
label = pht('Jump to next file.');
this._installJumpKey('J', label, 1, 'file');
label = pht('Jump to previous file.');
this._installJumpKey('K', label, -1, 'file');
label = pht('Jump to next inline comment.');
this._installJumpKey('n', label, 1, 'comment');
label = pht('Jump to previous inline comment.');
this._installJumpKey('p', label, -1, 'comment');
label = pht('Jump to the table of contents.');
this._installKey('t', label, this._ontoc);
label = pht('Reply to selected inline comment.');
this._installKey('r', label, this._onreply);
label = pht('Edit selected inline comment.');
this._installKey('e', label, this._onedit);
},
isAsleep: function() {
return this._asleep;
},
newChangesetForNode: function(node) {
var changeset = JX.DiffChangeset.getForNode(node);
this._changesets.push(changeset);
changeset.setChangesetList(this);
return changeset;
},
getChangesetForNode: function(node) {
return JX.DiffChangeset.getForNode(node);
},
getInlineByID: function(id) {
var inline = null;
for (var ii = 0; ii < this._changesets.length; ii++) {
inline = this._changesets[ii].getInlineByID(id);
if (inline) {
break;
}
}
return inline;
},
_ifawake: function(f) {
// This function takes another function and only calls it if the
// changeset list is awake, so we basically just ignore events when we
// are asleep. This may move up the stack at some point as we do more
// with Quicksand/Sheets.
if (this.isAsleep()) {
return;
}
return f.apply(this, [].slice.call(arguments, 1));
},
_onload: function(e) {
var data = e.getNodeData('differential-load');
// NOTE: We can trigger a load from either an explicit "Load" link on
// the changeset, or by clicking a link in the table of contents. If
// the event was a table of contents link, we let the anchor behavior
// run normally.
if (data.kill) {
e.kill();
}
var node = JX.$(data.id);
var changeset = this.getChangesetForNode(node);
changeset.load();
// TODO: Move this into Changeset.
var routable = changeset.getRoutable();
if (routable) {
routable.setPriority(2000);
}
},
_installKey: function(key, label, handler) {
handler = JX.bind(this, this._ifawake, handler);
return new JX.KeyboardShortcut(key, label)
.setHandler(handler)
.register();
},
_installJumpKey: function(key, label, delta, filter) {
filter = filter || null;
var handler = JX.bind(this, this._onjumpkey, delta, filter);
return this._installKey(key, label, handler);
},
_ontoc: function(manager) {
var toc = JX.$('toc');
manager.scrollTo(toc);
},
_onreply: function(manager) {
var cursor = this._cursorItem;
if (cursor) {
if (cursor.type == 'comment') {
var inline = cursor.inline;
if (inline.canReply()) {
- manager.focusOn(null);
+ this.setFocus(null);
inline.reply();
return;
}
}
}
var pht = this.getTranslations();
this._warnUser(pht('You must select a comment to reply to.'));
},
_onedit: function(manager) {
var cursor = this._cursorItem;
if (cursor) {
if (cursor.type == 'comment') {
var inline = cursor.inline;
if (inline.canEdit()) {
- manager.focusOn(null);
+ this.setFocus(null);
inline.edit();
return;
}
}
}
var pht = this.getTranslations();
this._warnUser(pht('You must select a comment to edit.'));
},
_warnUser: function(message) {
new JX.Notification()
.setContent(message)
.alterClassName('jx-notification-alert', true)
.setDuration(1000)
.show();
},
_onjumpkey: function(delta, filter, manager) {
var state = this._getSelectionState();
var cursor = state.cursor;
var items = state.items;
// If there's currently no selection and the user tries to go back,
// don't do anything.
if ((cursor === null) && (delta < 0)) {
return;
}
while (true) {
if (cursor === null) {
cursor = 0;
} else {
cursor = cursor + delta;
}
// If we've gone backward past the first change, bail out.
if (cursor < 0) {
return;
}
// If we've gone forward off the end of the list, bail out.
if (cursor >= items.length) {
return;
}
// If we're selecting things of a particular type (like only files)
// and the next item isn't of that type, move past it.
if (filter !== null) {
if (items[cursor].type !== filter) {
continue;
}
}
// Otherwise, we've found a valid item to select.
break;
}
this._setSelectionState(items[cursor], manager);
},
_getSelectionState: function() {
var items = this._getSelectableItems();
var cursor = null;
if (this._cursorItem !== null) {
for (var ii = 0; ii < items.length; ii++) {
var item = items[ii];
if (this._cursorItem.target === item.target) {
cursor = ii;
break;
}
}
}
return {
cursor: cursor,
items: items
};
},
_setSelectionState: function(item, manager) {
this._cursorItem = item;
this._redrawSelection(manager, true);
return this;
},
_redrawSelection: function(manager, scroll) {
- manager = manager || this._lastKeyboardManager;
- this._lastKeyboardManager = manager;
-
- if (this.isAsleep()) {
- manager.focusOn(null);
- return;
- }
-
var cursor = this._cursorItem;
if (!cursor) {
- manager.focusOn(null);
+ this.setFocus(null);
return;
}
- manager.focusOn(cursor.nodes.begin, cursor.nodes.end);
+ this.setFocus(cursor.nodes.begin, cursor.nodes.end);
if (scroll) {
manager.scrollTo(cursor.nodes.begin);
}
return this;
},
_getSelectableItems: function() {
var result = [];
for (var ii = 0; ii < this._changesets.length; ii++) {
var items = this._changesets[ii].getSelectableItems();
for (var jj = 0; jj < items.length; jj++) {
result.push(items[jj]);
}
}
return result;
},
_onmore: function(e) {
e.kill();
var node = e.getNode('differential-changeset');
var changeset = this.getChangesetForNode(node);
var data = e.getNodeData('show-more');
var target = e.getNode('context-target');
changeset.loadContext(data.range, target);
},
_onmenu: function(e) {
var button = e.getNode('differential-view-options');
var data = JX.Stratcom.getData(button);
if (data.menu) {
// We've already built this menu, so we can let the menu itself handle
// the event.
return;
}
e.prevent();
var pht = this.getTranslations();
var node = JX.DOM.findAbove(
button,
'div',
'differential-changeset');
var changeset = this.getChangesetForNode(node);
var menu = new JX.PHUIXDropdownMenu(button);
var list = new JX.PHUIXActionListView();
var add_link = function(icon, name, href, local) {
if (!href) {
return;
}
var link = new JX.PHUIXActionView()
.setIcon(icon)
.setName(name)
.setHref(href)
.setHandler(function(e) {
if (local) {
window.location.assign(href);
} else {
window.open(href);
}
menu.close();
e.prevent();
});
list.addItem(link);
return link;
};
var reveal_item = new JX.PHUIXActionView()
.setIcon('fa-eye');
list.addItem(reveal_item);
var visible_item = new JX.PHUIXActionView()
.setHandler(function(e) {
var diff = JX.DOM.scry(
JX.$(data.containerID),
'table',
'differential-diff');
JX.Stratcom.invoke('differential-toggle-file', null, {diff: diff});
e.prevent();
menu.close();
});
list.addItem(visible_item);
add_link('fa-file-text', pht('Browse in Diffusion'), data.diffusionURI);
add_link('fa-file-o', pht('View Standalone'), data.standaloneURI);
var up_item = new JX.PHUIXActionView()
.setHandler(function(e) {
if (changeset.isLoaded()) {
var renderer = changeset.getRenderer();
if (renderer == '1up') {
renderer = '2up';
} else {
renderer = '1up';
}
changeset.setRenderer(renderer);
}
changeset.reload();
e.prevent();
menu.close();
});
list.addItem(up_item);
var encoding_item = new JX.PHUIXActionView()
.setIcon('fa-font')
.setName(pht('Change Text Encoding...'))
.setHandler(function(e) {
var params = {
encoding: changeset.getEncoding()
};
new JX.Workflow('/services/encoding/', params)
.setHandler(function(r) {
changeset.setEncoding(r.encoding);
changeset.reload();
})
.start();
e.prevent();
menu.close();
});
list.addItem(encoding_item);
var highlight_item = new JX.PHUIXActionView()
.setIcon('fa-sun-o')
.setName(pht('Highlight As...'))
.setHandler(function(e) {
var params = {
highlight: changeset.getHighlight()
};
new JX.Workflow('/services/highlight/', params)
.setHandler(function(r) {
changeset.setHighlight(r.highlight);
changeset.reload();
})
.start();
e.prevent();
menu.close();
});
list.addItem(highlight_item);
add_link('fa-arrow-left', pht('Show Raw File (Left)'), data.leftURI);
add_link('fa-arrow-right', pht('Show Raw File (Right)'), data.rightURI);
add_link('fa-pencil', pht('Open in Editor'), data.editor, true);
add_link('fa-wrench', pht('Configure Editor'), data.editorConfigure);
menu.setContent(list.getNode());
menu.listen('open', function() {
// When the user opens the menu, check if there are any "Show More"
// links in the changeset body. If there aren't, disable the "Show
// Entire File" menu item since it won't change anything.
var nodes = JX.DOM.scry(JX.$(data.containerID), 'a', 'show-more');
if (nodes.length) {
reveal_item
.setDisabled(false)
.setName(pht('Show All Context'))
.setIcon('fa-file-o')
.setHandler(function(e) {
changeset.loadAllContext();
e.prevent();
menu.close();
});
} else {
reveal_item
.setDisabled(true)
.setIcon('fa-file')
.setName(pht('All Context Shown'))
.setHandler(function(e) { e.prevent(); });
}
encoding_item.setDisabled(!changeset.isLoaded());
highlight_item.setDisabled(!changeset.isLoaded());
if (changeset.isLoaded()) {
if (changeset.getRenderer() == '2up') {
up_item
.setIcon('fa-list-alt')
.setName(pht('View Unified'));
} else {
up_item
.setIcon('fa-files-o')
.setName(pht('View Side-by-Side'));
}
} else {
up_item
.setIcon('fa-refresh')
.setName(pht('Load Changes'));
}
visible_item
.setDisabled(true)
.setIcon('fa-expand')
.setName(pht('Can\'t Toggle Unloaded File'));
var diffs = JX.DOM.scry(
JX.$(data.containerID),
'table',
'differential-diff');
if (diffs.length > 1) {
JX.$E(
'More than one node with sigil "differential-diff" was found in "'+
data.containerID+'."');
} else if (diffs.length == 1) {
var diff = diffs[0];
visible_item.setDisabled(false);
if (JX.Stratcom.getData(diff).hidden) {
visible_item
.setName(pht('Expand File'))
.setIcon('fa-expand');
} else {
visible_item
.setName(pht('Collapse File'))
.setIcon('fa-compress');
}
} else {
// Do nothing when there is no diff shown in the table. For example,
// the file is binary.
}
});
data.menu = menu;
menu.open();
},
_onhide: function(e) {
this._onhidereveal(e, true);
},
_onreveal: function(e) {
this._onhidereveal(e, false);
},
_onhidereveal: function(e, is_hide) {
e.kill();
var inline = this._getInlineForEvent(e);
inline.setHidden(is_hide);
},
_onaction: function(action, e) {
e.kill();
var inline = this._getInlineForEvent(e);
var is_ref = false;
// If we don't have a natural inline object, the user may have clicked
// an action (like "Delete") inside a preview element at the bottom of
// the page.
// If they did, try to find an associated normal inline to act on, and
// pretend they clicked that instead. This makes the overall state of
// the page more consistent.
// However, there may be no normal inline (for example, because it is
// on a version of the diff which is not visible). In this case, we
// act by reference.
if (inline === null) {
var data = e.getNodeData('differential-inline-comment');
inline = this.getInlineByID(data.id);
if (inline) {
is_ref = true;
} else {
switch (action) {
case 'delete':
this._deleteInlineByID(data.id);
return;
}
}
}
// TODO: For normal operations, highlight the inline range here.
switch (action) {
case 'edit':
inline.edit();
break;
case 'done':
inline.toggleDone();
break;
case 'delete':
inline.delete(is_ref);
break;
case 'reply':
inline.reply();
break;
}
},
redrawPreview: function() {
// TODO: This isn't the cleanest way to find the preview form, but
// rendering no longer has direct access to it.
var forms = JX.DOM.scry(document.body, 'form', 'transaction-append');
if (forms.length) {
JX.DOM.invoke(forms[0], 'shouldRefresh');
}
},
+ setFocus: function(node, extended_node) {
+ this._focusStart = node;
+ this._focusEnd = extended_node;
+ this._redrawFocus();
+ },
+
+ _redrawFocus: function() {
+ var node = this._focusStart;
+ var extended_node = this._focusEnd || node;
+
+ var reticle = this._getFocusNode();
+ if (!node) {
+ JX.DOM.remove(reticle);
+ return;
+ }
+
+ // Outset the reticle some pixels away from the element, so there's some
+ // space between the focused element and the outline.
+ var p = JX.Vector.getPos(node);
+ var s = JX.Vector.getAggregateScrollForNode(node);
+
+ p.add(s).add(-4, -4).setPos(reticle);
+ // Compute the size we need to extend to the full extent of the focused
+ // nodes.
+ JX.Vector.getPos(extended_node)
+ .add(-p.x, -p.y)
+ .add(JX.Vector.getDim(extended_node))
+ .add(8, 8)
+ .setDim(reticle);
+
+ JX.DOM.getContentFrame().appendChild(reticle);
+ },
+
+ _getFocusNode: function() {
+ if (!this._focusNode) {
+ var node = JX.$N('div', {className : 'keyboard-focus-focus-reticle'});
+ this._focusNode = node;
+ }
+ return this._focusNode;
+ },
+
_deleteInlineByID: function(id) {
var uri = this.getInlineURI();
var data = {
op: 'refdelete',
id: id
};
var handler = JX.bind(this, this.redrawPreview);
new JX.Workflow(uri, data)
.setHandler(handler)
.start();
},
_getInlineForEvent: function(e) {
var node = e.getNode('differential-changeset');
if (!node) {
return null;
}
var changeset = this.getChangesetForNode(node);
var inline_row = e.getNode('inline-row');
return changeset.getInlineForRow(inline_row);
}
}
});
diff --git a/webroot/rsrc/js/core/KeyboardShortcutManager.js b/webroot/rsrc/js/core/KeyboardShortcutManager.js
index c9a1fbe64e..281c12a8f3 100644
--- a/webroot/rsrc/js/core/KeyboardShortcutManager.js
+++ b/webroot/rsrc/js/core/KeyboardShortcutManager.js
@@ -1,201 +1,158 @@
/**
* @provides phabricator-keyboard-shortcut-manager
* @requires javelin-install
* javelin-util
* javelin-stratcom
* javelin-dom
* javelin-vector
* @javelin
*/
JX.install('KeyboardShortcutManager', {
construct : function() {
this._shortcuts = [];
JX.Stratcom.listen('keypress', null, JX.bind(this, this._onkeypress));
JX.Stratcom.listen('keydown', null, JX.bind(this, this._onkeydown));
JX.Stratcom.listen('keyup', null, JX.bind(this, this._onkeyup));
},
statics : {
_instance : null,
/**
* Some keys don't invoke keypress events in some browsers. We handle these
* on keydown instead of keypress.
*/
_downkeys : {
left: 1,
right: 1,
up: 1,
down: 1
},
/**
* Some keys require Alt to be pressed in order to type them on certain
* keyboard layouts.
*/
_altkeys: {
// "Alt+L" on German layouts.
'@': 1,
// "Alt+Shift+7" on German layouts.
'\\': 1
},
getInstance : function() {
if (!JX.KeyboardShortcutManager._instance) {
JX.KeyboardShortcutManager._instance = new JX.KeyboardShortcutManager();
}
return JX.KeyboardShortcutManager._instance;
}
},
members : {
_shortcuts : null,
- _focusReticle : null,
/**
* Instead of calling this directly, you should call
* KeyboardShortcut.register().
*/
addKeyboardShortcut : function(s) {
this._shortcuts.push(s);
},
getShortcutDescriptions : function() {
var desc = [];
for (var ii = 0; ii < this._shortcuts.length; ii++) {
desc.push({
keys : this._shortcuts[ii].getKeys(),
description : this._shortcuts[ii].getDescription()
});
}
return desc;
},
/**
* Scroll an element into view.
*/
scrollTo : function(node) {
var scroll_distance = JX.Vector.getAggregateScrollForNode(node);
var node_position = JX.$V(node);
JX.DOM.scrollToPosition(0, node_position.y + scroll_distance.y - 60);
},
- /**
- * Move the keyboard shortcut focus to an element.
- *
- * @param Node Node to focus, or pass null to clear the focus.
- * @param Node To focus multiple nodes (like rows in a table), specify the
- * top-left node as the first parameter and the bottom-right
- * node as the focus extension.
- * @return void
- */
- focusOn : function(node, extended_node) {
- this._clearReticle();
-
- if (!node) {
- return;
- }
-
- var r = JX.$N('div', {className : 'keyboard-focus-focus-reticle'});
-
- extended_node = extended_node || node;
-
- // Outset the reticle some pixels away from the element, so there's some
- // space between the focused element and the outline.
- var p = JX.Vector.getPos(node);
- var s = JX.Vector.getAggregateScrollForNode(node);
-
- p.add(s).add(-4, -4).setPos(r);
- // Compute the size we need to extend to the full extent of the focused
- // nodes.
- JX.Vector.getPos(extended_node)
- .add(-p.x, -p.y)
- .add(JX.Vector.getDim(extended_node))
- .add(8, 8)
- .setDim(r);
- JX.DOM.getContentFrame().appendChild(r);
-
- this._focusReticle = r;
- },
-
- _clearReticle : function() {
- this._focusReticle && JX.DOM.remove(this._focusReticle);
- this._focusReticle = null;
- },
_onkeypress : function(e) {
if (!(this._getKey(e) in JX.KeyboardShortcutManager._downkeys)) {
this._onkeyhit(e);
}
},
_onkeyhit : function(e) {
var self = JX.KeyboardShortcutManager;
var raw = e.getRawEvent();
if (raw.ctrlKey || raw.metaKey) {
// Never activate keyboard shortcuts if modifier keys are also
// depressed.
return;
}
// For most keystrokes, don't activate keyboard shortcuts if the Alt
// key is depressed. However, we continue if the character requires the
// use of Alt to type it on some keyboard layouts.
var key = this._getKey(e);
if (raw.altKey && !(key in self._altkeys)) {
return;
}
var target = e.getTarget();
var ignore = ['input', 'select', 'textarea', 'object', 'embed'];
if (JX.DOM.isType(target, ignore)) {
// Never activate keyboard shortcuts if the user has some other control
// focused.
return;
}
var key = this._getKey(e);
var shortcuts = this._shortcuts;
for (var ii = 0; ii < shortcuts.length; ii++) {
var keys = shortcuts[ii].getKeys();
for (var jj = 0; jj < keys.length; jj++) {
if (keys[jj] == key) {
shortcuts[ii].getHandler()(this);
e.kill(); // Consume the event
return;
}
}
}
},
_onkeydown : function(e) {
this._handleTooltipKeyEvent(e, true);
if (this._getKey(e) in JX.KeyboardShortcutManager._downkeys) {
this._onkeyhit(e);
}
},
_onkeyup : function(e) {
this._handleTooltipKeyEvent(e, false);
},
_getKey : function(e) {
return e.getSpecialKey() || String.fromCharCode(e.getRawEvent().charCode);
},
_handleTooltipKeyEvent : function(e, is_keydown) {
if (e.getRawEvent().keyCode != 18) {
// If this isn't the alt/option key, don't do anything.
return;
}
// Fire all the shortcut handlers.
var shortcuts = this._shortcuts;
for (var ii = 0; ii < shortcuts.length; ii++) {
var handler = shortcuts[ii].getTooltipHandler();
handler && handler(this, is_keydown);
}
}
}
});

File Metadata

Mime Type
text/x-diff
Expires
Fri, Jul 31, 11:18 AM (1 d, 10 h)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
16432378
Default Alt Text
(25 KB)

Event Timeline