From 73e94d3f975ba8cc591ebdaf9d02d2c5d22f4ecc Mon Sep 17 00:00:00 2001 From: Julian Viereck Date: Tue, 15 Feb 2011 06:24:59 +0800 Subject: [PATCH] Add bookmarklet/textarea build --- Makefile.dryice.textarea.js | 194 +++++++++++ build_support/boot_textarea.js | 459 +++++++++++++++++++++++++ build_support/editor_textarea.html | 95 +++++ build_support/mini_require_textarea.js | 128 +++++++ lib/ace/edit_session.js | 16 +- 5 files changed, 884 insertions(+), 8 deletions(-) create mode 100755 Makefile.dryice.textarea.js create mode 100644 build_support/boot_textarea.js create mode 100644 build_support/editor_textarea.html create mode 100644 build_support/mini_require_textarea.js diff --git a/Makefile.dryice.textarea.js b/Makefile.dryice.textarea.js new file mode 100755 index 00000000..d7c337bc --- /dev/null +++ b/Makefile.dryice.textarea.js @@ -0,0 +1,194 @@ +#!/usr/bin/env node +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Fabian Jakobs + * Julian Viereck + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +var copy = require('dryice').copy; + +var aceHome = __dirname; + +function shadow(input) { + console.log("shadow input"); + if (typeof input !== 'string') { + input = input.toString(); + } + + return input.replace(/define\(/g, "__ace_shadowed__.define("); +} + +console.log('# ace ---------'); + +var project = copy.createCommonJsProject([ + aceHome + '/support/pilot/lib', + aceHome + '/lib' +]); + +copy({ + source: "build_support/editor_textarea.html", + dest: 'build/editor.html' +}); + +var ace = copy.createDataObject(); +copy({ + source: [ + 'build_support/mini_require_textarea.js' + ], + dest: ace +}); +copy({ + source: [ + copy.source.commonjs({ + project: project, + require: [ + "pilot/fixoldbrowsers", + "pilot/index", + "pilot/plugin_manager", + "pilot/environment", + "ace/editor", + "ace/edit_session", + "ace/undomanager", + "ace/theme/textmate", + "ace/mode/text", + "ace/mode/matching_brace_outdent", + "ace/virtual_renderer" + ] + }) + ], + filter: [ copy.filter.moduleDefines ], + dest: ace +}); +copy({ + source: { + root: project, + include: /.*\.css$|.*\.html$/, + exclude: /tests?\// + }, + filter: [ copy.filter.addDefines ], + dest: ace +}); +copy({ + source: { + root: project, + include: /.*\.png$|.*\.gif$/, + exclude: /tests?\// + }, + filter: [ copy.filter.base64 ], + dest: ace +}); +copy({ + source: [ + 'build_support/boot_textarea.js' + ], + dest: ace +}); + +// Create the compressed and uncompressed output files +copy({ + source: ace, + filter: [ + shadow, + copy.filter.uglifyjs + ], + dest: 'build/src/ace.js' +}); +copy({ + source: ace, + filter: [ + shadow, + ], + dest: 'build/src/ace-uncompressed.js' +}); + +console.log('# ace modes ---------'); + +// create modes +project.assumeAllFilesLoaded(); +["css", "html", "javascript", "php", "python", "xml", "ruby", "java", "c_cpp", "coffee"].forEach(function(mode) { + console.log("mode " + mode); + copy({ + source: [ + copy.source.commonjs({ + project: project.clone(), + require: [ 'ace/mode/' + mode ] + }) + ], + filter: [ + copy.filter.moduleDefines, + shadow, + copy.filter.uglifyjs + ], + dest: "build/src/mode-" + mode + ".js" + }); +}); + +console.log('# ace themes ---------'); + +// create themes +[ + "clouds", "clouds_midnight", "cobalt", "dawn", "idle_fingers", "kr_theme", + "mono_industrial", "monokai", "pastel_on_dark", "twilight" +].forEach(function(theme) { + console.log("theme " + theme); + copy({ + source: [{ + root: aceHome + '/lib', + include: "ace/theme/" + theme + ".js" + }], + filter: [ + copy.filter.moduleDefines, + shadow, + copy.filter.uglifyjs + ], + dest: "build/src/theme-" + theme + ".js" + }); +}); + +console.log('# License | Readme | Changelog ---------'); + +// copy text files +copy({ + source: aceHome + "/LICENSE", + dest: 'build/LICENSE' +}); +copy({ + source: aceHome + "/Readme.md", + dest: 'build/Readme.md' +}); +copy({ + source: aceHome + "/ChangeLog.txt", + dest: 'build/ChangeLog.txt' +}); diff --git a/build_support/boot_textarea.js b/build_support/boot_textarea.js new file mode 100644 index 00000000..56e272df --- /dev/null +++ b/build_support/boot_textarea.js @@ -0,0 +1,459 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Skywriter. + * + * The Initial Developer of the Original Code is + * Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Kevin Dangoor (kdangoor@mozilla.com) + * Julian Viereck + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +(function() { + +var require = window.__ace_shadowed__.require; +var deps = [ + "pilot/fixoldbrowsers", + "pilot/index", + "pilot/plugin_manager", + "pilot/environment", + "ace/editor", + "ace/edit_session", + "ace/virtual_renderer", + "ace/undomanager", + "ace/theme/textmate" +]; + +require(deps, function() { + var catalog = require("pilot/plugin_manager").catalog; + catalog.registerPlugins([ "pilot/index" ]); + + var Dom = require("pilot/dom"); + var Event = require("pilot/event"); + + var Editor = require("ace/editor").Editor; + var EditSession = require("ace/edit_session").EditSession; + var UndoManager = require("ace/undomanager").UndoManager; + var Renderer = require("ace/virtual_renderer").VirtualRenderer; + + window.__ace_shadowed__.edit = function(el) { + if (typeof(el) == "string") { + el = document.getElementById(el); + } + + var doc = new EditSession(Dom.getInnerText(el)); + doc.setUndoManager(new UndoManager()); + el.innerHTML = ''; + + var editor = new Editor(new Renderer(el, "ace/theme/textmate")); + editor.setSession(doc); + + var env = require("pilot/environment").create(); + catalog.startupPlugins({ env: env }).then(function() { + env.document = doc; + env.editor = env; + editor.resize(); + Event.addListener(window, "resize", function() { + editor.resize(); + }); + el.env = env; + }); + return editor; + } + + if (window.__ace_shadowed_loaded__) { + window.__ace_shadowed_loaded__(); + } +}); + +/** + * Returns the CSS property of element. + * 1) If the CSS property is on the style object of the element, use it, OR + * 2) Compute the CSS property + * + * If the property can't get computed, is 'auto' or 'intrinsic', the former + * calculated property is uesd (this can happen in cases where the textarea + * is hidden and has no dimension styles). + */ +var getCSSProperty = function(element, container, property) { + var ret = element.style[property] + || document.defaultView.getComputedStyle(element, ''). + getPropertyValue(property); + + if (!ret || ret == 'auto' || ret == 'intrinsic') { + ret = container.style[property]; + } + return ret; +}; + +function applyStyles(elm, styles) { + for (style in styles) { + elm.style[style] = styles[style]; + } +} + +function setupContainer(element, getValue) { + if (element.type != 'textarea') { + throw "Textarea required!"; + } + + var parentNode = element.parentNode; + + // This will hold the Bespin editor. + var container = document.createElement('div'); + + // To put Bespin in the place of the textarea, we have to copy a + // few of the textarea's style attributes to the div container. + // + // The problem is, that the properties have to get computed (they + // might be defined by a CSS file on the page - you can't access + // such rules that apply to an element via elm.style). Computed + // properties are converted to pixels although the dimension might + // be given as percentage. When the window resizes, the dimensions + // defined by percentages changes, so the properties have to get + // recomputed to get the new/true pixels. + var resizeEvent = function() { + var style = 'position:relative;'; + [ + 'margin-top', 'margin-left', 'margin-right', 'margin-bottom' + ].forEach(function(item) { + style += item + ':' + + getCSSProperty(element, container, item) + ';'; + }); + + // Calculating the width/height of the textarea is somewhat + // tricky. To do it right, you have to include the paddings + // to the sides as well (eg. width = width + padding-left, -right). + // This works well, as long as the width of the element is not + // set or given in pixels. In this case and after the textarea + // is hidden, getCSSProperty(element, container, 'width') will + // still return pixel value. If the element has realtiv dimensions + // (e.g. width='95') getCSSProperty(...) will return pixel values + // only as long as the textarea is visible. After it is hidden + // getCSSProperty will return the relativ dimensions as they + // are set on the element (in the case of width, 95). + // Making the sum of pixel vaules (e.g. padding) and realtive + // values (e.g. ) is not possible. As such the padding styles + // are ignored. + + // The complete width is the width of the textarea + the padding + // to the left and right. + var width = getCSSProperty(element, container, 'width'); + var height = getCSSProperty(element, container, 'height'); + style += 'height:' + height + ';width:' + width + ';'; + + // Set the display property to 'inline-block'. + style += 'display:inline-block;'; + container.setAttribute('style', style); + }; + window.addEventListener('resize', resizeEvent, false); + + // Call the resizeEvent once, so that the size of the container is + // calculated. + resizeEvent(); + + // Insert the div container after the element. + if (element.nextSibling) { + parentNode.insertBefore(container, element.nextSibling); + } else { + parentNode.appendChild(container); + } + + // Override the forms onsubmit function. Set the innerHTML and value + // of the textarea before submitting. + while (parentNode !== document) { + if (parentNode.tagName.toUpperCase() === 'FORM') { + var oldSumit = parentNode.onsubmit; + // Override the onsubmit function of the form. + parentNode.onsubmit = function(evt) { + element.value = getValue(); + element.innerHTML = getValue(); + // If there is a onsubmit function already, then call + // it with the current context and pass the event. + if (oldSumit) { + oldSumit.call(this, evt); + } + } + break; + } + parentNode = parentNode.parentNode; + } + return container; +} + +window.__ace_shadowed__.transformTextarea = function(element) { + var session; + var container = setupContainer(element, function() { + return session.getValue(); + }); + + // Hide the element. + element.style.display = 'none'; + container.style.background = 'white'; + + // + var editorDiv = document.createElement("div"); + applyStyles(editorDiv, { + top: "0px", + left: "0px", + right: "0px", + bottom: "0px" + }); + container.appendChild(editorDiv); + + var settingOpener = document.createElement("div"); + applyStyles(settingOpener, { + position: "absolute", + width: "15px", + right: "0px", + bottom: "0px", + background: "red", + cursor: "pointer", + textAlign: "center", + fontSize: "12px" + }); + settingOpener.innerHTML = "I"; + + var settingDiv = document.createElement("div"); + applyStyles(settingDiv, { + top: "0px", + left: "0px", + right: "0px", + bottom: "0px", + position: "absolute", + padding: "5px", + background: "rgba(0, 0, 0, 0.6)", + zIndex: 100, + color: "white", + display: "none" + }); + container.appendChild(settingDiv); + + // Power up ace on the textarea: + var ace = window.__ace_shadowed__; + var require = ace.require; + var define = ace.define; + var options = {}; + + var editor = ace.edit(editorDiv); + session = editor.getSession(); + + session.setValue(element.value || element.innerHTML); + editor.focus(); + + // Add the settingPanel opener to the editor's div. + editorDiv.appendChild(settingOpener); + + // Create the API. + var api = setupApi(editor, editorDiv, settingDiv, ace, options) + + // Create the setting's panel. + setupSettingPanel(settingDiv, settingOpener, api, options); + + return api; +} + +function setupApi(editor, editorDiv, settingDiv, ace, options) { + var load = ace.load; + var session = editor.getSession(); + var renderer = editor.renderer; + + function toBool(value) { + return value == "true"; + } + + var ret = { + setDisplaySettings: function(display) { + settingDiv.style.display = display ? "block" : "none"; + }, + + setOption: function(key, value) { + if (options[key] == value) return; + + switch (key) { + case "gutter": + renderer.setShowGutter(toBool(value)); + break; + + case "mode": + if (value != "text") { + // Load the required mode file. Files get loaded only once. + load("mode-" + value + ".js", function() { + var aceMode = require("ace/mode/" + value).Mode; + session.setMode(new aceMode()); + }); + } else { + session.setMode(new (require("ace/mode/text").Mode)); + } + break; + + case "theme": + if (value != "textmate") { + // Load the required theme file. Files get loaded only once. + load("theme-" + value + ".js", function() { + editor.setTheme("ace/theme/" + value); + }); + } else { + editor.setTheme("ace/theme/textmate"); + } + break; + + case "fontSize": + editorDiv.style.fontSize = value; + break; + } + + options[key] = value; + }, + + getOption: function(key) { + return options[key]; + }, + + getOptions: function() { + return options; + } + } + + for (option in ace.options) { + ret.setOption(option, ace.options[option]); + } + + return ret; +} + +function setupSettingPanel(settingDiv, settingOpener, api, options) { + var BOOL = { + "true": true, + "false": false + } + + var desc = { + mode: "Mode:", + gutter: "Display Gutter:", + theme: "Theme:", + fontSize: "Font Size:" + } + + var optionValues = { + mode: { + text: "Plain", + javascript: "JavaScript", + coffee: "CoffeeScript", + html: "HTML", + css: "CSS", + c_cpp: "C++", + php: "PHP", + ruby: "Ruby", + python: "Python" + + }, + theme: { + textmate: "Textmate", + eclipse: "Eclipse", + clouds: "Clouds", + clouds_midnight: "Clouds Midnight", + cobalt: "Cobalt", + dawn: "Dawn", + idle_fingers: "Idle Fingers", + kr_theme: "Kr Theme", + mono_industrial: "Mono Industrial", + monokai: "Monokai", + pastel_on_dark: "Pastel On Dark", + twilight: "Twilight", + }, + gutter: BOOL, + fontSize: { + "10px": "10px", + "12px": "12px", + "14px": "14px", + "16px": "16px" + } + } + + var table = []; + table.push(""); + + function renderOption(builder, option, obj, cValue) { + builder.push("") + } + + for (var option in options) { + table.push(""); + table.push(""); + } + table.push("
SettingValue
", desc[option], ""); + renderOption(table, option, optionValues[option], options[option]); + table.push("
"); + settingDiv.innerHTML = table.join(""); + + var selects = settingDiv.querySelectorAll("select"); + for (var i = 0; i < selects.length; i++) { + selects[i].onchange = function(e) { + var option = e.target.title; + var value = e.target.value; + api.setOption(option, value); + } + } + + var button = document.createElement("input"); + button.type = "button"; + button.value = "Hide"; + button.onclick = function() { + api.setDisplaySettings(false); + } + settingDiv.appendChild(button); + + settingOpener.onclick = function() { + api.setDisplaySettings(true); + } +} + +// Default startup options. +window.__ace_shadowed__.options = { + mode: "text", + theme: "textmate", + gutter: "false", + fontSize: "12px" +} + +})() diff --git a/build_support/editor_textarea.html b/build_support/editor_textarea.html new file mode 100644 index 00000000..1a5bf4a1 --- /dev/null +++ b/build_support/editor_textarea.html @@ -0,0 +1,95 @@ + + + + + + Editor + + + +
+SourceUrl: + +
+ + + + + diff --git a/build_support/mini_require_textarea.js b/build_support/mini_require_textarea.js new file mode 100644 index 00000000..9161bcf6 --- /dev/null +++ b/build_support/mini_require_textarea.js @@ -0,0 +1,128 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Ajax.org Code Editor (ACE). + * + * The Initial Developer of the Original Code is + * Ajax.org B.V. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Fabian Jakobs + * Julian Viereck + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Define a module along with a payload + * @param module a name for the payload + * @param payload a function to call with (require, exports, module) params + */ + +(function() { + +var _define = function(module, payload) { + if (typeof module !== 'string') { + if (_define.original) + _define.original.apply(window, arguments); + else { + console.error('dropping module because define wasn\'t a string.'); + console.trace(); + } + return; + } + + if (!_define.modules) + _define.modules = {}; + + _define.modules[module] = payload; +}; + +/** + * Get at functionality define()ed using the function above + */ +var _require = function(module, callback) { + if (Object.prototype.toString.call(module) === "[object Array]") { + var params = []; + for (var i = 0, l = module.length; i < l; ++i) { + var dep = lookup(module[i]); + if (!dep && _require.original) + return _require.original.apply(window, arguments); + params.push(dep); + }; + if (callback) { + callback.apply(null, params); + } + } + + if (typeof module === 'string') { + var payload = lookup(module); + if (!payload && _require.original) + return _require.original.apply(window, arguments); + + if (callback) { + callback(); + } + + return payload; + }; +} + +_require.packaged = true; +_require.noWorker = true; + +/** + * Internal function to lookup moduleNames and resolve them by calling the + * definition function if needed. + */ +var lookup = function(moduleName) { + var module = _define.modules[moduleName]; + if (module == null) { + console.error('Missing module: ' + moduleName); + return null; + } + + if (typeof module === 'function') { + var exports = {}; + module(_require, exports, { id: moduleName, uri: '' }); + // cache the resulting module object for next time + _define.modules[moduleName] = exports; + return exports; + } + + return module; +}; + +/** + * Expose as "shadowed" object to the outside world. + */ + +window.__ace_shadowed__ = { + require: _require, + define: _define +}; + +})(); \ No newline at end of file diff --git a/lib/ace/edit_session.js b/lib/ace/edit_session.js index eeb9feef..f7ffd251 100644 --- a/lib/ace/edit_session.js +++ b/lib/ace/edit_session.js @@ -209,7 +209,7 @@ var EditSession = function(text, mode) { this.addMarker = function(range, clazz, type, inFront) { var id = this.$markerId++; - + var marker = { range : range, type : type || "line", @@ -217,7 +217,7 @@ var EditSession = function(text, mode) { clazz : clazz, inFront: !!inFront } - + if (inFront) { this.$frontMarkers[id] = marker; this._dispatchEvent("changeFrontMarker") @@ -225,26 +225,26 @@ var EditSession = function(text, mode) { this.$backMarkers[id] = marker; this._dispatchEvent("changeBackMarker") } - + return id; }; - + this.removeMarker = function(markerId) { var marker = this.$frontMarkers[markerId] || this.$backMarkers[markerId]; if (!marker) return; - + var markers = marker.inFront ? this.$frontMarkers : this.$backMarkers; if (marker) { delete (markers[markerId]); this._dispatchEvent(marker.inFront ? "changeFrontMarker" : "changeBackMarker"); } }; - + this.getMarkers = function(inFront) { return inFront ? this.$frontMarkers : this.$backMarkers; }; - + /** * Error: * { @@ -334,7 +334,7 @@ var EditSession = function(text, mode) { if (this.$worker) this.$worker.terminate(); - if (window.Worker) + if (window.Worker && !require.noWorker) this.$worker = mode.createWorker(this); else this.$worker = null;