diff --git a/web_last_viewed_records/__init__.py b/web_last_viewed_records/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/web_last_viewed_records/__openerp__.py b/web_last_viewed_records/__openerp__.py
new file mode 100644
index 000000000..88bef27dc
--- /dev/null
+++ b/web_last_viewed_records/__openerp__.py
@@ -0,0 +1,26 @@
+{
+ 'name' : 'Last viewed records',
+ 'version' : '1.0.0',
+ 'author' : 'Ivan Yelizariev',
+ 'category' : 'Base',
+ 'website' : 'https://yelizariev.github.io',
+ 'description': """
+The idea is taken from SugarCRM's "Last viewed" feature.
+
+This module doesn't affect on server performance, because it uses browser's localStorage to save history. But dissadvantage is that history is not synced accross browsers.
+
+FIXME: doesn't work in a res.config view
+
+Tested on 8.0 ab7b5d7732a7c222a0aea45bd173742acd47242d.
+
+Further information and discussion: https://yelizariev.github.io/odoo/module/2015/02/18/last-viewed-records.html
+ """,
+ 'depends' : ['web', 'mail'],
+ 'data':[
+ 'views.xml',
+ ],
+ 'qweb' : [
+ "static/src/xml/*.xml",
+ ],
+ 'installable': True
+}
diff --git a/web_last_viewed_records/static/src/css/main.css b/web_last_viewed_records/static/src/css/main.css
new file mode 100644
index 000000000..7529374db
--- /dev/null
+++ b/web_last_viewed_records/static/src/css/main.css
@@ -0,0 +1,45 @@
+.oe_last_viewed_item.selected{
+ font-weight:bold;
+}
+
+.openerp .oe_last_viewed_item .oe_e {
+ font-size: 22px;
+ font-weight: 300 !important;
+}
+
+.openerp .oe_last_viewed_item .list:after, .openerp .oe_last_viewed_item .tree:after {
+ padding: 2px;
+ content: "i";
+}
+.openerp .oe_last_viewed_item .form:after {
+ content: "m";
+}
+.openerp .oe_last_viewed_item .graph:after {
+ font-family: "mnmliconsRegular" !important;
+ font-size: 21px;
+ font-weight: 300 !important;
+ content: "}";
+ top: -2px;
+ position: relative;
+}
+.openerp .oe_last_viewed_item .gantt:after {
+ font-family: "mnmliconsRegular" !important;
+ font-size: 21px;
+ font-weight: 300 !important;
+ content: "y";
+ top: -2px;
+ position: relative;
+}
+.openerp .oe_last_viewed_item .calendar:after {
+ content: "P";
+}
+.openerp .oe_last_viewed_item .kanban:after {
+ content: "k";
+}
+.openerp .oe_last_viewed_item .diagram:after {
+ content: "f";
+}
+.oe_last_viewed_icon{
+ font-family: "entypoRegular" !important;
+ font-size: 1.8em;
+}
\ No newline at end of file
diff --git a/web_last_viewed_records/static/src/js/main.js b/web_last_viewed_records/static/src/js/main.js
new file mode 100644
index 000000000..1d20925e7
--- /dev/null
+++ b/web_last_viewed_records/static/src/js/main.js
@@ -0,0 +1,174 @@
+openerp.web_last_viewed_records = function(instance){
+ var QWeb = instance.web.qweb;
+ var _t = instance.web._t;
+
+ instance.web.ActionManager.include({
+ last_viewed_history_var: 'odoo_last_viewed',
+ last_viewed_history_size: 8,
+ init:function(){
+ this._super.apply(this, arguments);
+
+ this.last_viewed = [];
+ var last_viewed = this.load_last_viewed_history().last_viewed || [];
+ for (var i=last_viewed.length-1; i>=0; i--){
+ this.unshift_last_viewed(last_viewed[i]);
+ }
+ },
+ add_last_viewed: function(item){
+ if (_.any(this.last_viewed, function(x){
+ if (x['title'] != item['title'] ||
+ x['view_type'] != item['view_type'] ||
+ x['url']['model'] != item['url']['model'])
+ return false;
+ if (x['view_type'] == 'form' && x['id'] != item['id'])
+ return false;
+ return true;
+ }))
+ return;
+
+ //save json data to localStorage
+ var data = this.load_last_viewed_history();
+ data.last_viewed = data.last_viewed || []
+ data.last_viewed.unshift(item);
+ data.last_viewed.splice(this.last_viewed_history_size);
+ this.save_last_viewed_history(data);
+
+ this.unshift_last_viewed(item);
+ },
+ unshift_last_viewed: function(item){
+ var self = this;
+ this.last_viewed.unshift(item);
+ this.last_viewed.splice(this.last_viewed_history_size);
+ },
+ load_last_viewed_history: function(){
+ var data = localStorage[this.last_viewed_history_var] || '{}';
+ return JSON.parse(data);
+ },
+ save_last_viewed_history: function(data){
+ localStorage[this.last_viewed_history_var] = JSON.stringify(data);
+ },
+ // icon map: http://bistro.convergencecms.co/entypo
+ _model2icon: {
+ 'res.partner':'+',
+ 'crm.lead':'4',
+ 'sale.order':'l',
+ 'account.analytic.account':'7',
+ 'crm.phonecall':'!',
+ 'hr.employee':'.',
+ 'hr.applicant':'-',
+ 'project.project':'t',
+ 'project.task':'W',
+ 'account.invoice':'h',
+ 'ir.module.module':'Z',
+ 'hr_timesheet_sheet.sheet': 'N',
+ 'res.groups': ',',
+ 'res.company': '_',
+ 'res.user': 'ó',
+ 'gamification.challenge':'è',
+ 'gamification.badge':'8',
+ },
+ get_last_viewed_title: function(){
+ var titles = [];
+ for (var i = 0; i < this.last_viewed.length; i += 1) {
+ var item = this.last_viewed[i];
+ var label = item.title;
+ var selected = false;//item.action.id == this.inner_action.id && item.action.res_id == this.inner_action.res_id;
+ var view_type = item.view_type;
+ var url = $.param(item.url);
+ var model = item.url.model;
+ var title = model;
+
+ var icon = this._model2icon[model];
+ if (!icon && /\.settings/.test(model))
+ icon = 'c';
+ if (icon)
+ icon = _.str.sprintf('%s', icon);
+ titles.push(_.str.sprintf('%s %s ',
+ title,
+ url,
+ selected && 'selected' || '',
+ icon || '',
+ label,
+ view_type != 'form' && view_type || ''
+ ));
+ }
+ return titles.join(' | ');
+ },
+ })
+
+ instance.web.ViewManagerAction.include({
+ try_add_last_viewed: function(view_type){
+ var view = this.views[view_type];
+ var act = view.options.action;
+ if (!act.type)
+ return false;
+
+ if (act.target == 'new')
+ //skip widgets and popup forms
+ return false;
+
+ var url = {
+ 'view_type': view_type,
+ 'model': act.res_model,
+ 'menu_id': act.menu_id,
+ 'action': act.id
+ }
+ var title = act.display_name;
+ var dr = view.controller.datarecord;
+ if (dr){
+ title = dr.display_name || title;
+ if (view_type=='form'){
+ url['id'] = dr.id;
+ }
+ }
+ if (view_type=='form' && !url['id'])
+ return false;
+ var last_viewed_item = {
+ 'title': title,
+ 'url': url,
+ 'view_type': view_type,
+ }
+ this.ActionManager.add_last_viewed(last_viewed_item);
+
+ return true;
+ },
+ do_create_view: function(view_type) {
+ var self = this;
+
+ var res = this._super.apply(this, arguments);
+
+ var view = this.views[view_type];
+
+ var exec = function(){
+ if (self.active_view == view_type && self.try_add_last_viewed(view_type)){
+ self.update_last_viewed_title()
+ }
+ }
+ exec();
+ view.controller.on('change:title', this, function(){
+ exec()
+ })
+
+ return res;
+
+ },
+ update_last_viewed_title: function(){
+ this.$el.find('.oe_view_manager_last_viewed').html(this.get_action_manager().get_last_viewed_title());
+ },
+ set_title: function(){
+ this._super.apply(this, arguments);
+ if (this.action.target!='new')
+ this.update_last_viewed_title();
+ }
+
+ })
+ instance.mail.Wall.include({
+ start: function() {
+ this._super();
+ this.update_last_viewed_title();
+ },
+ update_last_viewed_title: function(){
+ this.$el.find('.oe_view_manager_last_viewed').html(this.ActionManager.get_last_viewed_title());
+ },
+ });
+}
diff --git a/web_last_viewed_records/static/src/xml/main.xml b/web_last_viewed_records/static/src/xml/main.xml
new file mode 100644
index 000000000..b19c40ef7
--- /dev/null
+++ b/web_last_viewed_records/static/src/xml/main.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web_last_viewed_records/views.xml b/web_last_viewed_records/views.xml
new file mode 100644
index 000000000..c553a2b86
--- /dev/null
+++ b/web_last_viewed_records/views.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+