Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
index f7d28ed57c..e2a6de0e43 100644
--- a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
+++ b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
@@ -1,218 +1,250 @@
<?php
final class PhabricatorTypeaheadModularDatasourceController
extends PhabricatorTypeaheadDatasourceController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$request = $this->getRequest();
$viewer = $request->getUser();
$query = $request->getStr('q');
$is_browse = ($request->getURIData('action') == 'browse');
// Default this to the query string to make debugging a little bit easier.
$raw_query = nonempty($request->getStr('raw'), $query);
// This makes form submission easier in the debug view.
$class = nonempty($request->getURIData('class'), $request->getStr('class'));
$sources = id(new PhutilSymbolLoader())
->setAncestorClass('PhabricatorTypeaheadDatasource')
->loadObjects();
if (isset($sources[$class])) {
$source = $sources[$class];
$source->setParameters($request->getRequestData());
// NOTE: Wrapping the source in a Composite datasource ensures we perform
// application visibility checks for the viewer, so we do not need to do
// those separately.
$composite = new PhabricatorTypeaheadRuntimeCompositeDatasource();
$composite->addDatasource($source);
$composite
->setViewer($viewer)
->setQuery($query)
->setRawQuery($raw_query);
$hard_limit = 1000;
if ($is_browse) {
$limit = 10;
$offset = $request->getInt('offset');
if (($offset + $limit) >= $hard_limit) {
// Offset-based paging is intrinsically slow; hard-cap how far we're
// willing to go with it.
return new Aphront404Response();
}
$composite
->setLimit($limit + 1)
->setOffset($offset);
}
$results = $composite->loadResults();
if ($is_browse) {
$next_link = null;
if (count($results) > $limit) {
$results = array_slice($results, 0, $limit, $preserve_keys = true);
if (($offset + (2 * $limit)) < $hard_limit) {
$next_uri = id(new PhutilURI($request->getRequestURI()))
->setQueryParam('offset', $offset + $limit);
$next_link = javelin_tag(
'a',
array(
'href' => $next_uri,
'class' => 'typeahead-browse-more',
'sigil' => 'typeahead-browse-more',
'mustcapture' => true,
),
pht('More Results'));
} else {
// If the user has paged through more than 1K results, don't
// offer to page any further.
$next_link = javelin_tag(
'div',
array(
'class' => 'typeahead-browse-hard-limit',
),
pht('You reach the edge of the abyss.'));
}
}
$items = array();
foreach ($results as $result) {
$token = PhabricatorTypeaheadTokenView::newForTypeaheadResult(
$result);
$items[] = phutil_tag(
'div',
array(
'class' => 'grouped',
),
$token);
}
$markup = array(
$items,
$next_link,
);
if ($request->isAjax()) {
$content = array(
'markup' => hsprintf('%s', $markup),
);
return id(new AphrontAjaxResponse())->setContent($content);
}
$this->requireResource('typeahead-browse-css');
$this->initBehavior('typeahead-browse');
- $markup = phutil_tag(
+ $input_id = celerity_generate_unique_node_id();
+ $frame_id = celerity_generate_unique_node_id();
+
+ $config = array(
+ 'inputID' => $input_id,
+ 'frameID' => $frame_id,
+ 'uri' => (string)$request->getRequestURI(),
+ );
+ $this->initBehavior('typeahead-search', $config);
+
+ $search = javelin_tag(
+ 'input',
+ array(
+ 'type' => 'text',
+ 'id' => $input_id,
+ 'class' => 'typeahead-browse-input',
+ 'autocomplete' => 'off',
+ 'placeholder' => $source->getPlaceholderText(),
+ ));
+
+ $frame = phutil_tag(
'div',
array(
'class' => 'typeahead-browse-frame',
+ 'id' => $frame_id,
),
$markup);
+ $browser = array(
+ phutil_tag(
+ 'div',
+ array(
+ 'class' => 'typeahead-browse-header',
+ ),
+ $search),
+ $frame,
+ );
+
return $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FORM)
+ ->setRenderDialogAsDiv(true)
->setTitle(get_class($source)) // TODO: Provide nice names.
- ->appendChild($markup)
+ ->appendChild($browser)
->addCancelButton('/', pht('Close'));
}
} else if ($is_browse) {
return new Aphront404Response();
} else {
$results = array();
}
$content = mpull($results, 'getWireFormat');
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())->setContent($content);
}
// If there's a non-Ajax request to this endpoint, show results in a tabular
// format to make it easier to debug typeahead output.
foreach ($sources as $key => $source) {
// This can happen with composite sources like user or project, as well
// generic ones like NoOwner
if (!$source->getDatasourceApplicationClass()) {
continue;
}
if (!PhabricatorApplication::isClassInstalledForViewer(
$source->getDatasourceApplicationClass(),
$viewer)) {
unset($sources[$key]);
}
}
$options = array_fuse(array_keys($sources));
asort($options);
$form = id(new AphrontFormView())
->setUser($viewer)
->setAction('/typeahead/class/')
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Source Class'))
->setName('class')
->setValue($class)
->setOptions($options))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Query'))
->setName('q')
->setValue($request->getStr('q')))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Raw Query'))
->setName('raw')
->setValue($request->getStr('raw')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Query')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Token Query'))
->setForm($form);
$table = new AphrontTableView($content);
$table->setHeaders(
array(
pht('Name'),
pht('URI'),
pht('PHID'),
pht('Priority'),
pht('Display Name'),
pht('Display Type'),
pht('Image URI'),
pht('Priority Type'),
pht('Icon'),
pht('Closed'),
pht('Sprite'),
));
$result_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Token Results (%s)', $class))
->appendChild($table);
return $this->buildApplicationPage(
array(
$form_box,
$result_box,
),
array(
'title' => pht('Typeahead Results'),
'device' => false,
));
}
}
diff --git a/webroot/rsrc/css/aphront/typeahead-browse.css b/webroot/rsrc/css/aphront/typeahead-browse.css
index c50c693bd4..d8f5381a8f 100644
--- a/webroot/rsrc/css/aphront/typeahead-browse.css
+++ b/webroot/rsrc/css/aphront/typeahead-browse.css
@@ -1,34 +1,47 @@
/**
* @provides typeahead-browse-css
*/
.typeahead-browse-more,
.typeahead-browse-hard-limit {
display: block;
padding: 8px;
margin: 8px 0 0;
text-align: center;
}
.typeahead-browse-more {
background: {$lightblue};
border: 1px solid {$lightblueborder};
}
.typeahead-browse-more.loading {
opacity: 0.8;
}
.typeahead-browse-hard-limit {
background: {$lightgreybackground};
border: 1px solid {$lightgreyborder};
color: {$lightgreytext};
}
.typeahead-browse-frame {
overflow-x: hidden;
overflow-y: auto;
padding: 4px;
height: 260px;
border: 1px solid {$lightgreyborder};
}
+
+.typeahead-browse-frame.loading {
+ opacity: 0.8;
+}
+
+.typeahead-browse-header {
+ padding: 4px 0;
+}
+
+input.typeahead-browse-input {
+ margin: 0;
+ width: 100%;
+}
diff --git a/webroot/rsrc/js/application/typeahead/behavior-typeahead-search.js b/webroot/rsrc/js/application/typeahead/behavior-typeahead-search.js
new file mode 100644
index 0000000000..93a0c0ef67
--- /dev/null
+++ b/webroot/rsrc/js/application/typeahead/behavior-typeahead-search.js
@@ -0,0 +1,56 @@
+/**
+ * @provides javelin-behavior-typeahead-search
+ * @requires javelin-behavior
+ * javelin-stratcom
+ * javelin-workflow
+ * javelin-dom
+ */
+
+JX.behavior('typeahead-search', function(config) {
+ var input = JX.$(config.inputID);
+ var frame = JX.$(config.frameID);
+ var last = input.value;
+
+ function update() {
+ if (input.value == last) {
+ // This is some kind of non-input keypress like an arrow key. Don't
+ // send a query to the server.
+ return;
+ }
+
+ // Call load() in a little while. If the user hasn't typed anything else,
+ // we'll send a request to get results.
+ setTimeout(JX.bind(null, load, input.value), 100);
+ }
+
+ function load(value) {
+ if (value != input.value) {
+ // The user has typed some more text, so don't send a request yet. We
+ // want to wait for them to stop typing.
+ return;
+ }
+
+ JX.DOM.alterClass(frame, 'loading', true);
+ new JX.Workflow(config.uri, {q: value})
+ .setHandler(function(r) {
+ if (value != input.value) {
+ // The user typed some more stuff while the request was in flight,
+ // so ignore the response.
+ return;
+ }
+
+ last = input.value;
+ JX.DOM.setContent(frame, JX.$H(r.markup));
+ JX.DOM.alterClass(frame, 'loading', false);
+ })
+ .start();
+ }
+
+ JX.DOM.listen(input, ['keydown', 'keypress', 'keyup'], null, function() {
+ // We need to delay this to actually read the value after the keypress.
+ setTimeout(update, 0);
+ });
+
+ JX.DOM.focus(input);
+
+});

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 3, 3:27 PM (10 h, 35 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
165957
Default Alt Text
(10 KB)

Event Timeline