├── .idea ├── .name ├── misc.xml ├── scopes │ └── scope_settings.xml ├── encodings.xml ├── vcs.xml ├── jsLibraryMappings.xml ├── inspectionProfiles │ ├── profiles_settings.xml │ └── Project_Default.xml ├── modules.xml └── rangy.iml ├── .gitignore ├── test ├── claridge.jpg ├── empty.html ├── serializertests.js ├── ie9.html ├── removerange.html ├── unittests.html ├── selectionsaverestoretests.js ├── index.html ├── highlightertests.js ├── textareatests.html ├── html5.html ├── ietextnodes.html ├── ie9controlrange.html ├── bold.html ├── textrangeperformancetests.html ├── textrangeperformance.html ├── rangetests.html ├── featuretests.html ├── classappliertests.html ├── tests.css ├── textranges.html ├── selectiontests.html ├── commandtests.html ├── serializertests.html ├── selectionsaverestoretests.html ├── commandnewtests.html ├── textrangetests-old.html ├── highlightertests.html ├── textinputs_jquery.html ├── textrangetests.html ├── featuretests.js ├── textrangeperformancetests.js ├── controlrange2.html ├── textareatests.js ├── words.html ├── domtests.js ├── controlrange.html ├── classapplier.html ├── serializer.html ├── saverestore.html ├── domrange.html ├── textrangelength.html ├── extend.html └── testutils.js ├── demos ├── football.png ├── index.html ├── demo.css ├── content.html ├── bookmark.html ├── scopedhighlights.html ├── bold.html ├── events.html ├── saverestore.html ├── serializer.html ├── highlighter.html └── classapplier.html ├── fiddlings ├── browserify │ ├── main.js │ └── test.html ├── requirejs │ ├── index.html │ ├── app.js │ └── app │ │ └── main.js ├── 218.html ├── 214.html └── spec │ └── innerText_files │ └── dfn.js ├── .npmignore ├── release_process.txt ├── README.md ├── package.json ├── LICENSE ├── roadmap.txt ├── src └── modules │ ├── inactive │ ├── commands │ │ ├── rangy-italic.js │ │ ├── rangy-bold_new.js │ │ ├── rangy-bold.js │ │ └── rangy-applyclass.js │ └── rangy-events.js │ ├── rangy-util.js │ └── rangy-selectionsaverestore.js └── external ├── jshashtable.js └── log4javascript_stub.js /.idea/.name: -------------------------------------------------------------------------------- 1 | rangy -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | dist 3 | /node_modules/ 4 | -------------------------------------------------------------------------------- /test/claridge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timdown/rangy/HEAD/test/claridge.jpg -------------------------------------------------------------------------------- /demos/football.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timdown/rangy/HEAD/demos/football.png -------------------------------------------------------------------------------- /fiddlings/browserify/main.js: -------------------------------------------------------------------------------- 1 | var rangy = require("rangy-browser/lib/rangy-core"); 2 | console.log(rangy); -------------------------------------------------------------------------------- /test/empty.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | roadmap.txt 2 | release_process.txt 3 | /.idea 4 | /builder 5 | /dist 6 | /demos 7 | /external 8 | /fiddlings 9 | /spec 10 | /src 11 | /test 12 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /fiddlings/requirejs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /fiddlings/browserify/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/serializertests.js: -------------------------------------------------------------------------------- 1 | xn.test.suite("Class Applier module tests", function(s) { 2 | s.tearDown = function() { 3 | document.getElementById("test").innerHTML = ""; 4 | }; 5 | 6 | s.test("canDeserializeRange test", function(t) { 7 | t.assertFalse(rangy.canDeserializeRange("0/9999:1,0/9999:20{a1b2c3d4}")) 8 | }); 9 | 10 | }, false); -------------------------------------------------------------------------------- /test/ie9.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /fiddlings/requirejs/app.js: -------------------------------------------------------------------------------- 1 | // For any third party dependencies, like jQuery, place them in the lib folder. 2 | 3 | // Configure loading modules from the lib directory, 4 | // except for 'app' ones, which are in a sibling 5 | // directory. 6 | requirejs.config({ 7 | baseUrl: 'lib', 8 | paths: { 9 | app: '../app' 10 | } 11 | }); 12 | 13 | // Start loading the main app file. Put all of 14 | // your application logic in there. 15 | requirejs(['app/main']); -------------------------------------------------------------------------------- /fiddlings/requirejs/app/main.js: -------------------------------------------------------------------------------- 1 | define(function (require) { 2 | var rangy = require("rangy"); 3 | var stupidMagicAvoidance = require; 4 | rangy.init(); 5 | 6 | 7 | 8 | 9 | //window.rangy = rangy; 10 | 11 | console.log(rangy); 12 | 13 | window.setTimeout(function() { 14 | stupidMagicAvoidance(["./rangy-classapplier.js"], function() { 15 | console.log(rangy.modules.ClassApplier); 16 | }); 17 | 18 | }, 500); 19 | }); -------------------------------------------------------------------------------- /release_process.txt: -------------------------------------------------------------------------------- 1 | - Change README if necessary 2 | - Write release notes 3 | - Change version number in package.json 4 | - Build using node builder/build.js 5 | - Commit and push 6 | - Update version number in bower.json in rangy-release 7 | - Update code files in rangy-release 8 | - Create release in GitHub. Probably an API to do this. Tag and provide release notes as .md file as script argument 9 | - Update npm with `npm publish` 10 | - Update Bower by adding tag to rangy-release and pushing tags to GitHub 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rangy 2 | ===== 3 | 4 | A cross-browser JavaScript range and selection library. 5 | 6 | ## The future 7 | 8 | I've started [a discussion](https://github.com/timdown/rangy/discussions/487) about the future of Rangy. 9 | 10 | ## AMD 11 | 12 | Rangy 1.3 has AMD support. 13 | 14 | ## NPM 15 | 16 | There is an official Rangy module on NPM called [`rangy`](https://www.npmjs.org/package/rangy). 17 | 18 | ## Documentation 19 | 20 | Documentation is in [the GitHub wiki](https://github.com/timdown/rangy/wiki). 21 | 22 | ## Bower 23 | 24 | There is an official Rangy package for Bower with Rangy 1.2 and 1.3 versions, called `rangy`. 25 | 26 | -------------------------------------------------------------------------------- /.idea/rangy.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /test/removerange.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | removeRange test 6 | 17 | 18 | 19 |

Some text and a bold

20 | 21 | -------------------------------------------------------------------------------- /test/unittests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - Tests 6 | 7 | 8 | 9 | 12 | 13 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /test/selectionsaverestoretests.js: -------------------------------------------------------------------------------- 1 | xn.test.suite("Selection save/restore module tests", function(s) { 2 | s.tearDown = function() { 3 | document.getElementById("test").innerHTML = ""; 4 | }; 5 | 6 | s.test("Issue 140 (saveSelection reverses backward selection)", function(t) { 7 | var testEl = document.getElementById("test"); 8 | testEl.innerHTML = "test"; 9 | var range = rangy.createRange(); 10 | range.setStartAndEnd(testEl.firstChild, 1, 3); 11 | var sel = rangy.getSelection(); 12 | sel.addRange(range, "backward"); 13 | 14 | t.assert(sel.isBackward()); 15 | t.assertEquals(sel.rangeCount, 1); 16 | t.assert(sel.getRangeAt(0).equals(range)); 17 | 18 | rangy.saveSelection(); 19 | 20 | t.assert(sel.isBackward()); 21 | t.assertEquals(sel.rangeCount, 1); 22 | }); 23 | }, false); 24 | -------------------------------------------------------------------------------- /demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Rangy demos

