[utils]load_image() to handle broken images, ReviewArea, remove old files (#5263)

* [image] load_image() to handle broken images

* add reviewArea component

* remove frappe.ui.BaseList

* remove old build.js

* title in page and list
This commit is contained in:
Prateeksha Singh 2018-03-27 10:54:22 +05:30 committed by Rushabh Mehta
parent bc12dc57d4
commit 747e012d59
9 changed files with 175 additions and 913 deletions

View file

@ -1,360 +0,0 @@
/*eslint-disable no-console */
const path = require('path');
const fs = require('fs');
const babel = require('babel-core');
const less = require('less');
const chokidar = require('chokidar');
const path_join = path.resolve;
// for file watcher
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const touch = require("touch");
// basic setup
const sites_path = path_join(__dirname, '..', '..', '..', 'sites');
const apps_path = path_join(__dirname, '..', '..', '..', 'apps'); // the apps folder
const apps_contents = fs.readFileSync(path_join(sites_path, 'apps.txt'), 'utf8');
const apps = apps_contents.split('\n');
const app_paths = apps.map(app => path_join(apps_path, app, app)) // base_path of each app
const assets_path = path_join(sites_path, 'assets');
let build_map = make_build_map();
let compiled_js_cache = {}; // cache each js file after it is compiled
const file_watcher_port = get_conf().file_watcher_port;
// command line args
const action = process.argv[2] || '--build';
if (['--build', '--watch'].indexOf(action) === -1) {
console.log('Invalid argument: ', action);
process.exit();
}
if (action === '--build') {
const minify = process.argv[3] === '--minify' ? true : false;
build(minify);
}
if (action === '--watch') {
watch();
}
function build(minify) {
for (const output_path in build_map) {
pack(output_path, build_map[output_path], minify);
}
touch(path_join(sites_path, '.build'), {force:true});
}
let socket_connection = false;
function watch() {
http.listen(file_watcher_port, function () {
console.log('file watching on *:', file_watcher_port);
});
if (process.env.CI) {
// don't watch inside CI
return;
}
compile_less().then(() => {
build();
watch_less(function (filename) {
if(socket_connection) {
io.emit('reload_css', filename);
}
});
watch_js(//function (filename) {
// if(socket_connection) {
// io.emit('reload_js', filename);
// }
//}
);
watch_build_json();
});
io.on('connection', function (socket) {
socket_connection = true;
socket.on('disconnect', function() {
socket_connection = false;
})
});
}
function pack(output_path, inputs, minify, file_changed) {
let output_txt = '';
for (const file of inputs) {
if (!fs.existsSync(file)) {
console.log('File not found: ', file);
continue;
}
let force_compile = false;
if (file_changed) {
// if file_changed is passed and is equal to file, force_compile it
force_compile = file_changed === file;
}
let file_content = get_compiled_file(file, output_path, minify, force_compile);
if(!minify) {
output_txt += `\n/*\n *\t${file}\n */\n`
}
output_txt += file_content;
output_txt = output_txt.replace(/['"]use strict['"];/, '');
}
const target = path_join(assets_path, output_path);
try {
fs.writeFileSync(target, output_txt);
console.log(`Wrote ${output_path} - ${get_file_size(target)}`);
return target;
} catch (e) {
console.log('Error writing to file', output_path);
console.log(e);
}
}
function get_compiled_file(file, output_path, minify, force_compile) {
const output_type = output_path.split('.').pop();
let file_content;
if (force_compile === false) {
// force compile is false
// attempt to get from cache
file_content = compiled_js_cache[file];
if (file_content) {
return file_content;
}
}
file_content = fs.readFileSync(file, 'utf-8');
if (file.endsWith('.html') && output_type === 'js') {
file_content = html_to_js_template(file, file_content);
}
if(file.endsWith('class.js')) {
file_content = minify_js(file_content, file);
}
if (minify && file.endsWith('.js') && !file.includes('/lib/') && output_type === 'js' && !file.endsWith('class.js')) {
file_content = babelify(file_content, file, minify);
}
compiled_js_cache[file] = file_content;
return file_content;
}
function babelify(content, path, minify) {
let presets = ['env'];
const plugins = ['transform-object-rest-spread']
// Minification doesn't work when loading Frappe Desk
// Avoid for now, trace the error and come back.
try {
return babel.transform(content, {
presets: presets,
plugins: plugins,
comments: false
}).code;
} catch (e) {
console.log('Cannot babelify', path);
console.log(e);
return content;
}
}
function minify_js(content, path) {
try {
return babel.transform(content, {
comments: false
}).code;
} catch (e) {
console.log('Cannot minify', path);
console.log(e);
return content;
}
}
function make_build_map() {
const build_map = {};
for (const app_path of app_paths) {
const build_json_path = path_join(app_path, 'public', 'build.json');
if (!fs.existsSync(build_json_path)) continue;
let build_json = fs.readFileSync(build_json_path);
try {
build_json = JSON.parse(build_json);
} catch (e) {
console.log(e);
continue;
}
for (const target in build_json) {
const sources = build_json[target];
const new_sources = [];
for (const source of sources) {
const s = path_join(app_path, source);
new_sources.push(s);
}
if (new_sources.length)
build_json[target] = new_sources;
else
delete build_json[target];
}
Object.assign(build_map, build_json);
}
return build_map;
}
function compile_less() {
return new Promise(function (resolve) {
const promises = [];
for (const app_path of app_paths) {
const public_path = path_join(app_path, 'public');
const less_path = path_join(public_path, 'less');
if (!fs.existsSync(less_path)) continue;
const files = fs.readdirSync(less_path);
for (const file of files) {
if(file.includes('variables.less')) continue;
promises.push(compile_less_file(file, less_path, public_path))
}
}
Promise.all(promises).then(() => {
console.log('Less files compiled');
resolve();
});
});
}
function compile_less_file(file, less_path, public_path) {
const file_content = fs.readFileSync(path_join(less_path, file), 'utf8');
const output_file = file.split('.')[0] + '.css';
console.log('compiling', file);
return less.render(file_content, {
paths: [less_path],
filename: file,
sourceMap: false
}).then(output => {
const out_css = path_join(public_path, 'css', output_file);
fs.writeFileSync(out_css, output.css);
return out_css;
}).catch(e => {
console.log('Error compiling ', file);
console.log(e);
});
}
function watch_less(ondirty) {
const less_paths = app_paths.map(path => path_join(path, 'public', 'less'));
const to_watch = filter_valid_paths(less_paths);
chokidar.watch(to_watch).on('change', (filename) => {
console.log(filename, 'dirty');
var last_index = filename.lastIndexOf('/');
const less_path = filename.slice(0, last_index);
const public_path = path_join(less_path, '..');
filename = filename.split('/').pop();
compile_less_file(filename, less_path, public_path)
.then(css_file_path => {
// build the target css file for which this css file is input
for (const target in build_map) {
const sources = build_map[target];
if (sources.includes(css_file_path)) {
pack(target, sources);
ondirty && ondirty(target);
break;
}
}
});
touch(path_join(sites_path, '.build'), {force:true});
});
}
function watch_js(ondirty) {
chokidar.watch([
path_join(apps_path, '**', '*.js'),
path_join(apps_path, '**', '*.html')
]).on('change', (filename) => {
// build the target js file for which this js/html file is input
for (const target in build_map) {
const sources = build_map[target];
if (sources.includes(filename)) {
console.log(filename, 'dirty');
pack(target, sources, null, filename);
ondirty && ondirty(target);
// break;
}
}
touch(path_join(sites_path, '.build'), {force:true});
});
}
function watch_build_json() {
const build_json_paths = app_paths.map(path => path_join(path, 'public', 'build.json'));
const to_watch = filter_valid_paths(build_json_paths);
chokidar.watch(to_watch).on('change', (filename) => {
console.log(filename, 'updated');
build_map = make_build_map();
});
}
function filter_valid_paths(paths) {
return paths.filter(path => fs.existsSync(path));
}
function html_to_js_template(path, content) {
let key = path.split('/');
key = key[key.length - 1];
key = key.split('.')[0];
content = scrub_html_template(content);
return `frappe.templates['${key}'] = '${content}';\n`;
}
function scrub_html_template(content) {
content = content.replace(/\s/g, ' ');
content = content.replace(/(<!--.*?-->)/g, '');
return content.replace("'", "\'");
}
function get_file_size(filepath) {
const stats = fs.statSync(filepath);
const size = stats.size;
// convert it to humanly readable format.
const i = Math.floor(Math.log(size) / Math.log(1024));
return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
}
function get_conf() {
// defaults
var conf = {
file_watcher_port: 6787
};
var read_config = function(path) {
if (!fs.existsSync(path)) return;
var bench_config = JSON.parse(fs.readFileSync(path));
for (var key in bench_config) {
if (bench_config[key]) {
conf[key] = bench_config[key];
}
}
}
read_config(path_join(sites_path, 'common_site_config.json'));
return conf;
}

View file

@ -223,6 +223,17 @@ frappe.run_serially = function(tasks) {
return result;
};
frappe.load_image = (src, onload, onerror, preprocess = () => {}) => {
var tester = new Image();
tester.onload = function() {
onload(this);
};
tester.onerror = onerror;
preprocess(tester);
tester.src = src;
}
frappe.timeout = seconds => {
return new Promise((resolve) => {
setTimeout(() => resolve(), seconds * 1000);

View file

@ -23,6 +23,9 @@ frappe.ui.form.ControlButton = frappe.ui.form.ControlData.extend({
this.df.click();
}
},
hide() {
this.$input.hide();
},
set_input_areas: function() {
this._super();
$(this.disp_area).removeClass().addClass("hide");

View file

@ -144,11 +144,15 @@ frappe.views.BaseList = class BaseList {
}
setup_page_head() {
this.page.set_title(this.page_title);
this.set_title();
this.set_menu_items();
this.set_breadcrumbs();
}
set_title() {
this.page.set_title(this.page_title);
}
set_menu_items() {
const $secondary_action = this.page.set_secondary_action(
this.secondary_action.label,
@ -309,7 +313,9 @@ frappe.views.BaseList = class BaseList {
get_filters_for_args() {
// filters might have a fifth param called hidden,
// we don't want to pass that server side
return this.filter_area.get().map(filter => filter.slice(0, 4));
return this.filter_area
? this.filter_area.get().map(filter => filter.slice(0, 4))
: [];
}
get_args() {

View file

@ -624,7 +624,7 @@ frappe.utils = {
return result;
};
},
debounce: function debounce(func, wait, immediate) {
debounce: function(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;

View file

@ -1,532 +0,0 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
// new re-re-factored Listing object
// now called BaseList
//
// opts:
// parent
// method (method to call on server)
// args (additional args to method)
// get_args (method to return args as dict)
// show_filters [false]
// doctype
// filter_fields (if given, this list is rendered, else built from doctype)
// query or get_query (will be deprecated)
// query_max
// buttons_in_frame
// no_result_message ("No result")
// page_length (20)
// hide_refresh (False)
// no_toolbar
// new_doctype
// [function] render_row(parent, data)
// [function] onrun
// no_loading (no ajax indicator)
frappe.provide('frappe.ui');
frappe.ui.BaseList = Class.extend({
init: function (opts) {
this.opts = opts || {};
this.set_defaults();
if (opts) {
this.make();
}
},
set_defaults: function () {
this.page_length = 20;
this.start = 0;
this.data = [];
},
make: function (opts) {
if (opts) {
this.opts = opts;
}
this.prepare_opts();
$.extend(this, this.opts);
// make dom
this.wrapper = $(frappe.render_template('listing', this.opts));
this.parent.append(this.wrapper);
this.set_events();
if (this.page) {
this.wrapper.find('.list-toolbar-wrapper').hide();
}
if (this.show_filters) {
this.make_filters();
}
},
prepare_opts: function () {
if (this.opts.new_doctype) {
if (!frappe.boot.user.can_create.includes(this.opts.new_doctype)) {
this.opts.new_doctype = null;
}
}
if (!this.opts.no_result_message) {
this.opts.no_result_message = __('Nothing to show');
}
if (!this.opts.page_length) {
this.opts.page_length = this.user_settings && this.user_settings.limit || 20;
}
this.opts._more = __('More');
},
add_button: function (label, click, icon) {
if (this.page) {
return this.page.add_menu_item(label, click, icon)
} else {
this.wrapper.find('.list-toolbar-wrapper').removeClass('hide');
return $('<button class="btn btn-default"></button>')
.appendTo(this.wrapper.find('.list-toolbar'))
.html((icon ? ('<i class="' + icon + '"></i> ') : '') + label)
.click(click);
}
},
set_events: function () {
var me = this;
// next page
this.wrapper.find('.btn-more').click(function () {
me.run(true);
});
this.wrapper.find(".btn-group-paging").on('click', '.btn', function () {
me.page_length = cint($(this).attr("data-value"));
me.wrapper.find(".btn-group-paging .btn-info").removeClass("btn-info");
$(this).addClass("btn-info");
// always reset when changing list page length
me.run();
});
// select the correct page length
if (this.opts.page_length !== 20) {
this.wrapper.find(".btn-group-paging .btn-info").removeClass("btn-info");
this.wrapper
.find(".btn-group-paging .btn[data-value='" + this.opts.page_length + "']")
.addClass('btn-info');
}
// title
if (this.title) {
this.wrapper.find('h3').html(this.title).show();
}
// new
this.set_primary_action();
if (me.no_toolbar || me.hide_toolbar) {
me.wrapper.find('.list-toolbar-wrapper').hide();
}
},
set_primary_action: function () {
var me = this;
if (this.new_doctype) {
this.page.set_primary_action(
__("New"),
me.make_new_doc.bind(me, me.new_doctype),
"octicon octicon-plus"
);
} else {
this.page.clear_primary_action();
}
},
make_new_doc: function (doctype) {
var me = this;
frappe.model.with_doctype(doctype, function () {
if (me.custom_new_doc) {
me.custom_new_doc(doctype);
} else {
if (me.filter_list) {
frappe.route_options = {};
me.filter_list.get_filters().forEach(function (f, i) {
if (f[2] === "=" && !frappe.model.std_fields_list.includes(f[1])) {
frappe.route_options[f[1]] = f[3];
}
});
}
frappe.new_doc(doctype, true);
}
});
},
make_filters: function () {
this.make_standard_filters();
this.filter_list = new frappe.ui.FilterList({
base_list: this,
parent: this.wrapper.find('.list-filters').show(),
doctype: this.doctype,
filter_fields: this.filter_fields,
default_filters: this.default_filters || []
});
// default filter for submittable doctype
if (frappe.model.is_submittable(this.doctype)) {
this.filter_list.add_filter(this.doctype, "docstatus", "!=", 2);
}
},
make_standard_filters: function() {
var me = this;
if (this.standard_filters_added) {
return;
}
if (this.meta) {
var filter_count = 1;
if(this.is_list_view) {
$(`<span class="octicon octicon-search text-muted small"></span>`)
.prependTo(this.page.page_form);
}
this.page.add_field({
fieldtype: 'Data',
label: 'ID',
condition: 'like',
fieldname: 'name',
onchange: () => { me.refresh(true); }
});
this.meta.fields.forEach(function(df, i) {
if(df.in_standard_filter && !frappe.model.no_value_type.includes(df.fieldtype)) {
let options = df.options;
let condition = '=';
let fieldtype = df.fieldtype;
if (['Text', 'Small Text', 'Text Editor', 'Data'].includes(fieldtype)) {
fieldtype = 'Data';
condition = 'like';
}
if(df.fieldtype == "Select" && df.options) {
options = df.options.split("\n");
if(options.length > 0 && options[0] != "") {
options.unshift("");
options = options.join("\n");
}
}
let f = me.page.add_field({
fieldtype: fieldtype,
label: __(df.label),
options: options,
fieldname: df.fieldname,
condition: condition,
onchange: () => {me.refresh(true);}
});
filter_count ++;
if (filter_count > 3) {
$(f.wrapper).addClass('hidden-sm').addClass('hidden-xs');
}
if (filter_count > 5) {
return false;
}
}
});
}
this.standard_filters_added = true;
},
update_standard_filters: function(filters) {
let me = this;
for(let key in this.page.fields_dict) {
let field = this.page.fields_dict[key];
let value = field.get_value();
if (value) {
if (field.df.condition==='like' && !value.includes('%')) {
value = '%' + value + '%';
}
filters.push([
me.doctype,
field.df.fieldname,
field.df.condition || '=',
value
]);
}
}
},
clear: function () {
this.data = [];
this.wrapper.find('.result-list').empty();
this.wrapper.find('.result').show();
this.wrapper.find('.no-result').hide();
this.start = 0;
this.onreset && this.onreset();
},
set_filters_from_route_options: function ({clear_filters=true} = {}) {
var me = this;
if(this.filter_list && clear_filters) {
this.filter_list.clear_filters();
}
for(var field in frappe.route_options) {
var value = frappe.route_options[field];
var doctype = null;
// if `Child DocType.fieldname`
if (field.includes(".")) {
doctype = field.split(".")[0];
field = field.split(".")[1];
}
// find the table in which the key exists
// for example the filter could be {"item_code": "X"}
// where item_code is in the child table.
// we can search all tables for mapping the doctype
if (!doctype) {
doctype = frappe.meta.get_doctype_for_field(me.doctype, field);
}
if (doctype && me.filter_list) {
if ($.isArray(value)) {
me.filter_list.add_filter(doctype, field, value[0], value[1]);
} else {
me.filter_list.add_filter(doctype, field, "=", value);
}
}
}
frappe.route_options = null;
},
run: function(more) {
setTimeout(() => this._run(more), 100);
},
_run: function (more) {
var me = this;
if (!more) {
this.start = 0;
this.onreset && this.onreset();
}
var args = this.get_call_args();
this.save_user_settings_locally(args);
// user_settings are saved by db_query.py when dirty
$.extend(args, {
user_settings: frappe.model.user_settings[this.doctype]
});
return frappe.call({
method: this.opts.method || 'frappe.desk.query_builder.runquery',
type: "GET",
freeze: this.opts.freeze !== undefined ? this.opts.freeze : true,
args: args,
callback: function (r) {
me.dirty = false;
me.render_results(r);
},
no_spinner: this.opts.no_loading
});
},
save_user_settings_locally: function (args) {
if (this.opts.save_user_settings && this.doctype && !this.docname) {
// save list settings locally
var user_settings = frappe.model.user_settings[this.doctype];
var different = false;
if (!user_settings) {
return;
}
if (!frappe.utils.arrays_equal(args.filters, user_settings.filters)) {
// settings are dirty if filters change
user_settings.filters = args.filters;
different = true;
}
if (user_settings.order_by !== args.order_by) {
user_settings.order_by = args.order_by;
different = true;
}
if (user_settings.limit !== args.limit_page_length) {
user_settings.limit = args.limit_page_length || 20
different = true;
}
// save fields in list settings
if (args.save_user_settings_fields) {
user_settings.fields = args.fields;
}
if (different) {
user_settings.updated_on = moment().toString();
}
}
},
get_call_args: function () {
// load query
if (!this.method) {
var query = this.get_query && this.get_query() || this.query;
query = this.add_limits(query);
var args = {
query_max: this.query_max,
as_dict: 1
}
args.simple_query = query;
} else {
var args = {
start: this.start,
page_length: this.page_length
}
}
// append user-defined arguments
if (this.args)
$.extend(args, this.args)
if (this.get_args) {
$.extend(args, this.get_args());
}
return args;
},
render_results: function (r) {
if (this.start === 0)
this.clear();
this.wrapper.find('.btn-more, .list-loading').hide();
var values = [];
if (r.message) {
values = this.get_values_from_response(r.message);
}
var show_results = true;
if(this.show_no_result) {
if($.isFunction(this.show_no_result)) {
show_results = !this.show_no_result()
} else {
show_results = !this.show_no_result;
}
}
// render result view when
// length > 0 OR
// explicitly set by flag
if (values.length || show_results) {
this.data = this.data.concat(values);
this.render_view(values);
this.update_paging(values);
} else if (this.start === 0) {
// show no result message
this.wrapper.find('.result').hide();
var msg = '';
var no_result_message = this.no_result_message;
if(no_result_message && $.isFunction(no_result_message)) {
msg = no_result_message();
} else if(typeof no_result_message === 'string') {
msg = no_result_message;
} else {
msg = __('No Results')
}
this.wrapper.find('.no-result').html(msg).show();
}
this.wrapper.find('.list-paging-area')
.toggle(values.length > 0|| this.start > 0);
// callbacks
if (this.onrun) this.onrun();
if (this.callback) this.callback(r);
this.wrapper.trigger("render-complete");
},
get_values_from_response: function (data) {
// make dictionaries from keys and values
if (data.keys && $.isArray(data.keys)) {
return frappe.utils.dict(data.keys, data.values);
} else {
return data;
}
},
render_view: function (values) {
// override this method in derived class
},
update_paging: function (values) {
if (values.length >= this.page_length) {
this.wrapper.find('.btn-more').show();
this.start += this.page_length;
}
},
refresh: function () {
this.run();
},
add_limits: function (query) {
return query + ' LIMIT ' + this.start + ',' + (this.page_length + 1);
},
set_filter: function (fieldname, label, no_run, no_duplicate) {
var filter = this.filter_list.get_filter(fieldname);
if (filter) {
var value = cstr(filter.field.get_value());
if (value.includes(label)) {
// already set
return false
} else if (no_duplicate) {
filter.set_values(this.doctype, fieldname, "=", label);
} else {
// second filter set for this field
if (fieldname == '_user_tags' || fieldname == "_liked_by") {
// and for tags
this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%');
} else {
// or for rest using "in"
filter.set_values(this.doctype, fieldname, 'in', value + ', ' + label);
}
}
} else {
// no filter for this item,
// setup one
if (['_user_tags', '_comments', '_assign', '_liked_by'].includes(fieldname)) {
this.filter_list.add_filter(this.doctype, fieldname, 'like', '%' + label + '%');
} else {
this.filter_list.add_filter(this.doctype, fieldname, '=', label);
}
}
if (!no_run)
this.run();
},
init_user_settings: function () {
this.user_settings = frappe.model.user_settings[this.doctype] || {};
},
call_for_selected_items: function (method, args) {
var me = this;
args.names = this.get_checked_items().map(function (item) {
return item.name;
});
frappe.call({
method: method,
args: args,
freeze: true,
callback: function (r) {
if (!r.exc) {
if (me.list_header) {
me.list_header.find(".list-select-all").prop("checked", false);
}
me.refresh(true);
}
}
});
}
});

View file

@ -18,7 +18,7 @@ frappe.ui.CommentArea = class CommentArea {
this.no_wrapper = no_wrapper;
this.make();
// Load emojis initially from https://git.io/frappe-emoji
frappe.chat.emoji();
// All good.
@ -106,15 +106,7 @@ frappe.ui.CommentArea = class CommentArea {
},
callbacks: {
onChange: () => {
if(input.summernote('isEmpty')) {
button
.removeClass('btn-primary')
.addClass('btn-default');
} else {
button
.removeClass('btn-default')
.addClass('btn-primary');
}
this.set_state();
},
onKeydown: (e) => {
var key = frappe.ui.keys.get_key(e);
@ -179,6 +171,22 @@ frappe.ui.CommentArea = class CommentArea {
this.note_editor.on('click', () => input.summernote('focus'));
}
check_state() {
return !(this.input.summernote('isEmpty'));
}
set_state() {
if(this.check_state()) {
this.button
.removeClass('btn-default')
.addClass('btn-primary');
} else {
this.button
.removeClass('btn-primary')
.addClass('btn-default');
}
}
destroy() {
this.input.summernote('destroy');
}
@ -201,3 +209,115 @@ frappe.ui.CommentArea = class CommentArea {
this.on_submit && this.on_submit(this.val());
}
};
frappe.ui.ReviewArea = class ReviewArea extends frappe.ui.CommentArea {
setup_dom() {
const header = !this.no_wrapper ?
`<div class="comment-input-header">
<span class="small text-muted">${__("Add your review")}</span>
<button class="btn btn-default btn-comment btn-xs disabled pull-right">
${__("Submit Review")}
</button>
</div>` : '';
const footer = !this.no_wrapper ?
`<div class="text-muted small">
${__("Ctrl+Enter to submit")}
</div>` : '';
const ratingArea = !this.no_wrapper ?
`<div class="rating-area text-muted small" style="margin-bottom: 5px">
${ __("Your rating: ") }
<i class='fa fa-fw fa-star-o star-icon' data-index=0></i>
<i class='fa fa-fw fa-star-o star-icon' data-index=1></i>
<i class='fa fa-fw fa-star-o star-icon' data-index=2></i>
<i class='fa fa-fw fa-star-o star-icon' data-index=3></i>
<i class='fa fa-fw fa-star-o star-icon' data-index=4></i>
</div>` : '';
this.wrapper = $(`
<div class="comment-input-wrapper">
${ header }
<div class="comment-input-container">
${ ratingArea }
<input class="form-control review-subject" type="text"
placeholder="${__('Subject')}"
style="border-radius: 3px; border-color: #ebeff2">
</input>
<div class="form-control comment-input"></div>
${ footer }
</div>
</div>
`);
this.wrapper.appendTo(this.parent);
this.input = this.parent.find('.comment-input');
this.subject = this.parent.find('.review-subject');
this.button = this.parent.find('.btn-comment');
this.ratingArea = this.parent.find('.rating-area');
this.rating = 0;
}
check_state() {
return !(this.input.summernote('isEmpty') ||
this.rating === 0 || !this.subject.val().length);
}
set_state() {
if(this.check_state()) {
this.button
.removeClass('btn-default disabled')
.addClass('btn-primary');
} else {
this.button
.removeClass('btn-primary')
.addClass('btn-default disabled');
}
}
reset() {
this.set_rating(0);
this.subject.val('');
this.input.summernote('code', '');
}
bind_events() {
super.bind_events();
this.ratingArea.on('click', '.star-icon', (e) => {
let index = $(e.target).attr('data-index');
this.set_rating(parseInt(index) + 1);
})
this.subject.on('change', () => {
this.set_state();
})
}
set_rating(rating) {
this.ratingArea.find('.star-icon').each((i, icon) => {
let star = $(icon);
if(i < rating) {
star.removeClass('fa-star-o');
star.addClass('fa-star');
} else {
star.removeClass('fa-star');
star.addClass('fa-star-o');
}
})
this.rating = rating;
this.set_state();
}
val(value) {
if(value === undefined) {
return {
rating: this.rating,
subject: this.subject.val(),
content: this.input.summernote('code')
}
}
// Set html if value is specified
this.input.summernote('code', value);
}
}

View file

@ -444,14 +444,15 @@ frappe.ui.Page = Class.extend({
return this.$title_area;
},
set_title: function(txt, icon) {
set_title: function(txt, icon = '', stripHtml = true, tabTitle = '') {
if(!txt) txt = "";
// strip html
txt = strip_html(txt);
if(stripHtml) {
txt = strip_html(txt);
}
this.title = txt;
frappe.utils.set_title(txt);
frappe.utils.set_title(tabTitle || txt);
if(icon) {
txt = '<span class="'+ icon +' text-muted" style="font-size: inherit;"></span> ' + txt;
}

View file

@ -4,6 +4,7 @@ frappe.ui.Sidebar = class Sidebar {
constructor({ wrapper, css_class }) {
this.wrapper = wrapper;
this.css_class = css_class;
this.items = {};
this.make_dom();
}
@ -16,7 +17,7 @@ frappe.ui.Sidebar = class Sidebar {
this.$sidebar = this.wrapper.find('.' + this.css_class);
}
add_item(item, section) {
add_item(item, section, h6=false) {
let $section, $li_item;
if(!section && this.wrapper.find('.sidebar-menu').length === 0) {
// if no section, add section with no heading
@ -29,14 +30,26 @@ frappe.ui.Sidebar = class Sidebar {
$li_item = $(`<li>`);
item.appendTo($li_item);
} else {
$li_item = $(`
<li><a ${item.href ? `href="${item.href}"` : ''}>${item.label}</a></li>
`).click(
const className = h6 ? 'h6' : '';
const html = `<li class=${className}>
<a ${item.href ? `href="${item.href}"` : ''}>${item.label}</a>
</li>`;
$li_item = $(html).click(
() => item.on_click && item.on_click()
);
}
$section.append($li_item);
if(item.name) {
this.items[item.name] = $li_item;
}
}
remove_item(name) {
if(this.items[name]) {
this.items[name].remove();
}
}
get_section(section_heading="") {