summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
authorGery Debongnie <ged@openerp.com>2014-02-25 10:13:10 +0100
committerGery Debongnie <ged@openerp.com>2014-02-25 10:13:10 +0100
commit893b7041f97a5f98dbb95affdfeecbcdb6db69ed (patch)
tree463444a7e755b66a6fb3f7aefc5c5f2b6a6bf074 /addons/web/static/src
parent2ab61cf001fab1d30050598931bfa39956cf4883 (diff)
parenteaa20f9586f5d9a7a52ed5a7cc565d620e0ad44a (diff)
[MERGE] merge from trunk
bzr revid: ged@openerp.com-20140225091310-pzkj5ip17esl52kz
Diffstat (limited to 'addons/web/static/src')
-rw-r--r--addons/web/static/src/css/Makefile7
-rw-r--r--addons/web/static/src/css/base.css48
-rw-r--r--addons/web/static/src/css/base.sass44
-rw-r--r--addons/web/static/src/img/throbber-large.gifbin0 -> 28519 bytes
-rw-r--r--addons/web/static/src/js/chrome.js13
-rw-r--r--addons/web/static/src/js/data.js6
-rw-r--r--addons/web/static/src/js/formats.js2
-rw-r--r--addons/web/static/src/js/search.js9
-rw-r--r--addons/web/static/src/js/testing.js111
-rw-r--r--addons/web/static/src/js/view_form.js23
-rw-r--r--addons/web/static/src/js/view_list.js7
-rw-r--r--addons/web/static/src/js/views.js16
-rw-r--r--addons/web/static/src/xml/base.xml43
13 files changed, 158 insertions, 171 deletions
diff --git a/addons/web/static/src/css/Makefile b/addons/web/static/src/css/Makefile
index f4f1f6d1bb5..ba88946baac 100644
--- a/addons/web/static/src/css/Makefile
+++ b/addons/web/static/src/css/Makefile
@@ -1,2 +1,5 @@
-sass:
- sass -t expanded --compass --unix-newlines --watch base.sass:base.css& \ No newline at end of file
+all: *.css
+%.css: %.sass
+ sass -t expanded --compass --unix-newlines $< $@
+watch:
+ sass -t expanded --compass --unix-newlines --watch .:.
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css
index 6b568f8846e..e43a2fb654e 100644
--- a/addons/web/static/src/css/base.css
+++ b/addons/web/static/src/css/base.css
@@ -78,6 +78,20 @@
.openerp td {
vertical-align: top;
}
+.openerp .oe_title {
+ width: 50%;
+ float: left;
+}
+.openerp .oe_title:after {
+ content: ".";
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+}
+.openerp .oe_form_group {
+ clear: both;
+}
.openerp .zebra tbody tr:nth-child(odd) td {
background-color: #f0f0fa;
background-color: #efeff8;
@@ -1205,10 +1219,7 @@
.openerp .oe_about .oe_bottom a {
color: #eeeeee;
}
-.openerp .oe_form_uri {
- color: #7c7bad;
-}
-.openerp .oe_form_uri:hover {
+.openerp a.oe_form_uri:hover {
text-decoration: underline;
}
.openerp .oe_application {
@@ -1594,7 +1605,6 @@
border-radius: 0 3px 3px 0;
}
.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_category, .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value {
- height: 18px;
padding: 0 4px;
}
.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_category {
@@ -2021,7 +2031,6 @@
}
.openerp .oe_form > :not(.oe_form_nosheet) header .oe_button {
margin: 3px 2px 1px;
- float: left;
}
.openerp .oe_form > :not(.oe_form_nosheet) header .oe_button:first-child {
margin-left: 6px;
@@ -2142,7 +2151,7 @@
}
.openerp .oe_form .oe_form_label_help[for] span, .openerp .oe_form .oe_form_label[for] span {
font-size: 80%;
- color: darkGreen;
+ color: darkgreen;
vertical-align: top;
position: relative;
top: -4px;
@@ -3021,25 +3030,12 @@
font-size: 95%;
line-height: 1.2em;
}
-.openerp .oe_debug_view_log label {
- display: block;
- width: 49%;
- text-align: right;
- float: left;
- font-weight: bold;
- color: #000099;
-}
-.openerp .oe_debug_view_log span {
- display: block;
- width: 49%;
- float: right;
- color: #333333;
-}
.openerp .navbar {
min-height: 32px;
margin-bottom: 0px;
border: none;
z-index: 1;
+ position: static;
background-color: #414141;
background-color: #454343;
background-image: -webkit-gradient(linear, left top, left bottom, from(#646060), to(#262626));
@@ -3138,10 +3134,6 @@
border: none;
padding: 10px 0 3px 0;
}
-.openerp .jqstooltip {
- height: auto !important;
- width: auto !important;
-}
.openerp h5 {
font-weight: bold;
font-size: smaller;
@@ -3153,6 +3145,12 @@
margin: 3px 3px 0 !important;
}
+.jqstooltip {
+ height: auto !important;
+ width: auto !important;
+ padding: 0;
+}
+
@-moz-document url-prefix() {
.openerp .oe_searchview .oe_searchview_search {
top: -1px;
diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass
index 5bb2bb9b448..b19db9e4ebd 100644
--- a/addons/web/static/src/css/base.sass
+++ b/addons/web/static/src/css/base.sass
@@ -189,6 +189,17 @@ $sheet-padding: 16px
vertical-align: middle
td
vertical-align: top
+ .oe_title
+ width: 50%
+ float: left
+ .oe_title:after
+ content: "."
+ display: block
+ height: 0
+ clear: both
+ visibility: hidden
+ .oe_form_group
+ clear: both
.zebra tbody tr:nth-child(odd) td
background-color: #f0f0fa
@include vertical-gradient(#f0f0fa, #eeeef6)
@@ -1002,10 +1013,8 @@ $sheet-padding: 16px
color: #eee
// }}}
// ActionManager {{{
- .oe_form_uri
- color: $link-color
- &:hover
- text-decoration: underline
+ a.oe_form_uri:hover
+ text-decoration: underline
.oe_application
width: 100%
height: 100%
@@ -1302,7 +1311,6 @@ $sheet-padding: 16px
background: $tag-bg-light
@include radius(0 3px 3px 0)
.oe_facet_category, .oe_facet_value
- height: 18px
padding: 0 4px
.oe_facet_category
color: white
@@ -1639,7 +1647,6 @@ $sheet-padding: 16px
float: right
.oe_button
margin: 3px 2px 1px
- float: left
&:first-child
margin-left: 6px
@@ -2419,18 +2426,6 @@ $sheet-padding: 16px
.oe_debug_view_log
font-size: 95%
line-height: 1.2em
- .oe_debug_view_log label
- display: block
- width: 49%
- text-align: right
- float: left
- font-weight: bold
- color: #009
- .oe_debug_view_log span
- display: block
- width: 49%
- float: right
- color: #333
// }}}
// Bootstrap HACKS {{{
.navbar
@@ -2438,6 +2433,7 @@ $sheet-padding: 16px
margin-bottom: 0px
border: none
z-index: 1
+ position: static
background-color: #414141
@include vertical-gradient(#646060, #262626)
.navbar-default
@@ -2517,10 +2513,7 @@ $sheet-padding: 16px
border: none
padding: 10px 0 3px 0
- // Customize for kanban tooltip
- .jqstooltip
- height: auto !important
- width: auto !important
+
// Customize for chatter
h5
@@ -2531,7 +2524,12 @@ $sheet-padding: 16px
.oe_msg_subtype_check
margin: 3px 3px 0 !important
// }}}
-
+// Customize for kanban tooltip
+.jqstooltip
+ height: auto !important
+ width: auto !important
+ padding: 0
+
@-moz-document url-prefix()
.openerp
.oe_searchview .oe_searchview_search
diff --git a/addons/web/static/src/img/throbber-large.gif b/addons/web/static/src/img/throbber-large.gif
new file mode 100644
index 00000000000..453b272979d
--- /dev/null
+++ b/addons/web/static/src/img/throbber-large.gif
Binary files differ
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index afa761d7025..461bfefac1a 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -1336,10 +1336,15 @@ instance.web.WebClient = instance.web.Client.extend({
var state = $.bbq.getState(true);
if (_.isEmpty(state) || state.action == "login") {
self.menu.has_been_loaded.done(function() {
- var first_menu_id = self.menu.$el.find("a:first").data("menu");
- if(first_menu_id) {
- self.menu.menu_click(first_menu_id);
- }
+ new instance.web.Model("res.users").call("read", [self.session.uid, ["action_id"]]).done(function(data) {
+ var first_menu_id = self.menu.$el.find("a:first").data("menu");
+ if(first_menu_id)
+ self.menu.menu_click(first_menu_id);
+
+ if(data.action_id) {
+ self.action_manager.do_action(data.action_id[0]);
+ }
+ });
});
} else {
$(window).trigger('hashchange');
diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js
index 78e3001d746..cc595c30688 100644
--- a/addons/web/static/src/js/data.js
+++ b/addons/web/static/src/js/data.js
@@ -452,6 +452,9 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin,
* @returns {$.Deferred}
*/
read_ids: function (ids, fields, options) {
+ if (_.isEmpty(ids))
+ return $.Deferred().resolve([]);
+
options = options || {};
// TODO: reorder results to match ids list
return this._model.call('read',
@@ -872,7 +875,8 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
});
var return_records = function() {
var records = _.map(ids, function(id) {
- return _.extend({}, _.detect(self.cache, function(c) {return c.id === id;}).values, {"id": id});
+ var c = _.find(self.cache, function(c) {return c.id === id;});
+ return _.isUndefined(c) ? c : _.extend({}, c.values, {"id": id});
});
if (self.debug_mode) {
if (_.include(records, undefined)) {
diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js
index 2f6bbcebc4f..3c32dbe0426 100644
--- a/addons/web/static/src/js/formats.js
+++ b/addons/web/static/src/js/formats.js
@@ -205,7 +205,7 @@ instance.web.format_value = function (value, descriptor, value_if_empty) {
case 'selection': case 'statusbar':
// Each choice is [value, label]
if(_.isArray(value)) {
- value = value[0];
+ return value[1];
}
var result = _(descriptor.selection).detect(function (choice) {
return choice[0] === value;
diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js
index fa9bae9969d..b667d135bdd 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -414,7 +414,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
context: this.dataset.get_context(),
});
- $.when(load_view).then(function (r) {
+ this.alive($.when(load_view)).then(function (r) {
return self.search_view_loaded(r);
}).fail(function () {
self.ready.reject.apply(null, arguments);
@@ -473,12 +473,9 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
* Sets up search view's view-wide auto-completion widget
*/
setup_global_completion: function () {
- var self = this;
-
var autocomplete = this.$el.autocomplete({
source: this.proxy('complete_global_search'),
select: this.proxy('select_completion'),
- search: function () { self.$el.autocomplete('close'); },
focus: function (e) { e.preventDefault(); },
html: true,
autoFocus: true,
@@ -486,6 +483,10 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
delay: 250,
}).data('autocomplete');
+ this.$el.on('input', function () {
+ this.$el.autocomplete('close');
+ }.bind(this));
+
// MonkeyPatch autocomplete instance
_.extend(autocomplete, {
_renderItem: function (ul, item) {
diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js
index f607b278437..1f1c718ad5f 100644
--- a/addons/web/static/src/js/testing.js
+++ b/addons/web/static/src/js/testing.js
@@ -15,6 +15,37 @@ openerp.testing = {};
list_editable: ['list', 'form', 'data'],
};
+ var initialized = [];
+ /**
+ * openerp.init is a broken-ass piece of shit, makes it impossible to
+ * progressively add modules, using it, retarded openerp_inited flag
+ * means the first tests being run get their dependencies loaded
+ * (basically just base and web), and for *every other module* the tests
+ * run without the dependencies being correctly loaded
+ */
+ function init(modules) {
+ if (!initialized.length) {
+ openerp.init(modules);
+ initialized = openerp._modules.slice();
+ return;
+ }
+ var to_initialize = _.difference(modules, initialized);
+ for (var i = 0; i < to_initialize.length; i++) {
+ var modname = to_initialize[i];
+ var fct = openerp[modname];
+ if (typeof fct === 'function') {
+ var module = openerp[modname] = {};
+ for(var k in fct) {
+ if (!fct.hasOwnProperty(k)) { continue; }
+ module[k] = fct[k];
+ }
+ fct(openerp, module)
+ }
+ initialized.push(modname);
+ openerp._modules.push(modname);
+ }
+ }
+
testing.dependencies = window['oe_all_dependencies'] || [];
testing.current_module = null;
testing.templates = { };
@@ -165,9 +196,6 @@ openerp.testing = {};
});
};
- var openerp_inited = false;
-
- var db = window['oe_db_info'];
testing.section = function (name, options, body) {
if (_.isFunction(options)) {
body = options;
@@ -206,47 +234,10 @@ openerp.testing = {};
.push(env._oe.setup, env._oe.teardown)
.push(options.setup, options.teardown);
var opts = _.defaults({}, options, env._oe);
- // FIXME: if this test is ignored, will still query
- if (opts.rpc === 'rpc' && !db) {
- QUnit.config.autostart = false;
- db = {
- source: null,
- supadmin: null,
- password: null
- };
- var $msg = $('<form style="margin: 0 1em 1em;">')
- .append('<h3>A test needs to clone a database</h3>')
- .append('<h4>Please provide the source clone information</h4>')
- .append(' Source DB: ').append('<input name="source">').append('<br>')
- .append(' DB Password: ').append('<input name="supadmin">').append('<br>')
- .append('Admin Password: ').append('<input name="password">').append('<br>')
- .append('<input type="submit" value="OK"/>')
- .submit(function (e) {
- e.preventDefault();
- e.stopPropagation();
- db.source = $msg.find('input[name=source]').val();
- db.supadmin = $msg.find('input[name=supadmin]').val();
- db.password = $msg.find('input[name=password]').val();
- QUnit.start();
- $.unblockUI();
- });
- $.blockUI({
- message: $msg,
- css: {
- fontFamily: 'monospace',
- textAlign: 'left',
- whiteSpace: 'pre-wrap',
- cursor: 'default'
- }
- });
- }
QUnit.test(name, function () {
var instance = openerp;
- if (!openerp_inited) {
- openerp.init(module_deps);
- openerp_inited = true;
- }
+ init(module_deps);
instance.session = new instance.web.Session();
instance.session.uid = 42;
if (_.isNumber(opts.asserts)) {
@@ -276,44 +267,6 @@ openerp.testing = {};
instance.session.responses[spec] = handler;
};
break;
- case 'rpc':
- async = true;
- (function () {
- // Bunch of random base36 characters
- var dbname = 'test_' + Math.random().toString(36).slice(2);
- // Add db setup/teardown at the start of the stack
- case_stack = case_stack.unshift(function (instance) {
- // FIXME hack: don't want the session to go through shitty loading process of everything
- instance.session.session_init = testing.noop;
- instance.session.load_modules = testing.noop;
- instance.session.session_bind();
- return instance.session.rpc('/web/database/duplicate', {
- fields: [
- {name: 'super_admin_pwd', value: db.supadmin},
- {name: 'db_original_name', value: db.source},
- {name: 'db_name', value: dbname}
- ]
- }).then(function (result) {
- if (result.error) {
- return $.Deferred().reject(result.error).promise();
- }
- return instance.session.session_authenticate(
- dbname, 'admin', db.password, true);
- });
- }, function (instance) {
- return instance.session.rpc('/web/database/drop', {
- fields: [
- {name: 'drop_pwd', value: db.supadmin},
- {name: 'drop_db', value: dbname}
- ]
- }).then(function (result) {
- if (result.error) {
- return $.Deferred().reject(result.error).promise();
- }
- return result;
- });
- });
- })();
}
// Always execute tests asynchronously
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index 4576e9d328d..f1fc377fa94 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -1030,8 +1030,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
open_defaults_dialog: function () {
var self = this;
var display = function (field, value) {
+ if (!value) { return value; }
if (field instanceof instance.web.form.FieldSelection) {
- return _(field.values).find(function (option) {
+ return _(field.get('values')).find(function (option) {
return option[0] === value;
})[1];
} else if (field instanceof instance.web.form.FieldMany2One) {
@@ -3091,7 +3092,7 @@ instance.web.form.CompletionFieldMixin = {
// quick create
var raw_result = _(data.result).map(function(x) {return x[1];});
if (search_val.length > 0 && !_.include(raw_result, search_val) &&
- ! (self.options && self.options.no_quick_create)) {
+ ! (self.options && (self.options.no_create || self.options.no_quick_create))) {
values.push({
label: _.str.sprintf(_t('Create "<strong>%s</strong>"'),
$('<span />').text(search_val).html()),
@@ -3102,13 +3103,15 @@ instance.web.form.CompletionFieldMixin = {
});
}
// create...
- values.push({
- label: _t("Create and Edit..."),
- action: function() {
- self._search_create_popup("form", undefined, self._create_context(search_val));
- },
- classname: 'oe_m2o_dropdown_option'
- });
+ if (!(self.options && self.options.no_create)){
+ values.push({
+ label: _t("Create and Edit..."),
+ action: function() {
+ self._search_create_popup("form", undefined, self._create_context(search_val));
+ },
+ classname: 'oe_m2o_dropdown_option'
+ });
+ }
return values;
});
@@ -5129,7 +5132,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
.on('blurred', null, function () {self.trigger('blurred');});
this.m2o = new instance.web.form.FieldMany2One(fm, { attrs: {
- name: 'm2o',
+ name: 'Referenced Document',
modifiers: JSON.stringify({readonly: this.get('effective_readonly')}),
}});
this.m2o.on("change:value", this, this.data_changed);
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index 8e3923dcaf2..e01ab29f914 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -1654,9 +1654,12 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
function synchronized(fn) {
var fn_mutex = new $.Mutex();
return function () {
+ var obj = this;
var args = _.toArray(arguments);
- args.unshift(this);
- return fn_mutex.exec(fn.bind.apply(fn, args));
+ return fn_mutex.exec(function () {
+ if (obj.isDestroyed()) { return $.when(); }
+ return fn.apply(obj, args)
+ });
};
}
var DataGroup = instance.web.Class.extend({
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index 79a75551c8d..c9229c3c70a 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -595,6 +595,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
action_views_ids : views_ids
}, self.flags, self.flags[view.view_type] || {}, view.options || {})
});
+
views_ids[view.view_type] = view.view_id;
});
if (this.flags.views_switcher === false) {
@@ -602,7 +603,11 @@ instance.web.ViewManager = instance.web.Widget.extend({
}
// If no default view defined, switch to the first one in sequence
var default_view = this.flags.default_view || this.views_src[0].view_type;
- return this.switch_mode(default_view);
+
+
+ return this.switch_mode(default_view, null, this.flags[default_view] && this.flags[default_view].options);
+
+
},
switch_mode: function(view_type, no_store, view_options) {
var self = this;
@@ -810,6 +815,11 @@ instance.web.ViewManager = instance.web.Widget.extend({
contexts: [action_context].concat(contexts || []),
group_by_seq: groupbys || []
}).done(function (results) {
+ if (results.error) {
+ throw new Error(
+ _.str.sprintf(_t("Failed to evaluate search criterions")+": \n%s",
+ JSON.stringify(results.error)));
+ }
self.dataset._model = new instance.web.Model(
self.dataset.model, results.context, results.domain);
var groupby = results.group_by.length
@@ -1254,7 +1264,7 @@ instance.web.Sidebar = instance.web.Widget.extend({
this.dataset = dataset;
this.model_id = model_id;
if (args && args[0].error) {
- this.do_warn( instance.web.qweb.render('message_error_uploading'), args[0].error);
+ this.do_warn(_t('Uploading Error'), args[0].error);
}
if (!model_id) {
this.on_attachments_loaded([]);
@@ -1338,7 +1348,7 @@ instance.web.View = instance.web.Widget.extend({
"context": this.dataset.get_context(),
});
}
- return view_loaded_def.then(function(r) {
+ return this.alive(view_loaded_def).then(function(r) {
self.fields_view = r;
// add css classes that reflect the (absence of) access rights
self.$el.addClass('oe_view')
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index fc9c2154d01..80b82c1f40b 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -536,23 +536,32 @@
</t>
<t t-name="ViewManagerDebugViewLog">
<div class="oe_debug_view_log">
- <label>ID:</label>
- <span><t t-esc="perm.id"/></span>
-
- <label>XML ID:</label>
- <span><t t-esc="perm.xmlid"/></span>
-
- <label>Creation User:</label>
- <span><t t-esc="format(perm.create_uid, { 'type' : 'many2one' }, '/')"/></span>
-
- <label>Creation Date:</label>
- <span><t t-esc="format(perm.create_date, { 'type' : 'datetime' }, '/')"/></span>
-
- <label>Latest Modification by:</label>
- <span><t t-esc="format(perm.write_uid, { 'type' : 'many2one' }, '/')"/></span>
-
- <label>Latest Modification Date:</label>
- <span><t t-esc="format(perm.write_date, { 'type' : 'datetime' }, '/')"/></span>
+ <table class="table table-condensed table-striped">
+ <tr>
+ <th>ID:</th>
+ <td><t t-esc="perm.id"/></td>
+ </tr>
+ <tr>
+ <th>XML ID:</th>
+ <td><t t-esc="perm.xmlid or '/'"/></td>
+ </tr>
+ <tr>
+ <th>Creation User:</th>
+ <td><t t-esc="format(perm.create_uid, { 'type' : 'many2one' }, '/')"/></td>
+ </tr>
+ <tr>
+ <th>Creation Date:</th>
+ <td><t t-esc="format(perm.create_date, { 'type' : 'datetime' }, '/')"/></td>
+ </tr>
+ <tr>
+ <th>Latest Modification by:</th>
+ <td><t t-esc="format(perm.write_uid, { 'type' : 'many2one' }, '/')"/></td>
+ </tr>
+ <tr>
+ <th>Latest Modification Date:</th>
+ <td><t t-esc="format(perm.write_date, { 'type' : 'datetime' }, '/')"/></td>
+ </tr>
+ </table>
</div>
</t>
<t t-name="ViewPager">