8 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rangy", 3 | "description": "A cross-browser DOM range and selection library", 4 | "version": "1.3.2", 5 | "author": { 6 | "name": "Tim Down", 7 | "email": "tim@timdown.co.uk", 8 | "url": "https://timdown.co.uk/" 9 | }, 10 | "keywords": ["range", "selection", "caret", "DOM"], 11 | "homepage": "https://github.com/timdown/rangy", 12 | "bugs": { 13 | "url": "https://github.com/timdown/rangy/issues" 14 | }, 15 | "licenses": [ 16 | { 17 | "type": "MIT", 18 | "url": "https://opensource.org/licenses/mit-license.php" 19 | } 20 | ], 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/timdown/rangy.git" 24 | }, 25 | "main": "lib/rangy-core.js", 26 | "directories": { 27 | "lib": "./lib" 28 | }, 29 | "dependencies": {}, 30 | "devDependencies": { 31 | "terser": "^5.14.2", 32 | "rimraf": "^3.0.2", 33 | "jshint": "^2.13.5", 34 | "archiver": "^5.3.1" 35 | } 36 | } -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Tim Down 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /test/highlightertests.js: -------------------------------------------------------------------------------- 1 | xn.test.suite("Highlighter module tests", function(s) { 2 | s.tearDown = function() { 3 | document.getElementById("test").innerHTML = ""; 4 | }; 5 | 6 | s.test("highlightSelection test", function(t) { 7 | var applier = rangy.createClassApplier("c1"); 8 | var highlighter = rangy.createHighlighter(); 9 | highlighter.addClassApplier(applier); 10 | 11 | var testEl = document.getElementById("test"); 12 | var range = rangyTestUtils.createRangeInHtml(testEl, 'one [two] three four'); 13 | range.select(); 14 | 15 | var highlights = highlighter.highlightSelection("c1"); 16 | 17 | t.assertEquals(highlights.length, 1); 18 | 19 | 20 | //t.assertEquals(highlights.length, 1); 21 | 22 | 23 | }); 24 | 25 | s.test("Options test (issue 249)", function(t) { 26 | var applier = rangy.createClassApplier("c1"); 27 | var highlighter = rangy.createHighlighter(); 28 | highlighter.addClassApplier(applier); 29 | 30 | highlighter.highlightSelection("c1", { selection: rangy.getSelection() }); 31 | }); 32 | 33 | }, false); 34 | -------------------------------------------------------------------------------- /test/textareatests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - Tests 6 | 7 | 8 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /fiddlings/218.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
This is my text1 which has a 345678 footnote
20 | 27 | 28 | -------------------------------------------------------------------------------- /test/html5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - HTML5 Tests 6 | 7 | 8 | 15 | 16 | 17 | 20 | 21 | 26 | 27 | 28 | 29 |
30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/ietextnodes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | 26 |
27 | 
28 | One
29 | 
30 | 
31 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /demos/demo.css: -------------------------------------------------------------------------------- 1 | #intro { 2 | font-size: 125%; 3 | color: green; 4 | } 5 | 6 | p.small { 7 | font-size: 75%; 8 | } 9 | 10 | span.smaller { 11 | font-size: 75%; 12 | } 13 | 14 | pre { 15 | padding: 3px; 16 | } 17 | #content { 18 | margin-left: 330px; 19 | } 20 | 21 | #buttons { 22 | left: 10px; 23 | position: fixed; 24 | border: solid black 1px; 25 | background-color: lightgoldenrodyellow; 26 | padding: 5px; 27 | width: 300px; 28 | } 29 | 30 | html.ie6 #buttons { 31 | position: absolute; 32 | } 33 | 34 | #buttons h3 { 35 | font-size: 125%; 36 | font-weight: bold; 37 | margin: 0; 38 | padding: 0.25em 0; 39 | } 40 | 41 | #buttons h4 { 42 | font-size: 100%; 43 | font-weight: normal; 44 | font-style: italic; 45 | margin: 0; 46 | padding: 0.25em 0; 47 | } 48 | 49 | #code { 50 | width: 95%; 51 | } 52 | 53 | #selectioncontent { 54 | border: solid black 1px; 55 | padding: 0.125em; 56 | background-color: white; 57 | overflow: auto; 58 | } 59 | 60 | *.unselectable { 61 | -moz-user-select: -moz-none; 62 | -khtml-user-select: none; 63 | -webkit-user-select: none; 64 | 65 | /* 66 | Introduced in IE 10. 67 | See http://ie.microsoft.com/testdrive/HTML5/msUserSelect/ 68 | */ 69 | -ms-user-select: none; 70 | user-select: none; 71 | } 72 | 73 | *.warning { 74 | border: solid darkred 2px; 75 | padding: 3px; 76 | background-color: #f77; 77 | } 78 | -------------------------------------------------------------------------------- /test/ie9controlrange.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 27 | 28 | 29 | and 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/bold.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 32 | 37 | 38 | 39 |
Some lovely bold content and strong stuff and 40 | a styled span, yeah, and a classy span
41 | 42 | 43 | -------------------------------------------------------------------------------- /test/textrangeperformancetests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy - TextRange-to-Range Performace Tests 5 | 6 | 7 | 8 | 9 | 10 | 13 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /test/textrangeperformance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy - Text Range Performance Tests 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/rangetests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy - Tests 5 | 6 | 7 | 8 | 9 | 10 | 13 | 19 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/featuretests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy - TextRange-to-Range Performace Tests 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /test/classappliertests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - Class Applier Tests 6 | 7 | 8 | 9 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/tests.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: verdana, arial, helvetica, sans-serif; 3 | font-size: 81.25%; 4 | } 5 | 6 | h2 { 7 | font-size: 100%; 8 | padding: 0; 9 | margin: 0.1em 0 0.1em 0; 10 | } 11 | 12 | div.xn_test_suite_container { 13 | border: solid #cccccc 1px; 14 | padding: 2px 5px; 15 | margin: 2px 0; 16 | } 17 | 18 | div.xn_test_progressbar_container { 19 | border: solid black 1px; 20 | } 21 | 22 | div.xn_test_progressbar_container *.success { 23 | background-color: #00ff00; 24 | } 25 | 26 | div.xn_test_progressbar_container *.failure { 27 | background-color: red; 28 | } 29 | 30 | div.xn_test_overallprogressbar_container { 31 | position: relative; 32 | } 33 | 34 | div.xn_test_overallprogressbar_container h1 { 35 | margin: 0; 36 | padding: 2px; 37 | font-size: 125%; 38 | font-weight: bold; 39 | white-space: nowrap; 40 | } 41 | 42 | dl *.success { 43 | color: green; 44 | } 45 | 46 | dl *.failure { 47 | color: red; 48 | } 49 | 50 | span.xn_test_expander { 51 | padding: 0; 52 | border: solid black 1px; 53 | cursor: pointer; 54 | cursor: hand; 55 | line-height: 100%; 56 | font-weight: bold; 57 | margin-right: 1em; 58 | font-size: 11px; 59 | } 60 | 61 | dl.xn_test_expanded { 62 | display: block; 63 | } 64 | 65 | dl.xn_test_collapsed { 66 | display: none; 67 | } 68 | 69 | div.xn_test_suite_success { 70 | border: solid 2px limegreen; 71 | } 72 | 73 | div.xn_test_suite_failure { 74 | border: solid 2px red; 75 | } 76 | 77 | pre.xn_test_log_report { 78 | background-color: #f5f5f5; 79 | padding: 3px; 80 | border: solid gray 1px; 81 | font-size: 11px; 82 | font-family: Courier New, Courier, monospace; 83 | } 84 | 85 | code.xn_test_stacktrace { 86 | color: red; 87 | overflow: scroll; 88 | } 89 | 90 | *.xn_test_hidden { 91 | display: none; 92 | } -------------------------------------------------------------------------------- /test/textranges.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 39 | 40 | 41 |

Bold text bold

42 | 43 | -------------------------------------------------------------------------------- /test/selectiontests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - Selection Tests 6 | 7 | 8 | 9 | 12 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/commandtests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - Command Tests 6 | 7 | 8 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/serializertests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - Serializer Tests 6 | 7 | 8 | 9 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/selectionsaverestoretests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - Selection save and restore tests 6 | 7 | 8 | 9 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/commandnewtests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - Command Tests 6 | 7 | 8 | 9 | 16 | 17 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/textrangetests-old.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - Text Range Tests 6 | 7 | 8 | 9 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 |
35 |
36 | One 37 |
38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/highlightertests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - Highlighter Tests 6 | 7 | 8 | 9 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/textinputs_jquery.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Rangy Text Inputs jQuery plugin demo 6 | 7 | 8 | 15 | 16 | 17 | 18 |

19 | 20 | 21 | 22 | Start: 23 | End: 24 |

25 | 26 |

27 | 28 |

29 | 30 |

31 | 32 | 33 | 34 | Start: 35 | End: 36 | 37 |

38 | 45 |

46 | 47 | -------------------------------------------------------------------------------- /test/textrangetests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rangy - Text Range Tests 6 | 7 | 8 | 9 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
Some text 35 |
36 |
37 | One 38 |
39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /test/featuretests.js: -------------------------------------------------------------------------------- 1 | xn.test.suite("Browser feature tests", function(s) { 2 | rangy.init(); 3 | 4 | // Detect browser version roughly. It doesn't matter too much: these are only rough tests designed to test whether 5 | // Rangy's feature detection is hopelessly wrong 6 | 7 | 8 | var browser = jQuery.browser; 9 | var isIe = !!browser.msie; 10 | var isMozilla = !!browser.mozilla; 11 | var isOpera = !!browser.opera; 12 | var version = parseFloat(browser.version); 13 | 14 | s.test("DOM Range support", function(t) { 15 | t.assertEquals(rangy.features.implementsDomRange, !isIe || version >= 9); 16 | }); 17 | 18 | s.test("TextRange support", function(t) { 19 | t.assertEquals(rangy.features.implementsTextRange, isIe && version >= 4); 20 | }); 21 | 22 | s.test("document.selection support", function(t) { 23 | t.assertEquals(rangy.features.implementsTextRange, isIe && version >= 4); 24 | }); 25 | 26 | s.test("window.getSelection() support", function(t) { 27 | t.assertEquals(rangy.features.implementsWinGetSelection, !isIe || version >= 9); 28 | }); 29 | 30 | s.test("selection has rangeCount", function(t) { 31 | t.assertEquals(rangy.features.selectionHasRangeCount, !isIe || version >= 9); 32 | }); 33 | 34 | s.test("selection has anchor and focus support", function(t) { 35 | t.assertEquals(rangy.features.selectionHasAnchorAndFocus, !isIe || version >= 9); 36 | }); 37 | 38 | s.test("selection has extend() method", function(t) { 39 | t.assertEquals(rangy.features.selectionHasExtend, !isIe); 40 | }); 41 | 42 | s.test("HTML parsing", function(t) { 43 | t.assertEquals(rangy.features.htmlParsingConforms, !isIe); 44 | }); 45 | 46 | s.test("Multiple ranges per selection support", function(t) { 47 | t.assertEquals(rangy.features.selectionSupportsMultipleRanges, isMozilla); 48 | }); 49 | 50 | s.test("Collapsed non-editable selections support", function(t) { 51 | t.assertEquals(rangy.features.collapsedNonEditableSelectionsSupported, !isOpera); 52 | }); 53 | }, false); 54 | -------------------------------------------------------------------------------- /test/textrangeperformancetests.js: -------------------------------------------------------------------------------- 1 | rangy.config.preferTextRange = true; 2 | 3 | xn.test.suite("Range miscellaneous", function(s) { 4 | rangy.init(); 5 | 6 | var elementCount = 1000; 7 | var testCount = 20; 8 | 9 | function setUp(t) { 10 | t.testEl = document.createElement("div"); 11 | t.testEl.innerHTML = new Array(elementCount + 1).join("One twothree four"); 12 | document.body.appendChild(t.testEl); 13 | var textRange = document.body.createTextRange(); 14 | textRange.moveToElementText(t.testEl); 15 | var textLength = textRange.text.length; 16 | 17 | t.textRanges = []; 18 | 19 | for (var i = 0, start, end; i < testCount; ++i) { 20 | textRange = document.body.createTextRange(); 21 | textRange.moveToElementText(t.testEl); 22 | 23 | start = Math.floor(textLength * Math.random()); 24 | end = start + Math.floor((textLength - start) * Math.random()); 25 | 26 | textRange.collapse(true); 27 | textRange.moveEnd("character", end); 28 | textRange.moveStart("character", start); 29 | 30 | if (Math.random() < 0.3) { 31 | textRange.collapse(true); 32 | } 33 | t.textRanges[i] = textRange; 34 | } 35 | } 36 | 37 | function tearDown(t) { 38 | t.testEl.parentNode.removeChild(t.testEl); 39 | } 40 | 41 | if (document.body.createTextRange) { 42 | s.test("TextRange to Range control", function(t) { 43 | //t.assertEquals(t.testEl.childNodes.length, 2 * elementCount); 44 | for (var i = 0, len = t.textRanges.length, range; i < len; ++i) { 45 | t.textRanges[i].select(); 46 | } 47 | }, setUp, tearDown); 48 | 49 | s.test("TextRange to Range speed test (binary search)", function(t) { 50 | rangy.init(); 51 | for (var i = 0, len = t.textRanges.length, sel; i < len; ++i) { 52 | t.textRanges[i].select(); 53 | sel = rangy.getSelection(); 54 | t.assertEquals(t.textRanges[i].text, sel.toString()); 55 | } 56 | }, setUp, tearDown); 57 | } 58 | }, false); 59 | -------------------------------------------------------------------------------- /test/controlrange2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 39 | 40 | 41 |
42 | Pictures of Steve Claridge: 43 | claridge 44 | claridge 45 | claridge 46 |
47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /roadmap.txt: -------------------------------------------------------------------------------- 1 | 1.3 2 | --- 3 | 4 | - [X] TextRange module 5 | (http://groups.google.com/group/rangy/browse_frm/thread/bd7a351e63a16474) 6 | - [X] Allow Window, Document, iframe and DOM node as params to range/selection creation methods 7 | - [X] Add rangy.features.implementsWinGetSelection and rangy.features.implementsDocSelection 8 | - [X] Check Range and Selection against WHATWG Range spec algorithms 9 | - [X] Highlighter module. Review and rewrite existing. 10 | - [X] Added select() method to range 11 | - [X] Rename CSS class applier module to "class applier module" 12 | - [X] Add handling for img and similar elements in class applier module 13 | - [X] AMD support 14 | - [X] Add getNativeTextRange() to selection for IE 11 15 | - [X] Add Range setStartAndEnd(). Overloaded? eg. two args collapsed, three args (node, startOffset, endOffset), 16 | four args (startNode, startOffset, endNode, endOffset) (consider this) 17 | 18 | 1.4 19 | --- 20 | 21 | - [?] Consider range.restrict(node) 22 | - [?] Consider filter option in createClassApplier() options object 23 | - [ ] Either a utils module or an FAQ page with code snippets for common use cases, including: 24 | - [X] Simple selection save/restore (bookmark?) (is this necessary?) 25 | - [ ] Insert HTML 26 | (http://stackoverflow.com/questions/2213376/how-to-find-cursor-position-in-a-contenteditable-div/2213514#2213514) 27 | - [?] Some kind of jQuery integration module? 28 | - [ ] Move IE <= 8 support into a separate optional module 29 | - [ ] Add withinRange and withinNode options to move(), moveStart() and moveEnd() methods 30 | - [?] Positions module 31 | (http://stackoverflow.com/questions/4122315/how-to-find-xy-position-in-javascript-with-offset/4123495#4123495) 32 | - [ ] Config module or something so that config can work with AMD. See PR #285 33 | (https://github.com/timdown/rangy/pull/285) 34 | - [ ] Move to one of the common testing libraries 35 | - [ ] Update build not to use a fresh Git checkout 36 | - [ ] Investigate shadow DOM (issue #307) 37 | 38 | Possible features for some version 39 | ---------------------------------- 40 | 41 | - [?] Commands module with basic inline commands (bold, italic, colour, font face, font size, background colour, etc.) 42 | (http://stackoverflow.com/questions/2887101/apply-style-to-range-of-text-with-javascript-in-uiwebview/2888969#2888969) 43 | - [?] More commands (block? Insert line break? Think about this, don't want to build a WYSIWYG editor) 44 | - [ ] Add selection extend()? Don't think this is possible. 45 | - [ ] Option in TextRange module for alternative ways to extract text for an element (see email from Bruce Augustine) 46 | -------------------------------------------------------------------------------- /demos/content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy Bold Demo 5 | 18 | 26 | 27 | 28 |
Test
29 |
Test
30 |
Test
31 | 32 |

2

33 |

One

34 | Two 35 |

Three

36 | 37 |

38 | Association football is a sport played between two teams. It is usually called football, but in 39 | some countries, such as the United States, it is called soccer. In 40 | Japan, New Zealand, South Africa, Australia, Canada and 41 | Republic of Ireland, both words are commonly used. 42 |

43 |

44 | Each team has 11 players on the field. One of these players is the goalkeeper, and the other ten are 45 | known as "outfield players." The game is played by kicking a ball into the opponent's goal. A 46 | match has 90 minutes of play, with a break of 15 minutes in the middle. The break in the middle is called 47 | half-time. 48 |

49 |

Competitions

50 |

51 | There are many competitions for football, for both football clubs and countries. Football clubs usually play 52 | other teams in their own country, with a few exceptions. Cardiff City F.C. from Wales for example, play 53 | in the English leagues and in the English FA Cup. 54 |

55 |

Who plays football

56 |
57 | Football is the world's most popular sport. It is played in more
58 | countries than any other game. In fact, FIFA (the Federation
59 | Internationale de Football Association) has more members than the
60 | United Nations.
61 | 
62 | It is played by both males and females.
63 | 
64 | 
65 | 
66 | 67 |

68 | Text adapted from Simple Wikipedia page on 69 | Association Football, licensed under the 70 | Creative 71 | Commons Attribution/Share-Alike License. 72 |

73 | 74 | -------------------------------------------------------------------------------- /test/textareatests.js: -------------------------------------------------------------------------------- 1 | xn.test.suite("Textarea", function(s) { 2 | var enormoString = new Array(1e7).join("x"); 3 | 4 | s.setUp = function(t) { 5 | t.textarea = document.createElement("textarea"); 6 | t.textarea.id = "ta"; 7 | t.textNode = t.textarea.appendChild(document.createTextNode("")); 8 | t.textNode.data = enormoString; 9 | document.body.appendChild(t.textarea); 10 | }; 11 | 12 | s.tearDown = function(t) { 13 | document.body.removeChild(t.textarea); 14 | }; 15 | 16 | /* 17 | s.test("Changing value affects text node", function(t) { 18 | t.textarea.value = "Test"; 19 | t.assertEquals(t.textarea.value, t.textNode.data); 20 | }); 21 | */ 22 | 23 | s.test("Changing text node affects value", function(t) { 24 | t.textNode.data = "Test"; 25 | t.assertEquals(t.textarea.value, t.textNode.data); 26 | }); 27 | 28 | s.test("Setting long value via text node", function(t) { 29 | t.textNode.data = enormoString; 30 | t.assertEquals(t.textarea.value, enormoString); 31 | }); 32 | 33 | s.test("Setting long value via value", function(t) { 34 | t.textarea.value = enormoString; 35 | t.assertEquals(t.textarea.value, enormoString); 36 | }); 37 | 38 | s.test("Setting long value via text node after replacing text area", function(t) { 39 | var newTextarea = t.textarea.cloneNode(false); 40 | //alert(newTextarea.id); 41 | newTextarea.id = "temptextarea"; 42 | var newTextNode = newTextarea.appendChild(document.createTextNode(enormoString)); 43 | t.textarea.parentNode.insertBefore(newTextarea, t.textarea); 44 | t.textarea.parentNode.removeChild(t.textarea); 45 | newTextarea.id = t.textarea.id; 46 | t.textarea = newTextarea; 47 | t.assertEquals(t.textarea.value, enormoString); 48 | }); 49 | 50 | /* 51 | s.test("Amending long value via changing text node data", function(t) { 52 | //t.textNode.data = enormoString; 53 | t.textNode.data = "yyy" + t.textNode.data; 54 | t.assertEquals(t.textarea.value, "yyy" + enormoString); 55 | t.textNode.data = "yyy" + t.textNode.data; 56 | t.assertEquals(t.textarea.value, "yyy" + enormoString); 57 | }); 58 | 59 | s.test("Amending long value via changing text node insertData", function(t) { 60 | //t.textNode.data = enormoString; 61 | t.textNode.insertData(0, "yyy"); 62 | t.assertEquals(t.textarea.value, "yyy" + enormoString); 63 | t.textNode.insertData(0, "yyy"); 64 | t.assertEquals(t.textarea.value, "yyy" + enormoString); 65 | }); 66 | */ 67 | 68 | /* 69 | var boolCount = 1e6; 70 | 71 | s.test("!!", function(t) { 72 | var a = [], i = boolCount; 73 | while (i--) { 74 | a.push(!!i); 75 | } 76 | }); 77 | 78 | s.test("Boolean", function(t) { 79 | var a = [], i = boolCount; 80 | while (i--) { 81 | a.push(Boolean(i)); 82 | } 83 | }); 84 | */ 85 | }, false); 86 | -------------------------------------------------------------------------------- /src/modules/inactive/commands/rangy-italic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Selection save and restore module for Rangy. 3 | * Italic command 4 | * 5 | * Part of Rangy, a cross-browser JavaScript range and selection library 6 | * https://github.com/timdown/rangy 7 | * 8 | * Depends on Rangy core. 9 | * 10 | * Copyright %%build:year%%, Tim Down 11 | * Licensed under the MIT license. 12 | * Version: %%build:version%% 13 | * Build date: %%build:date%% 14 | */ 15 | rangy.createModule("ItalicCommand", function(api, module) { 16 | api.requireModules( ["Commands"] ); 17 | 18 | var dom = api.dom, commandUtil = api.Command.util; 19 | var log = log4javascript.getLogger("rangy.ItalicCommand"); 20 | 21 | function ItalicCommand() { 22 | 23 | } 24 | 25 | api.Command.create(ItalicCommand, { 26 | relevantCssProperty: "fontStyle", 27 | 28 | defaultOptions: { 29 | ignoreWhiteSpace: true 30 | }, 31 | 32 | getSpecifiedValue: function(element) { 33 | return element.style.fontStyle || (/^(em|i)$/i.test(element.tagName) ? "italic" : null); 34 | }, 35 | 36 | createNonCssElement: function(node, value) { 37 | return (value == "italic") ? dom.getDocument(node).createElement("i") : null; 38 | }, 39 | 40 | getRangeValue: function(range, context) { 41 | var textNodes = commandUtil.getEffectiveTextNodes(range, context), i = textNodes.length, value; 42 | log.info("getRangeValue on " + range.inspect() + ", text nodes: " + textNodes); 43 | if (textNodes.length == 0) { 44 | return commandUtil.getEffectiveValue(range.commonAncestorContainer, context) == "italic"; 45 | } else { 46 | while (i--) { 47 | value = commandUtil.getEffectiveValue(textNodes[i], context); 48 | log.info("getRangeValue value " + value); 49 | if (value != "italic") { 50 | log.info("getRangeValue returning false"); 51 | return false; 52 | } 53 | } 54 | return true; 55 | } 56 | }, 57 | 58 | getSelectionValue: function(sel, context) { 59 | var selRanges = sel.getAllRanges(); 60 | for (var i = 0, len = selRanges.length; i < len; ++i) { 61 | if (!this.getRangeValue(selRanges[i], context)) { 62 | return false; 63 | } 64 | } 65 | return len > 0; 66 | }, 67 | 68 | getNewRangeValue: function(range, context) { 69 | return this.getRangeValue(range, context) ? "normal" : "italic"; 70 | }, 71 | 72 | getNewSelectionValue: function(sel, context) { 73 | return this.getSelectionValue(sel, context) ? "normal" : "italic"; 74 | }, 75 | 76 | applyValueToRange: function(range, context) { 77 | var decomposed = commandUtil.decomposeRange(range, context.rangesToPreserve); 78 | 79 | for (var i = 0, len = decomposed.length; i < len; ++i) { 80 | commandUtil.setNodeValue(decomposed[i], context); 81 | } 82 | } 83 | }); 84 | 85 | api.registerCommand("italic", new ItalicCommand()); 86 | 87 | }); 88 | -------------------------------------------------------------------------------- /test/words.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 53 | 54 | 55 | 56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 68 |
69 | 70 | 71 |
72 | %%This 'is' a Mr. Tickle, and Mr Bump 5$ passé sentence.
This' is "another" tip-top $64 $5.63 amazing fun   sentence. Yes 'five pence' and 60%. Look! Another, one" don't don''t don"t 50:50 !yes! %%70%% 70%% cheese. 73 | 74 | one] two} three{ four [ five) six( seven; eight: nine@ ten~ eleven# twelve/ thirteen& four* five£ £6 75 |
76 | 77 | -------------------------------------------------------------------------------- /test/domtests.js: -------------------------------------------------------------------------------- 1 | xn.test.suite("Range", function(s) { 2 | function createTestNodes(parentNode, limit, copies) { 3 | if (limit > 0) { 4 | var n = parentNode.appendChild(document.createElement("div")); 5 | n.appendChild(document.createTextNode("Before ")); 6 | var p = n.appendChild(document.createElement("div")); 7 | n.appendChild(document.createTextNode(" after")); 8 | for (var i = 0; i < copies; i++) { 9 | createTestNodes(p, limit - 1, copies); 10 | } 11 | } 12 | } 13 | 14 | var testNode = document.createElement("div"); 15 | createTestNodes(testNode, 14, 2); 16 | 17 | var recursiveNodes, nonRecursiveNodes, iteratorNodes; 18 | var dom = rangy.dom; 19 | 20 | s.test("Iterate nodes (iterator)", function(t) { 21 | iteratorNodes = []; 22 | var it = dom.createIterator(testNode), node; 23 | while ( (node = it.next()) ) { 24 | iteratorNodes.push(node); 25 | } 26 | }); 27 | 28 | s.test("Check results", function(t) { 29 | t.assertArraysEquivalent(recursiveNodes, nonRecursiveNodes); 30 | }); 31 | 32 | s.test("Check results", function(t) { 33 | t.assertArraysEquivalent(iteratorNodes, nonRecursiveNodes); 34 | }); 35 | 36 | 37 | var arrayContains = function(arr, val) { 38 | var i = arr.length; 39 | while (i--) { 40 | if (arr[i] === val) { 41 | return true; 42 | } 43 | } 44 | return false; 45 | }; 46 | 47 | var validNodeTypes = [1, 3, 4, 5, 6, 8, 9, 10]; 48 | 49 | 50 | var numNodeTypes = 100; 51 | var isValid1, isValid2; 52 | 53 | s.test("Node types regex", function(t) { 54 | isValid1 = []; 55 | var i = numNodeTypes; 56 | var regex = new RegExp("^(" + validNodeTypes.join("|") + ")$"); 57 | while (i--) { 58 | isValid1[i] = regex.test((i % 12)); 59 | } 60 | }); 61 | 62 | s.test("Node types array contains", function(t) { 63 | isValid2 = []; 64 | var i = numNodeTypes; 65 | while (i--) { 66 | isValid2[i] = arrayContains(validNodeTypes, i % 12); 67 | } 68 | }); 69 | 70 | s.test("Check results", function(t) { 71 | t.assertArraysEquivalent(isValid1, isValid2); 72 | }); 73 | 74 | s.test("comparePoints 1", function(t) { 75 | var div = document.createElement("div"); 76 | var text1 = div.appendChild(document.createTextNode("One")); 77 | var b = div.appendChild(document.createElement("b")); 78 | var text2 = b.appendChild(document.createTextNode("Two")); 79 | document.body.appendChild(div); 80 | 81 | t.assertEquals(dom.comparePoints(text1, 1, text1, 2), -1); 82 | t.assertEquals(dom.comparePoints(text1, 2, text1, 2), 0); 83 | t.assertEquals(dom.comparePoints(text1, 3, text1, 2), 1); 84 | t.assertEquals(dom.comparePoints(div, 0, text1, 2), -1); 85 | t.assertEquals(dom.comparePoints(div, 1, text1, 2), 1); 86 | 87 | /* 88 | var range = rangy.createRange(); 89 | range.setStart(text1, 2); 90 | range.setEnd(text2, 2); 91 | */ 92 | }); 93 | 94 | 95 | 96 | }, false); 97 | -------------------------------------------------------------------------------- /test/controlrange.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 63 | 64 | 65 |
66 | Pictures of Steve Claridge: 67 | claridge 68 | claridge 69 | claridge 70 | span 71 |
72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /fiddlings/214.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 49 | 50 | 51 | 52 |
53 | Click me 54 | 57 |
58 | 76 | 77 | -------------------------------------------------------------------------------- /test/classapplier.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 37 | 59 | 60 | 61 |
62 | 29 min: After that let-off, Portugal tear down the other end and sow panic in the Brazilian defence. 63 |
64 | 66 | 67 | 68 | 69 | 70 | 71 |

Zero

72 |

One two

73 |

Four five

74 |

29 min: After that let-off, Portugal tear down the other end and sow panic in the Brazilian defence, Juan forced into a clusmy-looking tackle on Tiago, who tumbles theatrically. Portugal players howl for a penalty, the ref books Tiago for diving.

75 |

28 min: Portugal switch off defensively, allowing Nilmar to collect a through-ball and shoot from eight yards. The keeper pushes it on to the psot and out! Great save. "Klose ..." blurts David Roberts. "... but no cigar."

76 |
77 | Some preformatted text.
78 | 
79 |     Wonder how it'll do
80 | 
81 | with this, plus some line breaks
82 | 
83 | 
84 | 
85 | 86 | -------------------------------------------------------------------------------- /src/modules/rangy-util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities module for Rangy. 3 | * A collection of common selection and range-related tasks, using Rangy. 4 | * 5 | * Part of Rangy, a cross-browser JavaScript range and selection library 6 | * https://github.com/timdown/rangy 7 | * 8 | * Depends on Rangy core. 9 | * 10 | * Copyright %%build:year%%, Tim Down 11 | * Licensed under the MIT license. 12 | * Version: %%build:version%% 13 | * Build date: %%build:date%% 14 | */ 15 | /* build:modularizeWithRangyDependency */ 16 | rangy.createModule("Util", ["WrappedSelection"], function(api, module) { 17 | var rangeProto = api.rangePrototype; 18 | var selProto = api.selectionPrototype; 19 | 20 | selProto.pasteText = function(text) { 21 | this.deleteFromDocument(); 22 | var range = this.getRangeAt(0); 23 | var textNode = range.getDocument().createTextNode(text); 24 | range.insertNode(textNode); 25 | this.setSingleRange(range); 26 | }; 27 | 28 | rangeProto.pasteText = function(text) { 29 | this.deleteContents(); 30 | var textNode = this.getDocument().createTextNode(text); 31 | this.insertNode(textNode); 32 | }; 33 | 34 | selProto.pasteHtml = function(html) { 35 | this.deleteFromDocument(); 36 | var range = this.getRangeAt(0); 37 | var frag = this.createContextualFragment(html); 38 | var lastNode = frag.lastChild; 39 | range.insertNode(frag); 40 | if (lastNode) { 41 | range.setStartAfter(lastNode) 42 | } 43 | this.setSingleRange(range); 44 | }; 45 | 46 | rangeProto.pasteHtml = function(html) { 47 | this.deleteContents(); 48 | var frag = this.createContextualFragment(html); 49 | this.insertNode(frag); 50 | }; 51 | 52 | selProto.selectNodeContents = function(node) { 53 | var range = api.createRange(this.win); 54 | range.selectNodeContents(node); 55 | this.setSingleRange(range); 56 | }; 57 | 58 | api.createRangeFromNode = function(node) { 59 | var range = api.createRange(node); 60 | range.selectNode(node); 61 | return range; 62 | }; 63 | 64 | api.createRangeFromNodeContents = function(node) { 65 | var range = api.createRange(node); 66 | range.selectNodeContents(node); 67 | return range; 68 | }; 69 | 70 | api.selectNodeContents = function(node) { 71 | api.getSelection().selectNodeContents(node); 72 | }; 73 | 74 | rangeProto.selectSelectedTextElements = (function() { 75 | function isInlineElement(node) { 76 | return node.nodeType == 1 && api.dom.getComputedStyleProperty(node, "display") == "inline"; 77 | } 78 | 79 | function getOutermostNodeContainingText(range, node) { 80 | var outerNode = null; 81 | var nodeRange = range.cloneRange(); 82 | nodeRange.selectNode(node); 83 | if (nodeRange.toString() !== "") { 84 | while ( (node = node.parentNode) && isInlineElement(node) && range.containsNodeText(node) ) { 85 | outerNode = node; 86 | } 87 | } 88 | return outerNode; 89 | } 90 | 91 | return function() { 92 | var startNode = getOutermostNodeContainingText(this, this.startContainer); 93 | if (startNode) { 94 | this.setStartBefore(startNode); 95 | } 96 | 97 | var endNode = getOutermostNodeContainingText(this, this.endContainer); 98 | if (endNode) { 99 | this.setEndAfter(endNode); 100 | } 101 | }; 102 | })(); 103 | 104 | // TODO: simple selection save/restore 105 | }); 106 | /* build:modularizeEnd */ -------------------------------------------------------------------------------- /test/serializer.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Serializer Test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 27 | 28 | 29 |

Serializer Test

30 |

Below is some editable content:

31 |

32 | The cabbage is a popular cultivar of a the species 33 | Brassica oleracea Linne (Capitata Group) 34 | of the Family Brassicaceae (or Cruciferae), and is used as 35 | a leafy green vegetable. It is a 36 | herbaceous, 37 | biennial, 38 | dicotyledonous 39 | 40 | flowering plant distinguished by a short stem upon 41 | which is crowded a mass of leaves, usually green but in some varieties red or purplish, which while immature 42 | form a characteristic compact, globular cluster (cabbagehead). 43 |

44 |

45 | The cabbage is a popular cultivar of a the species 46 | Brassica oleracea Linne (Capitata Group) 47 | of the Family Brassicaceae (or Cruciferae), and is used as 48 | a leafy green vegetable. It is a 49 | herbaceous, 50 | biennial, 51 | dicotyledonous 52 | 53 | flowering plant distinguished by a short stem upon 54 | which is crowded a mass of leaves, usually green but in some varieties red or purplish, which while immature 55 | form a characteristic compact, globular cluster (cabbagehead). 56 |

57 |

58 | Press the button to refresh this page and restore the current selection from the cookie: 59 | 60 | 61 | 62 |

63 | 64 | 65 | -------------------------------------------------------------------------------- /external/jshashtable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2010 Tim Down. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | var Hashtable=(function(){var p="function";var n=(typeof Array.prototype.splice==p)?function(s,r){s.splice(r,1)}:function(u,t){var s,v,r;if(t===u.length-1){u.length=t}else{s=u.slice(t+1);u.length=t;for(v=0,r=s.length;v 3 | 4 | 5 | Selection save/restore test 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 49 | 50 | 51 |

Selection save/restore test

52 | 53 |

Below is some editable content:

54 |

55 | The cabbage is a popular cultivar of a the species 56 | Brassica oleracea Linne (Capitata Group) 57 | of the Family Brassicaceae (or Cruciferae), and is used as 58 | a leafy green vegetable. It is a 59 | herbaceous, 60 | biennial, 61 | dicotyledonous 62 | 63 | flowering plant distinguished by a short stem upon 64 | which is crowded a mass of leaves, usually green but in some varieties red or purplish, which while immature 65 | form a characteristic compact, globular cluster (cabbagehead). This is some area to type. 66 |

67 | 68 | 69 |

70 | Select something in the editable area above. Click on the "Save selection" button. Now click somewhere on the 71 | page to destroy the selection, and then press "Restore selection". 72 |

73 | 74 | -------------------------------------------------------------------------------- /demos/bookmark.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy Core Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 23 | 24 | 65 | 66 | 67 |

68 | Please use the simple editor below to create a link, demonstrating the getBoookmark() and 69 | moveToBoookmark() methods of Rangy's selection object. 70 |

71 |
72 |
73 | 74 |
75 |
76 | 77 | 78 |
79 |
80 | Association football is a sport played between two teams. It is usually called football, but in 81 | some countries, such as the United States, it is called soccer. In 82 | Japan, New Zealand, South Africa, Australia, Canada and 83 | Republic of Ireland, both words are commonly used. 84 |
85 |
86 |

87 | Text adapted from Simple Wikipedia page on 88 | Association Football, licensed under the 89 | Creative 90 | Commons Attribution/Share-Alike License. 91 |

92 | 93 | -------------------------------------------------------------------------------- /src/modules/inactive/commands/rangy-bold_new.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Selection save and restore module for Rangy. 3 | * Bold command 4 | * 5 | * Part of Rangy, a cross-browser JavaScript range and selection library 6 | * https://github.com/timdown/rangy 7 | * 8 | * Depends on Rangy core. 9 | * 10 | * Copyright %%build:year%%, Tim Down 11 | * Licensed under the MIT license. 12 | * Version: %%build:version%% 13 | * Build date: %%build:date%% 14 | */ 15 | rangy.createModule("BoldCommand", function(api, module) { 16 | api.requireModules( ["Commands"] ); 17 | 18 | var dom = api.dom, commandUtil = api.Command.util; 19 | var log = log4javascript.getLogger("rangy.BoldCommand"); 20 | 21 | function BoldCommand() { 22 | 23 | } 24 | 25 | api.SimpleInlineCommand.create(BoldCommand, { 26 | relevantCssProperty: "fontWeight", 27 | 28 | defaultOptions: { 29 | tagName: "b" 30 | }, 31 | 32 | getSpecifiedValue: function(element) { 33 | return element.style.fontWeight || (/^(strong|b)$/i.test(element.tagName) ? "bold" : null); 34 | }, 35 | 36 | valuesEqual: function(val1, val2) { 37 | val1 = ("" + val1).toLowerCase(); 38 | val2 = ("" + val2).toLowerCase(); 39 | return val1 == val2 40 | || (val1 == "bold" && val2 == "700") 41 | || (val2 == "bold" && val1 == "700") 42 | || (val1 == "normal" && val2 == "400") 43 | || (val2 == "normal" && val1 == "400"); 44 | }, 45 | 46 | createNonCssElement: function(node, value, context) { 47 | if (value == "bold" || value == "700") { 48 | return dom.getDocument(node).createElement(context.options.tagName); 49 | } 50 | 51 | return null; 52 | }, 53 | 54 | isBoldCssValue: function(value) { 55 | return /^(bold|700|800|900)$/.test(value); 56 | }, 57 | 58 | getRangeValue: function(range, context) { 59 | var textNodes = commandUtil.getEffectiveTextNodes(range, context), i = textNodes.length, value; 60 | log.info("getRangeValue on " + range.inspect() + ", text nodes: " + textNodes); 61 | 62 | if (textNodes.length == 0) { 63 | return this.isBoldCssValue(commandUtil.getEffectiveValue(range.commonAncestorContainer, context)) 64 | } else { 65 | while (i--) { 66 | value = commandUtil.getEffectiveValue(textNodes[i], context); 67 | log.info("getRangeValue value " + value); 68 | if (!this.isBoldCssValue(value)) { 69 | log.info("getRangeValue returning false"); 70 | return false; 71 | } 72 | } 73 | return true; 74 | } 75 | }, 76 | 77 | getSelectionValue: function(sel, context) { 78 | var selRanges = sel.getAllRanges(); 79 | for (var i = 0, len = selRanges.length; i < len; ++i) { 80 | if (!this.getRangeValue(selRanges[i], context)) { 81 | return false; 82 | } 83 | } 84 | return len > 0; 85 | }, 86 | 87 | getNewRangeValue: function(range, context) { 88 | return this.getRangeValue(range, context) ? "normal" : "bold"; 89 | }, 90 | 91 | getNewSelectionValue: function(sel, context) { 92 | return this.getSelectionValue(sel, context) ? "normal" : "bold"; 93 | }, 94 | 95 | applyValueToRange: function(range, context) { 96 | var decomposed = commandUtil.decomposeRange(range, context.rangesToPreserve); 97 | 98 | log.info("applyValueToRange " + range.inspect()) 99 | 100 | for (var i = 0, len = decomposed.length; i < len; ++i) { 101 | log.info("Setting node value on: " + dom.inspectNode(decomposed[i])) 102 | commandUtil.setNodeValue(decomposed[i], context); 103 | } 104 | } 105 | }); 106 | 107 | api.registerCommand("bold", new BoldCommand()); 108 | 109 | }); 110 | -------------------------------------------------------------------------------- /src/modules/inactive/commands/rangy-bold.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Selection save and restore module for Rangy. 3 | * Bold command 4 | * 5 | * Part of Rangy, a cross-browser JavaScript range and selection library 6 | * https://github.com/timdown/rangy 7 | * 8 | * Depends on Rangy core. 9 | * 10 | * Copyright %%build:year%%, Tim Down 11 | * Licensed under the MIT license. 12 | * Version: %%build:version%% 13 | * Build date: %%build:date%% 14 | */ 15 | rangy.createModule("BoldCommand", function(api, module) { 16 | api.requireModules( ["Commands"] ); 17 | 18 | var dom = api.dom, commandUtil = api.Command.util; 19 | var log = log4javascript.getLogger("rangy.BoldCommand"); 20 | 21 | function BoldCommand() { 22 | 23 | } 24 | 25 | api.Command.create(BoldCommand, { 26 | relevantCssProperty: "fontWeight", 27 | 28 | defaultOptions: { 29 | tagName: "b", 30 | ignoreWhiteSpace: true 31 | }, 32 | 33 | getSpecifiedValue: function(element) { 34 | return element.style.fontWeight || (/^(strong|b)$/i.test(element.tagName) ? "bold" : null); 35 | }, 36 | 37 | valuesEqual: function(val1, val2) { 38 | val1 = ("" + val1).toLowerCase(); 39 | val2 = ("" + val2).toLowerCase(); 40 | return val1 == val2 41 | || (val1 == "bold" && val2 == "700") 42 | || (val2 == "bold" && val1 == "700") 43 | || (val1 == "normal" && val2 == "400") 44 | || (val2 == "normal" && val1 == "400"); 45 | }, 46 | 47 | createNonCssElement: function(node, value, context) { 48 | if (value == "bold" || value == "700") { 49 | return dom.getDocument(node).createElement(context.options.tagName); 50 | } 51 | 52 | return null; 53 | }, 54 | 55 | isBoldCssValue: function(value) { 56 | return /^(bold|700|800|900)$/.test(value); 57 | }, 58 | 59 | getRangeValue: function(range, context) { 60 | var textNodes = commandUtil.getEffectiveTextNodes(range, context), i = textNodes.length, value; 61 | log.info("getRangeValue on " + range.inspect() + ", text nodes: " + textNodes); 62 | 63 | if (textNodes.length == 0) { 64 | return this.isBoldCssValue(commandUtil.getEffectiveValue(range.commonAncestorContainer, context)) 65 | } else { 66 | while (i--) { 67 | value = commandUtil.getEffectiveValue(textNodes[i], context); 68 | log.info("getRangeValue value " + value); 69 | if (!this.isBoldCssValue(value)) { 70 | log.info("getRangeValue returning false"); 71 | return false; 72 | } 73 | } 74 | return true; 75 | } 76 | }, 77 | 78 | getSelectionValue: function(sel, context) { 79 | var selRanges = sel.getAllRanges(); 80 | for (var i = 0, len = selRanges.length; i < len; ++i) { 81 | if (!this.getRangeValue(selRanges[i], context)) { 82 | return false; 83 | } 84 | } 85 | return len > 0; 86 | }, 87 | 88 | getNewRangeValue: function(range, context) { 89 | return this.getRangeValue(range, context) ? "normal" : "bold"; 90 | }, 91 | 92 | getNewSelectionValue: function(sel, context) { 93 | return this.getSelectionValue(sel, context) ? "normal" : "bold"; 94 | }, 95 | 96 | applyValueToRange: function(range, context) { 97 | var decomposed = commandUtil.decomposeRange(range, context.rangesToPreserve); 98 | 99 | log.info("applyValueToRange " + range.inspect()) 100 | 101 | for (var i = 0, len = decomposed.length; i < len; ++i) { 102 | log.info("Setting node value on: " + dom.inspectNode(decomposed[i])) 103 | commandUtil.setNodeValue(decomposed[i], context); 104 | } 105 | } 106 | }); 107 | 108 | api.registerCommand("bold", new BoldCommand()); 109 | 110 | }); 111 | -------------------------------------------------------------------------------- /demos/scopedhighlights.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy Highlighter Module Demo 5 | 6 | 11 | 12 | 13 | 14 | 76 | 77 | 78 |
79 |

Highlighter

80 |

Make a selection in the document and use the buttons below to highlight and unhighlight.

81 | 82 | 83 | 84 |

Preserving highlights between page requests

85 |
86 | Highlights can be preserved between page requests. Press the following button to reload the page with the 87 | highlights preserved: 88 |
89 | 90 | 91 |
92 |
93 | 94 |
95 |

Highlighter module scoped highlights demo

96 |

97 | Please use your mouse and/or keyboard to make selections from the sample quotes below and use the buttons 98 | on the left hand size to create and remove highlights. 99 |

100 | 106 |
107 | 108 | -------------------------------------------------------------------------------- /fiddlings/spec/innerText_files/dfn.js: -------------------------------------------------------------------------------- 1 | // dfn.js 2 | // makes elements link back to all uses of the term 3 | // no copyright is asserted on this file 4 | 5 | var dfnTimer = new Date(); 6 | 7 | var dfnMapTarget = -1; 8 | var dfnMapDone = false; 9 | var dfnMap = {}; 10 | function initDfn() { 11 | var links = []; 12 | dfnMapTarget = document.links.length; 13 | for (var i = 0; i < dfnMapTarget; i += 1) 14 | links[i] = document.links[i]; 15 | var k = 0; 16 | var n = 0; 17 | var initDfnInternal = function () { 18 | n += 1; 19 | var start = new Date(); 20 | while (k < dfnMapTarget) { 21 | if (links[k].hash.length > 1) { 22 | if (links[k].className != "no-backref" && links[k].parentNode.className != "no-backref") { 23 | var s = links[k].hash.substr(1); 24 | if (!(s in dfnMap)) 25 | dfnMap[s] = []; 26 | dfnMap[s].push(links[k]); 27 | } 28 | } 29 | k += 1; 30 | if (new Date() - start > 1000) { 31 | setTimeout(initDfnInternal, 10000); 32 | return; 33 | } 34 | } 35 | dfnMapDone = true; 36 | document.body.className += " dfnEnabled"; 37 | if (getCookie('profile') == '1') 38 | document.getElementsByTagName('h2')[0].textContent += '; dfn.js: ' + (new Date() - dfnTimer) + 'ms to do ' + dfnMapTarget + ' links in ' + n + ' loops'; 39 | } 40 | initDfnInternal(); 41 | } 42 | 43 | var dfnPanel; 44 | var dfnUniqueId = 0; 45 | var dfnTimeout; 46 | document.addEventListener('click', dfnShow, false); 47 | function dfnShow(event) { 48 | if (dfnTimeout) { 49 | clearTimeout(dfnTimeout); 50 | dfnTimeout = null; 51 | } 52 | if (dfnPanel) { 53 | dfnPanel.parentNode.removeChild(dfnPanel); 54 | dfnPanel = null; 55 | } 56 | if (dfnMapDone) { 57 | var node = event.target; 58 | while (node && (node.nodeType != event.target.ELEMENT_NODE || node.tagName != "DFN")) 59 | node = node.parentNode; 60 | if (node) { 61 | var panel = document.createElement('div'); 62 | panel.className = 'dfnPanel'; 63 | if (node.id) { 64 | var permalinkP = document.createElement('p'); 65 | var permalinkA = document.createElement('a'); 66 | permalinkA.href = '#' + node.id; 67 | permalinkA.textContent = '#' + node.id; 68 | permalinkP.appendChild(permalinkA); 69 | panel.appendChild(permalinkP); 70 | } 71 | var p = document.createElement('p'); 72 | panel.appendChild(p); 73 | if (node.id in dfnMap || node.parentNode.id in dfnMap) { 74 | p.textContent = 'Referenced in:'; 75 | var ul = document.createElement('ul'); 76 | var lastHeader; 77 | var lastLi; 78 | var n; 79 | var sourceLinks = []; 80 | if (node.id in dfnMap) 81 | for (var i = 0; i < dfnMap[node.id].length; i += 1) 82 | sourceLinks.push(dfnMap[node.id][i]); 83 | if (node.parentNode.id in dfnMap) 84 | for (var i = 0; i < dfnMap[node.parentNode.id].length; i += 1) 85 | sourceLinks.push(dfnMap[node.parentNode.id][i]); 86 | for (var i = 0; i < sourceLinks.length; i += 1) { 87 | var link = sourceLinks[i]; 88 | var header = dfnGetCaption(link); 89 | var a = document.createElement('a'); 90 | if (!link.id) 91 | link.id = 'dfnReturnLink-' + dfnUniqueId++; 92 | a.href = '#' + link.id; 93 | if (header != lastHeader) { 94 | lastHeader = header; 95 | n = 1; 96 | var li = document.createElement('li'); 97 | var cloneHeader = header.cloneNode(true); 98 | while (cloneHeader.hasChildNodes()) 99 | a.appendChild(cloneHeader.firstChild); 100 | lastLi = li; 101 | li.appendChild(a); 102 | ul.appendChild(li); 103 | } else { 104 | n += 1; 105 | a.appendChild(document.createTextNode('(' + n + ')')); 106 | lastLi.appendChild(document.createTextNode(' ')); 107 | lastLi.appendChild(a); 108 | } 109 | } 110 | panel.appendChild(ul); 111 | } else { 112 | p.textContent = 'No references in this file.'; 113 | } 114 | node.appendChild(panel); 115 | dfnPanel = panel; 116 | } 117 | } else { 118 | dfnTimeout = setTimeout(dfnShow, 250, event); 119 | } 120 | } 121 | 122 | function dfnGetCaption(link) { 123 | var node = link; 124 | while (node) { 125 | if (node.nodeType == node.ELEMENT_NODE && node.tagName.match(/^H[1-6]$/)) { 126 | return node; 127 | } else if (!node.previousSibling) { 128 | node = node.parentNode; 129 | } else { 130 | node = node.previousSibling; 131 | if (node.nodeType == node.ELEMENT_NODE && node.className == "impl") { 132 | node = node.lastChild; 133 | } 134 | } 135 | } 136 | return null; 137 | } 138 | 139 | // setup (disabled for multipage copy) 140 | if (document.getElementById('head')) 141 | initDfn(); 142 | -------------------------------------------------------------------------------- /demos/bold.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy Bold Demo 5 | 6 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 46 | 47 | 48 |
49 |

Add / remove CSS classes to / from selection

50 | Make a selection in the document on the right and use the buttons below to toggle CSS classes on the selection: 51 | 52 |
53 | 55 |
56 | 57 |
58 |

Rangy Text Commands Module Demo

59 | 60 | 61 | 62 |

63 | Please use your mouse and/or keyboard to make selections from the sample content below and use the buttons on 64 | the left hand size to toggle CSS classes applied to text content within the selection. 65 |

66 | 67 |

68 | Association football is a sport played between two teams. It is usually called football, but in 69 | some countries, such as the United States, it is called soccer. In 70 | Japan, New Zealand, South Africa, Australia, Canada and 71 | Republic of Ireland, both words are commonly used. 72 |

73 |

74 | Each team has 11 players on the field. One of these players is the goalkeeper, and the other ten are 75 | known as "outfield players." The game is played by kicking a ball into the opponent's goal. A 76 | match has 90 minutes of play, with a break of 15 minutes in the middle. The break in the middle is called 77 | half-time. 78 |

79 |

Competitions (this section is editable)

80 |

81 | There are many competitions for football, for both football clubs and countries. Football clubs usually play 82 | other teams in their own country, with a few exceptions. Cardiff City F.C. from Wales for example, play 83 | in the English leagues and in the English FA Cup. 84 |

85 |

Who plays football (this section is editable and in pre-formatted text)

86 |
 87 | Football is the world's most popular sport. It is played in more
 88 | countries than any other game. In fact, FIFA (the Federation
 89 | Internationale de Football Association) has more members than the
 90 | United Nations.
 91 | 
 92 | It is played by both males and females.
 93 | 
 94 | 
 95 | 
96 |
97 | 98 |

99 | Text adapted from Simple Wikipedia page on 100 | Association Football, licensed under the 101 | Creative 102 | Commons Attribution/Share-Alike License. 103 |

104 | 105 | -------------------------------------------------------------------------------- /test/domrange.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | DOM Range 6 | 7 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 |
66 | 67 |
68 |

A subheading

69 |

A paragraph with bold text with italic words plus some using both.

70 |

A paragraph with bold text with italic words plus some using both.
71 | A paragraph with bold text with italic words 72 | claridge 73 | plus some using both.

74 |
    75 |
  • a
  • 76 |
  • b
  • 77 |
78 | 79 |
80 | 81 |
82 |

A paragraph with bold text with italic words plus some using both.
83 | A paragraph with bold claridgeclaridge text with bold italic text and italic words plus some using both.

84 | 85 |
    86 |
  • a
  • 87 |
  • b
  • 88 |
89 | 90 |
 91 | Some preformatted how it'll do
 92 | 
 93 | with this, plus some line breaks
 94 | 
 95 | 
 96 | 
97 |
onetwo
onetwo
98 | 99 |
100 |
101 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /src/modules/inactive/rangy-events.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Events module for Rangy. 3 | * Extensions to Range and Selection objects that dispatch mouse and touch events for ranges and selections 4 | * 5 | * Part of Rangy, a cross-browser JavaScript range and selection library 6 | * https://github.com/timdown/rangy 7 | * 8 | * Depends on Rangy core. 9 | * 10 | * Copyright %%build:year%%, Tim Down 11 | * Licensed under the MIT license. 12 | * Version: %%build:version%% 13 | * Build date: %%build:date%% 14 | */ 15 | rangy.createModule("Events", ["Position"], function(api, module) { 16 | var log = log4javascript.getLogger("rangy.events"); 17 | var DomPosition = api.dom.DomPosition; 18 | 19 | var documentEventStores = []; 20 | 21 | function getEventStore(range) { 22 | var doc = api.DomRange.getRangeDocument(range); 23 | var i = documentEventStores.length, store; 24 | while (i--) { 25 | store = documentEventStores[i]; 26 | if (store.doc === doc) { 27 | return store; 28 | } 29 | } 30 | 31 | store = new DocumentEventStore(doc); 32 | documentEventStores.push(store); 33 | return store; 34 | } 35 | 36 | function getPositionFromEvent(doc, evt) { 37 | return api.util.areHostProperties(evt, ["rangeOffset", "rangeParent"]) ? 38 | new DomPosition(evt.rangeParent, evt.rangeOffset) : 39 | api.positionFromPoint(evt.clientX, evt.clientY, doc); 40 | } 41 | 42 | function DocumentEventStore(doc) { 43 | this.doc = doc; 44 | this.listenersByType = {}; 45 | } 46 | 47 | DocumentEventStore.prototype = { 48 | createDomListener: function(eventType, rangeListeners) { 49 | var that = this; 50 | 51 | var listener = function(evt) { 52 | var pos = getPositionFromEvent(that.doc, evt); 53 | //console.log(evt.type, pos); 54 | for (var i = 0, rangeListener; rangeListener = rangeListeners[i++]; ) { 55 | if (rangeListener.range.isPointInRange(pos.node, pos.offset)) { 56 | rangeListener.listener.call(rangeListener.range, { 57 | originalEvent: evt, 58 | type: evt.type, 59 | caretPosition: { 60 | offsetNode: pos.node, 61 | offset: pos.offset 62 | } 63 | }); 64 | } 65 | } 66 | }; 67 | 68 | api.util.addListener(that.doc, eventType, listener); 69 | 70 | return listener; 71 | }, 72 | 73 | addListener: function(range, eventType, listener) { 74 | var listenersByType = this.listenersByType; 75 | if (!listenersByType.hasOwnProperty(eventType)) { 76 | var rangeListeners = []; 77 | listenersByType[eventType] = { 78 | domListener: this.createDomListener(eventType, rangeListeners), 79 | rangeListeners: rangeListeners 80 | }; 81 | } 82 | // Start listening to events of this type 83 | var listenersForType = listenersByType[eventType]; 84 | listenersForType.rangeListeners.push( { range: range, listener: listener} ); 85 | }, 86 | 87 | removeListener: function(range, eventType, listener) { 88 | var listenersByType = this.listenersByType; 89 | if (listenersByType.hasOwnProperty(eventType)) { 90 | var rangeListenersForType = listenersByType[eventType].rangeListeners; 91 | for (var i = 0, len = rangeListenersForType.length; i < len; ++i) { 92 | if (rangeListenersForType[i].range === range && rangeListenersForType[i].listener === listener) { 93 | rangeListenersForType.splice(i, 1); 94 | console.log("REMOVED"); 95 | break; 96 | } 97 | } 98 | } 99 | }, 100 | 101 | removeRangeListeners: function(range) { 102 | var listenersByType = this.listenersByType, rangeListenersForType, i, len; 103 | for (var eventType in listenersByType) { 104 | if (listenersByType.hasOwnProperty(eventType)) { 105 | rangeListenersForType = listenersByType[eventType].rangeListeners; 106 | for (i = 0, len = rangeListenersForType.length; i < len; ++i) { 107 | if (rangeListenersForType[i].range === range) { 108 | rangeListenersForType.splice(i, 1); 109 | --i; 110 | console.log("REMOVED"); 111 | } 112 | } 113 | } 114 | } 115 | }, 116 | 117 | cleanDetachedRanges: function() { 118 | // TODO: Implement this 119 | } 120 | }; 121 | 122 | api.rangePrototype.addListener = function(eventType, listener) { 123 | getEventStore(this).addListener(this, eventType, listener); 124 | }; 125 | 126 | api.rangePrototype.removeListener = function(eventType, listener) { 127 | getEventStore(this).removeListener(this, eventType, listener); 128 | }; 129 | 130 | api.rangePrototype.removeAllListeners = function() { 131 | getEventStore(this).removeRangeListeners(this); 132 | }; 133 | }); 134 | -------------------------------------------------------------------------------- /demos/events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TextRange Demo 5 | 6 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 94 | 95 | 96 |
97 |

Event log

98 | 99 |
100 | 101 |
102 |

Rangy Range Events Demo

103 | 104 |

105 | Rangy's Events module provides mouse and 106 | touch (NOT YET IMPLEMENTED) events on Ranges. Please use your mouse to make a selection in the sample 107 | content below and hover your mouse over the selection. Events are logged in the text box on the left. 108 |

109 |

110 | Association football is a sport played between two teams. It is usually called football, but in 111 | some countries, such as the United States, it is called soccer. In 112 | Japan, New Zealand, South Africa, Australia, Canada and 113 | Republic of Ireland, both words are commonly used. 114 |

115 |

116 | Each team has 11 players on the field. One of these players is the goalkeeper, and the other ten are 117 | known as "outfield players." The game is played by kicking a ball into the opponent's goal. A 118 | match has 90 minutes of play, with a break of 15 minutes in the middle. The break in the middle is called 119 | half-time. 120 |

121 |

Competitions (this section is editable)

122 |

123 | There are many competitions for football, for both football clubs and countries. Football clubs usually play 124 | other teams in their own country, with a few exceptions. Cardiff City F.C. from Wales for example, play 125 | in the English leagues and in the English FA Cup. 126 |

127 |

Who plays football (this section is editable and in pre-formatted text)

128 |
129 | Football is the world's most popular sport. It is played in more
130 | countries than any other game. In fact, FIFA (the Federation
131 | Internationale de Football Association) has more members than the
132 | United Nations.
133 | 
134 | It is played by both males and females.
135 | 
136 | 
137 | 
138 | 139 |
140 | 141 | -------------------------------------------------------------------------------- /external/log4javascript_stub.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2008 Tim Down. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | var log4javascript_stub=(function(){var log4javascript;function ff(){return function(){};} 19 | function copy(obj,props){for(var i in props){obj[i]=props[i];}} 20 | var f=ff();var Logger=ff();copy(Logger.prototype,{addChild:f,getEffectiveAppenders:f,invalidateAppenderCache:f,getAdditivity:f,setAdditivity:f,addAppender:f,removeAppender:f,removeAllAppenders:f,log:f,setLevel:f,getLevel:f,getEffectiveLevel:f,trace:f,debug:f,info:f,warn:f,error:f,fatal:f,isEnabledFor:f,isTraceEnabled:f,isDebugEnabled:f,isInfoEnabled:f,isWarnEnabled:f,isErrorEnabled:f,isFatalEnabled:f,callAppenders:f,group:f,groupEnd:f,time:f,timeEnd:f,assert:f,parent:new Logger()});var getLogger=function(){return new Logger();};function EventSupport(){};copy(EventSupport.prototype,{setEventTypes:f,addEventListener:f,removeEventListener:f,dispatchEvent:f,eventTypes:[],eventListeners:{}});function Log4JavaScript(){} 21 | Log4JavaScript.prototype=new EventSupport();log4javascript=new Log4JavaScript();log4javascript={isStub:true,version:"1.4",edition:"log4javascript",setEventTypes:f,addEventListener:f,removeEventListener:f,dispatchEvent:f,eventTypes:[],eventListeners:{},logLog:{setQuietMode:f,setAlertAllErrors:f,debug:f,displayDebug:f,warn:f,error:f},handleError:f,setEnabled:f,isEnabled:f,setTimeStampsInMilliseconds:f,isTimeStampsInMilliseconds:f,evalInScope:f,setShowStackTraces:f,getLogger:getLogger,getDefaultLogger:getLogger,getNullLogger:getLogger,getRootLogger:getLogger,resetConfiguration:f,Level:ff(),LoggingEvent:ff(),Layout:ff(),Appender:ff()};log4javascript.LoggingEvent.prototype={getThrowableStrRep:f,getCombinedMessages:f};log4javascript.Level.prototype={toString:f,equals:f,isGreaterOrEqual:f};var level=new log4javascript.Level();copy(log4javascript.Level,{ALL:level,TRACE:level,DEBUG:level,INFO:level,WARN:level,ERROR:level,FATAL:level,OFF:level});log4javascript.Layout.prototype={defaults:{},format:f,ignoresThrowable:f,getContentType:f,allowBatching:f,getDataValues:f,setKeys:f,setCustomField:f,hasCustomFields:f,setTimeStampsInMilliseconds:f,isTimeStampsInMilliseconds:f,getTimeStampValue:f,toString:f};log4javascript.SimpleDateFormat=ff();log4javascript.SimpleDateFormat.prototype = {setMinimalDaysInFirstWeek:f,getMinimalDaysInFirstWeek:f,format:f}; 22 | log4javascript.PatternLayout=ff();log4javascript.PatternLayout.prototype=new log4javascript.Layout();log4javascript.Appender=ff();log4javascript.Appender.prototype=new EventSupport();copy(log4javascript.Appender.prototype,{layout:new log4javascript.PatternLayout(),threshold:log4javascript.Level.ALL,loggers:[],doAppend:f,append:f,setLayout:f,getLayout:f,setThreshold:f,getThreshold:f,setAddedToLogger:f,setRemovedFromLogger:f,group:f,groupEnd:f,toString:f});log4javascript.SimpleLayout=ff();log4javascript.SimpleLayout.prototype=new log4javascript.Layout();log4javascript.NullLayout=ff();log4javascript.NullLayout.prototype=new log4javascript.Layout();log4javascript.XmlLayout=ff();log4javascript.XmlLayout.prototype=new log4javascript.Layout();copy(log4javascript.XmlLayout.prototype,{escapeCdata:f,isCombinedMessages:f});log4javascript.JsonLayout=ff();log4javascript.JsonLayout.prototype=new log4javascript.Layout();copy(log4javascript.JsonLayout.prototype,{isReadable:f,isCombinedMessages:f});log4javascript.HttpPostDataLayout=ff();log4javascript.HttpPostDataLayout.prototype=new log4javascript.Layout();log4javascript.PatternLayout=ff();log4javascript.PatternLayout.prototype=new log4javascript.Layout();log4javascript.AlertAppender=ff();log4javascript.AlertAppender.prototype=new log4javascript.Appender();log4javascript.BrowserConsoleAppender=ff();log4javascript.BrowserConsoleAppender.prototype=new log4javascript.Appender();log4javascript.AjaxAppender=ff();log4javascript.AjaxAppender.prototype=new log4javascript.Appender();copy(log4javascript.AjaxAppender.prototype,{getSessionId:f,setSessionId:f,isTimed:f,setTimed:f,getTimerInterval:f,setTimerInterval:f,isWaitForResponse:f,setWaitForResponse:f,getBatchSize:f,setBatchSize:f,isSendAllOnUnload:f,setSendAllOnUnload:f,setRequestSuccessCallback:f,setFailCallback:f,getPostVarName:f,setPostVarName:f,sendAll:f,defaults:{requestSuccessCallback:null,failCallback:null}});function ConsoleAppender(){} 23 | ConsoleAppender.prototype=new log4javascript.Appender();copy(ConsoleAppender.prototype,{create:f,isNewestMessageAtTop:f,setNewestMessageAtTop:f,isScrollToLatestMessage:f,setScrollToLatestMessage:f,getWidth:f,setWidth:f,getHeight:f,setHeight:f,getMaxMessages:f,setMaxMessages:f,isShowCommandLine:f,setShowCommandLine:f,isShowHideButton:f,setShowHideButton:f,isShowCloseButton:f,setShowCloseButton:f,getCommandLineObjectExpansionDepth:f,setCommandLineObjectExpansionDepth:f,isInitiallyMinimized:f,setInitiallyMinimized:f,isUseDocumentWrite:f,setUseDocumentWrite:f,group:f,groupEnd:f,clear:f,focus:f,focusCommandLine:f,focusSearch:f,getCommandWindow:f,setCommandWindow:f,executeLastCommand:f,getCommandLayout:f,setCommandLayout:f,evalCommandAndAppend:f,addCommandLineFunction:f,storeCommandHistory:f,unload:f});ConsoleAppender.addGlobalCommandLineFunction=f;log4javascript.InPageAppender=ff();log4javascript.InPageAppender.prototype=new ConsoleAppender();copy(log4javascript.InPageAppender.prototype,{addCssProperty:f,hide:f,show:f,isVisible:f,close:f,defaults:{layout:new log4javascript.PatternLayout(),maxMessages:null}});log4javascript.InlineAppender=log4javascript.InPageAppender;log4javascript.PopUpAppender=ff();log4javascript.PopUpAppender.prototype=new ConsoleAppender();copy(log4javascript.PopUpAppender.prototype,{isUseOldPopUp:f,setUseOldPopUp:f,isComplainAboutPopUpBlocking:f,setComplainAboutPopUpBlocking:f,isFocusPopUp:f,setFocusPopUp:f,isReopenWhenClosed:f,setReopenWhenClosed:f,close:f,hide:f,show:f,defaults:{layout:new log4javascript.PatternLayout(),maxMessages:null}});return log4javascript;})();if(typeof window.log4javascript=="undefined"){var log4javascript=log4javascript_stub;} -------------------------------------------------------------------------------- /test/textrangelength.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Text Range length 6 | 136 | 137 |
138 | 
139 | Test
140 | 
141 | 
142 | -------------------------------------------------------------------------------- /demos/saverestore.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy Selection save/restore module demo 5 | 6 | 7 | 8 | 70 | 71 | 72 |

Selection save/restore test

73 | 74 |
75 |

Save and restore selection

76 | Select something in the main page area to the right. Click on the "Save selection" button. Now click somewhere 77 | on the page to destroy the selection, and then press "Restore selection". 78 | 79 | 80 |
81 | 82 |
83 |

Rangy Save / Restore Selection Module Demo

84 | 85 | 86 | 87 |

88 | Please use your mouse and/or keyboard to make selections from the sample content below and use the buttons 89 | on the left hand size to save and restore the selection. 90 |

91 | 92 |

93 | Association football is a sport played between two teams. It is usually called football, but in 94 | some countries, such as the United States, it is called soccer. In 95 | Japan, New Zealand, South Africa, Australia, Canada and 96 | Republic of Ireland, both words are commonly used. 97 |

98 |

99 | Each team has 11 players on the field. One of these players is the goalkeeper, and the other ten are 100 | known as "outfield players." The game is played by kicking a ball into the opponent's goal. A 101 | match has 90 minutes of play, with a break of 15 minutes in the middle. The break in the middle is called 102 | half-time. 103 |

104 |

Competitions (this section is editable)

105 |

106 | There are many competitions for football, for both football clubs and countries. Football clubs usually play 107 | other teams in their own country, with a few exceptions. Cardiff City F.C. from Wales for example, play 108 | in the English leagues and in the English FA Cup. 109 |

110 |

Who plays football (this section is editable and in pre-formatted text)

111 |
112 | Football is the world's most popular sport. It is played in more
113 | countries than any other game. In fact, FIFA (the Federation
114 | Internationale de Football Association) has more members than the
115 | United Nations.
116 | 
117 | It is played by both males and females.
118 | 
119 | 
120 | 
121 | 124 |
125 | 126 |

127 | Text adapted from Simple Wikipedia page on 128 | Association Football, licensed under the 129 | Creative 130 | Commons Attribution/Share-Alike License. 131 |

132 | 133 | 134 | -------------------------------------------------------------------------------- /test/extend.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Selection extend tests 6 | 7 | 14 | 15 | 16 | 19 | 20 | 134 | 135 | 136 | 137 |
138 |
139 |
This contains the selection
140 | 141 | -------------------------------------------------------------------------------- /demos/serializer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy Serializer Module Demo 5 | 6 | 7 | 8 | 66 | 67 | 68 |
69 |

Serialize selection

70 | Click the button to serialize the selection. The serialized selection will appear in the text box. 71 | 72 | 73 | 74 |

Deserialize selection

75 | Paste a serialized selection into the text box below and click the button to restore the serialized selection. 76 | 77 | 78 | 79 |

Preserving the selection between page requests

80 | The selection on this page will be preserved between page requests. Press the following button or your browser's 81 | refresh button to refresh the page 82 |
83 | 84 |
85 | 86 |
87 |

Rangy Serializer Module Demo

88 | 89 | 90 | 91 |

92 | Please use your mouse and/or keyboard to make selections from the sample content below and use the buttons on 93 | the left hand size to serialize and deserialize the selection. Also, the selection will be preserved when you 94 | refresh the page. 95 |

96 | 97 |

98 | Association football is a sport played between two teams. It is usually called football, but in 99 | some countries, such as the United States, it is called soccer. In 100 | Japan, New Zealand, South Africa, Australia, Canada and 101 | Republic of Ireland, both words are commonly used. 102 |

103 |

104 | Each team has 11 players on the field. One of these players is the goalkeeper, and the other ten are 105 | known as "outfield players." The game is played by kicking a ball into the opponent's goal. A 106 | match has 90 minutes of play, with a break of 15 minutes in the middle. The break in the middle is called 107 | half-time. 108 |

109 |

Competitions (this section is editable)

110 |

111 | There are many competitions for football, for both football clubs and countries. Football clubs usually play 112 | other teams in their own country, with a few exceptions. Cardiff City F.C. from Wales for example, play 113 | in the English leagues and in the English FA Cup. 114 |

115 |

Who plays football (this section is editable and in pre-formatted text)

116 |
117 | Football is the world's most popular sport. It is played in more
118 | countries than any other game. In fact, FIFA (the Federation
119 | Internationale de Football Association) has more members than the
120 | United Nations.
121 | 
122 | It is played by both males and females.
123 | 
124 | 
125 | 
126 | 129 |
130 | 131 |

132 | Text adapted from Simple Wikipedia page on 133 | Association Football, licensed under the 134 | Creative 135 | Commons Attribution/Share-Alike License. 136 |

137 | 138 | -------------------------------------------------------------------------------- /demos/highlighter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy Highlighter Module Demo 5 | 6 | 19 | 20 | 21 | 22 | 86 | 87 | 88 |
89 |

Highlighter

90 |

Make a selection in the document and use the buttons below to highlight and unhighlight.

91 | 92 | 93 | 94 |
95 | 96 | 97 | 98 |

Preserving highlights between page requests

99 |
100 | Highlights can be preserved between page requests. Press the following button to reload the page with the 101 | highlights preserved: 102 |
103 | 104 | 105 |
106 |
107 | 108 |
109 |

Rangy Highlighter Module Demo

110 |

111 | Please use your mouse and/or keyboard to make selections from the sample content below and use the buttons 112 | on the left hand size to create and remove highlights. 113 |

114 |

115 | Association football is a sport played between two teams. It is usually called football, but in 116 | some countries, such as the United States, it is called soccer. In 117 | Japan, New Zealand, South Africa, Australia, Canada and 118 | Republic of Ireland, both words are commonly used. 119 |

120 |

121 | Each team has 11 players on the field. One of these players is the goalkeeper, and the other ten are 122 | known as "outfield players." The game is played by kicking a ball into the opponent's goal. A 123 | match has 90 minutes of play, with a break of 15 minutes in the middle. The break in the middle is called 124 | half-time. 125 |

126 |

Competitions

127 |

128 | There are many competitions for football, for both football clubs and countries. Football clubs usually play 129 | other teams in their own country, with a few exceptions. Cardiff City F.C. from Wales for example, play 130 | in the English leagues and in the English FA Cup. 131 |

132 |

Who plays football (this section is in pre-formatted text)

133 |
134 | Football is the world's most popular sport. It is played in more
135 | countries than any other game. In fact, FIFA (the Federation
136 | Internationale de Football Association) has more members than the
137 | United Nations.
138 | 
139 | It is played by both males and females.
140 | 
141 | 
142 | 
143 |
144 | 145 |

146 | Text adapted from Simple Wikipedia page on 147 | Association Football, licensed under the 148 | Creative 149 | Commons Attribution/Share-Alike License. 150 |

151 | 152 | -------------------------------------------------------------------------------- /src/modules/inactive/commands/rangy-applyclass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Selection save and restore module for Rangy. 3 | * ApplyClass command 4 | * 5 | * Part of Rangy, a cross-browser JavaScript range and selection library 6 | * https://github.com/timdown/rangy 7 | * 8 | * Depends on Rangy core. 9 | * 10 | * Copyright %%build:year%%, Tim Down 11 | * Licensed under the MIT license. 12 | * Version: %%build:version%% 13 | * Build date: %%build:date%% 14 | */ 15 | rangy.createModule("ApplyClassCommand", function(api, module) { 16 | api.requireModules( ["Commands"] ); 17 | 18 | var dom = api.dom, commandUtil = api.Command.util; 19 | var log = log4javascript.getLogger("rangy.ApplyClassCommand"); 20 | 21 | var defaultTagNames = ["span"]; 22 | 23 | function hasClass(el, className) { 24 | return el.className && new RegExp("(?:^|\\s)" + className + "(?:\\s|$)").test(el.className); 25 | } 26 | 27 | function addClass(el, className) { 28 | if (el.className) { 29 | if (!hasClass(el, className)) { 30 | el.className += " " + className; 31 | } 32 | } else { 33 | el.className = className; 34 | } 35 | } 36 | 37 | var removeClass = (function() { 38 | function replacer(matched, whitespaceBefore, whitespaceAfter) { 39 | return (whitespaceBefore && whitespaceAfter) ? " " : ""; 40 | } 41 | 42 | return function(el, className) { 43 | if (el.className) { 44 | el.className = el.className.replace(new RegExp("(?:^|\\s)" + className + "(?:\\s|$)"), replacer); 45 | } 46 | }; 47 | })(); 48 | 49 | function sortClassName(className) { 50 | return className.split(/\s+/).sort().join(" "); 51 | } 52 | 53 | function getSortedClassName(el) { 54 | return sortClassName(el.className); 55 | } 56 | 57 | function haveSameClasses(el1, el2) { 58 | return getSortedClassName(el1) == getSortedClassName(el2); 59 | } 60 | 61 | function ApplyClassCommand() { 62 | } 63 | 64 | api.Command.create(ApplyClassCommand, { 65 | defaultOptions: { 66 | tagName: "span", 67 | validTagNames: ["span"] 68 | }, 69 | 70 | isValidElementForClass: function(el, validTagNames) { 71 | return new RegExp("^(" + validTagNames.join("|") + ")$", "i").test(el.tagName); 72 | }, 73 | 74 | isModifiableElement: function(el, context) { 75 | if (!this.isValidElementForClass(el, context.options.validTagNames)) { 76 | return false; 77 | } 78 | 79 | // Extract attributes once and quit if more than one is found or the attribute is not "class" 80 | var hasAnyAttrs = false; 81 | for (var i = 0, len = el.attributes.length; i < len; ++i) { 82 | if (el.attributes[i].specified) { 83 | // If it's got more than one attribute, everything after this fails. 84 | if (hasAnyAttrs || el.attributes[i].name != "class") { 85 | return false; 86 | } 87 | hasAnyAttrs = true; 88 | } 89 | } 90 | }, 91 | 92 | isSimpleModifiableElement: function(el, context) { 93 | if (!this.isValidElementForClass(el, context.options.validTagNames)) { 94 | return false; 95 | } 96 | 97 | // Extract attributes once and quit if more than one is found or the attribute is not "class" 98 | var hasAnyAttrs = false; 99 | for (var i = 0, len = el.attributes.length; i < len; ++i) { 100 | if (el.attributes[i].specified) { 101 | // If it's got more than one attribute, everything after this fails. 102 | if (hasAnyAttrs || el.attributes[i].name != "class") { 103 | return false; 104 | } 105 | hasAnyAttrs = true; 106 | } 107 | } 108 | 109 | return true; 110 | }, 111 | 112 | createCssElement: function(doc, context) { 113 | return doc.createElement(context.options.tagName); 114 | }, 115 | 116 | styleCssElement: function(el, value) { 117 | el.className = value; 118 | }, 119 | 120 | getAncestorOrSelfWithClass: function(node, validTagNames, className) { 121 | while (node) { 122 | if (node.nodeType == 1 && this.isValidElementForClass(node, validTagNames) && hasClass(node, className)) { 123 | return node; 124 | } 125 | node = node.parentNode; 126 | } 127 | return null; 128 | }, 129 | 130 | getSpecifiedValue: function(element, context) { 131 | return hasClass(element, context.value) ? context.value : null; 132 | }, 133 | 134 | getEffectiveValue: function(element, context) { 135 | return this.getAncestorOrSelfWithClass(element, context.options.validTagNames, context.value) ? 136 | context.value : null; 137 | }, 138 | 139 | createNonCssElement: function(node, value) { 140 | return (value == "bold" || value == "700") ? dom.getDocument(node).createElement("b") : null; 141 | }, 142 | 143 | getRangeValue: function(range, context) { 144 | var textNodes = commandUtil.getEffectiveTextNodes(range, context), i = textNodes.length, value; 145 | log.info("getRangeValue on " + range.inspect() + ", text nodes: " + textNodes); 146 | while (i--) { 147 | log.warn("effective value on " + textNodes[i].data + ": " + commandUtil.getEffectiveValue(textNodes[i], context)); 148 | if (commandUtil.getEffectiveValue(textNodes[i], context) === null) { 149 | return false; 150 | } 151 | } 152 | log.info("getRangeValue returning true"); 153 | return textNodes.length > 0; 154 | }, 155 | 156 | getSelectionValue: function(sel, context) { 157 | var selRanges = sel.getAllRanges(); 158 | for (var i = 0, len = selRanges.length; i < len; ++i) { 159 | if (!this.getRangeValue(selRanges[i], context)) { 160 | return false; 161 | } 162 | } 163 | return len > 0; 164 | }, 165 | 166 | getNewSelectionValue: function(sel, context) { 167 | return this.getSelectionValue(sel, context) ? "" : context.value; 168 | }, 169 | 170 | applyValueToRange: function(range, context) { 171 | var decomposed = range.decompose(context.rangesToPreserve); 172 | 173 | for (var i = 0, len = decomposed.length; i < len; ++i) { 174 | commandUtil.setNodeValue(decomposed[i], context); 175 | } 176 | } 177 | }); 178 | 179 | api.registerCommand("applyclass", new ApplyClassCommand()); 180 | 181 | }); 182 | -------------------------------------------------------------------------------- /demos/classapplier.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rangy CSS Class Applier Module Demo 5 | 6 | 26 | 27 | 28 | 96 | 97 | 98 |
99 |

Add / remove CSS classes to / from selection

100 | Make a selection in the document on the right and use the buttons below to toggle CSS classes on the selection: 101 | 102 |
103 | 105 |
106 | 108 |
109 | 111 |
112 | 113 |
114 |

Rangy CSS Class Applier Module Demo

115 | 116 | 117 | 118 |

119 | Please use your mouse and/or keyboard to make selections from the sample content below and use the buttons 120 | on the left hand size to toggle CSS classes applied to content (now also including images) within the 121 | selection. 122 |

123 | 124 |

125 | Association football is a sport played between two teams. It is usually called 126 | football, but in some countries, such as the United States, it is called 127 | soccer. In Japan, New Zealand, South Africa, 128 | Australia, Canada and Republic of Ireland, both words are commonly used. 129 |

130 |

131 | Each team has 11 players on the field. One of these players is the goalkeeper, and the other ten are 132 | known as "outfield players." The game is played by kicking a ball 133 | football into the opponent's goal. A 134 | match has 90 minutes of play, with a break of 15 minutes in the middle. The break in the middle is called 135 | half-time. 136 |

137 |

Competitions (this section is editable)

138 |

139 | There are many competitions for football, for both football clubs and countries. Football clubs usually play 140 | other teams in their own country, with a few exceptions. Cardiff City F.C. from Wales for example, play 141 | in the English leagues and in the English FA Cup. 142 |

143 |

Who plays football (this section is editable and in pre-formatted text)

144 |
145 | Football is the world's most popular sport. It is played in more
146 | countries than any other game. In fact, FIFA (the Federation
147 | Internationale de Football Association) has more members than the
148 | United Nations.
149 | 
150 | It is played by both males and females.
151 | 
152 |   

First para

153 |

Second para

154 | 155 |
156 |
157 | 158 |

159 | Text adapted from Simple Wikipedia page on 160 | Association Football, licensed under the 161 | Creative 162 | Commons Attribution/Share-Alike License. 163 |

164 | 165 | -------------------------------------------------------------------------------- /test/testutils.js: -------------------------------------------------------------------------------- 1 | var rangyTestUtils = (function() { 2 | function createNodeTree(levels, copiesPerLevel) { 3 | function createTestNodes(parentNode, limit, copies) { 4 | if (limit > 0) { 5 | var n = parentNode.appendChild(document.createElement("div")); 6 | n.appendChild(document.createTextNode("Before ")); 7 | var p = n.appendChild(document.createElement("div")); 8 | n.appendChild(document.createTextNode(" after")); 9 | for (var i = 0; i < copies; i++) { 10 | createTestNodes(p, limit - 1, copies); 11 | } 12 | } 13 | } 14 | 15 | var testNode = document.createElement("div"); 16 | createTestNodes(testNode, levels, copiesPerLevel); 17 | 18 | return testNode; 19 | } 20 | 21 | var nextIterationId = 1; 22 | var nodeIterationIds = new Hashtable(); 23 | 24 | function iterateNodes(node, func, includeSelf, iterationId) { 25 | if (!iterationId) { 26 | iterationId = nextIterationId++; 27 | } 28 | if (nodeIterationIds.get(node) == iterationId) { 29 | throw new Error("Node already iterated: " + rangy.dom.inspectNode(node)); 30 | } 31 | if (includeSelf) { 32 | func(node); 33 | } 34 | nodeIterationIds.put(node, iterationId); 35 | for (var child = node.firstChild, nextChild; !!child; child = nextChild) { 36 | nextChild = child.nextSibling; 37 | iterateNodes(child, func, true, iterationId); 38 | } 39 | } 40 | 41 | function RangeInfo() {} 42 | 43 | RangeInfo.prototype = { 44 | setStart: function(node, offset) { 45 | this.sc = node; 46 | this.so = offset; 47 | }, 48 | setEnd: function(node, offset) { 49 | this.ec = node; 50 | this.eo = offset; 51 | } 52 | }; 53 | 54 | function createRangeInHtml(containerEl, html) { 55 | containerEl.innerHTML = html; 56 | var range = rangy.createRange(), foundStart = false; 57 | var rangeInfo = new RangeInfo(); 58 | iterateNodes(containerEl, function(node) { 59 | if (node.nodeType == 3) { 60 | var openBracketIndex = node.data.indexOf("["); 61 | if (openBracketIndex != -1) { 62 | node.data = node.data.slice(0, openBracketIndex) + node.data.slice(openBracketIndex + 1); 63 | rangeInfo.setStart(node, openBracketIndex); 64 | foundStart = true; 65 | } 66 | 67 | var pipeIndex = node.data.indexOf("|"); 68 | if (pipeIndex == 0) { 69 | node.data = node.data.slice(1); 70 | rangeInfo[foundStart ? "setEnd" : "setStart"](node.parentNode, rangy.dom.getNodeIndex(node)); 71 | foundStart = true; 72 | } else if (pipeIndex == node.length - 1) { 73 | node.data = node.data.slice(0, -1); 74 | rangeInfo[foundStart ? "setEnd" : "setStart"](node.parentNode, rangy.dom.getNodeIndex(node) + 1); 75 | foundStart = true; 76 | } 77 | 78 | var closeBracketIndex = node.data.indexOf("]"); 79 | if (closeBracketIndex != -1) { 80 | node.data = node.data.slice(0, closeBracketIndex) + node.data.slice(closeBracketIndex + 1); 81 | rangeInfo.setEnd(node, closeBracketIndex); 82 | } 83 | 84 | pipeIndex = node.data.indexOf("|"); 85 | if (pipeIndex == 0) { 86 | node.data = node.data.slice(1); 87 | rangeInfo.setEnd(node.parentNode, rangy.dom.getNodeIndex(node)); 88 | } else if (pipeIndex == node.length - 1) { 89 | node.data = node.data.slice(0, -1); 90 | rangeInfo.setEnd(node.parentNode, rangy.dom.getNodeIndex(node) + 1); 91 | } 92 | 93 | // Clear empty text node 94 | if (node.data.length == 0) { 95 | node.parentNode.removeChild(node); 96 | } 97 | } 98 | }, false); 99 | 100 | range.setStart(rangeInfo.sc, rangeInfo.so); 101 | range.setEnd(rangeInfo.ec, rangeInfo.eo); 102 | 103 | return range; 104 | } 105 | 106 | function getSortedClassName(el) { 107 | return el.className.split(/\s+/).sort().join(" "); 108 | } 109 | 110 | // Puts ranges in order, last in document first. 111 | function compareRanges(r1, r2) { 112 | return r2.compareBoundaryPoints(r1.START_TO_START, r1); 113 | } 114 | 115 | function htmlAndRangeToString(containerEl, range) { 116 | function isElementRangeBoundary(el, offset, range, isStart) { 117 | var prefix = isStart ? "start" : "end"; 118 | return (el == range[prefix + "Container"] && offset == range[prefix + "Offset"]); 119 | } 120 | 121 | function getHtml(node, includeSelf) { 122 | var html = ""; 123 | if (node.nodeType == 1) { 124 | if (includeSelf) { 125 | html = "<" + node.tagName.toLowerCase(); 126 | if (node.id) { 127 | html += ' id="' + node.id + '"'; 128 | } 129 | if (node.className) { 130 | html += ' class="' + getSortedClassName(node) + '"'; 131 | } 132 | if (node.href) { 133 | html += ' href="' + node.href + '"'; 134 | } 135 | if (node.style.cssText) { 136 | var style = node.style.cssText.toLowerCase().replace(/\s+$/, ""); 137 | if (style.slice(-1) != ";") { 138 | style += ";"; 139 | } 140 | html += ' style="' + style + '"'; 141 | } 142 | html += ">"; 143 | } 144 | 145 | for (var i = 0, children = node.childNodes, len = children.length; i <= len; ++i) { 146 | if (isElementRangeBoundary(node, i, range, true)) { 147 | html += "|"; 148 | } 149 | if (isElementRangeBoundary(node, i, range, false)) { 150 | html += "|"; 151 | } 152 | if (i != len) { 153 | html += getHtml(children[i], true); 154 | } 155 | } 156 | 157 | if (includeSelf) { 158 | html += ""; 159 | } 160 | } else if (includeSelf && node.nodeType == 3) { 161 | var text = node.data; 162 | if (node == range.endContainer) { 163 | text = text.slice(0, range.endOffset) + "]" + text.slice(range.endOffset); 164 | } 165 | if (node == range.startContainer) { 166 | text = text.slice(0, range.startOffset) + "[" + text.slice(range.startOffset); 167 | } 168 | 169 | html += text; 170 | } 171 | return html; 172 | } 173 | 174 | return getHtml(containerEl, false); 175 | } 176 | 177 | 178 | return { 179 | createNodeTree: createNodeTree, 180 | RangeInfo: RangeInfo, 181 | iterateNodes: iterateNodes, 182 | createRangeInHtml: createRangeInHtml, 183 | getSortedClassName: getSortedClassName, 184 | htmlAndRangeToString: htmlAndRangeToString 185 | } 186 | 187 | })(); 188 | -------------------------------------------------------------------------------- /src/modules/rangy-selectionsaverestore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Selection save and restore module for Rangy. 3 | * Saves and restores user selections using marker invisible elements in the DOM. 4 | * 5 | * Part of Rangy, a cross-browser JavaScript range and selection library 6 | * https://github.com/timdown/rangy 7 | * 8 | * Depends on Rangy core. 9 | * 10 | * Copyright %%build:year%%, Tim Down 11 | * Licensed under the MIT license. 12 | * Version: %%build:version%% 13 | * Build date: %%build:date%% 14 | */ 15 | /* build:modularizeWithRangyDependency */ 16 | rangy.createModule("SaveRestore", ["WrappedSelection"], function(api, module) { 17 | var dom = api.dom; 18 | var removeNode = dom.removeNode; 19 | var isDirectionBackward = api.Selection.isDirectionBackward; 20 | var markerTextChar = "\ufeff"; 21 | 22 | function gEBI(id, doc) { 23 | return (doc || document).getElementById(id); 24 | } 25 | 26 | function insertRangeBoundaryMarker(range, atStart) { 27 | var markerId = "selectionBoundary_" + (+new Date()) + "_" + ("" + Math.random()).slice(2); 28 | var markerEl; 29 | var doc = dom.getDocument(range.startContainer); 30 | 31 | // Clone the Range and collapse to the appropriate boundary point 32 | var boundaryRange = range.cloneRange(); 33 | boundaryRange.collapse(atStart); 34 | 35 | // Create the marker element containing a single invisible character using DOM methods and insert it 36 | markerEl = doc.createElement("span"); 37 | markerEl.id = markerId; 38 | markerEl.style.lineHeight = "0"; 39 | markerEl.style.display = "none"; 40 | markerEl.className = "rangySelectionBoundary"; 41 | markerEl.appendChild(doc.createTextNode(markerTextChar)); 42 | 43 | boundaryRange.insertNode(markerEl); 44 | return markerEl; 45 | } 46 | 47 | function setRangeBoundary(doc, range, markerId, atStart) { 48 | var markerEl = gEBI(markerId, doc); 49 | if (markerEl) { 50 | range[atStart ? "setStartBefore" : "setEndBefore"](markerEl); 51 | removeNode(markerEl); 52 | } else { 53 | module.warn("Marker element has been removed. Cannot restore selection."); 54 | } 55 | } 56 | 57 | function compareRanges(r1, r2) { 58 | return r2.compareBoundaryPoints(r1.START_TO_START, r1); 59 | } 60 | 61 | function saveRange(range, direction) { 62 | var startEl, endEl, doc = api.DomRange.getRangeDocument(range), text = range.toString(); 63 | var backward = isDirectionBackward(direction); 64 | 65 | if (range.collapsed) { 66 | endEl = insertRangeBoundaryMarker(range, false); 67 | return { 68 | document: doc, 69 | markerId: endEl.id, 70 | collapsed: true 71 | }; 72 | } else { 73 | endEl = insertRangeBoundaryMarker(range, false); 74 | startEl = insertRangeBoundaryMarker(range, true); 75 | 76 | return { 77 | document: doc, 78 | startMarkerId: startEl.id, 79 | endMarkerId: endEl.id, 80 | collapsed: false, 81 | backward: backward, 82 | toString: function() { 83 | return "original text: '" + text + "', new text: '" + range.toString() + "'"; 84 | } 85 | }; 86 | } 87 | } 88 | 89 | function restoreRange(rangeInfo, normalize) { 90 | var doc = rangeInfo.document; 91 | if (typeof normalize == "undefined") { 92 | normalize = true; 93 | } 94 | var range = api.createRange(doc); 95 | if (rangeInfo.collapsed) { 96 | var markerEl = gEBI(rangeInfo.markerId, doc); 97 | if (markerEl) { 98 | markerEl.style.display = "inline"; 99 | var previousNode = markerEl.previousSibling; 100 | 101 | // Workaround for issue 17 102 | if (previousNode && previousNode.nodeType == 3) { 103 | removeNode(markerEl); 104 | range.collapseToPoint(previousNode, previousNode.length); 105 | } else { 106 | range.collapseBefore(markerEl); 107 | removeNode(markerEl); 108 | } 109 | } else { 110 | module.warn("Marker element has been removed. Cannot restore selection."); 111 | } 112 | } else { 113 | setRangeBoundary(doc, range, rangeInfo.startMarkerId, true); 114 | setRangeBoundary(doc, range, rangeInfo.endMarkerId, false); 115 | } 116 | 117 | if (normalize) { 118 | range.normalizeBoundaries(); 119 | } 120 | 121 | return range; 122 | } 123 | 124 | function saveRanges(ranges, direction) { 125 | var rangeInfos = [], range, doc; 126 | var backward = isDirectionBackward(direction); 127 | 128 | // Order the ranges by position within the DOM, latest first, cloning the array to leave the original untouched 129 | ranges = ranges.slice(0); 130 | ranges.sort(compareRanges); 131 | 132 | for (var i = 0, len = ranges.length; i < len; ++i) { 133 | rangeInfos[i] = saveRange(ranges[i], backward); 134 | } 135 | 136 | // Now that all the markers are in place and DOM manipulation over, adjust each range's boundaries to lie 137 | // between its markers 138 | for (i = len - 1; i >= 0; --i) { 139 | range = ranges[i]; 140 | doc = api.DomRange.getRangeDocument(range); 141 | if (range.collapsed) { 142 | range.collapseAfter(gEBI(rangeInfos[i].markerId, doc)); 143 | } else { 144 | range.setEndBefore(gEBI(rangeInfos[i].endMarkerId, doc)); 145 | range.setStartAfter(gEBI(rangeInfos[i].startMarkerId, doc)); 146 | } 147 | } 148 | 149 | return rangeInfos; 150 | } 151 | 152 | function saveSelection(win) { 153 | if (!api.isSelectionValid(win)) { 154 | module.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus."); 155 | return null; 156 | } 157 | var sel = api.getSelection(win); 158 | var ranges = sel.getAllRanges(); 159 | var backward = (ranges.length == 1 && sel.isBackward()); 160 | 161 | var rangeInfos = saveRanges(ranges, backward); 162 | 163 | // Ensure current selection is unaffected 164 | if (backward) { 165 | sel.setSingleRange(ranges[0], backward); 166 | } else { 167 | sel.setRanges(ranges); 168 | } 169 | 170 | return { 171 | win: win, 172 | rangeInfos: rangeInfos, 173 | restored: false 174 | }; 175 | } 176 | 177 | function restoreRanges(rangeInfos) { 178 | var ranges = []; 179 | 180 | // Ranges are in reverse order of appearance in the DOM. We want to restore earliest first to avoid 181 | // normalization affecting previously restored ranges. 182 | var rangeCount = rangeInfos.length; 183 | 184 | for (var i = rangeCount - 1; i >= 0; i--) { 185 | ranges[i] = restoreRange(rangeInfos[i], true); 186 | } 187 | 188 | return ranges; 189 | } 190 | 191 | function restoreSelection(savedSelection, preserveDirection) { 192 | if (!savedSelection.restored) { 193 | var rangeInfos = savedSelection.rangeInfos; 194 | var sel = api.getSelection(savedSelection.win); 195 | var ranges = restoreRanges(rangeInfos), rangeCount = rangeInfos.length; 196 | 197 | if (rangeCount == 1 && preserveDirection && api.features.selectionHasExtend && rangeInfos[0].backward) { 198 | sel.removeAllRanges(); 199 | sel.addRange(ranges[0], true); 200 | } else { 201 | sel.setRanges(ranges); 202 | } 203 | 204 | savedSelection.restored = true; 205 | } 206 | } 207 | 208 | function removeMarkerElement(doc, markerId) { 209 | var markerEl = gEBI(markerId, doc); 210 | if (markerEl) { 211 | removeNode(markerEl); 212 | } 213 | } 214 | 215 | function removeMarkers(savedSelection) { 216 | var rangeInfos = savedSelection.rangeInfos; 217 | for (var i = 0, len = rangeInfos.length, rangeInfo; i < len; ++i) { 218 | rangeInfo = rangeInfos[i]; 219 | if (rangeInfo.collapsed) { 220 | removeMarkerElement(savedSelection.doc, rangeInfo.markerId); 221 | } else { 222 | removeMarkerElement(savedSelection.doc, rangeInfo.startMarkerId); 223 | removeMarkerElement(savedSelection.doc, rangeInfo.endMarkerId); 224 | } 225 | } 226 | } 227 | 228 | api.util.extend(api, { 229 | saveRange: saveRange, 230 | restoreRange: restoreRange, 231 | saveRanges: saveRanges, 232 | restoreRanges: restoreRanges, 233 | saveSelection: saveSelection, 234 | restoreSelection: restoreSelection, 235 | removeMarkerElement: removeMarkerElement, 236 | removeMarkers: removeMarkers 237 | }); 238 | }); 239 | /* build:modularizeEnd */ --------------------------------------------------------------------------------