MuK IT GmbH
4 years ago
3 changed files with 304 additions and 305 deletions
-
116muk_web_utils/__manifest__.py
-
258muk_web_utils/static/src/js/fields/color.js
-
235muk_web_utils/static/src/js/fields/path.js
@ -1,58 +1,58 @@ |
|||
################################################################################### |
|||
# |
|||
# Copyright (c) 2017-2019 MuK IT GmbH. |
|||
# |
|||
# This file is part of MuK Web Utils |
|||
# (see https://mukit.at). |
|||
# |
|||
# This program is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Lesser General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU Lesser General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU Lesser General Public License |
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################### |
|||
|
|||
{ |
|||
"name": "MuK Web Utils", |
|||
"summary": """Utility Features""", |
|||
"version": "13.0.1.0.0", |
|||
"category": "Extra Tools", |
|||
"license": "LGPL-3", |
|||
"author": "MuK IT", |
|||
"website": "http://www.mukit.at", |
|||
'live_test_url': 'https://mukit.at/r/SgN', |
|||
"contributors": [ |
|||
"Mathias Markl <mathias.markl@mukit.at>", |
|||
"Benedikt Jilek <benedikt.jilek@mukit.at>", |
|||
], |
|||
"depends": [ |
|||
"web_editor", |
|||
"muk_autovacuum", |
|||
], |
|||
"data": [ |
|||
"template/assets.xml", |
|||
"views/res_config_settings_view.xml", |
|||
"data/autovacuum.xml", |
|||
], |
|||
"qweb": [ |
|||
"static/src/xml/*.xml", |
|||
], |
|||
"images": [ |
|||
'static/description/banner.png' |
|||
], |
|||
"external_dependencies": { |
|||
"python": [], |
|||
"bin": [], |
|||
}, |
|||
"application": False, |
|||
"installable": True, |
|||
'auto_install': False, |
|||
} |
|||
################################################################################### |
|||
# |
|||
# Copyright (c) 2017-2019 MuK IT GmbH. |
|||
# |
|||
# This file is part of MuK Web Utils |
|||
# (see https://mukit.at). |
|||
# |
|||
# This program is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU Lesser General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU Lesser General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU Lesser General Public License |
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################### |
|||
|
|||
{ |
|||
"name": "MuK Web Utils", |
|||
"summary": """Utility Features""", |
|||
"version": "13.0.1.0.1", |
|||
"category": "Extra Tools", |
|||
"license": "LGPL-3", |
|||
"author": "MuK IT", |
|||
"website": "http://www.mukit.at", |
|||
'live_test_url': 'https://mukit.at/r/SgN', |
|||
"contributors": [ |
|||
"Mathias Markl <mathias.markl@mukit.at>", |
|||
"Benedikt Jilek <benedikt.jilek@mukit.at>", |
|||
], |
|||
"depends": [ |
|||
"web_editor", |
|||
"muk_autovacuum", |
|||
], |
|||
"data": [ |
|||
"template/assets.xml", |
|||
"views/res_config_settings_view.xml", |
|||
"data/autovacuum.xml", |
|||
], |
|||
"qweb": [ |
|||
"static/src/xml/*.xml", |
|||
], |
|||
"images": [ |
|||
'static/description/banner.png' |
|||
], |
|||
"external_dependencies": { |
|||
"python": [], |
|||
"bin": [], |
|||
}, |
|||
"application": False, |
|||
"installable": True, |
|||
'auto_install': False, |
|||
} |
@ -1,130 +1,130 @@ |
|||
/********************************************************************************** |
|||
* |
|||
* Copyright (c) 2017-2019 MuK IT GmbH. |
|||
* |
|||
* This file is part of MuK Web Utils |
|||
* (see https://mukit.at).
|
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Lesser General Public License as published by |
|||
* the Free Software Foundation, either version 3 of the License, or |
|||
* (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Lesser General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Lesser General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
**********************************************************************************/ |
|||
|
|||
odoo.define('muk_web_utils.color', function (require) { |
|||
"use strict"; |
|||
|
|||
var core = require('web.core'); |
|||
var fields = require('web.basic_fields'); |
|||
var registry = require('web.field_registry'); |
|||
var colorpicker = require('web.colorpicker'); |
|||
|
|||
var AbstractField = require('web.AbstractField'); |
|||
|
|||
var _t = core._t; |
|||
var QWeb = core.qweb; |
|||
|
|||
var FieldColor = fields.InputField.extend({ |
|||
events: _.extend({}, fields.InputField.prototype.events, { |
|||
"click .mk_field_color_button": "_onCustomColorButtonClick", |
|||
}), |
|||
template: "muk_web_utils.FieldColor", |
|||
supportedFieldTypes: ['char'], |
|||
start: function() { |
|||
this.$input = this.$('.mk_field_color_input'); |
|||
return this._super.apply(this, arguments); |
|||
}, |
|||
_renderEdit: function () { |
|||
this.$('.mk_field_color_input').val( |
|||
this._formatValue(this.value) |
|||
); |
|||
this.$('.mk_field_color_input').css({ |
|||
'background-color': this._formatValue(this.value), |
|||
}); |
|||
}, |
|||
_renderReadonly: function () { |
|||
this.$el.text(this._formatValue(this.value)); |
|||
this.$el.css({'color': this._formatValue(this.value)}); |
|||
}, |
|||
_doAction: function() { |
|||
this._super.apply(this, arguments); |
|||
this.$('.mk_field_color_input').css({ |
|||
'background-color': this._getValue(), |
|||
}); |
|||
}, |
|||
_formatValue: function (value) { |
|||
return value; |
|||
}, |
|||
_parseValue: function (value) { |
|||
if((/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i).test(value)) { |
|||
return value; |
|||
} else { |
|||
throw new Error(_.str.sprintf(_t("'%s' is not a correct color value"), value)); |
|||
} |
|||
}, |
|||
_onCustomColorButtonClick: function () { |
|||
var ColorpickerDialog = new colorpicker(this, { |
|||
dialogClass: 'mk_field_color_picker', |
|||
defaultColor: this._getValue(), |
|||
}); |
|||
ColorpickerDialog.on('colorpicker:saved', this, function (event) { |
|||
this.$input.val(event.data.hex); |
|||
this._doAction(); |
|||
}); |
|||
ColorpickerDialog.open(); |
|||
}, |
|||
}); |
|||
|
|||
var FieldColorIndex = AbstractField.extend({ |
|||
events: _.extend({}, AbstractField.prototype.events, { |
|||
'change': '_onChange', |
|||
}), |
|||
template: 'muk_web_utils.FieldColorIndex', |
|||
supportedFieldTypes: ['integer'], |
|||
isSet: function () { |
|||
return this.value === 0 || this._super.apply(this, arguments); |
|||
}, |
|||
getFocusableElement: function () { |
|||
return this.$el.is('select') ? this.$el : $(); |
|||
}, |
|||
_renderEdit: function () { |
|||
this.$el.addClass('mk_color_index_' + this.value); |
|||
this.$('option[value="' + this.value + '"]').prop('selected', true); |
|||
}, |
|||
_renderReadonly: function () { |
|||
this.$el.addClass('mk_color_index_' + this.value); |
|||
this.$el.empty().text('Color ' + this._formatValue(this.value)); |
|||
}, |
|||
_onChange: function (event) { |
|||
this.$el.removeClass(function (index, className) { |
|||
return (className.match (/(^|\s)mk_color_index_\S+/g) || []).join(' '); |
|||
}); |
|||
this.$el.addClass('mk_color_index_' + this.$el.val()); |
|||
this._setValue(this.$el.val()); |
|||
}, |
|||
_parseValue: function (value) { |
|||
if(0 > value || value > 12) { |
|||
throw new Error(_.str.sprintf(_t("'%s' is not a correct color index (0-12)"), value)); |
|||
} |
|||
return value; |
|||
}, |
|||
}); |
|||
|
|||
registry.add('color_char', FieldColor); |
|||
registry.add('color_index', FieldColorIndex); |
|||
|
|||
return { |
|||
FieldColor: FieldColor, |
|||
FieldColorIndex: FieldColorIndex, |
|||
}; |
|||
|
|||
/********************************************************************************** |
|||
* |
|||
* Copyright (c) 2017-2019 MuK IT GmbH. |
|||
* |
|||
* This file is part of MuK Web Utils |
|||
* (see https://mukit.at).
|
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Lesser General Public License as published by |
|||
* the Free Software Foundation, either version 3 of the License, or |
|||
* (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Lesser General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Lesser General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
**********************************************************************************/ |
|||
|
|||
odoo.define('muk_web_utils.color', function (require) { |
|||
"use strict"; |
|||
|
|||
var core = require('web.core'); |
|||
var fields = require('web.basic_fields'); |
|||
var registry = require('web.field_registry'); |
|||
|
|||
var ColorpickerDialog = require('web.ColorpickerDialog'); |
|||
var AbstractField = require('web.AbstractField'); |
|||
|
|||
var _t = core._t; |
|||
var QWeb = core.qweb; |
|||
|
|||
var FieldColor = fields.InputField.extend({ |
|||
events: _.extend({}, fields.InputField.prototype.events, { |
|||
"click .mk_field_color_button": "_onCustomColorButtonClick", |
|||
}), |
|||
template: "muk_web_utils.FieldColor", |
|||
supportedFieldTypes: ['char'], |
|||
start: function() { |
|||
this.$input = this.$('.mk_field_color_input'); |
|||
return this._super.apply(this, arguments); |
|||
}, |
|||
_renderEdit: function () { |
|||
this.$('.mk_field_color_input').val( |
|||
this._formatValue(this.value) |
|||
); |
|||
this.$('.mk_field_color_input').css({ |
|||
'background-color': this._formatValue(this.value), |
|||
}); |
|||
}, |
|||
_renderReadonly: function () { |
|||
this.$el.text(this._formatValue(this.value)); |
|||
this.$el.css({'color': this._formatValue(this.value)}); |
|||
}, |
|||
_doAction: function() { |
|||
this._super.apply(this, arguments); |
|||
this.$('.mk_field_color_input').css({ |
|||
'background-color': this._getValue(), |
|||
}); |
|||
}, |
|||
_formatValue: function (value) { |
|||
return value; |
|||
}, |
|||
_parseValue: function (value) { |
|||
if((/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i).test(value)) { |
|||
return value; |
|||
} else { |
|||
throw new Error(_.str.sprintf(_t("'%s' is not a correct color value"), value)); |
|||
} |
|||
}, |
|||
_onCustomColorButtonClick: function () { |
|||
var ColorpickerDialog = new ColorpickerDialog(this, { |
|||
dialogClass: 'mk_field_color_picker', |
|||
defaultColor: this._getValue(), |
|||
}); |
|||
ColorpickerDialog.on('colorpicker:saved', this, function (event) { |
|||
this.$input.val(event.data.hex); |
|||
this._doAction(); |
|||
}); |
|||
ColorpickerDialog.open(); |
|||
}, |
|||
}); |
|||
|
|||
var FieldColorIndex = AbstractField.extend({ |
|||
events: _.extend({}, AbstractField.prototype.events, { |
|||
'change': '_onChange', |
|||
}), |
|||
template: 'muk_web_utils.FieldColorIndex', |
|||
supportedFieldTypes: ['integer'], |
|||
isSet: function () { |
|||
return this.value === 0 || this._super.apply(this, arguments); |
|||
}, |
|||
getFocusableElement: function () { |
|||
return this.$el.is('select') ? this.$el : $(); |
|||
}, |
|||
_renderEdit: function () { |
|||
this.$el.addClass('mk_color_index_' + this.value); |
|||
this.$('option[value="' + this.value + '"]').prop('selected', true); |
|||
}, |
|||
_renderReadonly: function () { |
|||
this.$el.addClass('mk_color_index_' + this.value); |
|||
this.$el.empty().text('Color ' + this._formatValue(this.value)); |
|||
}, |
|||
_onChange: function (event) { |
|||
this.$el.removeClass(function (index, className) { |
|||
return (className.match (/(^|\s)mk_color_index_\S+/g) || []).join(' '); |
|||
}); |
|||
this.$el.addClass('mk_color_index_' + this.$el.val()); |
|||
this._setValue(this.$el.val()); |
|||
}, |
|||
_parseValue: function (value) { |
|||
if(0 > value || value > 12) { |
|||
throw new Error(_.str.sprintf(_t("'%s' is not a correct color index (0-12)"), value)); |
|||
} |
|||
return value; |
|||
}, |
|||
}); |
|||
|
|||
registry.add('color_char', FieldColor); |
|||
registry.add('color_index', FieldColorIndex); |
|||
|
|||
return { |
|||
FieldColor: FieldColor, |
|||
FieldColorIndex: FieldColorIndex, |
|||
}; |
|||
|
|||
}); |
@ -1,119 +1,118 @@ |
|||
/********************************************************************************** |
|||
* |
|||
* Copyright (c) 2017-2019 MuK IT GmbH. |
|||
* |
|||
* This file is part of MuK Web Utils |
|||
* (see https://mukit.at).
|
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Lesser General Public License as published by |
|||
* the Free Software Foundation, either version 3 of the License, or |
|||
* (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Lesser General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Lesser General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
**********************************************************************************/ |
|||
|
|||
odoo.define('muk_web_utils.path', function (require) { |
|||
"use strict"; |
|||
|
|||
var core = require('web.core'); |
|||
var fields = require('web.basic_fields'); |
|||
var registry = require('web.field_registry'); |
|||
var colorpicker = require('web.colorpicker'); |
|||
|
|||
var AbstractField = require('web.AbstractField'); |
|||
|
|||
var _t = core._t; |
|||
var QWeb = core.qweb; |
|||
|
|||
var FieldPathNames = fields.FieldChar.extend({ |
|||
init: function(parent, name, record) { |
|||
this._super.apply(this, arguments); |
|||
this.max_width = this.nodeOptions.width || 500; |
|||
}, |
|||
_renderReadonly: function() { |
|||
var show_value = this._formatValue(this.value); |
|||
var text_witdh = $.fn.textWidth(show_value); |
|||
if(text_witdh >= this.max_width) { |
|||
var ratio_start = (1 - (this.max_width / text_witdh)) * show_value.length; |
|||
show_value = ".." + show_value.substring(ratio_start, show_value.length); |
|||
} |
|||
this.$el.text(show_value); |
|||
}, |
|||
}); |
|||
|
|||
var FieldPathJson = fields.FieldText.extend({ |
|||
events: _.extend({}, fields.FieldText.prototype.events, { |
|||
'click a' : '_onNodeClicked', |
|||
}), |
|||
init: function(parent, name, record) { |
|||
this._super.apply(this, arguments); |
|||
this.max_width = this.nodeOptions.width || 500; |
|||
this.seperator = this.nodeOptions.seperator || "/"; |
|||
this.prefix = this.nodeOptions.prefix || false; |
|||
this.suffix = this.nodeOptions.suffix || false; |
|||
}, |
|||
_renderReadonly: function() { |
|||
this.$el.empty(); |
|||
this._renderPath(); |
|||
}, |
|||
_renderPath: function() { |
|||
var text_width_measure = ""; |
|||
var path = JSON.parse(this.value || "[]"); |
|||
$.each(_.clone(path).reverse(), function(index, element) { |
|||
text_width_measure += element.name + "/"; |
|||
if($.fn.textWidth(text_width_measure) >= this.max_width) { |
|||
this.$el.prepend($('<span/>').text("..")); |
|||
} else { |
|||
if (index == 0) { |
|||
if(this.suffix) { |
|||
this.$el.prepend($('<span/>').text(this.seperator)); |
|||
} |
|||
this.$el.prepend($('<span/>').text(element.name)); |
|||
this.$el.prepend($('<span/>').text(this.seperator)); |
|||
} else { |
|||
this.$el.prepend($('<a/>', { |
|||
'class': 'oe_form_uri', |
|||
'data-model': element.model, |
|||
'data-id': element.id, |
|||
'href': "javascript:void(0);", |
|||
'text': element.name, |
|||
})); |
|||
if (index != path.length - 1) { |
|||
this.$el.prepend($('<span/>').text(this.seperator)); |
|||
} else if (this.prefix) { |
|||
this.$el.prepend($('<span/>').text(this.seperator)); |
|||
} |
|||
} |
|||
} |
|||
return ($.fn.textWidth(text_width_measure) < this.max_width); |
|||
}.bind(this)); |
|||
}, |
|||
_onNodeClicked : function(event) { |
|||
this.do_action({ |
|||
type: 'ir.actions.act_window', |
|||
res_model: $(event.currentTarget).data('model'), |
|||
res_id: $(event.currentTarget).data('id'), |
|||
views: [[false, 'form']], |
|||
target: 'current', |
|||
context: {}, |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
registry.add('path_names', FieldPathNames); |
|||
registry.add('path_json', FieldPathJson); |
|||
|
|||
return { |
|||
FieldPathNames: FieldPathNames, |
|||
FieldPathJson: FieldPathJson, |
|||
}; |
|||
|
|||
/********************************************************************************** |
|||
* |
|||
* Copyright (c) 2017-2019 MuK IT GmbH. |
|||
* |
|||
* This file is part of MuK Web Utils |
|||
* (see https://mukit.at).
|
|||
* |
|||
* This program is free software: you can redistribute it and/or modify |
|||
* it under the terms of the GNU Lesser General Public License as published by |
|||
* the Free Software Foundation, either version 3 of the License, or |
|||
* (at your option) any later version. |
|||
* |
|||
* This program is distributed in the hope that it will be useful, |
|||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
* GNU Lesser General Public License for more details. |
|||
* |
|||
* You should have received a copy of the GNU Lesser General Public License |
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
* |
|||
**********************************************************************************/ |
|||
|
|||
odoo.define('muk_web_utils.path', function (require) { |
|||
"use strict"; |
|||
|
|||
var core = require('web.core'); |
|||
var fields = require('web.basic_fields'); |
|||
var registry = require('web.field_registry'); |
|||
|
|||
var AbstractField = require('web.AbstractField'); |
|||
|
|||
var _t = core._t; |
|||
var QWeb = core.qweb; |
|||
|
|||
var FieldPathNames = fields.FieldChar.extend({ |
|||
init: function(parent, name, record) { |
|||
this._super.apply(this, arguments); |
|||
this.max_width = this.nodeOptions.width || 500; |
|||
}, |
|||
_renderReadonly: function() { |
|||
var show_value = this._formatValue(this.value); |
|||
var text_witdh = $.fn.textWidth(show_value); |
|||
if(text_witdh >= this.max_width) { |
|||
var ratio_start = (1 - (this.max_width / text_witdh)) * show_value.length; |
|||
show_value = ".." + show_value.substring(ratio_start, show_value.length); |
|||
} |
|||
this.$el.text(show_value); |
|||
}, |
|||
}); |
|||
|
|||
var FieldPathJson = fields.FieldText.extend({ |
|||
events: _.extend({}, fields.FieldText.prototype.events, { |
|||
'click a' : '_onNodeClicked', |
|||
}), |
|||
init: function(parent, name, record) { |
|||
this._super.apply(this, arguments); |
|||
this.max_width = this.nodeOptions.width || 500; |
|||
this.seperator = this.nodeOptions.seperator || "/"; |
|||
this.prefix = this.nodeOptions.prefix || false; |
|||
this.suffix = this.nodeOptions.suffix || false; |
|||
}, |
|||
_renderReadonly: function() { |
|||
this.$el.empty(); |
|||
this._renderPath(); |
|||
}, |
|||
_renderPath: function() { |
|||
var text_width_measure = ""; |
|||
var path = JSON.parse(this.value || "[]"); |
|||
$.each(_.clone(path).reverse(), function(index, element) { |
|||
text_width_measure += element.name + "/"; |
|||
if($.fn.textWidth(text_width_measure) >= this.max_width) { |
|||
this.$el.prepend($('<span/>').text("..")); |
|||
} else { |
|||
if (index == 0) { |
|||
if(this.suffix) { |
|||
this.$el.prepend($('<span/>').text(this.seperator)); |
|||
} |
|||
this.$el.prepend($('<span/>').text(element.name)); |
|||
this.$el.prepend($('<span/>').text(this.seperator)); |
|||
} else { |
|||
this.$el.prepend($('<a/>', { |
|||
'class': 'oe_form_uri', |
|||
'data-model': element.model, |
|||
'data-id': element.id, |
|||
'href': "javascript:void(0);", |
|||
'text': element.name, |
|||
})); |
|||
if (index != path.length - 1) { |
|||
this.$el.prepend($('<span/>').text(this.seperator)); |
|||
} else if (this.prefix) { |
|||
this.$el.prepend($('<span/>').text(this.seperator)); |
|||
} |
|||
} |
|||
} |
|||
return ($.fn.textWidth(text_width_measure) < this.max_width); |
|||
}.bind(this)); |
|||
}, |
|||
_onNodeClicked : function(event) { |
|||
this.do_action({ |
|||
type: 'ir.actions.act_window', |
|||
res_model: $(event.currentTarget).data('model'), |
|||
res_id: $(event.currentTarget).data('id'), |
|||
views: [[false, 'form']], |
|||
target: 'current', |
|||
context: {}, |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
registry.add('path_names', FieldPathNames); |
|||
registry.add('path_json', FieldPathJson); |
|||
|
|||
return { |
|||
FieldPathNames: FieldPathNames, |
|||
FieldPathJson: FieldPathJson, |
|||
}; |
|||
|
|||
}); |
Write
Preview
Loading…
Cancel
Save
Reference in new issue