diff --git a/core/doctype/communication/communication.txt b/core/doctype/communication/communication.txt index 9b38e9d31d..ceb4378e71 100644 --- a/core/doctype/communication/communication.txt +++ b/core/doctype/communication/communication.txt @@ -1,6 +1,6 @@ [ { - "creation": "2013-01-10 16:34:00", + "creation": "2013-01-28 17:06:59", "docstatus": 0, "modified": "2013-01-28 15:29:13", "modified_by": "Administrator", @@ -26,14 +26,11 @@ "permlevel": 0 }, { - "cancel": 1, - "create": 1, "doctype": "DocPerm", "name": "__common__", "parent": "Communication", "parentfield": "permissions", "parenttype": "DocType", - "permlevel": 0, "read": 1, "report": 1, "submit": 0, @@ -243,16 +240,6 @@ "fieldtype": "Date", "label": "Date" }, - { - "doctype": "DocField", - "fieldname": "file_list", - "fieldtype": "Text", - "hidden": 1, - "in_list_view": 1, - "label": "File List", - "no_copy": 1, - "print_hide": 1 - }, { "doctype": "DocField", "fieldname": "_user_tags", @@ -263,26 +250,53 @@ "print_hide": 1 }, { - "amend": 0, + "create": 1, "doctype": "DocPerm", + "permlevel": 0, "role": "Support Team" }, { - "amend": 0, + "create": 1, "doctype": "DocPerm", + "permlevel": 0, + "role": "Sales Manager" + }, + { + "create": 1, + "doctype": "DocPerm", + "permlevel": 0, + "role": "Sales User" + }, + { + "amend": 0, + "cancel": 0, + "create": 0, + "doctype": "DocPerm", + "match": "", + "permlevel": 1, "role": "Sales Manager" }, { "amend": 0, + "cancel": 0, + "create": 0, "doctype": "DocPerm", - "role": "Sales User" - }, - { - "doctype": "DocPerm", + "match": "", + "permlevel": 1, "role": "Support Manager" }, { + "cancel": 1, + "create": 1, "doctype": "DocPerm", + "permlevel": 0, + "role": "Support Manager" + }, + { + "cancel": 1, + "create": 1, + "doctype": "DocPerm", + "permlevel": 0, "role": "System Manager" } ] \ No newline at end of file diff --git a/core/doctype/doctype/doctype.py b/core/doctype/doctype/doctype.py index 065f0b872c..b0a1aebfbd 100644 --- a/core/doctype/doctype/doctype.py +++ b/core/doctype/doctype/doctype.py @@ -40,7 +40,8 @@ class DocType: sql('UPDATE tabDocType SET modified="%s" WHERE `name`="%s"' % (now(), p[0])) def scrub_field_names(self): - restricted = ('name','parent','idx','owner','creation','modified','modified_by','parentfield','parenttype') + restricted = ('name','parent','idx','owner','creation','modified','modified_by', + 'parentfield','parenttype',"file_list") for d in self.doclist: if d.parent and d.fieldtype: if (not d.fieldname): @@ -80,7 +81,6 @@ class DocType: validate_permissions(self.doclist.get({"doctype":"DocPerm"})) self.set_version() self.make_amendable() - self.make_file_list() self.check_link_replacement_error() def on_update(self): @@ -139,24 +139,6 @@ class DocType: "doctype_template.py"), 'r') as srcfile: pyfile.write(srcfile.read()) - def make_file_list(self): - """ - if allow_attach is checked and the column file_list doesn't exist, - create a new field 'file_list' - """ - if self.doc.allow_attach: - if not webnotes.conn.sql("""select name from tabDocField - where fieldname = 'file_list' and parent = %s""", self.doc.name): - new = self.doc.addchild('fields', 'DocField', self.doclist) - new.label = 'File List' - new.fieldtype = 'Text' - new.fieldname = 'file_list' - new.hidden = 1 - new.permlevel = 0 - new.print_hide = 1 - new.no_copy = 1 - new.idx = self.get_max_idx() + 1 - def make_amendable(self): """ if is_submittable is set, add amended_from docfields diff --git a/core/doctype/file_data/file_data.py b/core/doctype/file_data/file_data.py index 44a449c9e3..d1138a0b31 100644 --- a/core/doctype/file_data/file_data.py +++ b/core/doctype/file_data/file_data.py @@ -27,45 +27,27 @@ record of files naming for same name files: file.gif, file-1.gif, file-2.gif etc """ -import webnotes +import webnotes, webnotes.utils, os class DocType(): def __init__(self, d, dl): self.doc, self.doclist = d, dl - def autoname(self): - """save file by its name""" - if not self.doc.file_name: - raise Exception, 'file name missing' - - if not '.' in self.doc.file_name: - raise Exception, 'file name must have extension (.)' - - self.doc.file_name = self.doc.file_name.replace('-', '') - - parts = self.doc.file_name.split('.') - - same = webnotes.conn.sql("""select name from `tabFile Data` - where name=%s""", self.doc.file_name) - - if same: - # check for more - other_list = webnotes.conn.sql("""select name from `tabFile Data` - where name like '%s-%%.%s'""" % (parts[0], '.'.join(parts[1:]))) + def on_update(self): + # check duplicate assignement + n_records = webnotes.conn.sql("""select count(*) from `tabFile Data` + where file_name=%s + and attached_to_doctype=%s + and attached_to_name=%s""", (self.doc.file_name, self.doc.attached_to_doctype, + self.doc.attached_to_name))[0][0] + if n_records > 1: + webnotes.msgprint(webnotes._("Same file has already been attached to the record")) + raise webnotes.DuplicateEntryError - if other_list: - from webnotes.utils import cint - # gets the max number from format like name-###.ext - last_num = max( - (cint(other[0].split('.')[0].split('-')[-1]) - for other in other_list) - ) - else: - last_num = 0 - - new_id = "%03d" % (last_num + 1) - - # new name - self.doc.file_name = parts[0] + '-' + new_id + '.' + '.'.join(parts[1:]) - - self.doc.name = self.doc.file_name \ No newline at end of file + def on_trash(self): + if webnotes.conn.sql("""select count(*) from `tabFile Data` + where file_name=%s""", self.doc.file_name)[0][0]==1: + path = webnotes.utils.get_path("public", "files", self.doc.file_name) + if os.path.exists(path): + os.remove(path) + \ No newline at end of file diff --git a/core/doctype/file_data/file_data.txt b/core/doctype/file_data/file_data.txt index 37ea5f6e9d..35cfd07a9e 100644 --- a/core/doctype/file_data/file_data.txt +++ b/core/doctype/file_data/file_data.txt @@ -1,51 +1,83 @@ [ { - "owner": "Administrator", + "creation": "2012-12-12 11:19:22", "docstatus": 0, - "creation": "2012-11-30 18:13:34", + "modified": "2013-04-10 13:34:29", "modified_by": "Administrator", - "modified": "2012-12-11 14:56:34" + "owner": "Administrator" }, { - "read_only": 0, - "autoname": "FileData/.#####", - "name": "__common__", + "autoname": "File.######", "doctype": "DocType", - "module": "Core" + "module": "Core", + "name": "__common__", + "read_only": 0 }, { + "doctype": "DocField", "name": "__common__", "parent": "File Data", - "doctype": "DocField", + "parentfield": "fields", "parenttype": "DocType", "permlevel": 0, - "parentfield": "fields" + "read_only": 1 }, { - "name": "File Data", - "doctype": "DocType" + "cancel": 1, + "doctype": "DocPerm", + "name": "__common__", + "parent": "File Data", + "parentfield": "permissions", + "parenttype": "DocType", + "permlevel": 0, + "read": 1, + "role": "System Manager", + "write": 1 + }, + { + "doctype": "DocType", + "name": "File Data" }, { - "oldfieldtype": "Data", "doctype": "DocField", + "fieldname": "file_name", + "fieldtype": "Data", "label": "File Name", "oldfieldname": "file_name", - "fieldname": "file_name", - "fieldtype": "Data" + "oldfieldtype": "Data" }, { "doctype": "DocField", - "label": "File URL", "fieldname": "file_url", - "fieldtype": "Data" + "fieldtype": "Data", + "in_list_view": 1, + "label": "File URL" }, { - "oldfieldtype": "Link", "doctype": "DocField", - "label": "Module", - "oldfieldname": "module", - "fieldname": "module", + "fieldname": "attached_to_doctype", "fieldtype": "Link", - "options": "Module Def" + "in_list_view": 1, + "label": "Attached To DocType", + "options": "DocType", + "search_index": 1 + }, + { + "doctype": "DocField", + "fieldname": "attached_to_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Attached To Name", + "search_index": 1 + }, + { + "doctype": "DocField", + "fieldname": "file_size", + "fieldtype": "Int", + "in_list_view": 1, + "label": "File Size" + }, + { + "doctype": "DocPerm" } ] \ No newline at end of file diff --git a/core/doctype/letter_head/letter_head.js b/core/doctype/letter_head/letter_head.js index 013aff084a..60086ee016 100644 --- a/core/doctype/letter_head/letter_head.js +++ b/core/doctype/letter_head/letter_head.js @@ -57,8 +57,10 @@ cur_frm.cscript['set_from_image'] = function(doc, dt, dn) { if(!confirm('Are you sure you want to overwrite the existing HTML?')) return; } + + - var file_name = doc.file_list.split(',')[1] + var file_name = cur_frm.get_files()[0]; if(!in_list(['gif','jpg','jpeg','png'], file_name.split('.')[1].toLowerCase())) { msgprint("Please upload a web friendly (GIF, JPG or PNG) image file for the letter head"); diff --git a/core/doctype/letter_head/letter_head.txt b/core/doctype/letter_head/letter_head.txt index 817dcd2f7c..31183b6dbe 100644 --- a/core/doctype/letter_head/letter_head.txt +++ b/core/doctype/letter_head/letter_head.txt @@ -1,21 +1,18 @@ [ { - "creation": "2012-11-19 12:06:53", + "creation": "2012-11-22 17:45:46", "docstatus": 0, "modified": "2012-11-21 17:39:17", "modified_by": "Administrator", "owner": "Administrator" }, { - "_last_update": "1307340319", "allow_attach": 1, "autoname": "field:letter_head_name", "doctype": "DocType", "max_attachments": 3, "module": "Core", - "name": "__common__", - "section_style": "Simple", - "version": 1 + "name": "__common__" }, { "doctype": "DocField", @@ -26,6 +23,7 @@ "permlevel": 0 }, { + "cancel": 1, "create": 1, "doctype": "DocPerm", "name": "__common__", @@ -34,7 +32,9 @@ "parenttype": "DocType", "permlevel": 0, "read": 1, + "report": 1, "role": "System Manager", + "submit": 0, "write": 1 }, { @@ -83,8 +83,7 @@ "doctype": "DocField", "fieldname": "set_from_image", "fieldtype": "Button", - "label": "Set From Image", - "trigger": "Client" + "label": "Set From Image" }, { "depends_on": "letter_head_name", @@ -103,15 +102,6 @@ "hidden": 1, "label": "URL" }, - { - "doctype": "DocField", - "fieldname": "file_list", - "fieldtype": "Text", - "hidden": 1, - "label": "File LIst", - "oldfieldname": "file_list", - "oldfieldtype": "Text" - }, { "doctype": "DocPerm" } diff --git a/core/doctype/profile/profile.py b/core/doctype/profile/profile.py index b1334a7873..10bdd86a8b 100644 --- a/core/doctype/profile/profile.py +++ b/core/doctype/profile/profile.py @@ -187,7 +187,10 @@ Thank you,
'product': startup.product_name, 'user_fullname': get_user_fullname(webnotes.session['user']) } - sendmail_md(self.doc.email, subject=subject, msg=txt % args) + + sender = webnotes.session.user != "Administrator" and webnotes.session.user or None + + sendmail_md(recipients=self.doc.email, sender=sender, subject=subject, msg=txt % args) def on_trash(self): if self.doc.name in ["Administrator", "Guest"]: diff --git a/core/doctype/profile/profile.txt b/core/doctype/profile/profile.txt index f4ca204eec..e8b53df461 100644 --- a/core/doctype/profile/profile.txt +++ b/core/doctype/profile/profile.txt @@ -1,6 +1,6 @@ [ { - "creation": "2013-02-14 17:37:36", + "creation": "2013-03-07 12:26:21", "docstatus": 0, "modified": "2013-03-01 10:18:03", "modified_by": "Administrator", @@ -387,16 +387,6 @@ "oldfieldtype": "Read Only", "read_only": 1 }, - { - "doctype": "DocField", - "fieldname": "file_list", - "fieldtype": "Text", - "hidden": 1, - "label": "File List", - "no_copy": 1, - "oldfieldname": "file_list", - "oldfieldtype": "Text" - }, { "doctype": "DocField", "fieldname": "roles_assigned_to_user", diff --git a/core/doctype/report/report.txt b/core/doctype/report/report.txt index 98e91bb12f..cc1dbdd052 100644 --- a/core/doctype/report/report.txt +++ b/core/doctype/report/report.txt @@ -1,8 +1,8 @@ [ { - "creation": "2013-02-25 13:11:50", + "creation": "2013-03-09 15:45:57", "docstatus": 0, - "modified": "2013-02-25 14:18:36", + "modified": "2013-04-30 17:50:00", "modified_by": "Administrator", "owner": "Administrator" }, @@ -41,6 +41,7 @@ "fieldname": "report_name", "fieldtype": "Data", "label": "Report Name", + "read_only": 0, "reqd": 1 }, { @@ -50,6 +51,7 @@ "in_list_view": 1, "label": "Ref DocType", "options": "DocType", + "read_only": 0, "reqd": 1 }, { @@ -59,12 +61,20 @@ "in_list_view": 1, "label": "Is Standard", "options": "No\nYes", + "read_only": 0, "reqd": 1 }, + { + "doctype": "DocField", + "fieldname": "add_total_row", + "fieldtype": "Check", + "label": "Add Total Row" + }, { "doctype": "DocField", "fieldname": "column_break_4", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "read_only": 0 }, { "doctype": "DocField", @@ -72,25 +82,29 @@ "fieldtype": "Select", "label": "Report Type", "options": "Report Builder\nQuery Report\nScript Report", + "read_only": 0, "reqd": 1 }, { "doctype": "DocField", "fieldname": "disabled", "fieldtype": "Check", - "label": "Disabled" + "label": "Disabled", + "read_only": 0 }, { "doctype": "DocField", "fieldname": "section_break_6", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "read_only": 0 }, { "depends_on": "eval:doc.report_type==\"Query Report\"", "doctype": "DocField", "fieldname": "query", "fieldtype": "Code", - "label": "Query" + "label": "Query", + "read_only": 0 }, { "depends_on": "eval:doc.report_type==\"Report Builder\"", diff --git a/core/doctype/system_console/system_console.js b/core/doctype/system_console/system_console.js index 462082017f..f9a89c4d30 100644 --- a/core/doctype/system_console/system_console.js +++ b/core/doctype/system_console/system_console.js @@ -26,7 +26,7 @@ cur_frm.cscript['server_python'] = function(doc, dt, dn) { $c_obj(make_doclist(doc.doctype, doc.name), 'execute_server', '', function(r, rt) { doc = locals[doc.doctype][doc.name]; if(r.exc) { - doc.response = r.exc; + doc.response = (r.exc || []).join("\n"); } else { doc.response = 'Worked!'.bold() } diff --git a/core/page/data_import_tool/data_import_tool.js b/core/page/data_import_tool/data_import_tool.js index 580fbaaa1f..eb13aaab3d 100644 --- a/core/page/data_import_tool/data_import_tool.js +++ b/core/page/data_import_tool/data_import_tool.js @@ -141,7 +141,7 @@ wn.pages['data-import-tool'].onload = function(wrapper) { }); // add overwrite option - var $submit_btn = $('#dit-upload-area form input[type="submit"]'); + var $submit_btn = $('#dit-upload-area input[type="submit"]'); $('\ Overwrite\

If you are uploading a child table (for example Item Price), the all the entries of that table will be deleted (for that parent record) and new entries will be made.


') diff --git a/core/page/data_import_tool/data_import_tool.py b/core/page/data_import_tool/data_import_tool.py index c2724ed4ea..7aeff6672d 100644 --- a/core/page/data_import_tool/data_import_tool.py +++ b/core/page/data_import_tool/data_import_tool.py @@ -93,7 +93,7 @@ def get_template(): docfield = doctype_dl.get_field(t) if docfield and ((mandatory and docfield.reqd) or not (mandatory or docfield.reqd)) \ - and (t not in ('parenttype', 'trash_reason', 'file_list')) and not docfield.hidden: + and (t not in ('parenttype', 'trash_reason')) and not docfield.hidden: fieldrow.append(t) labelrow.append(docfield.label) mandatoryrow.append(docfield.reqd and 'Yes' or 'No') @@ -176,8 +176,12 @@ def upload(): return columns + # extra input params + import json + params = json.loads(webnotes.form_dict.get("params") or '{}') + # header - rows = read_csv_content_from_uploaded_file() + rows = read_csv_content_from_uploaded_file(params.get("ignore_encoding_errors")) start_row = get_start_row() header = rows[:start_row] data = rows[start_row:] @@ -195,7 +199,7 @@ def upload(): webnotes.conn.begin() - overwrite = webnotes.form_dict.get('overwrite') + overwrite = params.get('overwrite') doctype_dl = webnotes.model.doctype.get(doctype) # delete child rows (if parenttype) @@ -226,8 +230,7 @@ def upload(): ret.append('Inserted row for %s at #%s' % (getlink(parenttype, doc.parent), unicode(doc.idx))) else: - ret.append(import_doc(d, doctype, overwrite, row_idx, - webnotes.form_dict.get("_submit")=="on")) + ret.append(import_doc(d, doctype, overwrite, row_idx, params.get("_submit"))) except Exception, e: error = True ret.append('Error for row (#%d) %s : %s' % (row_idx, @@ -307,13 +310,10 @@ def delete_child_rows(rows, doctype): def import_doc(d, doctype, overwrite, row_idx, submit=False): """import main (non child) document""" - from webnotes.model.bean import Bean - if webnotes.conn.exists(doctype, d['name']): if overwrite: - doclist = webnotes.model.doc.get(doctype, d['name']) - doclist[0].fields.update(d) - bean = Bean(doclist) + bean = webnotes.bean(doctype, d['name']) + bean.doc.fields.update(d) if d.get("docstatus") == 1: bean.update_after_submit() else: @@ -323,12 +323,11 @@ def import_doc(d, doctype, overwrite, row_idx, submit=False): return 'Ignored row (#%d) %s (exists)' % (row_idx, getlink(doctype, d['name'])) else: - d['__islocal'] = 1 - dl = Bean([webnotes.model.doc.Document(fielddata = d)]) - dl.save() + bean = webnotes.bean([d]) + bean.insert() if submit: - dl.submit() + bean.submit() return 'Inserted row (#%d) %s' % (row_idx, getlink(doctype, - dl.doc.fields['name'])) + bean.doc.fields['name'])) diff --git a/public/build.json b/public/build.json index 0224d6e33a..9feb939c7b 100644 --- a/public/build.json +++ b/public/build.json @@ -155,6 +155,7 @@ "lib/public/js/legacy/widgets/form/form_comments.js", "lib/public/js/legacy/wn/widgets/form/sidebar.js", "lib/public/js/legacy/wn/widgets/form/comments.js", + "lib/public/js/wn/form/editors.js", "lib/public/js/wn/form/attachments.js", "lib/public/js/wn/form/linked_with.js", "lib/public/js/wn/form/states.js", diff --git a/public/css/ui/common.css b/public/css/ui/common.css index f7919b5713..d9fe7ef6b9 100644 --- a/public/css/ui/common.css +++ b/public/css/ui/common.css @@ -113,3 +113,18 @@ a { font-size: 32px; color: #888; } + +.wysiwyg-editor { + height: 400px; + background-color: white; + border-collapse: separate; + border: 1px solid rgb(204, 204, 204); + padding: 4px; + box-sizing: content-box; + -webkit-box-shadow: rgba(0, 0, 0, 0.0745098) 0px 1px 1px 0px inset; + box-shadow: rgba(0, 0, 0, 0.0745098) 0px 1px 1px 0px inset; + border-top-right-radius: 3px; border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; border-top-left-radius: 3px; + overflow: scroll; + outline: none; +} diff --git a/public/js/legacy/utils/datatype.js b/public/js/legacy/utils/datatype.js index c5beba7842..1494aaa04e 100644 --- a/public/js/legacy/utils/datatype.js +++ b/public/js/legacy/utils/datatype.js @@ -148,12 +148,12 @@ function replace_all(s, t1, t2) { function keys(obj) { var mykeys=[]; - for (key in obj) mykeys[mykeys.length]=key; + for (var key in obj) mykeys[mykeys.length]=key; return mykeys; } function values(obj) { var myvalues=[]; - for (key in obj) myvalues[myvalues.length]=obj[key]; + for (var key in obj) myvalues[myvalues.length]=obj[key]; return myvalues; } diff --git a/public/js/legacy/widgets/form/clientscriptAPI.js b/public/js/legacy/widgets/form/clientscriptAPI.js index 43fd935010..e6dc129f5c 100644 --- a/public/js/legacy/widgets/form/clientscriptAPI.js +++ b/public/js/legacy/widgets/form/clientscriptAPI.js @@ -219,9 +219,9 @@ _f.Frm.prototype.call_server = function(method, args, callback) { } _f.Frm.prototype.get_files = function() { - return $.map((cur_frm.doc.file_list || "").split("\n"), function(f) { - return f.split(",")[0] || null; - }); + return cur_frm.attachments + ? keys(cur_frm.attachments.get_file_list()).sort() + : [] ; } _f.Frm.prototype.set_query = function(fieldname, opt1, opt2) { diff --git a/public/js/legacy/widgets/form/fields.js b/public/js/legacy/widgets/form/fields.js index eb74133fcd..06b477e9f3 100644 --- a/public/js/legacy/widgets/form/fields.js +++ b/public/js/legacy/widgets/form/fields.js @@ -1202,14 +1202,15 @@ SelectField.prototype.make_input = function() { if(!cur_frm) return; var fl = cur_frm.doc.file_list; if(fl) { + fl = JSON.parse(fl); this.df.options = ''; - var fl = fl.split('\n'); - for(var i in fl) { - var fname = fl[i].split(',')[0]; + + for(var fname in fl) { if(fname.substr(0,4)!="http") fname = "files/" + fname; this.df.options += '\n' + fname; } + this.set_description(""); } else { this.df.options = '' diff --git a/public/js/legacy/widgets/form/form_fields.js b/public/js/legacy/widgets/form/form_fields.js index 450bd84754..da3aafab29 100644 --- a/public/js/legacy/widgets/form/form_fields.js +++ b/public/js/legacy/widgets/form/form_fields.js @@ -218,143 +218,36 @@ _f.CodeField.prototype.make_input = function() { this.label_span.innerHTML = this.df.label; + $(this.input_area).css({"min-height":"360px"}); + if(this.df.fieldtype=='Text Editor') { - $(this.input_area).css({"min-height":"360px"}); - this.input = $a(this.input_area, 'text_area', '', {fontSize:'12px'}); - this.myid = wn.dom.set_unique_id(this.input); - - // setup tiny mce - $(me.input).tinymce({ - // Location of TinyMCE script - script_url : 'lib/js/lib/tiny_mce_3.5.7/tiny_mce.js', - - // General options - theme : "advanced", - plugins : "style,inlinepopups,table,advimage", - extended_valid_elements: "script|embed", - - // w/h - width: '100%', - height: '360px', - - // buttons - theme_advanced_buttons1 : "bold,italic,underline,hr,|,justifyleft,justifycenter,|,formatselect,fontsizeselect,|,bullist,numlist,|,image,|,outdent,indent,|,link,|,forecolor,backcolor,|,code", - theme_advanced_buttons2 : "", - theme_advanced_buttons3 : "", - - theme_advanced_toolbar_location : "top", - theme_advanced_toolbar_align : "left", - theme_advanced_statusbar_location: "none", - theme_advanced_path: false, - - valid_elements : "*[*]", - - content_css: "lib/js/lib/tiny_mce_3.5.7/custom_content.css?q=1", - - oninit: function() { me.init_editor(); }, - setup: function(ed) { - ed.onChange.add(function(ed, l) { - me.set(l.content); - me.run_trigger(); - }); - } + this.input = new wn.editors.BootstrapWYSIWYG({ + parent: this.input_area, + change: function(value) { + me.set_value_and_run_trigger(value); + }, + field: this }); - - this.input.set_input = function(v) { - if(me.editor) { - me.editor.setContent(v==null ? "" : v); - } else { - $(me.input).val(v); - } - } - this.get_value = function() { - return me.editor && me.editor.getContent(); // tinyMCE - } - } else { - // setup ace - wn.require('lib/js/lib/ace/ace.js'); - - $(this.input_area).css('border','1px solid #aaa'); - this.pre = $("
").appendTo(this.input_area).get(0);
-
-		this.input = {};
-		this.myid = wn.dom.set_unique_id(this.pre);
-		this.editor = ace.edit(this.myid);
-
-		if(me.df.options=='Markdown' || me.df.options=='HTML') {
-			wn.require('lib/js/lib/ace/mode-html.js');	
-			var HTMLMode = require("ace/mode/html").Mode;
-		    me.editor.getSession().setMode(new HTMLMode());
-		}
-
-		else if(me.df.options=='Javascript') {
-			wn.require('lib/js/lib/ace/mode-javascript.js');	
-			var JavascriptMode = require("ace/mode/javascript").Mode;
-		    me.editor.getSession().setMode(new JavascriptMode());
-		}
-
-		else if(me.df.options=='Python') {
-			wn.require('lib/js/lib/ace/mode-python.js');	
-			var PythonMode = require("ace/mode/python").Mode;
-		    me.editor.getSession().setMode(new PythonMode());
-		}
-		
-		this.input.set_input = function(v) {
-			// during field refresh in run trigger, set_input is called
-			// if called during on_change, setting doesn't make sense
-			// and causes cursor to shift back to first position
-			if(me.changing_value) return;
-			
-			me.setting_value = true;
-			me.editor.getSession().setValue(v==null ? "" : v);
-			me.setting_value = false;
-		}
-		
-		this.get_value = function() {
-			return me.editor.getSession().getValue(); // tinyMCE
-		}
-		$(cur_frm.wrapper).bind('render_complete', function() {
-			me.editor.resize();
-			me.editor.getSession().on('change', function() {
-				if(me.setting_value) return;
-				var val = me.get_value();
-				if(locals[cur_frm.doctype][cur_frm.docname][me.df.fieldname] != val) {
-					me.set(me.get_value());
-
-					me.changing_value = true;
-					me.run_trigger();
-					me.changing_value = false;
-				}
-			})
+		this.input = new wn.editors.ACE({
+			parent: this.input_area,
+			change: function(value) {
+				me.set_value_and_run_trigger(value);
+			},
+			field: this
 		});
-		this.onrefresh = function() {
-			me.editor && me.editor.resize();
-		}
 	}
-	
+	this.get_value = function() {
+		return this.input.get_value();
+	}
 }
 
-_f.CodeField.prototype.init_editor = function() {
-	// attach onchange methods
-	var me = this;
-	this.editor = tinymce.get(this.myid);
-	this.editor.onKeyUp.add(function(ed, e) { 
-		me.set(ed.getContent()); 
-	});
-	this.editor.onPaste.add(function(ed, e) { 
-		me.set(ed.getContent());
-	});
-	this.editor.onSetContent.add(function(ed, e) { 
-		me.set(ed.getContent()); 
-	});
-	
-	// reset content
-	var c = locals[cur_frm.doctype][cur_frm.docname][this.df.fieldname];
-	if(cur_frm && c) {
-		this.editor.setContent(c);
+_f.CodeField.prototype.set_value_and_run_trigger = function(value) {
+	if(locals[cur_frm.doctype][cur_frm.docname][this.df.fieldname] != value) {
+		this.set(value);
+		this.changing_value = true;
+		this.run_trigger();
+		this.changing_value = false;
 	}
 }
 
@@ -363,7 +256,8 @@ _f.CodeField.prototype.set_disp = function(val) {
 	if(this.df.fieldtype=='Text Editor') {
 		this.disp_area.innerHTML = val;
 	} else {
-		this.disp_area.innerHTML = '';
+		this.disp_area.innerHTML = '';
 	}
 }
 
diff --git a/public/js/lib/beautify-html.js b/public/js/lib/beautify-html.js
new file mode 100644
index 0000000000..1d6270071c
--- /dev/null
+++ b/public/js/lib/beautify-html.js
@@ -0,0 +1,617 @@
+/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
+/*
+
+  The MIT License (MIT)
+
+  Copyright (c) 2007-2013 Einar Lielmanis and contributors.
+
+  Permission is hereby granted, free of charge, to any person
+  obtaining a copy of this software and associated documentation files
+  (the "Software"), to deal in the Software without restriction,
+  including without limitation the rights to use, copy, modify, merge,
+  publish, distribute, sublicense, and/or sell copies of the Software,
+  and to permit persons to whom the Software is furnished to do so,
+  subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+
+
+ Style HTML
+---------------
+
+  Written by Nochum Sossonko, (nsossonko@hotmail.com)
+
+  Based on code initially developed by: Einar Lielmanis, 
+    http://jsbeautifier.org/
+
+  Usage:
+    style_html(html_source);
+
+    style_html(html_source, options);
+
+  The options are:
+    indent_size (default 4)          — indentation size,
+    indent_char (default space)      — character to indent with,
+    max_char (default 250)            -  maximum amount of characters per line (0 = disable)
+    brace_style (default "collapse") - "collapse" | "expand" | "end-expand"
+            put braces on the same line as control statements (default), or put braces on own line (Allman / ANSI style), or just put end braces on own line.
+    unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted
+    indent_scripts (default normal)  - "keep"|"separate"|"normal"
+
+    e.g.
+
+    style_html(html_source, {
+      'indent_size': 2,
+      'indent_char': ' ',
+      'max_char': 78,
+      'brace_style': 'expand',
+      'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u']
+    });
+*/
+
+(function() {
+
+    function style_html(html_source, options, js_beautify, css_beautify) {
+    //Wrapper function to invoke all the necessary constructors and deal with the output.
+
+      var multi_parser,
+          indent_size,
+          indent_character,
+          max_char,
+          brace_style,
+          unformatted;
+
+      options = options || {};
+      indent_size = options.indent_size || 4;
+      indent_character = options.indent_char || ' ';
+      brace_style = options.brace_style || 'collapse';
+      max_char = options.max_char === 0 ? Infinity : options.max_char || 250;
+      unformatted = options.unformatted || ['a', 'span', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+
+      function Parser() {
+
+        this.pos = 0; //Parser position
+        this.token = '';
+        this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT
+        this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values
+          parent: 'parent1',
+          parentcount: 1,
+          parent1: ''
+        };
+        this.tag_type = '';
+        this.token_text = this.last_token = this.last_text = this.token_type = '';
+
+        this.Utils = { //Uilities made available to the various functions
+          whitespace: "\n\r\t ".split(''),
+          single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML
+          extra_liners: 'head,body,/html'.split(','), //for tags that need a line of whitespace before them
+          in_array: function (what, arr) {
+            for (var i=0; i= this.input.length) {
+              return content.length?content.join(''):['', 'TK_EOF'];
+            }
+
+            input_char = this.input.charAt(this.pos);
+            this.pos++;
+            this.line_char_count++;
+
+            if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+              if (content.length) {
+                space = true;
+              }
+              this.line_char_count--;
+              continue; //don't want to insert unnecessary space
+            }
+            else if (space) {
+              if (this.line_char_count >= this.max_char) { //insert a line when the max_char is reached
+                content.push('\n');
+                for (var i=0; i', 'igm');
+          reg_match.lastIndex = this.pos;
+          var reg_array = reg_match.exec(this.input);
+          var end_script = reg_array?reg_array.index:this.input.length; //absolute end of script
+          if(this.pos < end_script) { //get everything in between the script tags
+            content = this.input.substring(this.pos, end_script);
+            this.pos = end_script;
+          }
+          return content;
+        };
+
+        this.record_tag = function (tag){ //function to record a tag and its parent in this.tags Object
+          if (this.tags[tag + 'count']) { //check for the existence of this tag type
+            this.tags[tag + 'count']++;
+            this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
+          }
+          else { //otherwise initialize this tag type
+            this.tags[tag + 'count'] = 1;
+            this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
+          }
+          this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent)
+          this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
+        };
+
+        this.retrieve_tag = function (tag) { //function to retrieve the opening tag to the corresponding closer
+          if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
+            var temp_parent = this.tags.parent; //check to see if it's a closable tag.
+            while (temp_parent) { //till we reach '' (the initial value);
+              if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
+                break;
+              }
+              temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
+            }
+            if (temp_parent) { //if we caught something
+              this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
+              this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
+            }
+            delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
+            delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
+            if (this.tags[tag + 'count'] === 1) {
+              delete this.tags[tag + 'count'];
+            }
+            else {
+              this.tags[tag + 'count']--;
+            }
+          }
+        };
+
+        this.get_tag = function (peek) { //function to get a full tag and parse its type
+          var input_char = '',
+              content = [],
+              comment = '',
+              space = false,
+              tag_start, tag_end,
+              orig_pos = this.pos,
+              orig_line_char_count = this.line_char_count;
+
+          peek = peek !== undefined ? peek : false;
+
+          do {
+            if (this.pos >= this.input.length) {
+              if (peek) {
+                this.pos = orig_pos;
+                this.line_char_count = orig_line_char_count;
+              }
+              return content.length?content.join(''):['', 'TK_EOF'];
+            }
+
+            input_char = this.input.charAt(this.pos);
+            this.pos++;
+            this.line_char_count++;
+
+            if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
+              space = true;
+              this.line_char_count--;
+              continue;
+            }
+
+            if (input_char === "'" || input_char === '"') {
+              if (!content[1] || content[1] !== '!') { //if we're in a comment strings don't get treated specially
+                input_char += this.get_unformatted(input_char);
+                space = true;
+              }
+            }
+
+            if (input_char === '=') { //no space before =
+              space = false;
+            }
+
+            if (content.length && content[content.length-1] !== '=' && input_char !== '>' && space) {
+                //no space after = or before >
+              if (this.line_char_count >= this.max_char) {
+                this.print_newline(false, content);
+                this.line_char_count = 0;
+              }
+              else {
+                content.push(' ');
+                this.line_char_count++;
+              }
+              space = false;
+            }
+            if (input_char === '<') {
+              tag_start = this.pos - 1;
+            }
+            content.push(input_char); //inserts character at-a-time (or string)
+          } while (input_char !== '>');
+
+          var tag_complete = content.join('');
+          var tag_index;
+          if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
+            tag_index = tag_complete.indexOf(' ');
+          }
+          else { //otherwise go with the tag ending
+            tag_index = tag_complete.indexOf('>');
+          }
+          var tag_check = tag_complete.substring(1, tag_index).toLowerCase();
+          if (tag_complete.charAt(tag_complete.length-2) === '/' ||
+            this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /)
+            if ( ! peek) {
+              this.tag_type = 'SINGLE';
+            }
+          }
+          else if (tag_check === 'script') { //for later script handling
+            if ( ! peek) {
+              this.record_tag(tag_check);
+              this.tag_type = 'SCRIPT';
+            }
+          }
+          else if (tag_check === 'style') { //for future style handling (for now it justs uses get_content)
+            if ( ! peek) {
+              this.record_tag(tag_check);
+              this.tag_type = 'STYLE';
+            }
+          }
+          else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags
+            comment = this.get_unformatted('', tag_complete); //...delegate to get_unformatted function
+            content.push(comment);
+            // Preserve collapsed whitespace either before or after this tag.
+            if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)){
+                content.splice(0, 0, this.input.charAt(tag_start - 1));
+            }
+            tag_end = this.pos - 1;
+            if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)){
+                content.push(this.input.charAt(tag_end + 1));
+            }
+            this.tag_type = 'SINGLE';
+          }
+          else if (tag_check.charAt(0) === '!') { //peek for  so...
+                comment = this.get_unformatted('-->', tag_complete); //...delegate to get_unformatted
+                content.push(comment);
+              }
+              if ( ! peek) {
+                this.tag_type = 'START';
+              }
+            }
+            else if (tag_check.indexOf('[endif') !== -1) {//peek for ', tag_complete);
+              content.push(comment);
+              this.tag_type = 'SINGLE';
+            }
+          }
+          else if ( ! peek) {
+            if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending
+              this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors
+              this.tag_type = 'END';
+            }
+            else { //otherwise it's a start-tag
+              this.record_tag(tag_check); //push it on the tag stack
+              this.tag_type = 'START';
+            }
+            if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line
+              this.print_newline(true, this.output);
+            }
+          }
+
+          if (peek) {
+            this.pos = orig_pos;
+            this.line_char_count = orig_line_char_count;
+          }
+
+          return content.join(''); //returns fully formatted tag
+        };
+
+        this.get_unformatted = function (delimiter, orig_tag) { //function to return unformatted content in its entirety
+
+          if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) {
+            return '';
+          }
+          var input_char = '';
+          var content = '';
+          var space = true;
+          do {
+
+            if (this.pos >= this.input.length) {
+              return content;
+            }
+
+            input_char = this.input.charAt(this.pos);
+            this.pos++;
+
+            if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+              if (!space) {
+                this.line_char_count--;
+                continue;
+              }
+              if (input_char === '\n' || input_char === '\r') {
+                content += '\n';
+                /*  Don't change tab indention for unformatted blocks.  If using code for html editing, this will greatly affect 
 tags if they are specified in the 'unformatted array'
+                for (var i=0; i]*>\s*$/);
+
+            // if next_tag comes back but is not an isolated tag, then
+            // let's treat the 'a' tag as having content
+            // and respect the unformatted option
+            if (!tag || this.Utils.in_array(tag, unformatted)){
+                return true;
+            } else {
+                return false;
+            }
+        };
+
+        this.printer = function (js_source, indent_character, indent_size, max_char, brace_style) { //handles input/output and some other printing functions
+
+          this.input = js_source || ''; //gets the input for the Parser
+          this.output = [];
+          this.indent_character = indent_character;
+          this.indent_string = '';
+          this.indent_size = indent_size;
+          this.brace_style = brace_style;
+          this.indent_level = 0;
+          this.max_char = max_char;
+          this.line_char_count = 0; //count to see if max_char was exceeded
+
+          for (var i=0; i 0) {
+              this.indent_level--;
+            }
+          };
+        };
+        return this;
+      }
+
+      /*_____________________--------------------_____________________*/
+
+      multi_parser = new Parser(); //wrapping functions Parser
+      multi_parser.printer(html_source, indent_character, indent_size, max_char, brace_style); //initialize starting values
+
+      while (true) {
+          var t = multi_parser.get_token();
+          multi_parser.token_text = t[0];
+          multi_parser.token_type = t[1];
+
+        if (multi_parser.token_type === 'TK_EOF') {
+          break;
+        }
+
+        switch (multi_parser.token_type) {
+          case 'TK_TAG_START':
+            multi_parser.print_newline(false, multi_parser.output);
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.indent();
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_TAG_STYLE':
+          case 'TK_TAG_SCRIPT':
+            multi_parser.print_newline(false, multi_parser.output);
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_TAG_END':
+            //Print new line only if the tag has no content and has child
+            if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
+                var tag_name = multi_parser.token_text.match(/\w+/)[0];
+                var tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length -1].match(/<\s*(\w+)/);
+                if (tag_extracted_from_last_output === null || tag_extracted_from_last_output[1] !== tag_name) {
+                    multi_parser.print_newline(true, multi_parser.output);
+                }
+            }
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_TAG_SINGLE':
+            // Don't add a newline before elements that should remain unformatted.
+            var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i);
+            if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)){
+                multi_parser.print_newline(false, multi_parser.output);
+            }
+            multi_parser.print_token(multi_parser.token_text);
+            multi_parser.current_mode = 'CONTENT';
+            break;
+          case 'TK_CONTENT':
+            if (multi_parser.token_text !== '') {
+              multi_parser.print_token(multi_parser.token_text);
+            }
+            multi_parser.current_mode = 'TAG';
+            break;
+          case 'TK_STYLE':
+          case 'TK_SCRIPT':
+            if (multi_parser.token_text !== '') {
+              multi_parser.output.push('\n');
+              var text = multi_parser.token_text,
+                  _beautifier,
+                  script_indent_level = 1;
+              if (multi_parser.token_type === 'TK_SCRIPT') {
+                _beautifier = typeof js_beautify === 'function' && js_beautify;
+              } else if (multi_parser.token_type === 'TK_STYLE') {
+                _beautifier = typeof css_beautify === 'function' && css_beautify;
+              }
+
+              if (options.indent_scripts === "keep") {
+                script_indent_level = 0;
+              } else if (options.indent_scripts === "separate") {
+                script_indent_level = -multi_parser.indent_level;
+              }
+
+              var indentation = multi_parser.get_full_indent(script_indent_level);
+              if (_beautifier) {
+                // call the Beautifier if avaliable
+                text = _beautifier(text.replace(/^\s*/, indentation), options);
+              } else {
+                // simply indent the string otherwise
+                var white = text.match(/^\s*/)[0];
+                var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
+                var reindent = multi_parser.get_full_indent(script_indent_level -_level);
+                text = text.replace(/^\s*/, indentation)
+                       .replace(/\r\n|\r|\n/g, '\n' + reindent)
+                       .replace(/\s*$/, '');
+              }
+              if (text) {
+                multi_parser.print_token(text);
+                multi_parser.print_newline(true, multi_parser.output);
+              }
+            }
+            multi_parser.current_mode = 'TAG';
+            break;
+        }
+        multi_parser.last_token = multi_parser.token_type;
+        multi_parser.last_text = multi_parser.token_text;
+      }
+      return multi_parser.output.join('');
+    }
+
+    // If we're running a web page and don't have either of the above, add our one global
+    window.html_beautify = function(html_source, options) {
+        return style_html(html_source, options, window.js_beautify, window.css_beautify);
+    };
+
+}());
\ No newline at end of file
diff --git a/public/js/lib/bootstrap-wysiwyg.js b/public/js/lib/bootstrap-wysiwyg.js
new file mode 100644
index 0000000000..11da013f89
--- /dev/null
+++ b/public/js/lib/bootstrap-wysiwyg.js
@@ -0,0 +1,187 @@
+/* http://github.com/mindmup/bootstrap-wysiwyg */
+/*global jQuery, $, FileReader*/
+/*jslint browser:true*/
+jQuery(function ($) {
+	'use strict';
+	var readFileIntoDataUrl = function (fileInfo) {
+		var loader = $.Deferred(),
+			fReader = new FileReader();
+
+		wn.upload.upload_file(fileInfo, {
+			from_form: 1,
+			doctype: cur_frm.doctype, 
+			docname: cur_frm.docname
+		}, function(fileid, filename, r) {
+			if(!r.exc) {
+				if(fileid) 
+					cur_frm.attachments.update_attachment(fileid, filename);
+				loader.resolve("files/" + filename);
+			}
+		});
+		return loader.promise();
+	};
+	$.fn.cleanHtml = function () {
+		var html = $(this).html();
+		return html && html.replace(/(
|\s|

<\/div>| )*$/, ''); + }; + $.fn.wysiwyg = function (userOptions) { + var editor = this, + selectedRange, + defaultOptions = { + hotKeys: { + 'ctrl+b meta+b': 'bold', + 'ctrl+i meta+i': 'italic', + 'ctrl+u meta+u': 'underline', + 'ctrl+z meta+z': 'undo', + 'ctrl+y meta+y meta+shift+z': 'redo', + 'ctrl+l meta+l': 'justifyleft', + 'ctrl+e meta+e': 'justifycenter', + 'ctrl+j meta+j': 'justifyfull', + 'shift+tab': 'outdent', + 'tab': 'indent' + }, + toolbarSelector: '[data-role=editor-toolbar]', + commandRole: 'edit', + activeToolbarClass: 'btn-info', + selectionMarker: 'edit-focus-marker', + selectionColor: 'darkgrey' + }, + options, + updateToolbar = function () { + if (options.activeToolbarClass) { + $(options.toolbarSelector).find('.btn[data-' + options.commandRole + ']').each(function () { + var command = $(this).data(options.commandRole); + if (document.queryCommandState(command)) { + $(this).addClass(options.activeToolbarClass); + } else { + $(this).removeClass(options.activeToolbarClass); + } + }); + } + }, + execCommand = function (commandWithArgs, valueArg) { + var commandArr = commandWithArgs.split(' '), + command = commandArr.shift(), + args = commandArr.join(' ') + (valueArg || ''); + document.execCommand(command, 0, args); + updateToolbar(); + }, + bindHotkeys = function (hotKeys) { + $.each(hotKeys, function (hotkey, command) { + editor.keydown(hotkey, function (e) { + if (editor.attr('contenteditable') && editor.is(':visible')) { + e.preventDefault(); + e.stopPropagation(); + execCommand(command); + } + }).keyup(hotkey, function (e) { + if (editor.attr('contenteditable') && editor.is(':visible')) { + e.preventDefault(); + e.stopPropagation(); + } + }); + }); + }, + getCurrentRange = function () { + var sel = window.getSelection(); + if (sel.getRangeAt && sel.rangeCount) { + return sel.getRangeAt(0); + } + }, + saveSelection = function () { + selectedRange = getCurrentRange(); + }, + restoreSelection = function () { + var selection = window.getSelection(); + if (selectedRange) { + selection.removeAllRanges(); + selection.addRange(selectedRange); + } + }, + insertFiles = function (files) { + editor.focus(); + $.each(files, function (idx, fileInfo) { + if (/^image\//.test(fileInfo.type)) { + $.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) { + execCommand('insertimage', dataUrl); + }); + } + }); + }, + markSelection = function (input, color) { + restoreSelection(); + document.execCommand('hiliteColor', 0, color || 'transparent'); + saveSelection(); + input.data(options.selectionMarker, color); + }, + bindToolbar = function (toolbar, options) { + toolbar.find('a[data-' + options.commandRole + ']').click(function () { + restoreSelection(); + editor.focus(); + execCommand($(this).data(options.commandRole)); + saveSelection(); + }); + toolbar.find('[data-toggle=dropdown]').click(restoreSelection); + + toolbar.find('input[type=text][data-' + options.commandRole + ']').on('webkitspeechchange change', function () { + var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */ + this.value = ''; + restoreSelection(); + if (newValue) { + editor.focus(); + execCommand($(this).data(options.commandRole), newValue); + } + saveSelection(); + }).on('focus', function () { + var input = $(this); + if (!input.data(options.selectionMarker)) { + markSelection(input, options.selectionColor); + input.focus(); + } + }).on('blur', function () { + var input = $(this); + if (input.data(options.selectionMarker)) { + markSelection(input, false); + } + }); + toolbar.find('input[type=file][data-' + options.commandRole + ']').change(function () { + restoreSelection(); + if (this.type === 'file' && this.files && this.files.length > 0) { + insertFiles(this.files); + } + saveSelection(); + this.value = ''; + }); + }, + initFileDrops = function () { + editor.on('dragenter dragover', false) + .on('drop', function (e) { + var dataTransfer = e.originalEvent.dataTransfer; + e.stopPropagation(); + e.preventDefault(); + if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) { + insertFiles(dataTransfer.files); + } + }); + }; + options = $.extend({}, defaultOptions, userOptions); + bindHotkeys(options.hotKeys); + initFileDrops(); + bindToolbar($(options.toolbarSelector), options); + editor.attr('contenteditable', true) + .on('mouseup keyup mouseout', function () { + saveSelection(); + updateToolbar(); + }); + $(window).bind('touchend', function (e) { + var isInside = (editor.is(e.target) || editor.has(e.target).length > 0), + currentRange = getCurrentRange(), + clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset); + if (!clear || isInside) { + saveSelection(); + updateToolbar(); + } + }); + return this; + }; +}); \ No newline at end of file diff --git a/public/js/lib/jquery/jquery.hotkeys.js b/public/js/lib/jquery/jquery.hotkeys.js new file mode 100644 index 0000000000..5905f9db2a --- /dev/null +++ b/public/js/lib/jquery/jquery.hotkeys.js @@ -0,0 +1,100 @@ +/* + * jQuery Hotkeys Plugin + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * + * Based upon the plugin by Tzury Bar Yochay: + * http://github.com/tzuryby/hotkeys + * + * Original idea by: + * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ +*/ + +(function(jQuery){ + + jQuery.hotkeys = { + version: "0.8", + + specialKeys: { + 8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause", + 20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home", + 37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del", + 96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7", + 104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/", + 112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8", + 120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta" + }, + + shiftNums: { + "`": "~", "1": "!", "2": "@", "3": "#", "4": "$", "5": "%", "6": "^", "7": "&", + "8": "*", "9": "(", "0": ")", "-": "_", "=": "+", ";": ": ", "'": "\"", ",": "<", + ".": ">", "/": "?", "\\": "|" + } + }; + + function keyHandler( handleObj ) { + // Only care when a possible input has been specified + if ( typeof handleObj.data !== "string" ) { + return; + } + + var origHandler = handleObj.handler, + keys = handleObj.data.toLowerCase().split(" "), + textAcceptingInputTypes = ["text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", "datetime-local", "search", "color"]; + + handleObj.handler = function( event ) { + // Don't fire in text-accepting inputs that we didn't directly bind to + if ( this !== event.target && (/textarea|select/i.test( event.target.nodeName ) || + jQuery.inArray(event.target.type, textAcceptingInputTypes) > -1 ) ) { + return; + } + + // Keypress represents characters, not special keys + var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[ event.which ], + character = String.fromCharCode( event.which ).toLowerCase(), + key, modif = "", possible = {}; + + // check combinations (alt|ctrl|shift+anything) + if ( event.altKey && special !== "alt" ) { + modif += "alt+"; + } + + if ( event.ctrlKey && special !== "ctrl" ) { + modif += "ctrl+"; + } + + // TODO: Need to make sure this works consistently across platforms + if ( event.metaKey && !event.ctrlKey && special !== "meta" ) { + modif += "meta+"; + } + + if ( event.shiftKey && special !== "shift" ) { + modif += "shift+"; + } + + if ( special ) { + possible[ modif + special ] = true; + + } else { + possible[ modif + character ] = true; + possible[ modif + jQuery.hotkeys.shiftNums[ character ] ] = true; + + // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" + if ( modif === "shift+" ) { + possible[ jQuery.hotkeys.shiftNums[ character ] ] = true; + } + } + + for ( var i = 0, l = keys.length; i < l; i++ ) { + if ( possible[ keys[i] ] ) { + return origHandler.apply( this, arguments ); + } + } + }; + } + + jQuery.each([ "keydown", "keyup", "keypress" ], function() { + jQuery.event.special[ this ] = { add: keyHandler }; + }); + +})( jQuery ); \ No newline at end of file diff --git a/public/js/wn/form/attachments.js b/public/js/wn/form/attachments.js index 734393293d..cb9788ac82 100644 --- a/public/js/wn/form/attachments.js +++ b/public/js/wn/form/attachments.js @@ -41,7 +41,7 @@ wn.ui.form.Attachments = Class.extend({ }, max_reached: function() { // no of attachments - var n = this.frm.doc.file_list ? this.frm.doc.file_list.split('\n').length : 0; + var n = keys(this.get_file_list()).length; // button if the number of attachments is less than max if(n < this.frm.meta.max_attachments || !this.frm.meta.max_attachments) { @@ -50,7 +50,8 @@ wn.ui.form.Attachments = Class.extend({ return true; }, refresh: function() { - if(this.frm.doc.__islocal || !this.frm.meta.allow_attach) { + var doc = this.frm.doc; + if(doc.__islocal || !this.frm.meta.allow_attach) { this.parent.toggle(false); return; } @@ -59,20 +60,19 @@ wn.ui.form.Attachments = Class.extend({ this.$list.empty(); - var fl = this.get_filelist(); + var file_list = this.get_file_list(); + var file_names = keys(file_list).sort(); // add attachment objects - for(var i=0; i\ +
\ +
\ + \ + \ +
\ +
\ + \ + \ +
\ +
\ + \ + \ + \ +
\ +
\ + \ + \ + \ + \ +
\ +
\ + \ + \ +
\ +
\ + \ + \ + -\ +
\ +
\ + \ + \ +
\ +
\ +
\ +
\ +
\ + \ +
\ +
\ + \ + \ +
\ +
\ + ').appendTo(this.opts.parent); + this.$parent = $(this.opts.parent); + this.$editor = $("#" + this.myid) + this.$textarea = this.$parent.find(".html-editor"); + this.input = this.$editor.get(0); + }, + make_bindings: function() { + var me = this; + var fonts = ['Serif', 'Sans', 'Arial', 'Arial Black', 'Courier', + 'Courier New', 'Comic Sans MS', 'Helvetica', 'Impact', 'Lucida Grande', + 'Lucida Sans', 'Tahoma', 'Times', 'Times New Roman', 'Verdana'], + fontTarget = this.$parent.find('[title=Font]').siblings('.dropdown-menu'); + + $.each(fonts, function (idx, fontName) { + fontTarget.append($('
  • '+ + fontName + '
  • ')); + }); + + //this.$parent.find('a[title]').tooltip({container:'body'}); + + this.$parent.find('.dropdown-menu input').click(function() {return false;}) + .change(function () { + $(this).parent('.dropdown-menu').siblings('.dropdown-toggle') + .dropdown('toggle'); + }) + .keydown('esc', function () { + this.value='';$(this).change(); + }); + + // magic-overlay + this.$parent.find('[data-role=magic-overlay]').each(function () { + var overlay = $(this), target = $(overlay.data('target')); + overlay.css('opacity', 0).css('position', 'absolute') + .offset(target.offset()) + .width(40).height(30); + }); + + this.$editor + .wysiwyg() + .on("mouseup keyup mouseout", function() { + var value = $(this).html(); + if(value==null) value=""; + me.opts.change(value); + }) + this.$textarea + .on("change", function() { + var value = $(this).val(); + if(value==null) value=""; + me.opts.change(value); + }); + + this.current_editor = this.$editor; + this.$parent.find(".btn-html").click(function() { + if($(this).attr("disabled")=="disabled") return; + wn.require("lib/js/lib/beautify-html.js"); + me.$textarea.val(html_beautify(me.$editor.cleanHtml())); + me.$parent.find(".for-rich-text").toggle(false); + me.$parent.find(".for-html").toggle(true); + me.$parent.find(".btn-html").addClass("btn-info").attr("disabled", "disabled"); + me.$parent.find(".btn-rich-text").removeClass("btn-info").attr("disabled", false); + me.current_editor = me.$textarea; + }); + + this.$parent.find(".btn-rich-text").click(function() { + if($(this).attr("disabled")=="disabled") return; + me.$editor.html(me.$textarea.val()); + me.$parent.find(".for-rich-text").toggle(true); + me.$parent.find(".for-html").toggle(false); + me.$parent.find(".btn-html").removeClass("btn-info").attr("disabled", false); + me.$parent.find(".btn-rich-text").addClass("btn-info").attr("disabled", "disabled"); + me.current_editor = me.$editor; + }); + + }, + set_input: function(value) { + if(this.value!=value) { + this.value = value==null ? "" : value; + this.$editor.html(this.value); + this.$textarea.val(this.value); + } + }, + get_value: function() { + if(this.current_editor==this.$editor) + return this.$editor.cleanHtml(); + else + return this.$textarea.val(); + } +}) + +//// TinyMCE + +wn.editors.TinyMCE = Class.extend({ + init: function(opts) { + this.opts = opts; + this.make(); + }, + make: function() { + var me = this; + this.input = $("