├── .gitignore ├── .gitmodules ├── CHANGELOG.markdown ├── Info.plist ├── Jakefile ├── LICENSE ├── README.markdown ├── Resources └── WKTextView │ └── editor.html ├── WKTextView.j ├── auxiliary ├── build.sh └── closure-editor-requirements.js ├── sample ├── .gitignore ├── AppController.j ├── Info.plist ├── Jakefile ├── Resources │ ├── WKTextView │ ├── silk │ │ ├── comment.png │ │ ├── comments.png │ │ ├── link.png │ │ ├── link_break.png │ │ ├── page.png │ │ ├── page_add.png │ │ ├── page_edit.png │ │ ├── page_white.png │ │ ├── page_white_add.png │ │ ├── page_white_edit.png │ │ ├── page_white_text.png │ │ ├── page_white_world.png │ │ ├── picture.png │ │ ├── text_align_center.png │ │ ├── text_align_justify.png │ │ ├── text_align_left.png │ │ ├── text_align_right.png │ │ ├── text_bold.png │ │ ├── text_italic.png │ │ ├── text_list_bullets.png │ │ ├── text_list_numbers.png │ │ ├── text_strikethrough.png │ │ ├── text_underline.png │ │ └── world_link.png │ └── spinner.gif ├── index-debug.html ├── index.html └── main.j └── samplify.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .DS_Store 3 | sample.dist 4 | Resources/WKTextView/closure-editor.js 5 | Build 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "auxiliary/google-closure-library"] 2 | path = auxiliary/google-closure-library 3 | url = git://github.com/aljungberg/google-closure-library.git 4 | -------------------------------------------------------------------------------- /CHANGELOG.markdown: -------------------------------------------------------------------------------- 1 | WKTextView Changelog 2 | ==================== 3 | 4 | # Version 0.5 (February 22nd, 2012) 5 | 6 | ## New Features 7 | * Switched to Google Closure editor. 8 | * New scrolling method: use native scrollbars so that the browser does all the work tracking the cursor position and scrolling to follow it. This makes for a very native like scrollbar experience. A faux Cappuccino scrollbar is rendered on top to maintain the Cappuccino look and feel. 9 | * Allow any background colour. 10 | * Option to auto-hide scrollbars. 11 | 12 | ## Changes and Fixes 13 | * Fixed: in Firefox, setting the view value would fire a "cursor did move" event but getSelection would return null leading to an exception. 14 | * Editor padding and margin support has been added back in. (The 0.4 solution to add padding around the whole editor made the scrollbar appear inside of the margin.) 15 | * Fixed get method for font and added getter for font size and color. 16 | * Improved "cursor did move" and scrolling updates, which sometimes would get one step behind. 17 | * Fixed some scrollbar flickering when animating text view resizes. 18 | * Expand text area to at least fill the height of the editor frame to allow a click anywhere to start editing. Also made fontName safe to call before full initialisation. 19 | * textViewDidBeginEditing and textViewDidEndEditing support. 20 | * Fixed: 'field is already uneditable' error and an error caused by a premature resize before the editor fully loaded. 21 | * Explicitly place the cursor at the start of the content when the editor activates after a reset or text change. This allows the editor to be activated by tabbing to it, something which before would result in the editor activating but not showing any cursor. 22 | * Fixed: various Firefox crashes when trying to manipulate a hidden editor. 23 | * Fixed: with Firefox, clicking the editor did not make it the first responder. 24 | * Fixed: the checkLoad timer would keep firing when a text view was hidden, burning CPU needlessly. Now only fire the check once and only repeat it if the view is actually visible and hence has a chance of loading any time soon. 25 | * Fixed: `editor.html` had to be placed in the main Resources folder of an app including WKTextView. It is now properly loaded from the WKTextView Framework folder. 26 | * Fixed: "unknown variable CPTimer" error. 27 | * Fixed: failure if the user sets the editor alue immediately after intialising an off-screen view. 28 | * IE specific fixes. 29 | 30 | # Version 0.4 (May 1st, 2010) 31 | 32 | ## New Features 33 | * Upgraded WysiHat. This version ups the minimum requirements to IE 7, Firefox 3, Safari 4 or Chrome 4. WKTextView was updated to use WysiHat as a contentEditable div instead of an iframe. 34 | * Implemented setEnabled/isEnabled. When disabled the editor does not display a text edit cursor; it does not listen to mouse events, etc. 35 | * Support for editor padding was removed since it too was a source of jumping bugs, although on a smaller scale. If you need padding just add it around the editor instead. 36 | * Jakefile to create debug and release builds (`jake debug` and `jake release`). 37 | * setFontSizeForSelection. 38 | * setColorForSelection. 39 | 40 | ## Changes and Fixes 41 | * WKTextView can now become the first responder. Tabbing out of the field is possible with Shift-Tab, the standard key for tabbing backwards. Tab forward is not possible to allow the tab character to be written. 42 | * Fixed: clicking on a WKTextView would cause it to gain focus without requesting first responder status. The most visible artifact of this was that tabbing out of the view if it was activated by mouse didn't work as expected because Cappuccino didn't know the view was selected to begin with. 43 | * Activating an editor by clicking on it now makes its window the key window. 44 | * Backgrounds underneath WKTextView now shine through. 45 | * Fixed: some variables leaking into the global scope. 46 | * Fixed: uninitialized height could cause a race condition in Webkit. 47 | * Fixed: in Firefox, clicking on the WKTextView did not make it the first responder, and shift tab did not back tab out of the editor. 48 | * Renamed setFont to setFontNameForSelection. 49 | * Content height calculations are much improved and it looks like most of the jumping bugs (hitting enter to insert a new row while at the last row in a text taller than the view height) are resolved. Performance might also be improved. 50 | * Removed some left over logging. 51 | 52 | # Version 0.3 (January 13th, 2010) 53 | 54 | ## New Features 55 | * New delegate method textViewDidChange. 56 | 57 | ## Changes and Fixes 58 | * Fixed: text views with a vertical scrollbar would have 15 of their rightmost pixels missing until the window was first resized. 59 | * Fixed: sometimes a text select would be followed by an immediate deselect; clicking with Safari could result in text being reflowed; moving the cursor in Opera back and forth lead to spaces being inserted at the cursor position. 60 | * Fixed: the wysihat submodule pointed to the non public github repository. 61 | 62 | # Version 0.2 (December 2nd, 2009) 63 | 64 | ## New Features 65 | * Support for insert image, bullets, ordered lists, font selection, link, unlink, strikethrough and text alignment. 66 | * The current font can now be read from the editor with the font method. 67 | * New delegate method textViewCursorDidMove:. 68 | * Added a setTextValue: method. 69 | 70 | ## Changes and Fixes 71 | * Fixed: editor height calculation didn't work in Firefox. The replacement code is also more elegant and could be more efficient for large amounts of text. 72 | * Fixed: deleting or inserting text would not cause the scrollbar to update without first mousing over it. 73 | * Workaround for focus lost when the toolbar is clicked: the new option setShouldFocusAfterAction: makes the editor automatically focus after actions such as boldSelection. 74 | * Fixed: textViewDidLoad is not sent until the WysiHat ready flag is set. This resolves some problems where content could not be set in Firefox immediately in a textViewDidLoad callback. 75 | * Wysihat now available as a submodule, making it easier to grab the right hacked version. 76 | * Basic support for the editor scrolling automatically to follow the cursor. 77 | * Wyzihat is now correctly spelled Wysihat in most places. 78 | 79 | # Version 0.1 (November 27) 80 | 81 | * First release. 82 | -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CPBundleIdentifier 6 | net.wireload.WKTextView 7 | CPBundleInfoDictionaryVersion 8 | 6.0 9 | CPBundleName 10 | WKTextView 11 | CPBundlePackageType 12 | FMWK 13 | CPBundleVersion 14 | 0.1 15 | 16 | 17 | -------------------------------------------------------------------------------- /Jakefile: -------------------------------------------------------------------------------- 1 | /* 2 | * Jakefile 3 | * WKTextView 4 | * 5 | * Created by Alexander Ljungberg on April 14, 2010. 6 | * Copyright 2010, WireLoad, LLC All rights reserved. 7 | */ 8 | 9 | var ENV = require("system").env, 10 | FILE = require("file"), 11 | JAKE = require("jake"), 12 | task = JAKE.task, 13 | FileList = JAKE.FileList, 14 | app = require("cappuccino/jake").app, 15 | configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug", 16 | OS = require("os"); 17 | 18 | app ("wyzihatkit", function(task) 19 | { 20 | task.setBuildIntermediatesPath(FILE.join("Build", "wyzihatkit.build", configuration)); 21 | task.setBuildPath(FILE.join("Build", configuration)); 22 | 23 | task.setProductName("WKTextView"); 24 | task.setIdentifier("wyzihatkit"); 25 | task.setVersion("1.0"); 26 | task.setAuthor("Alexander Ljungberg"); 27 | task.setEmail("aljungberg@wireload.net"); 28 | task.setSummary("WKTextView"); 29 | task.setSources((new FileList("**/*.j")).exclude(FILE.join("Build", "**")).exclude(FILE.join("sample", "**")).exclude(FILE.join("sample.dist", "**"))); 30 | task.setResources(new FileList("Resources/**")); 31 | task.setInfoPlistPath("Info.plist"); 32 | 33 | if (configuration === "Debug") 34 | task.setCompilerFlags("-DDEBUG -g"); 35 | else 36 | task.setCompilerFlags("-O"); 37 | }); 38 | 39 | function printResults(configuration) 40 | { 41 | print("----------------------------"); 42 | print(configuration+" app built at path: "+FILE.join("Build", configuration, "WKTextView")); 43 | print("----------------------------"); 44 | } 45 | 46 | task ("default", ["wyzihatkit"], function() 47 | { 48 | printResults(configuration); 49 | }); 50 | 51 | task ("build", ["default"]); 52 | 53 | task ("debug", function() 54 | { 55 | ENV["CONFIGURATION"] = "Debug"; 56 | JAKE.subjake(["."], "build", ENV); 57 | }); 58 | 59 | task ("release", function() 60 | { 61 | ENV["CONFIGURATION"] = "Release"; 62 | JAKE.subjake(["."], "build", ENV); 63 | }); 64 | 65 | task ("run", ["debug"], function() 66 | { 67 | OS.system(["open", FILE.join("Build", "Debug", "wyzihatkit", "index.html")]); 68 | }); 69 | 70 | task ("run-release", ["release"], function() 71 | { 72 | OS.system(["open", FILE.join("Build", "Release", "wyzihatkit", "index.html")]); 73 | }); 74 | 75 | task ("deploy", ["release"], function() 76 | { 77 | FILE.mkdirs(FILE.join("Build", "Deployment", "wyzihatkit")); 78 | OS.system(["press", "-f", FILE.join("Build", "Release", "wyzihatkit"), FILE.join("Build", "Deployment", "wyzihatkit")]); 79 | printResults("Deployment") 80 | }); 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2009 Alexander Ljungberg and Harry Vangberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | WKTextView 2 | ========== 3 | 4 | A [Cappuccino](http://cappuccino.org/) control providing rich text editing through use of a browser driving content editable field. Under the hood, the [Google Closure Library](http://code.google.com/closure/library/) editor is used. 5 | 6 | Until a proper `CPTextView` is written this will probably be one of the most full featured rich text editor for Cappuccino. 7 | 8 | You can view an online demo [here](http://wireload.net/open_source/wktextview-sample/index.html). 9 | 10 | ## Features 11 | 12 | * Bold, italics, underline, strike through. 13 | * Left, right, center and justify alignment. 14 | * Bulleted and numbered lists. 15 | * Links. 16 | * Images. 17 | * Fonts. 18 | * Outputs regular HTML. 19 | * Vaguely resembles a proper CPTextView in its API. 20 | 21 | ## Installation 22 | 23 | Link the `WKTextView` folder into your `Frameworks` folder. 24 | 25 | Create and combine the `Resources/WKTextView/closure-editor.js` file using the WKTextView modified version of the Closure editor: 26 | 27 | git submodule init 28 | git submodule update 29 | cd auxiliary 30 | # Edit build.sh to provide the correct path to closure.jar. 31 | sh build.sh 32 | 33 | ## Usage 34 | 35 | textView = [[WKTextView alloc] initWithFrame:effectiveFrame]; 36 | [textView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; 37 | [textView setDelegate:self]; 38 | 39 | The view needs to load resources such as `editor.html` and `closure-editor.js`. Wait for the `textViewDidLoad:` delegate call before using. 40 | 41 | ## Sample 42 | 43 | A sample program is provided in the `sample` folder. To compile and view, run the `samplify.sh` script, then open up `index-debug.html` in a browser: 44 | 45 | sh samplify.sh 46 | open sample.dist/index-debug.html 47 | 48 | # License 49 | 50 | WKTextView is released under the Apache License 2.0. The sample incorporates Creative Commons icons from [FamFamFam](http://www.famfamfam.com/lab/icons/silk/). 51 | 52 | # Authors 53 | 54 | * Alexander Ljungberg, [WireLoad Inc](http://wireload.net) 55 | * Evadne Wu 56 | * Klaas Pieter Annema 57 | * Paul Baumgart 58 | * xanados 59 | 60 | ## Thanks to 61 | 62 | * Harry Vangberg 63 | -------------------------------------------------------------------------------- /Resources/WKTextView/editor.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | WYSIWYG 6 | 7 | 45 | 46 | 47 | 48 | 49 | 50 |
51 |
52 |
53 |
54 |
55 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /WKTextView.j: -------------------------------------------------------------------------------- 1 | /* 2 | * WKTextView.j 3 | * WKTextView 4 | * 5 | * Created by Alexander Ljungberg, WireLoad Inc. 6 | */ 7 | 8 | @import 9 | @import 10 | 11 | @implementation _WKWebView : CPWebView 12 | 13 | - (DOMWindow)DOMWindow 14 | { 15 | var contentWindow = nil; 16 | try 17 | { 18 | contentWindow = [super DOMWindow]; 19 | } 20 | catch (e) 21 | { 22 | // Do nothing. When the Web View is not added to the DOM, it booms. 23 | // Just ignore the boom because WKTextView checks multiple times. 24 | } 25 | return contentWindow; 26 | } 27 | 28 | @end 29 | 30 | WKTextCursorHeightFactor = 0.2; 31 | WKTextViewDefaultFont = "Verdana"; 32 | 33 | var _CancelEvent = function(ev) { 34 | if (!ev) 35 | ev = window.event; 36 | if (ev && ev.stopPropagation) 37 | ev.stopPropagation(); 38 | else if (ev && ev.cancelBubble) 39 | ev.cancelBubble = true; 40 | }; 41 | 42 | var _EditorEvents = [ 43 | 'onmousedown', 44 | 'onmouseup', 45 | 'onkeypress', 46 | 'onkeydown', 47 | 'onkeyup' 48 | ]; 49 | 50 | /*! 51 | A closure editor based rich text editor widget. 52 | 53 | Beware of the load times. Wait for the load event. 54 | */ 55 | @implementation WKTextView : _WKWebView 56 | { 57 | id delegate @accessors; 58 | CPTimer loadTimer; 59 | Object editor; 60 | Object _scrollDiv; 61 | BOOL shouldFocusAfterAction; 62 | BOOL suppressAutoFocus; 63 | BOOL editable; 64 | BOOL enabled; 65 | BOOL autohidesScrollers @accessors; 66 | 67 | CPString lastFont; 68 | CPString lastColorString; 69 | CPColor lastColor; 70 | CPDictionary eventHandlerSwizzler; 71 | 72 | CPScroller _verticalScroller; 73 | float _verticalLineScroll; 74 | float _verticalPageScroll; 75 | 76 | boolean _cursorPlaced; 77 | 78 | boolean _isTryingToBecomeFirstResponder; 79 | } 80 | 81 | + (CPString)defaultThemeClass 82 | { 83 | return "wktextview"; 84 | } 85 | 86 | + (id)themeAttributes 87 | { 88 | return [CPDictionary dictionaryWithObjects:[CGInsetMake(4.0, 4.0, 4.0, 4.0)] 89 | forKeys:[@"content-inset"]]; 90 | } 91 | 92 | - (id)initWithFrame:(CGRect)aFrame 93 | { 94 | if (self = [super initWithFrame:aFrame]) 95 | { 96 | lastColor = [CPColor blackColor]; 97 | 98 | _verticalPageScroll = 10; 99 | _verticalLineScroll = 10; 100 | 101 | autohidesScrollers = YES; 102 | 103 | [self setDrawsBackground:NO]; 104 | [self setBackgroundColor:[CPColor whiteColor]]; 105 | 106 | eventHandlerSwizzler = [[CPDictionary alloc] init]; 107 | shouldFocusAfterAction = YES; 108 | [self setEditable: YES]; 109 | [self setEnabled: YES]; 110 | [self setScrollMode:CPWebViewScrollNative]; 111 | [self setMainFrameURL:[[CPBundle bundleForClass:[self class]] pathForResource:"WKTextView/editor.html"]]; 112 | 113 | _verticalScroller = [[CPScroller alloc] initWithFrame:CGRectMake(0.0, 0.0, [CPScroller scrollerWidth], MAX(CGRectGetHeight([self bounds]), [CPScroller scrollerWidth] + 1))]; 114 | [_verticalScroller setAutoresizingMask:CPViewMinXMargin]; 115 | [_verticalScroller setTarget:self]; 116 | [_verticalScroller setAction:@selector(_verticalScrollerDidScroll:)]; 117 | [_verticalScroller setStyle:[CPScrollView globalScrollerStyle]]; 118 | 119 | [self addSubview:_verticalScroller]; 120 | [self _updateScrollbar]; 121 | 122 | // Check if the document was loaded immediately. This could happen if we're loaded from 123 | // a file URL. 124 | [self checkLoad]; 125 | } 126 | return self; 127 | } 128 | 129 | - (void)_startedLoading 130 | { 131 | // If the frame reloads for whatever reason, the editor is gone. 132 | editor = nil; 133 | _cursorPlaced = NO; 134 | [super _startedLoading]; 135 | } 136 | 137 | - (void)viewDidHide 138 | { 139 | // Editor can't be used at all while hidden due to the iframe unloading. 140 | editor = nil; 141 | _cursorPlaced = NO; 142 | } 143 | 144 | - (void)viewDidUnhide 145 | { 146 | if (editor === nil) 147 | [self checkLoad]; 148 | else 149 | [self _actualizeEnabledState]; 150 | } 151 | 152 | - (void)_finishedLoading 153 | { 154 | [super _finishedLoading]; 155 | [self checkLoad]; 156 | } 157 | 158 | - (void)checkLoad 159 | { 160 | // We can't load if hidden. Load checking will be resumed by viewDidUnhide later. 161 | if ([self isHiddenOrHasHiddenAncestor]) 162 | return; 163 | 164 | // Is the editor ready? 165 | var maybeEditor = [self objectByEvaluatingJavaScriptFromString:"typeof(__closure_editor) != 'undefined' ? __closure_editor : null"]; 166 | 167 | if (maybeEditor) 168 | { 169 | _scrollDiv = maybeEditor.__scroll_div; 170 | [self setEditor:maybeEditor]; 171 | 172 | if (loadTimer) 173 | { 174 | [loadTimer invalidate]; 175 | loadTimer = nil; 176 | } 177 | 178 | if (_html != nil) 179 | [self setHtmlValue:_html]; 180 | 181 | if ([delegate respondsToSelector:@selector(textViewDidLoad:)]) 182 | [delegate textViewDidLoad:self]; 183 | 184 | return; 185 | } 186 | 187 | // If we still don't have an editor, check again later. 188 | if (!loadTimer || ![loadTimer isValid]) 189 | loadTimer = [CPTimer scheduledTimerWithTimeInterval:0.1 target:self selector:"checkLoad" userInfo:nil repeats:NO]; 190 | } 191 | 192 | - (BOOL)acceptsFirstResponder 193 | { 194 | return [self isEditable] && [self isEnabled]; 195 | } 196 | 197 | - (BOOL)becomeFirstResponder 198 | { 199 | [self _didBeginEditing]; 200 | if (editor) 201 | { 202 | if (_cursorPlaced) 203 | editor.focus(); 204 | else 205 | { 206 | editor.focusAndPlaceCursorAtStart(); 207 | _cursorPlaced = YES; 208 | } 209 | } 210 | // ...if !editor this method will be called again once the editor is ready. 211 | 212 | return YES; 213 | } 214 | 215 | - (BOOL)resignFirstResponder 216 | { 217 | window.focus(); 218 | [self _didEndEditing]; 219 | return YES; 220 | } 221 | 222 | /*! 223 | Sets whether or not the receiver text view can be edited. 224 | */ 225 | - (void)setEditable:(BOOL)shouldBeEditable 226 | { 227 | editable = shouldBeEditable; 228 | } 229 | 230 | /*! 231 | Returns \c YES if the text view is currently editable by the user. 232 | */ 233 | - (BOOL)isEditable 234 | { 235 | return editable; 236 | } 237 | 238 | /*! 239 | Sets whether or not the receiver text view is enabled. 240 | */ 241 | - (void)setEnabled:(BOOL)shouldBeEnabled 242 | { 243 | if (enabled === shouldBeEnabled) 244 | return; 245 | 246 | enabled = shouldBeEnabled; 247 | 248 | [self _actualizeEnabledState]; 249 | } 250 | 251 | - (void)_actualizeEnabledState 252 | { 253 | if (editor) 254 | { 255 | var isEnabled = !editor.isUneditable(); 256 | if (!isEnabled && enabled) 257 | editor.makeEditable(); 258 | else if (isEnabled && !enabled) 259 | editor.makeUneditable(); 260 | 261 | // When contentEditable is off we must disable wysihat event handlers 262 | // or they'll cause errors e.g. if a user clicks a disabled WKTextView. 263 | /*var t = editor; 264 | for(var i=0; i<_EditorEvents.length; i++) { 265 | var ev = _EditorEvents[i]; 266 | if (!enabled && t[ev] !== _CancelEvent) 267 | { 268 | [eventHandlerSwizzler setObject:t[ev] forKey:ev]; 269 | t[ev] = _CancelEvent; 270 | } 271 | else if (enabled && t[ev] === _CancelEvent) 272 | { 273 | t[ev] = [eventHandlerSwizzler objectForKey:ev]; 274 | } 275 | }*/ 276 | } 277 | } 278 | 279 | - (void)_actualizeTheme 280 | { 281 | if (!editor || !editor.__padder_div) 282 | return; 283 | 284 | var contentInset = [self currentValueForThemeAttribute:@"content-inset"]; 285 | editor.__padder_div.style.padding = "" + (contentInset.top || 0) + "px " + (contentInset.right || 0) + "px " + (contentInset.bottom || 0) + "px " + (contentInset.left || 0) + "px"; 286 | 287 | // The new padding might require a different minimum height. 288 | [self _resizeWebFrame]; 289 | } 290 | 291 | - (void)layoutSubviews 292 | { 293 | [super layoutSubviews]; 294 | 295 | [self _actualizeTheme]; 296 | } 297 | 298 | - (void)setAutohidesScrollers:(BOOL)aFlag 299 | { 300 | if (autohidesScrollers === aFlag) 301 | return; 302 | 303 | autohidesScrollers = aFlag; 304 | 305 | [self _updateScrollbar]; 306 | } 307 | 308 | /*! 309 | Returns \c YES if the text view is currently enabled. 310 | */ 311 | - (BOOL)isEnabled 312 | { 313 | return enabled; 314 | } 315 | 316 | /*! 317 | Sets whether the editor should automatically take focus after an action 318 | method is invoked such as boldSelection or setFont. This is useful when 319 | binding to a toolbar. 320 | */ 321 | - (void)setShouldFocusAfterAction:(BOOL)aFlag 322 | { 323 | shouldFocusAfterAction = aFlag; 324 | } 325 | 326 | - (BOOL)shouldFocusAfterAction 327 | { 328 | return shouldFocusAfterAction; 329 | } 330 | 331 | - (BOOL)tryToBecomeFirstResponder 332 | { 333 | if (_isTryingToBecomeFirstResponder) 334 | return YES; 335 | 336 | var win = [self window]; 337 | if ([win firstResponder] === self) 338 | return YES; 339 | 340 | // We have to emulate select pieces of CPWindow's event handling 341 | // here since the iframe bypasses the regular event handling. 342 | var becameFirst = false; 343 | 344 | _isTryingToBecomeFirstResponder = YES; 345 | try 346 | { 347 | if ([self acceptsFirstResponder]) 348 | { 349 | becameFirst = [win makeFirstResponder:self]; 350 | if (becameFirst) 351 | { 352 | if (![win isKeyWindow]) 353 | [win makeKeyAndOrderFront:self]; 354 | [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; 355 | } 356 | } 357 | } finally 358 | { 359 | _isTryingToBecomeFirstResponder = NO; 360 | } 361 | 362 | return becameFirst; 363 | } 364 | 365 | - (void)setEditor:(Object)anEditor 366 | { 367 | if (editor === anEditor) 368 | return; 369 | 370 | if (![self DOMWindow]) 371 | return; 372 | 373 | editor = anEditor; 374 | _iframe.allowTransparency = true; 375 | 376 | [self DOMWindow].document.body.style.backgroundColor = 'transparent'; 377 | 378 | // FIXME execCommand doesn't work well without the view having been focused 379 | // on at least once. 380 | // editor.focus(); 381 | 382 | suppressAutoFocus = YES; 383 | //[self setFontNameForSelection:WKTextViewDefaultFont]; 384 | suppressAutoFocus = NO; 385 | 386 | if (editor['WKTextView_Installed'] === undefined) 387 | { 388 | var win = [self DOMWindow], 389 | doc = win.document; 390 | 391 | var onmousedown = function(ev) { 392 | // If selection was successful, allow the event to continue propagate so that the 393 | // cursor is placed in the right spot. 394 | return [self tryToBecomeFirstResponder]; 395 | } 396 | 397 | defaultKeydown = doc.onkeydown; 398 | var onkeydown = function(ev) { 399 | if (!ev) 400 | ev = window.event; 401 | 402 | var key = ev.keyCode; 403 | if (!key) 404 | key = ev.which; 405 | 406 | // Shift+Tab 407 | if (ev.shiftKey && key == 9) 408 | { 409 | setTimeout(function() 410 | { 411 | [[self window] selectPreviousKeyView:self]; 412 | [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; 413 | }, 0.0); 414 | return false; 415 | } 416 | else 417 | { 418 | if (defaultKeydown) 419 | return defaultKeydown(ev); 420 | return true; 421 | } 422 | }; 423 | 424 | var onscroll = function(ev) { 425 | if (!ev) 426 | ev = window.event; 427 | 428 | [self _updateScrollbar]; 429 | [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; 430 | return true; 431 | } 432 | 433 | if (doc.addEventListener) 434 | { 435 | doc.addEventListener('mousedown', onmousedown, true); 436 | editor.addEventListener('keydown', onkeydown, true); 437 | doc.body.addEventListener('scroll', onscroll, true); 438 | } 439 | else if (doc.attachEvent) 440 | { 441 | doc.attachEvent('onmousedown', onmousedown); 442 | doc.attachEvent('onkeydown', onkeydown); 443 | doc.body.attachEvent('scroll', onscroll); 444 | } 445 | 446 | editor.__fieldChangeExternal = function() { 447 | [self _didChange]; 448 | // The normal run loop doesn't react to iframe events, so force immediate processing. 449 | [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; 450 | }; 451 | editor.__selectionChangeExternal = function() 452 | { 453 | [self _cursorDidMove]; 454 | 455 | // Workaround for Firefox not firing our iframe mousedown handler - we have 456 | // to do the first responder promotion here instead. 457 | [self tryToBecomeFirstResponder]; 458 | 459 | // The normal run loop doesn't react to iframe events, so force immediate processing. 460 | [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode]; 461 | }; 462 | 463 | editor['WKTextView_Installed'] = true; 464 | } 465 | 466 | [self _actualizeTheme]; 467 | [self _actualizeEnabledState]; 468 | [self _resizeWebFrame]; 469 | 470 | if ([[self window] firstResponder] === self) 471 | [self becomeFirstResponder]; 472 | } 473 | 474 | - (JSObject)editor 475 | { 476 | // editor can never be active while hidden. 477 | return [self isHiddenOrHasHiddenAncestor] ? nil : editor; 478 | } 479 | 480 | - (void)_updateScrollbar 481 | { 482 | if (!_verticalScroller) 483 | return; 484 | 485 | var scrollTop = 0, 486 | height = 1, 487 | frameHeight = CGRectGetHeight([self bounds]), 488 | scrollerWidth = CGRectGetWidth([_verticalScroller bounds]); 489 | 490 | if (_scrollDiv) 491 | { 492 | scrollTop = _scrollDiv.scrollTop; 493 | height = _scrollDiv.scrollHeight; 494 | } 495 | height = MAX(1, height); 496 | 497 | var difference = height - frameHeight, 498 | proportion = frameHeight / height; 499 | 500 | // Avoid showing the scrollbar when it would nearly fill the bar anyhow. 501 | // This avoids the bar flickering like crazy when animating the text field 502 | // growing or shrinking, as could otherwise happen due to the inner height 503 | // not having updated yet to fit to the outter height when the scroll bar 504 | // update happens. 505 | if (proportion > 0.99) 506 | proportion = 1; 507 | 508 | // Additionally, hide the scroller if there is no need to show one. 509 | [_verticalScroller setHidden:autohidesScrollers && proportion == 1]; 510 | 511 | [_verticalScroller setFloatValue:scrollTop / difference]; 512 | [_verticalScroller setKnobProportion:proportion]; 513 | [_verticalScroller setFrame:CGRectMake(CGRectGetMaxX([self bounds]) - scrollerWidth, 0, scrollerWidth, frameHeight)]; 514 | } 515 | 516 | - (void)_verticalScrollerDidScroll:(CPScroller)aScroller 517 | { 518 | if (!_scrollDiv) 519 | return; // Shouldn't happen. No editor means no scrollbar. 520 | 521 | // Based on CPScrollView _verticalScrollerDidScroll 522 | var scrollTop = _scrollDiv.scrollTop, 523 | height = _scrollDiv.scrollHeight, 524 | frameHeight = CGRectGetHeight([self bounds]), 525 | value = [aScroller floatValue]; 526 | 527 | switch ([_verticalScroller hitPart]) 528 | { 529 | case CPScrollerDecrementLine: scrollTop -= _verticalLineScroll; 530 | break; 531 | 532 | case CPScrollerIncrementLine: scrollTop += _verticalLineScroll; 533 | break; 534 | 535 | case CPScrollerDecrementPage: scrollTop -= frameHeight - _verticalPageScroll; 536 | break; 537 | 538 | case CPScrollerIncrementPage: scrollTop += frameHeight - _verticalPageScroll; 539 | break; 540 | 541 | case CPScrollerKnobSlot: 542 | case CPScrollerKnob: 543 | // We want integral bounds! 544 | default: scrollTop = ROUND(value * (height - frameHeight)); 545 | } 546 | 547 | _scrollDiv.scrollTop = scrollTop; 548 | } 549 | 550 | - (void)_didChange 551 | { 552 | // When the text changes, the height of the content may change. 553 | [self _updateScrollbar]; 554 | 555 | if ([delegate respondsToSelector:@selector(textViewDidChange:)]) 556 | { 557 | [delegate textViewDidChange:self]; 558 | } 559 | 560 | } 561 | 562 | - (void)_didBeginEditing 563 | { 564 | if ([delegate respondsToSelector:@selector(textViewDidBeginEditing:)]) 565 | [delegate textViewDidBeginEditing:self]; 566 | } 567 | 568 | - (void)_didEndEditing 569 | { 570 | if ([delegate respondsToSelector:@selector(textViewDidEndEditing:)]) 571 | [delegate textViewDidEndEditing:self]; 572 | } 573 | 574 | - (void)_cursorDidMove 575 | { 576 | if (![self DOMWindow]) 577 | return; 578 | 579 | if ([delegate respondsToSelector:@selector(textViewCursorDidMove:)]) 580 | { 581 | [delegate textViewCursorDidMove:self]; 582 | } 583 | } 584 | 585 | - (void)_resizeWebFrame 586 | { 587 | if (editor && editor.getElement()) 588 | { 589 | var contentInset = [self currentValueForThemeAttribute:@"content-inset"]; 590 | //editor.setMinHeight([self bounds].size.height - contentInset.top - contentInset.bottom); 591 | editor.getElement().style.minHeight = (CGRectGetHeight([self bounds]) - contentInset.top - contentInset.bottom) + "px"; 592 | } 593 | [self _updateScrollbar]; 594 | } 595 | 596 | - (void)_loadMainFrameURL 597 | { 598 | [self _startedLoading]; 599 | 600 | _ignoreLoadStart = YES; 601 | _ignoreLoadEnd = NO; 602 | 603 | _url = _mainFrameURL; 604 | _html = null; 605 | 606 | [self _load]; 607 | } 608 | 609 | - (void)_addKeypressHandler:(Function)aFunction 610 | { 611 | if ([self editor]) 612 | { 613 | var doc = [self DOMWindow].document; 614 | if (doc.addEventListener) 615 | { 616 | doc.addEventListener('keypress', aFunction, true); 617 | } 618 | else if (doc.attachEvent) 619 | { 620 | doc.attachEvent('onkeypress', 621 | function() { aFunction([self editor].event) }); 622 | //This needs to be tested in IE. I have no idea if [self editor] will have an event 623 | } 624 | } 625 | } 626 | 627 | - (CPString)htmlValue 628 | { 629 | if (![self editor]) 630 | return _html; 631 | 632 | return [self editor].getCleanContents(); 633 | } 634 | 635 | - (void)setHtmlValue:(CPString)html 636 | { 637 | if ([self editor] != nil) 638 | editor.setHtml(false, html, false, false); 639 | else 640 | _html = html; 641 | 642 | _cursorPlaced = NO; 643 | [self _didChange]; 644 | } 645 | 646 | - (void)_didPerformAction 647 | { 648 | if (shouldFocusAfterAction && !suppressAutoFocus) 649 | { 650 | [self DOMWindow].focus(); 651 | editor.focus(); 652 | } 653 | } 654 | 655 | - (@action)clearText:(id)sender 656 | { 657 | [self setHtmlValue:""]; 658 | [self _didChange]; 659 | [self _didPerformAction]; 660 | } 661 | 662 | - (void)insertHtml:(CPString)html 663 | { 664 | [CPException raise:CPUnsupportedMethodException reason:"not available with google-closure editor yet"]; 665 | 666 | /*[self editor].insertHTML(html); 667 | [self _didChange]; 668 | [self _didPerformAction];*/ 669 | } 670 | 671 | - (@action)boldSelection:(id)sender 672 | { 673 | editor.execCommand(editor.Command.BOLD, null); 674 | [self _didPerformAction]; 675 | } 676 | 677 | - (@action)underlineSelection:(id)sender 678 | { 679 | editor.execCommand(editor.Command.UNDERLINE, null); 680 | [self _didPerformAction]; 681 | } 682 | 683 | - (@action)italicSelection:(id)sender 684 | { 685 | editor.execCommand(editor.Command.ITALIC, null); 686 | [self _didPerformAction]; 687 | } 688 | 689 | - (@action)strikethroughSelection:(id)sender 690 | { 691 | editor.execCommand(editor.Command.STRIKE_THROUGH, null); 692 | [self _didPerformAction]; 693 | } 694 | 695 | - (@action)alignSelectionLeft:(id)sender 696 | { 697 | editor.execCommand(editor.Command.JUSTIFY_LEFT, null); 698 | [self _didPerformAction]; 699 | } 700 | 701 | - (@action)alignSelectionRight:(id)sender 702 | { 703 | editor.execCommand(editor.Command.JUSTIFY_RIGHT, null); 704 | [self _didPerformAction]; 705 | } 706 | 707 | - (@action)alignSelectionCenter:(id)sender 708 | { 709 | editor.execCommand(editor.Command.JUSTIFY_CENTER, null); 710 | [self _didPerformAction]; 711 | } 712 | 713 | - (@action)alignSelectionFull:(id)sender 714 | { 715 | editor.execCommand(editor.Command.JUSTIFY_FULL, null); 716 | [self _didPerformAction]; 717 | } 718 | 719 | - (@action)linkSelection:(id)sender 720 | { 721 | // TODO Show a sheet asking for a URL to link to. 722 | editor.execCommand(editor.Command.LINK, "http://www.wireload.net"); 723 | [self _didPerformAction]; 724 | } 725 | 726 | - (void)linkSelectionToURL:(CPString)aUrl 727 | { 728 | var appWindow = editor.getAppWindow(), 729 | prompt = appWindow['prompt']; 730 | 731 | appWindow['prompt'] = function() { 732 | return aUrl; 733 | }; 734 | 735 | editor.execCommand(editor.Command.LINK, null); 736 | appWindow['prompt'] = prompt; 737 | [self _didPerformAction]; 738 | } 739 | 740 | - (void)unlinkSelection:(id)sender 741 | { 742 | [self linkSelectionToURL:nil]; 743 | } 744 | 745 | - (@action)insertOrderedList:(id)sender 746 | { 747 | editor.execCommand(editor.Command.ORDERED_LIST, null); 748 | [self _didPerformAction]; 749 | } 750 | 751 | - (@action)insertUnorderedList:(id)sender 752 | { 753 | editor.execCommand(editor.Command.UNORDERED_LIST, null); 754 | [self _didPerformAction]; 755 | } 756 | 757 | - (@action)insertImage:(id)sender 758 | { 759 | // TODO Show a sheet asking for an image URL. 760 | } 761 | 762 | - (void)insertImageWithURL:(CPString)aUrl 763 | { 764 | editor.execCommand(editor.Command.IMAGE, aUrl); 765 | [self _didPerformAction]; 766 | } 767 | 768 | - (void)setFontNameForSelection:(CPString)aFont 769 | { 770 | lastFont = aFont; 771 | editor.execCommand(editor.Command.FONT_FACE, aFont); 772 | [self _didPerformAction]; 773 | } 774 | 775 | - (int)fontSizeRaw 776 | { 777 | try { 778 | return editor.queryCommandValue(editor.Command.FONT_SIZE); 779 | } catch(e) { 780 | return "16px"; 781 | } 782 | } 783 | 784 | - (int)fontSize 785 | { 786 | // Strangely we get font sizes back in pixels. 787 | var size = parseInt([self fontSizeRaw]), 788 | sizeMap = { 10:1, 13:2, 16:3, 18:4, 24:5, 32:6, 48:7}; 789 | if (size <= 7) 790 | return size; 791 | else if (size in sizeMap) 792 | return sizeMap[size]; 793 | else 794 | return 3; 795 | } 796 | 797 | /*! 798 | Set the font size for the selected text. Size is specified 799 | as a number between 1-6 which corresponds to small through xx-large. 800 | */ 801 | - (void)setFontSizeForSelection:(int)aSize 802 | { 803 | editor.execCommand(editor.Command.FONT_SIZE, aSize); 804 | [self _didPerformAction]; 805 | } 806 | 807 | - (CPString)font 808 | { 809 | try 810 | { 811 | var fontName = editor.queryCommandValue(editor.Command.FONT_FACE); 812 | } catch(e) { 813 | return lastFont; 814 | } 815 | 816 | // The font name may come through with quotes e.g. 'Apple Chancery' 817 | var format = /'(.*?)'/, 818 | r = fontName ? fontName.match(new RegExp(format)) : nil; 819 | 820 | if (r && r.length == 2) 821 | lastFont = r[1]; 822 | else if (fontName) 823 | lastFont = fontName; 824 | 825 | return lastFont; 826 | } 827 | 828 | - (CPColor)color 829 | { 830 | var colorString; 831 | try { 832 | colorString = editor.queryCommandValue(editor.Command.FONT_COLOR); 833 | } catch(e) { 834 | CPLog.warning(e); 835 | } 836 | // Avoid creating a new Color instance every time the cursor moves by reusing the last 837 | // instance. 838 | if (!colorString || colorString == lastColorString) 839 | return lastColor; 840 | lastColor = [[CPColor alloc] _initWithCSSString:colorString]; 841 | lastColorString = colorString; 842 | return lastColor; 843 | } 844 | 845 | - (void)setColorForSelection:(CPColor)aColor 846 | { 847 | editor.execCommand(editor.Command.FONT_COLOR, [aColor hexString]); 848 | [self _didPerformAction]; 849 | } 850 | 851 | @end 852 | 853 | -------------------------------------------------------------------------------- /auxiliary/build.sh: -------------------------------------------------------------------------------- 1 | CLOSURE_LIBRARY=google-closure-library 2 | CLOSURE_COMPILER=../../deps/closurecompiler/compiler.jar 3 | DESTINATION=../Resources/WKTextView/closure-editor.js 4 | 5 | $CLOSURE_LIBRARY/closure/bin/calcdeps.py -i closure-editor-requirements.js -p $CLOSURE_LIBRARY -o compiled \ 6 | -c $CLOSURE_COMPILER -f "--compilation_level=ADVANCED_OPTIMIZATIONS" >$DESTINATION 7 | -------------------------------------------------------------------------------- /auxiliary/closure-editor-requirements.js: -------------------------------------------------------------------------------- 1 | goog.require('goog.dom'); 2 | goog.require('goog.editor.Command'); 3 | goog.require('goog.editor.SeamlessField'); 4 | goog.require('goog.editor.plugins.BasicTextFormatter'); 5 | goog.require('goog.editor.plugins.EnterHandler'); 6 | goog.require('goog.editor.plugins.HeaderFormatter'); 7 | //goog.require('goog.editor.plugins.LinkDialogPlugin'); 8 | goog.require('goog.editor.plugins.ListTabHandler'); 9 | //goog.require('goog.editor.plugins.LoremIpsum'); 10 | goog.require('goog.editor.plugins.RemoveFormatting'); 11 | goog.require('goog.editor.plugins.SpacesTabHandler'); 12 | goog.require('goog.editor.plugins.UndoRedo'); 13 | 14 | goog.exportSymbol('goog.editor.Field.EventType.COMMAND_VALUE_CHANGE', goog.editor.Field.EventType.COMMAND_VALUE_CHANGE); 15 | goog.exportSymbol('goog.editor.Field.EventType.DELAYEDCHANGE', goog.editor.Field.EventType.DELAYEDCHANGE); 16 | goog.exportSymbol('goog.editor.Field.EventType.SELECTIONCHANGE', goog.editor.Field.EventType.SELECTIONCHANGE); 17 | 18 | goog.exportSymbol('goog.editor.Field.DELAYED_CHANGE_FREQUENCY', goog.editor.Field.DELAYED_CHANGE_FREQUENCY); 19 | 20 | goog.exportSymbol('goog.editor.SeamlessField', goog.editor.SeamlessField); 21 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'executeCommand', goog.editor.SeamlessField.prototype.executeCommand); 22 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'focus', goog.editor.SeamlessField.prototype.focus); 23 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'focusAndPlaceCursorAtStart', goog.editor.SeamlessField.prototype.focusAndPlaceCursorAtStart); 24 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'getAppWindow', goog.editor.SeamlessField.prototype.getAppWindow); 25 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'getCleanContents', goog.editor.SeamlessField.prototype.getCleanContents); 26 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'getElement', goog.editor.SeamlessField.prototype.getElement); 27 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'getPluginByClassId', goog.editor.SeamlessField.prototype.getPluginByClassId); 28 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'isUneditable', goog.editor.SeamlessField.prototype.isUneditable); 29 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'makeEditable', goog.editor.SeamlessField.prototype.makeEditable); 30 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'makeUneditable', goog.editor.SeamlessField.prototype.makeUneditable); 31 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'queryCommandValue', goog.editor.SeamlessField.prototype.queryCommandValue); 32 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'registerPlugin', goog.editor.SeamlessField.prototype.registerPlugin); 33 | goog.exportProperty(goog.editor.SeamlessField.prototype, 'setHtml', goog.editor.SeamlessField.prototype.setHtml); 34 | 35 | goog.exportSymbol('goog.editor.Command', goog.editor.Command); 36 | goog.exportProperty(goog.editor.Command, 'UNDO', goog.editor.Command.UNDO); 37 | goog.exportProperty(goog.editor.Command, 'REDO', goog.editor.Command.REDO); 38 | goog.exportProperty(goog.editor.Command, 'LINK', goog.editor.Command.LINK); 39 | goog.exportProperty(goog.editor.Command, 'FORMAT_BLOCK', goog.editor.Command.FORMAT_BLOCK); 40 | goog.exportProperty(goog.editor.Command, 'INDENT', goog.editor.Command.INDENT); 41 | goog.exportProperty(goog.editor.Command, 'OUTDENT', goog.editor.Command.OUTDENT); 42 | goog.exportProperty(goog.editor.Command, 'REMOVE_FORMAT', goog.editor.Command.REMOVE_FORMAT); 43 | goog.exportProperty(goog.editor.Command, 'STRIKE_THROUGH', goog.editor.Command.STRIKE_THROUGH); 44 | goog.exportProperty(goog.editor.Command, 'HORIZONTAL_RULE', goog.editor.Command.HORIZONTAL_RULE); 45 | goog.exportProperty(goog.editor.Command, 'SUBSCRIPT', goog.editor.Command.SUBSCRIPT); 46 | goog.exportProperty(goog.editor.Command, 'SUPERSCRIPT', goog.editor.Command.SUPERSCRIPT); 47 | goog.exportProperty(goog.editor.Command, 'UNDERLINE', goog.editor.Command.UNDERLINE); 48 | goog.exportProperty(goog.editor.Command, 'BOLD', goog.editor.Command.BOLD); 49 | goog.exportProperty(goog.editor.Command, 'ITALIC', goog.editor.Command.ITALIC); 50 | goog.exportProperty(goog.editor.Command, 'FONT_SIZE', goog.editor.Command.FONT_SIZE); 51 | goog.exportProperty(goog.editor.Command, 'FONT_FACE', goog.editor.Command.FONT_FACE); 52 | goog.exportProperty(goog.editor.Command, 'FONT_COLOR', goog.editor.Command.FONT_COLOR); 53 | // goog.exportProperty(goog.editor.Command, 'EMOTICON', goog.editor.Command.EMOTICON); 54 | goog.exportProperty(goog.editor.Command, 'BACKGROUND_COLOR', goog.editor.Command.BACKGROUND_COLOR); 55 | goog.exportProperty(goog.editor.Command, 'ORDERED_LIST', goog.editor.Command.ORDERED_LIST); 56 | goog.exportProperty(goog.editor.Command, 'UNORDERED_LIST', goog.editor.Command.UNORDERED_LIST); 57 | goog.exportProperty(goog.editor.Command, 'TABLE', goog.editor.Command.TABLE); 58 | goog.exportProperty(goog.editor.Command, 'JUSTIFY_CENTER', goog.editor.Command.JUSTIFY_CENTER); 59 | goog.exportProperty(goog.editor.Command, 'JUSTIFY_FULL', goog.editor.Command.JUSTIFY_FULL); 60 | goog.exportProperty(goog.editor.Command, 'JUSTIFY_RIGHT', goog.editor.Command.JUSTIFY_RIGHT); 61 | goog.exportProperty(goog.editor.Command, 'JUSTIFY_LEFT', goog.editor.Command.JUSTIFY_LEFT); 62 | goog.exportProperty(goog.editor.Command, 'BLOCKQUOTE', goog.editor.Command.BLOCKQUOTE); 63 | // goog.exportProperty(goog.editor.Command, 'DIR_LTR', goog.editor.Command.DIR_LTR); 64 | // goog.exportProperty(goog.editor.Command, 'DIR_RTL', goog.editor.Command.DIR_RTL); 65 | // goog.exportProperty(goog.editor.Command, 'IMAGE', goog.editor.Command.IMAGE); 66 | // goog.exportProperty(goog.editor.Command, 'EDIT_HTML', goog.editor.Command.EDIT_HTML); 67 | // goog.exportProperty(goog.editor.Command, 'DEFAULT_TAG', goog.editor.Command.DEFAULT_TAG); 68 | 69 | goog.exportSymbol('goog.events.listen', goog.events.listen); 70 | goog.exportSymbol('goog.editor.plugins.BasicTextFormatter', goog.editor.plugins.BasicTextFormatter); 71 | goog.exportSymbol('goog.editor.plugins.EnterHandler', goog.editor.plugins.EnterHandler); 72 | goog.exportSymbol('goog.editor.plugins.HeaderFormatter', goog.editor.plugins.HeaderFormatter); 73 | goog.exportSymbol('goog.editor.plugins.ListTabHandler', goog.editor.plugins.ListTabHandler); 74 | goog.exportSymbol('goog.editor.plugins.RemoveFormatting', goog.editor.plugins.RemoveFormatting); 75 | goog.exportSymbol('goog.editor.plugins.SpacesTabHandler', goog.editor.plugins.SpacesTabHandler); 76 | goog.exportSymbol('goog.editor.plugins.UndoRedo', goog.editor.plugins.UndoRedo); 77 | goog.exportSymbol('goog.editor.plugins.UndoRedoManager', goog.editor.plugins.UndoRedoManager); 78 | 79 | goog.exportProperty(goog.editor.plugins.UndoRedo.prototype, 'disable', goog.editor.plugins.UndoRedo.prototype.disable); 80 | goog.exportProperty(goog.editor.plugins.UndoRedo.prototype, 'enable', goog.editor.plugins.UndoRedo.prototype.enable); 81 | goog.exportProperty(goog.editor.plugins.UndoRedo.prototype, 'setUndoRedoManager', goog.editor.plugins.UndoRedo.prototype.setUndoRedoManager); 82 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | Frameworks/* 2 | Resources/WKTextView/wysihat.js 3 | -------------------------------------------------------------------------------- /sample/AppController.j: -------------------------------------------------------------------------------- 1 | /* 2 | * AppController.j 3 | * sample 4 | * 5 | * Created by Alexander Ljungberg on November 26, 2009. 6 | * Copyright 2009, WireLoad, LLC All rights reserved. 7 | */ 8 | 9 | @import 10 | @import 11 | 12 | var NewToolbarItemIdentifier = "NewToolbarItemIdentifier", 13 | BoldToolbarItemIdentifier = "BoldToolbarItemIdentifier", 14 | ItalicsToolbarItemIdentifier = "ItalicsToolbarItemIdentifier", 15 | UnderlineToolbarItemIdentifier = "UnderlineToolbarItemIdentifier", 16 | StrikethroughToolbarItemIdentifier = "StrikethroughToolbarItemIdentifier", 17 | AlignLeftToolbarItemIdentifier = "AlignLeftToolbarItemIdentifier", 18 | AlignRightToolbarItemIdentifier = "AlignRightToolbarItemIdentifier", 19 | AlignCenterToolbarItemIdentifier = "AlignCenterToolbarItemIdentifier", 20 | AlignFullToolbarItemIdentifier = "AlignFullToolbarItemIdentifier", 21 | InsertLinkToolbarItemIdentifier = "InsertLinkToolbarItemIdentifier", 22 | UnlinkToolbarItemIdentifier = "UnlinkToolbarItemIdentifier", 23 | InsertImageToolbarItemIdentifier = "InsertImageToolbarItemIdentifier", 24 | FontToolbarItemIdentifier = "FontToolbarItemIdentifier", 25 | BulletsToolbarItemIdentifier = "BulletsToolbarItemIdentifier", 26 | NumbersToolbarItemIdentifier = "NumbersToolbarItemIdentifier", 27 | RandomTextToolbarItemIdentifier = "RandomTextToolbarItemIdentifier"; 28 | 29 | @implementation AppController : CPObject 30 | { 31 | WKTextView editorView; 32 | CPToolBar toolbar; 33 | } 34 | 35 | - (void)applicationDidFinishLaunching:(CPNotification)aNotification 36 | { 37 | var theWindow = [[CPWindow alloc] initWithContentRect:CGRectMakeZero() styleMask:CPBorderlessBridgeWindowMask], 38 | contentView = [theWindow contentView]; 39 | 40 | editorView = [[WKTextView alloc] initWithFrame:[theWindow frame]]; 41 | [editorView setAutohidesScrollers:NO]; 42 | [editorView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable]; 43 | [editorView setDelegate:self]; 44 | [editorView setShouldFocusAfterAction:YES]; 45 | [contentView addSubview:editorView]; 46 | 47 | toolbar = [[CPToolbar alloc] initWithIdentifier:"Styling"]; 48 | [toolbar setDelegate:self]; 49 | [toolbar setVisible:YES]; 50 | [theWindow setToolbar:toolbar]; 51 | 52 | [theWindow orderFront:self]; 53 | } 54 | 55 | // Return an array of toolbar item identifier (all the toolbar items that may be present in the toolbar) 56 | - (CPArray)toolbarAllowedItemIdentifiers:(CPToolbar)aToolbar 57 | { 58 | return [NewToolbarItemIdentifier, CPToolbarSpaceItemIdentifier, BoldToolbarItemIdentifier, ItalicsToolbarItemIdentifier, UnderlineToolbarItemIdentifier, StrikethroughToolbarItemIdentifier, CPToolbarSpaceItemIdentifier, AlignLeftToolbarItemIdentifier, AlignRightToolbarItemIdentifier, AlignCenterToolbarItemIdentifier, AlignFullToolbarItemIdentifier, CPToolbarSpaceItemIdentifier, BulletsToolbarItemIdentifier, NumbersToolbarItemIdentifier, InsertLinkToolbarItemIdentifier, UnlinkToolbarItemIdentifier, InsertImageToolbarItemIdentifier, FontToolbarItemIdentifier, CPToolbarFlexibleSpaceItemIdentifier, RandomTextToolbarItemIdentifier]; 59 | } 60 | 61 | // Return an array of toolbar item identifier (the default toolbar items that are present in the toolbar) 62 | - (CPArray)toolbarDefaultItemIdentifiers:(CPToolbar)aToolbar 63 | { 64 | return [self toolbarAllowedItemIdentifiers:aToolbar]; 65 | } 66 | 67 | // Create the toolbar item that is requested by the toolbar. 68 | - (CPToolbarItem)toolbar:(CPToolbar)aToolbar itemForItemIdentifier:(CPString)anItemIdentifier willBeInsertedIntoToolbar:(BOOL)aFlag 69 | { 70 | // Create the toolbar item and associate it with its identifier 71 | var toolbarItem = [[CPToolbarItem alloc] initWithItemIdentifier:anItemIdentifier]; 72 | 73 | var mainBundle = [CPBundle mainBundle]; 74 | 75 | var actionMap = 76 | { 77 | NewToolbarItemIdentifier: { 'image': 'page_white.png', 'label': 'New', 'target': editorView, 'action':@selector(clearText:) }, 78 | BoldToolbarItemIdentifier: { 'image': 'text_bold.png', 'label': 'Bold', 'target': editorView, 'action':@selector(boldSelection:) }, 79 | ItalicsToolbarItemIdentifier: { 'image': 'text_italic.png', 'label': 'Italics', 'target': editorView, 'action':@selector(italicSelection:) }, 80 | UnderlineToolbarItemIdentifier: { 'image': 'text_underline.png', 'label': 'Under', 'target': editorView, 'action':@selector(underlineSelection:) }, 81 | RandomTextToolbarItemIdentifier: { 'image': 'page_white_text.png', 'label': 'Lorem', 'target': self, 'action':@selector(setRandomText:) }, 82 | StrikethroughToolbarItemIdentifier: { 'image': 'text_strikethrough.png','label': 'Strike', 'target': editorView, 'action':@selector(strikethroughSelection:) }, 83 | AlignLeftToolbarItemIdentifier: { 'image': 'text_align_left.png', 'label': 'Left', 'target': editorView, 'action':@selector(alignSelectionLeft:) }, 84 | AlignRightToolbarItemIdentifier: { 'image': 'text_align_right.png', 'label': 'Right', 'target': editorView, 'action':@selector(alignSelectionRight:) }, 85 | AlignCenterToolbarItemIdentifier: { 'image': 'text_align_center.png', 'label': 'Center', 'target': editorView, 'action':@selector(alignSelectionCenter:) }, 86 | AlignFullToolbarItemIdentifier: { 'image': 'text_align_justify.png','label': 'Justify', 'target': editorView, 'action':@selector(alignSelectionFull:) }, 87 | BulletsToolbarItemIdentifier: { 'image': 'text_list_bullets.png', 'label': 'Bullets', 'target': editorView, 'action':@selector(insertUnorderedList:) }, 88 | NumbersToolbarItemIdentifier: { 'image': 'text_list_numbers.png', 'label': 'Numbers', 'target': editorView, 'action':@selector(insertOrderedList:) }, 89 | InsertLinkToolbarItemIdentifier: { 'image': 'link.png', 'label': 'Link', 'target': self, 'action':@selector(doLink:) }, 90 | UnlinkToolbarItemIdentifier: { 'image': 'link_break.png', 'label': 'Unlink', 'target': editorView, 'action':@selector(unlinkSelection:) }, 91 | InsertImageToolbarItemIdentifier: { 'image': 'picture.png', 'label': 'Image', 'target': self, 'action':@selector(doImage:) }, 92 | }; 93 | 94 | action = actionMap[anItemIdentifier]; 95 | if (action) 96 | { 97 | var image = [[CPImage alloc] initWithContentsOfFile:[mainBundle pathForResource:@"silk/"+action['image']] size:CPSizeMake(16, 16)]; 98 | [toolbarItem setImage:image]; 99 | 100 | [toolbarItem setTarget:action['target']]; 101 | [toolbarItem setAction:action['action']]; 102 | [toolbarItem setLabel:action['label']]; 103 | 104 | [toolbarItem setMinSize:CGSizeMake(16, 16)]; 105 | [toolbarItem setMaxSize:CGSizeMake(16, 16)]; 106 | } 107 | else if (anItemIdentifier == FontToolbarItemIdentifier) 108 | { 109 | [toolbarItem setMinSize:CGSizeMake(160, 24)]; 110 | [toolbarItem setMaxSize:CGSizeMake(160, 24)]; 111 | 112 | var dropdown = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0, 0, 160, 24) pullsDown:NO]; 113 | [dropdown setTarget:self]; 114 | [dropdown setAction:@selector(doFont:)]; 115 | 116 | var fonts = [[CPFontManager sharedFontManager] availableFonts]; 117 | 118 | for(i=0; iLorem ipsum dolor sit amet, consectetur adipiscing elit. Ut est urna, vulputate sed viverra dignissim, consequat vitae eros. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse ut sapien enim, et pellentesque elit. In commodo facilisis est, et tempus lacus aliquam vitae. Maecenas quam nulla, elementum ut tristique quis, cursus a nisi. Duis mollis risus vel velit molestie convallis nec a purus. Donec neque arcu, suscipit sit amet mattis eu, fringilla ac sapien. Ut lorem nibh, mollis in tincidunt at, volutpat ut turpis. Maecenas nulla est, tincidunt pharetra consectetur vel, laoreet sed nibh. Pellentesque tempor diam vel elit commodo aliquet. Donec congue fringilla eros a tincidunt. Praesent accumsan mi tincidunt arcu ultricies nec pellentesque dolor faucibus. Mauris sed nisl in ligula porta congue et quis turpis. Suspendisse in lorem at felis tempus semper. In porta enim a ipsum aliquet consectetur.

Mauris ac tellus orci. Aenean egestas porta ornare. Cras nisl lorem, vulputate ac pellentesque eu, aliquet ac leo. Proin eros libero, tincidunt sed sodales eget, elementum non augue. Praesent convallis auctor venenatis. Suspendisse id urna quam. Aliquam sagittis, leo commodo laoreet interdum, arcu felis dictum velit, a sodales justo tortor a erat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis nibh magna, consequat et congue eu, bibendum id nisi. Cras gravida risus in nulla pharetra sagittis. Cras neque eros, consectetur nec bibendum eget, bibendum dictum libero. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nam rutrum dictum neque vel eleifend. Vivamus tempus, lorem vel ultricies ullamcorper, ante risus imperdiet massa, nec aliquam orci ipsum ut nisl. Aliquam id justo eu lorem dapibus tincidunt. Donec suscipit consequat metus, sed venenatis lorem malesuada sit amet. Ut at risus ut ligula vulputate auctor. Vivamus rutrum elementum porttitor. Fusce quam arcu, tristique eget consectetur eu, iaculis in urna.

Donec a metus ac elit faucibus sagittis non a ligula. In aliquet, lectus sed pulvinar bibendum, justo ligula faucibus sem, vestibulum eleifend lacus augue a eros. Suspendisse potenti. Phasellus vehicula blandit ultrices. Donec tortor nulla, fermentum nec viverra id, consequat non metus. Fusce nunc urna, aliquet sit amet varius ut, dapibus a sem. Aliquam erat volutpat. Vestibulum at enim et magna lacinia sollicitudin id nec dolor. Sed ultricies urna ut justo blandit tincidunt. Sed sit amet orci et justo pellentesque iaculis accumsan ac quam.

Nunc tristique felis quis leo blandit eget iaculis lacus hendrerit. Maecenas euismod consequat lacus quis porttitor. Quisque consequat, metus eu interdum vulputate, quam dolor porttitor dui, non faucibus quam nibh nec erat. Integer sit amet gravida quam. Proin nunc eros, tincidunt sit amet accumsan laoreet, dictum vel sapien. Praesent at fringilla orci. Etiam vehicula lacinia nisi, et molestie justo congue molestie. Maecenas tempus, quam nec placerat suscipit, lorem sapien feugiat augue, id pharetra augue enim eu nisl. Morbi ullamcorper lacus ac dolor ultricies vel pellentesque odio consequat. Maecenas a pellentesque nunc. Phasellus a varius massa. Vestibulum eget tortor eget ante iaculis molestie. Pellentesque eu augue metus, ut pellentesque purus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Curabitur consequat feugiat tincidunt. Proin tellus tortor, pharetra vel rhoncus ac, varius eget nisi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras et nunc metus. Pellentesque tincidunt iaculis erat id porta. Curabitur eget magna et velit eleifend tempor.

Vestibulum ultricies leo at augue malesuada congue. Maecenas laoreet metus et nunc consectetur placerat. Nulla facilisi. Duis iaculis tristique feugiat. Ut quis consectetur justo. Praesent condimentum sagittis dui, in lobortis tellus accumsan quis. In aliquam lacus non dolor accumsan rutrum. Etiam sed urna dolor. Donec consectetur lacus eu ante sodales feugiat. Aliquam aliquet nibh vel massa mattis pellentesque. Curabitur et tortor nisl, ut consequat felis. Mauris non orci at tortor ultrices condimentum. Aliquam erat volutpat. Mauris porttitor, diam convallis semper hendrerit, erat mi tempor dolor, id semper augue justo fermentum odio. Sed vitae nulla eu augue fringilla pellentesque vel ac neque. Nullam arcu nibh, auctor ut accumsan ac, ullamcorper eu velit. Integer in ligula nec felis auctor viverra. In commodo malesuada volutpat. Etiam justo elit, tincidunt ac semper sed, eleifend eu odio. Cras ac nulla eget lorem tempor venenatis.

"]; 156 | } 157 | 158 | - (void)textViewDidLoad:textView 159 | { 160 | // Update the selected font. 161 | [self textViewCursorDidMove:textView]; 162 | } 163 | 164 | - (void)textViewCursorDidMove:textView 165 | { 166 | var items = [toolbar visibleItems]; 167 | for (i=0; i 2 | 3 | 4 | 5 | CPApplicationDelegateClass 6 | AppController 7 | CPBundleName 8 | sample 9 | CPPrincipalClass 10 | CPApplication 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/Jakefile: -------------------------------------------------------------------------------- 1 | /* 2 | * Jakefile 3 | * sample 4 | * 5 | * Created by Alexander Ljungberg on November 26, 2009. 6 | * Copyright 2009, WireLoad, LLC All rights reserved. 7 | */ 8 | 9 | var ENV = require("system").env, 10 | FILE = require("file"), 11 | task = require("jake").task, 12 | FileList = require("jake").FileList, 13 | app = require("cappuccino/jake").app, 14 | configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug"; 15 | 16 | app ("sample", function(task) 17 | { 18 | task.setBuildIntermediatesPath(FILE.join("Build", "sample.build", configuration)); 19 | task.setBuildPath(FILE.join("Build", configuration)); 20 | 21 | task.setProductName("sample"); 22 | task.setIdentifier("com.yourcompany.sample"); 23 | task.setVersion("1.0"); 24 | task.setAuthor("WireLoad, LLC"); 25 | task.setEmail("feedback @nospam@ yourcompany.com"); 26 | task.setSummary("sample"); 27 | task.setSources(new FileList("**/*.j")); 28 | task.setResources(new FileList("Resources/*")); 29 | task.setIndexFilePath("index.html"); 30 | task.setInfoPlistPath("Info.plist"); 31 | 32 | if (configuration === "Debug") 33 | task.setCompilerFlags("-DDEBUG -g"); 34 | else 35 | task.setCompilerFlags("-O"); 36 | }); 37 | 38 | task ("default", ["sample"]); 39 | -------------------------------------------------------------------------------- /sample/Resources/WKTextView: -------------------------------------------------------------------------------- 1 | ../../Resources/WKTextView -------------------------------------------------------------------------------- /sample/Resources/silk/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/comment.png -------------------------------------------------------------------------------- /sample/Resources/silk/comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/comments.png -------------------------------------------------------------------------------- /sample/Resources/silk/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/link.png -------------------------------------------------------------------------------- /sample/Resources/silk/link_break.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/link_break.png -------------------------------------------------------------------------------- /sample/Resources/silk/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/page.png -------------------------------------------------------------------------------- /sample/Resources/silk/page_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/page_add.png -------------------------------------------------------------------------------- /sample/Resources/silk/page_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/page_edit.png -------------------------------------------------------------------------------- /sample/Resources/silk/page_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/page_white.png -------------------------------------------------------------------------------- /sample/Resources/silk/page_white_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/page_white_add.png -------------------------------------------------------------------------------- /sample/Resources/silk/page_white_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/page_white_edit.png -------------------------------------------------------------------------------- /sample/Resources/silk/page_white_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/page_white_text.png -------------------------------------------------------------------------------- /sample/Resources/silk/page_white_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/page_white_world.png -------------------------------------------------------------------------------- /sample/Resources/silk/picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/picture.png -------------------------------------------------------------------------------- /sample/Resources/silk/text_align_center.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/text_align_center.png -------------------------------------------------------------------------------- /sample/Resources/silk/text_align_justify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/text_align_justify.png -------------------------------------------------------------------------------- /sample/Resources/silk/text_align_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/text_align_left.png -------------------------------------------------------------------------------- /sample/Resources/silk/text_align_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/text_align_right.png -------------------------------------------------------------------------------- /sample/Resources/silk/text_bold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/text_bold.png -------------------------------------------------------------------------------- /sample/Resources/silk/text_italic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/text_italic.png -------------------------------------------------------------------------------- /sample/Resources/silk/text_list_bullets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/text_list_bullets.png -------------------------------------------------------------------------------- /sample/Resources/silk/text_list_numbers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/text_list_numbers.png -------------------------------------------------------------------------------- /sample/Resources/silk/text_strikethrough.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/text_strikethrough.png -------------------------------------------------------------------------------- /sample/Resources/silk/text_underline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/text_underline.png -------------------------------------------------------------------------------- /sample/Resources/silk/world_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/silk/world_link.png -------------------------------------------------------------------------------- /sample/Resources/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wireload/WKTextView/770d74c48a70b3be6fcb985c00f1aa9f930fe26b/sample/Resources/spinner.gif -------------------------------------------------------------------------------- /sample/index-debug.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | 15 | 16 | sample 17 | 18 | 22 | 23 | 24 | 25 | 39 | 40 | 47 | 48 | 54 | 55 | 56 | 57 | 58 |
59 | 64 | 65 | 81 |
82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /sample/index.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | 15 | 16 | sample 17 | 18 | 21 | 22 | 23 | 24 | 31 | 32 | 38 | 39 | 40 | 41 | 42 |
43 | 48 | 49 | 65 |
66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /sample/main.j: -------------------------------------------------------------------------------- 1 | /* 2 | * AppController.j 3 | * sample 4 | * 5 | * Created by Alexander Ljungberg on November 26, 2009. 6 | * Copyright 2009, WireLoad, LLC All rights reserved. 7 | */ 8 | 9 | @import 10 | @import 11 | 12 | @import "AppController.j" 13 | 14 | 15 | function main(args, namedArgs) 16 | { 17 | CPLogRegister(CPLogConsole); 18 | CPApplicationMain(args, namedArgs); 19 | } 20 | -------------------------------------------------------------------------------- /samplify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm -Rf sample.dist 3 | cp -R sample sample.dist 4 | cd sample.dist 5 | capp gen -f --force --build 6 | #rm Frameworks/WKTextView 7 | mkdir Frameworks/WKTextView 8 | cp ../WKTextView.j Frameworks/WKTextView/ 9 | rm Resources/WKTextView 10 | cp -Rf ../Resources/WKTextView Resources/WKTextView 11 | --------------------------------------------------------------------------------