diff --git a/frappe/build.js b/frappe/build.js deleted file mode 100644 index ae7545f22a..0000000000 --- a/frappe/build.js +++ /dev/null @@ -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; -} diff --git a/frappe/public/js/frappe/dom.js b/frappe/public/js/frappe/dom.js index be9356175f..b5b264dfd9 100644 --- a/frappe/public/js/frappe/dom.js +++ b/frappe/public/js/frappe/dom.js @@ -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); diff --git a/frappe/public/js/frappe/form/controls/button.js b/frappe/public/js/frappe/form/controls/button.js index f10dc61943..3068687f76 100644 --- a/frappe/public/js/frappe/form/controls/button.js +++ b/frappe/public/js/frappe/form/controls/button.js @@ -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"); diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index d842885bac..74a9e3e75d 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -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() { diff --git a/frappe/public/js/frappe/misc/utils.js b/frappe/public/js/frappe/misc/utils.js index b79d4ab45a..4298a06507 100644 --- a/frappe/public/js/frappe/misc/utils.js +++ b/frappe/public/js/frappe/misc/utils.js @@ -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; diff --git a/frappe/public/js/frappe/ui/base_list.js b/frappe/public/js/frappe/ui/base_list.js deleted file mode 100644 index 0d3b1957e8..0000000000 --- a/frappe/public/js/frappe/ui/base_list.js +++ /dev/null @@ -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 $('') - .appendTo(this.wrapper.find('.list-toolbar')) - .html((icon ? (' ') : '') + 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) { - $(``) - .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); - } - } - }); - } -}); diff --git a/frappe/public/js/frappe/ui/comment.js b/frappe/public/js/frappe/ui/comment.js index 36f5ff1aee..34f8130842 100644 --- a/frappe/public/js/frappe/ui/comment.js +++ b/frappe/public/js/frappe/ui/comment.js @@ -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 ? + `