Merge branch 'MDL-35819-m' of git://github.com/andrewnicols/moodle

This commit is contained in:
Dan Poltawski 2013-02-11 10:56:07 +08:00
commit a614fb7457
11 changed files with 598 additions and 178 deletions

View File

@ -48,15 +48,15 @@ $PAGE->set_context(context_system::instance());
if ($ajax) {
@header('Content-Type: text/plain; charset=utf-8');
} else {
echo $OUTPUT->header();
}
if (!$sm->string_exists($identifier.'_help', $component)) {
// strings on-diskc cache may be dirty - try to rebuild it and check again
// strings on disk-cache may be dirty - try to rebuild it and check again
$sm->load_component_strings($component, current_language(), true);
}
$data = new stdClass();
if ($sm->string_exists($identifier.'_help', $component)) {
$options = new stdClass();
$options->trusted = false;
@ -67,26 +67,38 @@ if ($sm->string_exists($identifier.'_help', $component)) {
$options->newlines = false;
$options->overflowdiv = !$ajax;
if ($ajax) {
// When using AJAX, the header should be H2 as it is in the same DOM as the calling page.
echo $OUTPUT->heading(format_string(get_string($identifier, $component)), 2, 'helpheading');
} else {
// When not using AJAX, the header should be H1 as it is in it's own window.
echo $OUTPUT->heading(format_string(get_string($identifier, $component)), 1, 'helpheading');
}
$data->heading = format_string(get_string($identifier, $component));
// Should be simple wiki only MDL-21695
echo format_text(get_string($identifier.'_help', $component), FORMAT_MARKDOWN, $options);
$data->text = format_text(get_string($identifier.'_help', $component), FORMAT_MARKDOWN, $options);
if ($sm->string_exists($identifier.'_link', $component)) { // Link to further info in Moodle docs
$link = get_string($identifier.'_link', $component);
$helplink = $identifier . '_link';
if ($sm->string_exists($helplink, $component)) { // Link to further info in Moodle docs
$link = get_string($helplink, $component);
$linktext = get_string('morehelp');
echo '<div class="helpdoclink">'.$OUTPUT->doc_link($link, $linktext).'</div>';
}
$data->doclink = new stdClass();
$url = new moodle_url(get_docs_url($link));
$data->doclink->link = $url->out();
$data->doclink->linktext = $linktext;
$data->doclink->class = ($CFG->doctonewwindow) ? 'helplinkpopup' : '';
$completedoclink = html_writer::tag('div', $OUTPUT->doc_link($link, $linktext), array('class' => 'helpdoclink'));
}
} else {
echo "<p><strong>TODO</strong>: missing help string [{$identifier}_help, $component]</p>";
$data->text = html_writer::tag('p',
html_writer::tag('strong', 'TODO') . ": missing help string [{$identifier}_help, {$component}]");
}
if (!$ajax) {
if ($ajax) {
echo json_encode($data);
} else {
echo $OUTPUT->header();
if (isset($data->heading)) {
echo $OUTPUT->heading($data->heading, 1, 'helpheading');
}
echo $data->text;
if (isset($completedoclink)) {
echo $completedoclink;
}
echo $OUTPUT->footer();
}

View File

@ -924,6 +924,7 @@ $string['list'] = 'List';
$string['listfiles'] = 'List of files in {$a}';
$string['listofallpeople'] = 'List of all people';
$string['listofcourses'] = 'List of courses';
$string['loadinghelp'] = 'Loading...';
$string['local'] = 'Local';
$string['localplugindeleteconfirm'] = 'You are about to completely delete the local plugin \'{$a}\'. This will completely delete everything in the database associated with this plugin. Are you SURE you want to continue?';
$string['localplugins'] = 'Local plugins';

View File

@ -1480,131 +1480,27 @@ M.util.help_popups = {
}
}
/**
* This code bas been deprecated and will be removed from Moodle 2.7
*
* Please see lib/yui/popuphelp/popuphelp.js for its replacement
*/
M.util.help_icon = {
Y : null,
instance : null,
initialised : false,
setup : function(Y) {
if (this.initialised) {
// Exit early if we have already completed setup
return;
setup : function(Y, properties) {
this.add(Y, properties);
},
add : function(Y) {
if (M.cfg.developerdebug) {
Y.log("You are using a deprecated function call (M.util.help_icon.add). " +
"Please look at rewriting your call to support lib/yui/popuphelp/popuphelp.js");
}
this.Y = Y;
Y.one('body').delegate('click', this.display, 'span.helplink a.tooltip', this);
this.initialised = true;
},
add : function(Y, properties) {
this.setup(Y);
},
display : function(event) {
event.preventDefault();
if (M.util.help_icon.instance === null) {
var Y = M.util.help_icon.Y;
Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', 'escape', function(Y) {
var help_content_overlay = {
helplink : null,
overlay : null,
init : function() {
var strclose = Y.Escape.html(M.str.form.close);
var footerbtn = Y.Node.create('<button class="closebtn">'+strclose+'</button>');
// Create an overlay from markup
this.overlay = new Y.Overlay({
footerContent: footerbtn,
bodyContent: '',
id: 'helppopupbox',
width:'400px',
visible : false,
constrain : true
});
this.overlay.render(Y.one(document.body));
footerbtn.on('click', this.close, this);
var boundingBox = this.overlay.get("boundingBox");
// Hide the menu if the user clicks outside of its content
boundingBox.get("ownerDocument").on("mousedown", function (event) {
var oTarget = event.target;
var menuButton = this.helplink;
if (!oTarget.compareTo(menuButton) &&
!menuButton.contains(oTarget) &&
!oTarget.compareTo(boundingBox) &&
!boundingBox.contains(oTarget)) {
this.overlay.hide();
}
}, this);
},
close : function(e) {
e.preventDefault();
this.helplink.focus();
this.overlay.hide();
},
display : function(event) {
var overlayPosition;
this.helplink = event.target.ancestor('span.helplink a', true);
if (Y.one('html').get('dir') === 'rtl') {
overlayPosition = [Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.LC];
} else {
overlayPosition = [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC];
}
this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
this.overlay.set("align", {node:this.helplink, points: overlayPosition});
var cfg = {
method: 'get',
context : this,
data : {
ajax : 1
},
on: {
success: function(id, o, node) {
this.display_callback(o.responseText);
},
failure: function(id, o, node) {
var debuginfo = o.statusText;
if (M.cfg.developerdebug) {
o.statusText += ' (' + ajaxurl + ')';
}
this.display_callback('bodyContent',debuginfo);
}
}
};
Y.io(this.helplink.get('href'), cfg);
this.overlay.show();
},
display_callback : function(content) {
var contentnode, heading;
contentnode = Y.Node.create('<div role="alert">' + content + '</div>');
this.overlay.set('bodyContent', contentnode);
heading = contentnode.one('h2');
if (heading) {
heading.set('tabIndex', 0);
heading.focus();
}
},
hideContent : function() {
help = this;
help.overlay.hide();
}
};
help_content_overlay.init();
M.util.help_icon.instance = help_content_overlay;
M.util.help_icon.instance.display(event);
if (!this.initialised) {
YUI().use('moodle-core-popuphelp', function() {
M.core.init_popuphelp([]);
});
} else {
M.util.help_icon.instance.display(event);
}
},
init : function(Y) {
this.Y = Y;
this.initialised = true;
}
};

View File

@ -364,9 +364,16 @@ class core_renderer extends renderer_base {
// flow player embedding support
$this->page->requires->js_function_call('M.util.load_flowplayer');
// Set up help link popups for all links with the helplinkpopup class
// Set up help link popups for all links with the helptooltip class
$this->page->requires->js_init_call('M.util.help_popups.setup');
// Setup help icon overlays.
$this->page->requires->yui_module('moodle-core-popuphelp', 'M.core.init_popuphelp');
$this->page->requires->strings_for_js(array(
'morehelp',
'loadinghelp',
), 'moodle');
$this->page->requires->js_function_call('setTimeout', array('fix_column_widths()', 20));
$focus = $this->page->focuscontrol;
@ -1932,7 +1939,7 @@ class core_renderer extends renderer_base {
$this->page->requires->string_for_js('close', 'form');
// and finally span
return html_writer::tag('span', $output, array('class' => 'helplink'));
return html_writer::tag('span', $output, array('class' => 'helptooltip'));
}
/**
@ -1989,14 +1996,11 @@ class core_renderer extends renderer_base {
// note: this title is displayed only if JS is disabled, otherwise the link will have the new ajax tooltip
$title = get_string('helpprefix2', '', trim($title, ". \t"));
$attributes = array('href'=>$url, 'title'=>$title, 'aria-haspopup' => 'true', 'class' => 'tooltip');
$attributes = array('href' => $url, 'title' => $title, 'aria-haspopup' => 'true');
$output = html_writer::tag('a', $output, $attributes);
$this->page->requires->js_init_call('M.util.help_icon.setup');
$this->page->requires->string_for_js('close', 'form');
// and finally span
return html_writer::tag('span', $output, array('class' => 'helplink'));
return html_writer::tag('span', $output, array('class' => 'helptooltip'));
}
/**

View File

@ -15,6 +15,11 @@ information provided here is intended especially for developers.
perform the whole deletion process. The function course_delete_module now takes care
of the whole process.
YUI changes:
* M.util.help_icon has been deprecated. Code should be updated to use moodle-core-popuphelp
instead. To do so, remove any existing JS calls to M.util.help_icon from your PHP and ensure
that your help link is placed in a span which has the class 'helplink'.
=== 2.4 ===
* Pagelib: Numerous deprecated functions were removed as classes page_base, page_course

86
lib/yui/popuphelp/popuphelp.js vendored Normal file
View File

@ -0,0 +1,86 @@
YUI.add('moodle-core-popuphelp', function(Y) {
function POPUPHELP() {
POPUPHELP.superclass.constructor.apply(this, arguments);
}
var SELECTORS = {
CLICKABLELINKS: 'span.helptooltip > a',
FOOTER: 'div.moodle-dialogue-ft'
},
CSS = {
ICON: 'icon',
ICONPRE: 'icon-pre'
},
ATTRS = {};
// Set the modules base properties.
POPUPHELP.NAME = 'moodle-core-popuphelp';
POPUPHELP.ATTRS = ATTRS;
Y.extend(POPUPHELP, Y.Base, {
panel: null,
initializer: function() {
Y.one('body').delegate('click', this.display_panel, SELECTORS.CLICKABLELINKS, this);
},
display_panel: function(e) {
if (!this.panel) {
this.panel = new M.core.tooltip({
bodyhandler: this.set_body_content,
footerhandler: this.set_footer,
initialheadertext: M.util.get_string('loadinghelp', 'moodle'),
initialfootertext: ''
});
}
// Call the tooltip setup.
this.panel.display_panel(e);
},
/**
* Override the footer handler to add a 'More help' link where relevant.
*
* @param {Object} helpobject The object returned from the AJAX call.
*/
set_footer: function(helpobject) {
// Check for an optional link to documentation on moodle.org.
if (helpobject.doclink) {
// Wrap a help icon and the morehelp text in an anchor. The class of the anchor should
// determine whether it's opened in a new window or not.
doclink = Y.Node.create('<a />')
.setAttrs({
'href': helpobject.doclink.link
})
.addClass(helpobject.doclink['class']);
helpicon = Y.Node.create('<img />')
.setAttrs({
'src': M.util.image_url('docs', 'core')
})
.addClass(CSS.ICON)
.addClass(CSS.ICONPRE);
doclink.appendChild(helpicon);
doclink.appendChild(helpobject.doclink.linktext);
// Set the footerContent to the contents of the doclink.
this.set('footerContent', doclink);
this.bb.one(SELECTORS.FOOTER).show();
} else {
this.bb.one(SELECTORS.FOOTER).hide();
}
}
});
M.core = M.core || {};
M.core.popuphelp = M.core.popuphelp || null;
M.core.init_popuphelp = M.core.init_popuphelp || function(config) {
// Only set up a single instance of the popuphelp.
if (!M.core.popuphelp) {
M.core.popuphelp = new POPUPHELP(config);
}
return M.core.popuphelp;
};
},
'@VERSION@', {
requires: ['moodle-core-tooltip']
});

434
lib/yui/tooltip/tooltip.js vendored Normal file
View File

@ -0,0 +1,434 @@
YUI.add('moodle-core-tooltip', function(Y) {
/**
* Provides the base tooltip class.
*
* @module moodle-core-tooltip
*/
/**
* A base class for a tooltip.
*
* @param {Object} config Object literal specifying tooltip configuration properties.
* @class M.core.tooltip
* @constructor
* @extends M.core.dialogue
*/
function TOOLTIP(config) {
if (!config) {
config = {};
}
// Override the default options provided by the parent class.
if (typeof config.draggable === 'undefined') {
config.draggable = true;
}
if (typeof config.constrain === 'undefined') {
config.constrain = true;
}
if (typeof config.lightbox === 'undefined') {
config.lightbox = false;
}
TOOLTIP.superclass.constructor.apply(this, [config]);
}
var SELECTORS = {
CLOSEBUTTON: '.closebutton'
},
CSS = {
PANELTEXT: 'tooltiptext'
},
RESOURCES = {
WAITICON: {
pix: 'i/loading_small',
component: 'moodle'
}
},
ATTRS = {};
/**
* Static property provides a string to identify the JavaScript class.
*
* @property NAME
* @type String
* @static
*/
TOOLTIP.NAME = 'moodle-core-tooltip';
/**
* Static property used to define the CSS prefix applied to tooltip dialogues.
*
* @property CSS_PREFIX
* @type String
* @static
*/
TOOLTIP.CSS_PREFIX = 'moodle-dialogue';
/**
* Static property used to define the default attribute configuration for the Tooltip.
*
* @property ATTRS
* @type String
* @static
*/
TOOLTIP.ATTRS = ATTRS;
/**
* The initial value of the header region before the content finishes loading.
*
* @attribute initialheadertext
* @type String
* @default ''
* @writeOnce
*/
ATTRS.initialheadertext = {
value: ''
};
/**
* The initial value of the body region before the content finishes loading.
*
* The supplid string will be wrapped in a div with the CSS.PANELTEXT class and a standard Moodle spinner
* appended.
*
* @attribute initialbodytext
* @type String
* @default ''
* @writeOnce
*/
ATTRS.initialbodytext = {
value: '',
setter: function(content) {
var parentnode,
spinner;
parentnode = Y.Node.create('<div />')
.addClass(CSS.PANELTEXT);
spinner = Y.Node.create('<img />')
.setAttribute('src', M.util.image_url(RESOURCES.WAITICON.pix, RESOURCES.WAITICON.component))
.addClass('spinner');
if (content) {
// If we have been provided with content, add it to the parent and make
// the spinner appear correctly inline
parentnode.set('text', content);
spinner.addClass('iconsmall');
} else {
// If there is no loading message, just make the parent node a lightbox
parentnode.addClass('content-lightbox');
}
parentnode.append(spinner);
return parentnode;
}
};
/**
* The initial value of the footer region before the content finishes loading.
*
* If a value is supplied, it will be wrapped in a <div> first.
*
* @attribute initialfootertext
* @type String
* @default ''
* @writeOnce
*/
ATTRS.initialfootertext = {
value: null,
setter: function(content) {
if (content) {
return Y.Node.create('<div />')
.set('text', content);
}
}
};
/**
* The function which handles setting the content of the title region.
* The specified function will be called with a context of the tooltip instance.
*
* The default function will simply set the value of the title to object.heading as returned by the AJAX call.
*
* @attribute headerhandler
* @type Function|String|null
* @default set_header_content
*/
ATTRS.headerhandler = {
value: 'set_header_content'
};
/**
* The function which handles setting the content of the body region.
* The specified function will be called with a context of the tooltip instance.
*
* The default function will simply set the value of the body area to a div containing object.text as returned
* by the AJAX call.
*
* @attribute bodyhandler
* @type Function|String|null
* @default set_body_content
*/
ATTRS.bodyhandler = {
value: 'set_body_content'
};
/**
* The function which handles setting the content of the footer region.
* The specified function will be called with a context of the tooltip instance.
*
* By default, the footer is not set.
*
* @attribute footerhandler
* @type Function|String|null
* @default null
*/
ATTRS.footerhandler = {
value: null
};
/**
* Set the Y.Cache object to use.
*
* By default a new Y.Cache object will be created for each instance of the tooltip.
*
* In certain situations, where multiple tooltips may share the same cache, it may be preferable to
* seed this cache from the calling method.
*
* @attribute textcache
* @type Y.Cache|null
* @default null
*/
ATTRS.textcache = {
value: null
};
/**
* Set the default size of the Y.Cache object.
*
* This is only used if no textcache is specified.
*
* @attribute textcachesize
* @type Number
* @default 10
*/
ATTRS.textcachesize = {
value: 10
};
Y.extend(TOOLTIP, M.core.dialogue, {
// The bounding box.
bb: null,
// Any event listeners we may need to cancel later.
listenevents: [],
// Cache of objects we've already retrieved.
textcache: null,
// The align position. This differs for RTL languages so we calculate once and store.
alignpoints: [
Y.WidgetPositionAlign.TL,
Y.WidgetPositionAlign.RC
],
initializer: function() {
// Set the initial values for the handlers.
// These cannot be set in the attributes section as context isn't present at that time.
if (!this.get('headerhandler')) {
this.set('headerhandler', this.set_header_content);
}
if (!this.get('bodyhandler')) {
this.set('bodyhandler', this.set_body_content);
}
if (!this.get('footerhandler')) {
this.set('footerhandler', function() {});
}
// Set up the dialogue with initial content.
this.setAttrs({
headerContent: this.get('initialheadertext'),
bodyContent: this.get('initialbodytext'),
footerContent: this.get('initialfootertext'),
zIndex: 150
});
// Hide and then render the dialogue.
this.hide();
this.render();
// Hook into a few useful areas.
this.bb = this.get('boundingBox');
// Change the alignment if this is an RTL language.
if (right_to_left()) {
this.alignpoints = [
Y.WidgetPositionAlign.TR,
Y.WidgetPositionAlign.LC
];
}
// Set up the text cache if it's not set up already.
if (!this.get('textcache')) {
this.set('textcache', new Y.Cache({
// Set a reasonable maximum cache size to prevent memory growth.
max: this.get('textcachesize')
}));
}
// Disable the textcache when in developerdebug.
if (M.cfg.developerdebug) {
this.get('textcache').set('max', 0);
}
return this;
},
/**
* Display the tooltip for the clicked link.
*
* The anchor for the clicked link is used, additionally appending ajax=1 to the parameters.
*
* @method display_panel
* @param {EventFacade} e The event from the clicked link. This is used to determine the clicked URL.
*/
display_panel: function(e) {
var clickedlink, thisevent, ajaxurl, config, cacheentry;
// Prevent the default click action and prevent the event triggering anything else.
e.preventDefault();
e.stopPropagation();
// Cancel any existing listeners and close the panel if it's already open.
this.cancel_events();
// Grab the clickedlink - this contains the URL we fetch and we align the panel to it.
clickedlink = e.target.ancestor('a', true);
// Align with the link that was clicked.
this.align(clickedlink, this.alignpoints);
// Reset the initial text to a spinner while we retrieve the text.
this.setAttrs({
headerContent: this.get('initialheadertext'),
bodyContent: this.get('initialbodytext'),
footerContent: this.get('initialfootertext')
});
// Now that initial setup has begun, show the panel.
this.show();
// Add some listen events to close on.
thisevent = this.bb.delegate('click', this.close_panel, SELECTORS.CLOSEBUTTON, this);
this.listenevents.push(thisevent);
thisevent = Y.one('body').on('key', this.close_panel, 'esc', this);
this.listenevents.push(thisevent);
// Listen for mousedownoutside events - clickoutside is broken on IE.
thisevent = this.bb.on('mousedownoutside', this.close_panel, this);
this.listenevents.push(thisevent);
ajaxurl = clickedlink.get('href');
cacheentry = this.get('textcache').retrieve(ajaxurl);
if (cacheentry) {
// The data from this help call was already cached so use that and avoid an AJAX call.
this._set_panel_contents(cacheentry.response);
} else {
// Retrieve the actual help text we should use.
config = {
method: 'get',
context: this,
sync: false,
data: {
// We use a slightly different AJAX URL to the one on the anchor to allow non-JS fallback.
ajax: 1
},
on: {
complete: function(tid, response) {
this._set_panel_contents(response.responseText, ajaxurl);
}
}
};
Y.io(clickedlink.get('href'), config);
}
},
_set_panel_contents: function(response, ajaxurl) {
var responseobject;
// Attempt to parse the response into an object.
try {
responseobject = Y.JSON.parse(response);
if (responseobject.error) {
this.close_panel();
return new M.core.ajaxException(responseobject);
}
} catch (error) {
this.close_panel();
return new M.core.exception({
name: error.name,
message: "Unable to retrieve the requested content. The following error was returned: " + error.message
});
}
// Set the contents using various handlers.
// We must use Y.bind to ensure that the correct context is used when the default handlers are overridden.
Y.bind(this.get('headerhandler'), this, responseobject)();
Y.bind(this.get('bodyhandler'), this, responseobject)();
Y.bind(this.get('footerhandler'), this, responseobject)();
if (ajaxurl) {
// Ensure that this data is added to the cache.
this.get('textcache').add(ajaxurl, response);
}
this.get('buttons').header[0].focus();
},
set_header_content: function(responseobject) {
this.set('headerContent', responseobject.heading);
},
set_body_content: function(responseobject) {
var bodycontent = Y.Node.create('<div />')
.set('innerHTML', responseobject.text)
.setAttribute('role', 'alert')
.addClass(CSS.PANELTEXT);
this.set('bodyContent', bodycontent);
},
close_panel: function(e) {
// Hide the panel first.
this.hide();
// Cancel the listeners that we added in display_panel.
this.cancel_events();
// Prevent any default click that the close button may have.
if (e) {
e.preventDefault();
}
},
cancel_events: function() {
// Detach all listen events to prevent duplicate triggers.
var thisevent;
while (this.listenevents.length) {
thisevent = this.listenevents.shift();
thisevent.detach();
}
}
});
M.core = M.core || {};
M.core.tooltip = M.core.tooltip = TOOLTIP;
},
'@VERSION@', {
requires: ['base', 'io-base', 'moodle-core-notification', 'json-parse',
'widget-position', 'widget-position-align', 'event-outside', 'cache']
}
);

View File

@ -519,18 +519,6 @@ body.tag .managelink {padding: 5px;}
*/
#webservice-doc-generator td {text-align: left;border: 0px solid black;}
/**
* Help Content (pop-up)
*/
#helppopupbox {background-color: #eee; border: 1px solid #848484;z-index: 10000 !important;}
#helppopupbox .yui3-widget-hd {float:right;margin:3px 3px 0 0;}
#helppopupbox .yui3-widget-bd {margin:0 1em 1em 1em;border-top:1px solid #eee;}
#helppopupbox .yui3-widget-ft {text-align: center;}
#helppopupbox .yui3-widget-ft .closebtn {margin:0 1em 1em 1em;}
#helppopupbox .helpheading {font-size: 1em;}
#helppopupbox .spinner {margin:1em;}
.dir-rtl #helppopupbox .yui3-widget-hd {float:left;margin:3px 0 0 3px;}
/**
* Custom menu
*/
@ -1023,6 +1011,22 @@ sup {vertical-align: super;}
padding-bottom:4px;
}
.moodle-dialogue .moodle-dialogue-bd .content-lightbox {
opacity: .75;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: white;
text-align: center;
padding: 10% 0;
}
/* Apply a default max-height on tooltip text */
.moodle-dialogue .tooltiptext {
max-height: 300px;
}
/* Question Bank - Question Chooser "Close" button */
#page-question-edit.dir-rtl a.container-close {right:auto;left:6px;}

View File

@ -445,13 +445,6 @@ input[type="radio"] {
padding: 0 0 1em;
}
/* YUI overlays
------------------------*/
#helppopupbox {
z-index: 99999 !important;
}
/* Embedded Pages
------------------------*/

View File

@ -180,9 +180,6 @@ table td.cell p {margin:0;}
#page-footer .logininfo {padding:0.3em 0 0.7em 0;}
#page-footer .moodledocs {text-align:center;background-color:#EFEFEF;padding:0.7em 0 0.8em 0;}
/* js help messages */
#helppopupbox .helpheading {margin-top:1em;}
/* pre, code, tt */
pre, code, tt {
font: 1em/1.3 monospace;

View File

@ -286,18 +286,6 @@ textarea#id_message {width:420px;}
.helplink img {
margin-left:5px;
}
#helppopupbox {
padding:10px 0;
}
#helppopupbox p {
padding:0 0 5px;
margin:0;
line-height:1.3em;
}
#helppopupbox .helpheading {
font-size:1.2em;
padding-bottom:10px;
}
/* Icons
-------------------------*/
img.icon {