Index: modules/help/help.css
===================================================================
RCS file: /cvs/drupal/drupal/modules/help/help.css,v
retrieving revision 1.1
diff -u -p -r1.1 help.css
--- modules/help/help.css 14 Aug 2006 07:14:49 -0000 1.1
+++ modules/help/help.css 10 Dec 2006 09:18:12 -0000
@@ -8,3 +8,22 @@
.help-items-last {
padding-right: 0;
}
+
+#help_guide_form {
+ position: absolute;
+ top: 5em;
+ right: 1em;
+ z-index: 10001;
+}
+
+#help_guide_form * {
+ display: inline;
+}
+
+#autocomplete.guide-popup {
+ z-index: 10002;
+}
+#autocomplete.guide-popup .path {
+ color: #777;
+ font-size: 0.9em;
+}
Index: modules/help/help.info
===================================================================
RCS file: /cvs/drupal/drupal/modules/help/help.info,v
retrieving revision 1.3
diff -u -p -r1.3 help.info
--- modules/help/help.info 21 Nov 2006 20:55:34 -0000 1.3
+++ modules/help/help.info 10 Dec 2006 09:18:12 -0000
@@ -3,3 +3,4 @@ name = Help
description = Manages the display of online help.
package = Core - optional
version = VERSION
+dependencies = search
Index: modules/help/help.js
===================================================================
RCS file: modules/help/help.js
diff -N modules/help/help.js
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/help/help.js 10 Dec 2006 09:18:12 -0000
@@ -0,0 +1,269 @@
+// $Id$
+
+/**
+ * Attaches the guide behaviour to all required fields
+ */
+Drupal.guideAutoAttach = function () {
+ var gdb = new Drupal.ACDB(Drupal.settings.guide.url);
+ $('input.guide').each(function () {
+ var uri = this.value;
+ var input = $(this)
+ .attr('autocomplete', 'OFF')[0];
+ $(input.form).submit(Drupal.guideSubmit);
+ new Drupal.guide(input, gdb);
+ });
+
+ // Tweak IE settings so that $('body').css('height') works
+ if (jQuery.browser.msie) {
+ $('html').add('body').css('height', '100%').css('left', '0');
+ }
+}
+
+/**
+ * Prevents the form from submitting if the suggestions popup is open
+ * and closes the suggestions popup when doing so.
+ */
+Drupal.guideSubmit = function () {
+ $('#autocomplete').each(function () {
+ this.owner.hidePopup();
+ });
+ return false;
+}
+
+/**
+ * A guide object
+ */
+Drupal.guide = function (input, db) {
+ var guide = this;
+ this.input = input;
+ this.db = db;
+
+ $(this.input)
+ .keydown(function (event) { return guide.onkeydown(this, event); })
+ .keyup(function (event) { guide.onkeyup(this, event) })
+ .blur(function () { guide.hidePopup(); guide.db.cancel(); });
+
+};
+
+/**
+ * Inherited methods from autocomplete.
+ */
+Drupal.guide.prototype.onkeydown = Drupal.jsAC.prototype.onkeydown;
+Drupal.guide.prototype.onkeyup = Drupal.jsAC.prototype.onkeyup;
+Drupal.guide.prototype.selectDown = Drupal.jsAC.prototype.selectDown;
+Drupal.guide.prototype.selectUp = Drupal.jsAC.prototype.selectUp;
+Drupal.guide.prototype.highlight = Drupal.jsAC.prototype.highlight;
+Drupal.guide.prototype.unhighlight = Drupal.jsAC.prototype.unhighlight;
+Drupal.guide.prototype.setStatus = Drupal.jsAC.prototype.setStatus;
+
+/**
+ * Puts the currently highlighted suggestion into the guide field and
+ * visits the target URL.
+ */
+Drupal.guide.prototype.select = function (node) {
+ this.input.value = node.guideValue;
+ location.href = node.guideUrl;
+}
+
+/**
+ * Hides the guide suggestions
+ */
+Drupal.guide.prototype.hidePopup = function (keycode) {
+ // Hide popup
+ var popup = this.popup;
+ if (popup) {
+ this.popup = null;
+ $(popup).fadeOut('fast', function() { $(popup).remove(); });
+ }
+
+ // Unexpose all items
+ this.unexpose();
+
+ // Select item if the right key or mousebutton was pressed
+ if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
+ this.select(this.selected);
+ }
+
+ // Remove selection
+ this.selected = false;
+}
+
+/**
+ * Positions the suggestions popup and starts a search
+ */
+Drupal.guide.prototype.populatePopup = function () {
+ // Show popup
+ if (this.popup) {
+ $(this.popup).remove();
+ }
+ var pos = Drupal.absolutePosition(this.input);
+ this.selected = false;
+ this.popup = document.createElement('div');
+ this.popup.id = 'autocomplete';
+ this.popup.className = 'guide-popup';
+ this.popup.owner = this;
+ $(this.popup).css({
+ top: (pos.y + this.input.offsetHeight) +'px',
+ left: pos.x +'px',
+ width: (this.input.offsetWidth - 4) +'px',
+ display: 'none'
+ });
+ $('body').append(this.popup);
+
+ // Do search
+ this.db.owner = this;
+ this.db.search(this.input.value);
+}
+
+/**
+ * Fills the suggestion popup with any matches received
+ */
+Drupal.guide.prototype.found = function (matches) {
+ // Unhighlight everything in the page.
+ this.unexpose();
+
+ // Prepare matches
+ var ul = document.createElement('ul');
+ var ac = this;
+ var i = 0;
+ for (key in matches) {
+ var li = document.createElement('li');
+ $(li)
+ .html('
'+ matches[key][0] +'
')
+ .mousedown(function () { ac.select(this); })
+ .mouseover(function () { ac.highlight(this); })
+ .mouseout(function () { ac.unhighlight(this); });
+ li.guideValue = key;
+ li.guideUrl = matches[key][1];
+ $(ul).append(li);
+
+ // Highlight this link in the page.
+ this.expose(key);
+ i++;
+ }
+
+ // Show popup with matches, if any
+ if (ul.childNodes.length > 0) {
+ $(this.popup).empty().append(ul).show();
+ }
+ else {
+ $(this.popup).css({visibility: 'hidden'});
+ this.hidePopup();
+ }
+}
+
+/**
+ * Unexpose all items on the page.
+ */
+Drupal.guide.prototype.unexpose = function () {
+ // Remove shade.
+ $('#guide-shade').remove();
+
+ // Remove bubbles.
+ if (this.exposed && this.exposed.length > 0) {
+ $(this.marked).removeClass('exposed');
+ $(this.exposed).remove();
+ this.exposed = [];
+ this.marked = [];
+ }
+}
+
+/**
+ * Expose links to this
+ */
+Drupal.guide.prototype.expose = function (path) {
+ var guide = this;
+ // Dim the page.
+ if ($('#guide-shade').size() == 0) {
+ $('').appendTo($('body')).css({
+ 'position': 'absolute',
+ 'top': '0px',
+ 'left': '0px',
+ 'right': '0px',
+ 'height': parseInt($('body').css('height')) + 30 + 'px',
+ 'margin-bottom': '-30px',
+ 'background': '#000',
+ 'opacity': 0.0,
+ 'z-index': 10000
+ })
+ .animate({ 'opacity': 0.5 });
+ this.exposed = [];
+ this.marked = []
+ }
+
+ var z = 10001;
+ /**
+ * Highlight a DOM node.
+ */
+ var _expose = function (node, opacity) {
+ // Check if it's been exposed already.
+ if ($(node).is('.exposed') || $(node.parentNode.parentNode).css('float') != 'none') {
+ return;
+ }
+ $(node).addClass('exposed');
+ guide.marked.push(node);
+
+ // Ensure we're positioning relative to the parent (prevents
+ // shrink-wrapping of content).
+ var p = node.parentNode;
+ if ($(p).css('position') == 'static') {
+ $(p).css('position', 'relative');
+ }
+
+ // Duplicate highlighted item.
+ var clone = node.cloneNode(true);
+ $(node).before(clone);
+ var glow = node.cloneNode(true);
+ $(node).before(glow);
+
+ // Place above all else.
+ $([glow, clone]).css({
+ 'position': 'absolute',
+ 'opacity': 0
+ });
+
+ // Style and animate (hardcoded, to override).
+ $(glow).css({
+ 'z-index': z++,
+ 'background': '#000',
+ 'padding': '12px 27px',
+ 'margin': '-10px -27px',
+ 'border-radius': '18px',
+ '-moz-border-radius': '18px'
+ })
+ .animate({ 'opacity': opacity * 0.25 });
+ $(clone).css({
+ 'z-index': z++,
+ 'background': '#fff',
+ 'border': '1px solid #ccc',
+ 'padding': '7px 22px',
+ 'margin': '-8px -23px',
+ 'border-radius': '14px',
+ '-moz-border-radius': '14px'
+ })
+ .animate({ 'opacity': opacity });
+
+ // Remember clone elements for later.
+ guide.exposed.push(glow);
+ guide.exposed.push(clone);
+ }
+
+ // Highlight exact matches.
+ $('a[@href$='+ path +']').each(function () { _expose(this, 1) });
+
+ // Highlight partial matches from 3 or more path components.
+ var path = path.split('/');
+ var part = '';
+ for (i in path) {
+ part += (part == '' ? '' : '/') + path[i];
+ if (i >= 2) {
+ $('a[@href$='+ part +']').each(function () { _expose(this, 0.5) });
+ }
+ }
+}
+
+
+// Global Killswitch
+if (Drupal.jsEnabled) {
+ $(document).ready(Drupal.guideAutoAttach);
+}
Index: modules/help/help.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/help/help.module,v
retrieving revision 1.66
diff -u -p -r1.66 help.module
--- modules/help/help.module 1 Dec 2006 22:47:53 -0000 1.66
+++ modules/help/help.module 10 Dec 2006 09:18:12 -0000
@@ -12,8 +12,19 @@
function help_menu($may_cache) {
$items = array();
+ $admin_access = user_access('access administration pages');
+
if ($may_cache) {
- $admin_access = user_access('access administration pages');
+ $items[] = array('path' => 'admin/help/guide/page',
+ 'callback' => 'help_guide_page',
+ 'access' => $admin_access,
+ 'type' => MENU_CALLBACK,
+ );
+
+ $items[] = array('path' => 'admin/help/guide/js',
+ 'callback' => 'help_guide_js',
+ 'access' => $admin_access,
+ 'type' => MENU_CALLBACK);
$items[] = array('path' => 'admin/help', 'title' => t('Help'),
'callback' => 'help_main',
@@ -27,6 +38,15 @@ function help_menu($may_cache) {
'type' => MENU_CALLBACK,
'access' => $admin_access);
}
+
+ }
+ else {
+ // Add guide search to all top-level admin pages.
+ if (arg(0) == 'admin' && arg(2) == '' && $admin_access) {
+ drupal_add_js('misc/autocomplete.js');
+ drupal_add_js(drupal_get_path('module', 'help') .'/help.js');
+ drupal_set_content('content', drupal_get_form('help_guide_form'));
+ }
}
return $items;
@@ -134,3 +154,149 @@ function help_page() {
}
return $output;
}
+
+/**
+ * Form callback: return the form for querying the guide.
+ */
+function help_guide_form() {
+ // Add CSS.
+ drupal_add_css(drupal_get_path('module', 'help') .'/help.css');
+
+ // Pass data to JS.
+ drupal_add_js(array('guide' => array('url' => url('admin/help/guide/js', NULL, NULL, TRUE))), 'setting');
+
+ $form['query'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Find'),
+ '#size' => 30,
+ '#attributes' => array('class' => 'guide form-autocomplete'),
+ );
+ return $form;
+}
+
+/**
+ * Menu callback: print a page with results for a guide query.
+ */
+function help_guide_page($query) {
+ // Query database
+ $results = help_guide_query($query);
+
+ // Fetch menu
+ $menu = menu_get_menu();
+
+ // Format results
+ $list = array();
+ foreach ($results as $path) {
+ $list[] = l($menu['items'][$menu['path index'][$path]]['title'], $path);
+ }
+ drupal_set_title(t('Find pages related to %search', array('%search' => $query)));
+ return theme('item_list', $list, t('The following pages were found:'));
+}
+
+/**
+ * Menu callback: return JSON results for a guide query.
+ */
+function help_guide_js($query) {
+ // Query database
+ $results = help_guide_query($query);
+ drupal_get_messages(NULL, TRUE);
+
+ // Fetch menu
+ $menu = menu_get_menu();
+
+ // Format results
+ $output = array();
+ foreach ($results as $path) {
+ $output[$path] = array(check_plain($menu['items'][$menu['path index'][$path]]['title']) . ' ('. check_plain($path) .')', check_url(url($path, NULL, NULL, TRUE)));
+ }
+
+ print drupal_to_js($output);
+ exit();
+}
+
+/**
+ * Perform a guide query.
+ */
+function help_guide_query($query) {
+ // Check if the index is available.
+ $path_map = help_guide_map();
+ if (count($path_map) == 0) {
+ return array();
+ }
+
+ // OR search
+ $query = implode(' OR ', explode(' ', $query));
+
+ // Query search index
+ $results = array();
+ $result = do_search($query, 'help');
+ foreach ($result as $item) {
+ $results[] = $path_map[$item->sid];
+ }
+
+ return $results;
+}
+
+/**
+ * Return the guide path map.
+ */
+function help_guide_map() {
+ // We use the menu cache, so we know the menu has rebuilt when it's been wiped.
+ if ($path_map = cache_get('help:path_map', 'cache_menu')) {
+ return unserialize($path_map->data);
+ }
+ else {
+ // Avoid multiple index runs at the same time.
+ cache_set('help:path_map', 'cache_menu', serialize(array()), CACHE_PERMANENT);
+
+ // Rebuild the index and path map.
+ return help_guide_index();
+ }
+}
+
+/**
+ * Re-index the guide.
+ */
+function help_guide_index() {
+ // Clean search index.
+ db_query("DELETE FROM {search_index} WHERE type = 'help'");
+
+ // Prepare path map.
+ $path_map = array();
+ $i = 0;
+
+ $menu = menu_get_menu();
+ foreach ($menu['path index'] as $path => $mid) {
+ // Fetch next item.
+ $item = $menu['items'][$mid];
+ if ($item['pid'] == 0 || !($item['type'] & (MENU_VISIBLE_IN_TREE | MENU_IS_LOCAL_TASK))) {
+ continue;
+ }
+
+ // Prepare snippet to index.
+ $output = ''. $item['title'] .'
';
+ $output .= ''. str_replace('/', ' ', $path) .'
';
+ $output .= ''. $item['description'] .'
';
+
+ // Fetch help.
+ foreach (module_list() as $name) {
+ $output .= module_invoke($name, 'help', $path);
+ }
+
+ // Fetch sid.
+ if (!isset($path_map[$path])) {
+ $path_map[$path] = ++$i;
+ }
+
+ // Add to index.
+ search_index($path_map[$path], 'help', $output);
+ }
+
+ // Update search totals.
+ search_update_totals();
+
+ // Update and return path map.
+ $path_map = array_flip($path_map);
+ cache_set('help:path_map', 'cache_menu', serialize($path_map), CACHE_PERMANENT);
+ return $path_map;
+}
Index: profiles/default/default.profile
===================================================================
RCS file: /cvs/drupal/drupal/profiles/default/default.profile,v
retrieving revision 1.2
diff -u -p -r1.2 default.profile
--- profiles/default/default.profile 29 Oct 2006 13:17:38 -0000 1.2
+++ profiles/default/default.profile 10 Dec 2006 09:18:12 -0000
@@ -8,7 +8,7 @@
* An array of modules to be enabled.
*/
function default_profile_modules() {
- return array('block', 'color', 'comment', 'filter', 'help', 'menu', 'node', 'system', 'taxonomy', 'user', 'watchdog');
+ return array('block', 'color', 'comment', 'filter', 'help', 'menu', 'node', 'search', 'system', 'taxonomy', 'user', 'watchdog');
}
/**
Index: themes/garland/style.css
===================================================================
RCS file: /cvs/drupal/drupal/themes/garland/style.css,v
retrieving revision 1.10
diff -u -p -r1.10 style.css
--- themes/garland/style.css 4 Dec 2006 23:15:40 -0000 1.10
+++ themes/garland/style.css 10 Dec 2006 09:18:12 -0000
@@ -878,7 +878,7 @@ table.system-status-report th {
color: #fff;
}
-tr.selected td a:link, tr.selected td a:visited, tr.selected td a:active {
+tr.selected td a:link, tr.selected td a:visited, tr.selected td a:active, #autocomplete.guide-popup li.selected span.path {
color: #d3e7f4;
}