├── .gitignore
├── gulpfile.js
├── test
├── fixtures
│ └── test.html
└── FragmentAnchor.spec.js
├── karma.conf.js
├── .travis.yml
├── LICENSE
├── package.json
├── dist
├── FragmentAnchor.min.js
├── FragmentAnchor.min.js.map
└── FragmentAnchor.js
├── FragmentAnchor.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage
2 | node_modules
3 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 |
3 | var babel = require('gulp-babel');
4 | var rename = require('gulp-rename');
5 | var sourcemaps = require('gulp-sourcemaps');
6 | var uglify = require('gulp-uglify');
7 |
8 |
9 | gulp.task('default', function () {
10 | return gulp.src('FragmentAnchor.js')
11 | .pipe(sourcemaps.init())
12 | .pipe(babel({modules: 'umd'}))
13 | .pipe(sourcemaps.write({sourceRoot: './'}))
14 | .pipe(gulp.dest('dist'))
15 | .pipe(sourcemaps.init({loadMaps: true}))
16 | .pipe(uglify())
17 | .pipe(rename({extname: '.min.js'}))
18 | .pipe(sourcemaps.write('./', {sourceRoot: './'}))
19 | .pipe(gulp.dest('dist'))
20 | });
21 |
--------------------------------------------------------------------------------
/test/fixtures/test.html:
--------------------------------------------------------------------------------
1 |
Pellentesque habitant morbi tristique senectus et netus et
2 | malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae,
3 | ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas
4 | semper. Aenean ultricies mi vitae est. Mauris placerat eleifend
5 | leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat
6 | wisi, condimentum sed, commodo vitae, ornare sit amet,
7 | wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum
8 | orci, sagittis tempus lacus enim ac dui. Donec non enim in
9 | turpis pulvinar facilisis. Ut felis.
10 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | module.exports = function(config) {
4 | config.set({
5 | browsers: ['PhantomJS'],
6 | frameworks: [
7 | 'fixture',
8 | 'browserify',
9 | 'chai',
10 | 'mocha',
11 | 'source-map-support'
12 | ],
13 | files: [
14 | 'test/*.spec.js',
15 | 'test/fixtures/*.html'
16 | ],
17 | reporters: ['progress', 'coverage'].concat(
18 | (process.env.COVERALLS_REPO_TOKEN ? ['coveralls'] : [])),
19 | preprocessors: {
20 | 'test/*.spec.js': ['browserify'],
21 | 'test/fixtures/*.html': ['html2js']
22 | },
23 | browserify: {
24 | debug: true,
25 | transform: ['babelify', 'browserify-istanbul']
26 | },
27 | coverageReporter: {
28 | reporters: [
29 | {type: 'lcovonly'},
30 | {type: 'text'}
31 | ]
32 | },
33 | singleRun: true
34 | });
35 | };
36 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '0.10'
4 | sudo: false
5 | before_script:
6 | - npm install -g karma-cli
7 | - npm install karma-coveralls
8 | notifications:
9 | email: false
10 | irc:
11 | channels:
12 | - chat.freenode.net#annotator
13 | on_success: change
14 | on_failure: change
15 | env:
16 | global:
17 | - secure: MKqJA6PJXOR+uofTSwi0085AMWoBnZen/Y0GNrvV1qkdB54HqhsDAq/iDzpxiGIOQ/lGIDo1B9TUCyMgI0nKKj/y+sGENg8tbIPdQTn5fFNuuvuNk49/Z6YoXrL5kHIsweVdhHUEqsIbPbSvXgPUSoK7S8AC6EmD/KeKsWjAKtao7E4XXUHyVCAsUzqz5cXMU4Aq74zGGd5hfm7PPTSxy5PWZD94FfxpD/4LELmHx5QSrWeG+4WQLFBXaVL3VZehOQcvbFzdqnk3nrkox7pFtf74UO18sD+7zN00xAm3OE7+6j5mg6d1lcfTysiCxiKLO3lo5rUmXesclI+lpZLRvMmMNJi8UerUvmFKsn1xbDS6Eqv+dzf/M1Am9hmuoPwpcFXudwwkN8M412cNN64OgUE5gG2VsSXr7/ijsElhxVLiRKV1LoGSTCNlROgAQAe0/L2IAt1gr2Kn4UQfs4mlT+hoAt6kkpKrrsEhPLfUjcgw5bsCqeaPQJXksgGqiTC1lcvdbCHEub/+ZG0b54kcTkX8/pliJwmofISJbKIycat/I9lXBDcpJ5i8aaKfvnHAQKG6PHylrYmQSJQBAifuKmLkwZStLzKNj9FmUUWf3UsIGeRNhPhjSfJx1LhIUs9J9MMIj7RPN+2P+tg2kbTxh90RG2q9OWmbpQwTXf3o2XQ=
18 | cache:
19 | directories:
20 | - node_modules
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015 Randall Leeds
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dom-anchor-fragment",
3 | "version": "1.0.4",
4 | "description": "Convert between DOM Range instances and fragment identifiers.",
5 | "scripts": {
6 | "test": "karma start"
7 | },
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/tilgovi/dom-anchor-fragment.git"
11 | },
12 | "keywords": [
13 | "dom",
14 | "anchor",
15 | "range",
16 | "fragment",
17 | "text"
18 | ],
19 | "author": "Randall Leeds ",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/tilgovi/dom-anchor-fragment/issues"
23 | },
24 | "homepage": "https://github.com/tilgovi/dom-anchor-fragment",
25 | "main": "dist/FragmentAnchor.js",
26 | "browser": "FragmentAnchor.js",
27 | "browserify": {
28 | "transform": [
29 | "babelify"
30 | ]
31 | },
32 | "dependencies": {},
33 | "devDependencies": {
34 | "babel": "^5.4.3",
35 | "babelify": "^6.1.1",
36 | "browserify": "^10.2.4",
37 | "browserify-istanbul": "^0.2.1",
38 | "chai": "^2.3.0",
39 | "gulp": "^3.9.0",
40 | "gulp-babel": "^5.1.0",
41 | "gulp-rename": "^1.2.2",
42 | "gulp-sourcemaps": "^1.5.2",
43 | "gulp-uglify": "^1.2.0",
44 | "karma": "^0.12.32",
45 | "karma-browserify": "^4.2.1",
46 | "karma-chai": "^0.1.0",
47 | "karma-coverage": "^0.3.1",
48 | "karma-fixture": "^0.2.4",
49 | "karma-html2js-preprocessor": "^0.1.0",
50 | "karma-mocha": "^0.1.10",
51 | "karma-phantomjs-launcher": "^0.1.4",
52 | "karma-sauce-launcher": "^0.2.11",
53 | "karma-source-map-support": "^1.0.0",
54 | "mocha": "^2.2.5"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/dist/FragmentAnchor.min.js:
--------------------------------------------------------------------------------
1 | !function(e,r){if("function"==typeof define&&define.amd)define(["exports","module"],r);else if("undefined"!=typeof exports&&"undefined"!=typeof module)r(exports,module);else{var t={exports:{}};r(t.exports,t),e.FragmentAnchor=t.exports}}(this,function(e,r){"use strict";function t(e,r){if(!(e instanceof r))throw new TypeError("Cannot call a class as a function")}var n=function(){function e(e,r){for(var t=0;t {
4 | before(() => {
5 | fixture.setBase('test/fixtures');
6 | });
7 |
8 | beforeEach(() => {
9 | fixture.load('test.html');
10 | });
11 |
12 | afterEach(() => {
13 | fixture.cleanup();
14 | });
15 |
16 | describe('constructor', () => {
17 | it('is a function', () => {
18 | assert.isFunction(FragmentAnchor);
19 | });
20 |
21 | it('requires root argument', () => {
22 | let construct = () => new FragmentAnchor();
23 | assert.throws(construct,'required parameter');
24 | });
25 |
26 | it('requires an id argument', () => {
27 | let construct = () => new FragmentAnchor(fixture.el);
28 | assert.throws(construct,'required parameter');
29 | });
30 |
31 | it('constructs a new instance with the given root and id', () => {
32 | let root = fixture.el;
33 | let instance = new FragmentAnchor(root, 'foo');
34 | assert.instanceOf(instance, FragmentAnchor);
35 | assert.equal(instance.root, fixture.el);
36 | assert.equal(instance.id, 'foo');
37 | });
38 | });
39 |
40 | describe('fromRange', () => {
41 | it('requires a root argument', () => {
42 | let construct = () => FragmentAnchor.fromRange();
43 | assert.throws(construct, 'required parameter');
44 | });
45 |
46 | it('requires a range argument', () => {
47 | let construct = () => FragmentAnchor.fromRange(document.body);
48 | assert.throws(construct, 'required parameter');
49 | });
50 |
51 | it('throws an error if no fragment identifier is found', () => {
52 | let root = fixture.el;
53 | let range = document.createRange();
54 | range.selectNode(root);
55 | let attempt = () => FragmentAnchor.fromRange(root, range);
56 | assert.throws(attempt, 'no fragment');
57 | });
58 |
59 | it('returns a FragmentAnchor if the common ancestor has an id', () => {
60 | let root = fixture.el;
61 | let range = document.createRange();
62 | range.selectNodeContents(root);
63 | let anchor = FragmentAnchor.fromRange(root, range);
64 | assert.equal(anchor.id, fixture.el.id);
65 | });
66 |
67 | it('returns a FragmentAnchor if any ancestor has an id', () => {
68 | let root = fixture.el;
69 | let range = document.createRange();
70 | range.selectNodeContents(fixture.el.children[0]);
71 | let anchor = FragmentAnchor.fromRange(root, range);
72 | assert.equal(anchor.id, fixture.el.id);
73 | });
74 | });
75 |
76 | describe('fromSelector', () => {
77 | it('requires a root argument', () => {
78 | let construct = () => FragmentAnchor.fromSelector();
79 | assert.throws(construct, 'required parameter');
80 | });
81 |
82 | it('requires a selector argument', () => {
83 | let construct = () => FragmentAnchor.fromSelector(fixture.el);
84 | assert.throws(construct, 'required parameter');
85 | });
86 |
87 | it('returns a FragmentAnchor from the value of the selector', () => {
88 | let selector = {
89 | value: 'foo',
90 | };
91 | let anchor = FragmentAnchor.fromSelector(fixture.el, selector);
92 | assert(anchor.root === fixture.el);
93 | assert(anchor.id === selector.value);
94 | });
95 | });
96 |
97 | describe('toRange', () => {
98 | it('returns a range selecting the contents of the Element', () => {
99 | let root = document.body;
100 | let anchor = new FragmentAnchor(root, fixture.el.id);
101 | let range = anchor.toRange();
102 | assert.strictEqual(range.commonAncestorContainer, fixture.el);
103 | });
104 |
105 | it('throws an error if no Element exists with the stored id', () => {
106 | let root = document.body;
107 | let anchor = new FragmentAnchor(root, 'bogus');
108 | let attempt = () => anchor.toRange();
109 | assert.throws(attempt, 'no element found');
110 | });
111 | });
112 |
113 | describe('toSelector', () => {
114 | it('returns a selector for an HTMLElement', () => {
115 | let anchor = new FragmentAnchor(document.body, fixture.el.id);
116 | let selector = anchor.toSelector();
117 | assert.equal(selector.type, 'FragmentSelector');
118 | assert.equal(selector.value, fixture.el.id);
119 | assert.equal(selector.conformsTo, 'https://tools.ietf.org/html/rfc3236');
120 | });
121 |
122 | it('returns a selector for an SVGElement', () => {
123 | let svg = document.createElementNS(
124 | 'http://www.w3.org/2000/svg', 'svg');
125 | let rect = document.createElementNS(
126 | 'http://www.w3.org/2000/svg', 'rect');
127 | rect.id = 'rectangle1';
128 | fixture.el.appendChild(svg);
129 | svg.appendChild(rect);
130 | let anchor = new FragmentAnchor(svg, rect.id);
131 | let selector = anchor.toSelector();
132 | assert.equal(selector.type, 'FragmentSelector');
133 | assert.equal(selector.value, rect.id);
134 | assert.equal(selector.conformsTo, 'http://www.w3.org/TR/SVG/');
135 | });
136 |
137 | it('throws an error if no Element exists with the stored id', () => {
138 | let anchor = new FragmentAnchor(fixture.el, 'bogus');
139 | let attempt = () => anchor.toSelector();
140 | assert.throws(attempt, 'no element found');
141 | });
142 | });
143 | });
144 |
--------------------------------------------------------------------------------
/dist/FragmentAnchor.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | if (typeof define === 'function' && define.amd) {
3 | define(['exports', 'module'], factory);
4 | } else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
5 | factory(exports, module);
6 | } else {
7 | var mod = {
8 | exports: {}
9 | };
10 | factory(mod.exports, mod);
11 | global.FragmentAnchor = mod.exports;
12 | }
13 | })(this, function (exports, module) {
14 | 'use strict';
15 |
16 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
17 |
18 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
19 |
20 | var FragmentAnchor = (function () {
21 | function FragmentAnchor(root, id) {
22 | _classCallCheck(this, FragmentAnchor);
23 |
24 | if (root === undefined) {
25 | throw new Error('missing required parameter "root"');
26 | }
27 | if (id === undefined) {
28 | throw new Error('missing required parameter "id"');
29 | }
30 |
31 | this.root = root;
32 | this.id = id;
33 | }
34 |
35 | _createClass(FragmentAnchor, [{
36 | key: 'toRange',
37 | value: function toRange() {
38 | var el = this.root.querySelector('#' + this.id);
39 | if (el == null) {
40 | throw new Error('no element found with id "' + this.id + '"');
41 | }
42 |
43 | var range = this.root.ownerDocument.createRange();
44 | range.selectNodeContents(el);
45 |
46 | return range;
47 | }
48 | }, {
49 | key: 'toSelector',
50 | value: function toSelector() {
51 | var el = this.root.querySelector('#' + this.id);
52 | if (el == null) {
53 | throw new Error('no element found with id "' + this.id + '"');
54 | }
55 |
56 | var conformsTo = 'https://tools.ietf.org/html/rfc3236';
57 | if (el instanceof SVGElement) {
58 | conformsTo = 'http://www.w3.org/TR/SVG/';
59 | }
60 |
61 | return {
62 | type: 'FragmentSelector',
63 | value: this.id,
64 | conformsTo: conformsTo
65 | };
66 | }
67 | }], [{
68 | key: 'fromRange',
69 | value: function fromRange(root, range) {
70 | if (root === undefined) {
71 | throw new Error('missing required parameter "root"');
72 | }
73 | if (range === undefined) {
74 | throw new Error('missing required parameter "range"');
75 | }
76 |
77 | var el = range.commonAncestorContainer;
78 | while (el != null && !el.id) {
79 | if (root.compareDocumentPosition(el) & Node.DOCUMENT_POSITION_CONTAINED_BY) {
80 | el = el.parentElement;
81 | } else {
82 | throw new Error('no fragment identifier found');
83 | }
84 | }
85 |
86 | return new FragmentAnchor(root, el.id);
87 | }
88 | }, {
89 | key: 'fromSelector',
90 | value: function fromSelector(root) {
91 | var selector = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
92 |
93 | return new FragmentAnchor(root, selector.value);
94 | }
95 | }]);
96 |
97 | return FragmentAnchor;
98 | })();
99 |
100 | module.exports = FragmentAnchor;
101 | });
102 | //# sourceMappingURL=data:application/json;base64,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Ozs7V0FwQ2tCLGNBQWM7OzttQkFBZCxjQUFjIiwiZmlsZSI6IkZyYWdtZW50QW5jaG9yLmpzIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IGRlZmF1bHQgY2xhc3MgRnJhZ21lbnRBbmNob3Ige1xuICBjb25zdHJ1Y3Rvcihyb290LCBpZCkge1xuICAgIGlmIChyb290ID09PSB1bmRlZmluZWQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignbWlzc2luZyByZXF1aXJlZCBwYXJhbWV0ZXIgXCJyb290XCInKTtcbiAgICB9XG4gICAgaWYgKGlkID09PSB1bmRlZmluZWQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignbWlzc2luZyByZXF1aXJlZCBwYXJhbWV0ZXIgXCJpZFwiJyk7XG4gICAgfVxuXG4gICAgdGhpcy5yb290ID0gcm9vdDtcbiAgICB0aGlzLmlkID0gaWQ7XG4gIH1cblxuICBzdGF0aWMgZnJvbVJhbmdlKHJvb3QsIHJhbmdlKSB7XG4gICAgaWYgKHJvb3QgPT09IHVuZGVmaW5lZCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdtaXNzaW5nIHJlcXVpcmVkIHBhcmFtZXRlciBcInJvb3RcIicpO1xuICAgIH1cbiAgICBpZiAocmFuZ2UgPT09IHVuZGVmaW5lZCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdtaXNzaW5nIHJlcXVpcmVkIHBhcmFtZXRlciBcInJhbmdlXCInKTtcbiAgICB9XG5cbiAgICBsZXQgZWwgPSByYW5nZS5jb21tb25BbmNlc3RvckNvbnRhaW5lcjtcbiAgICB3aGlsZSAoZWwgIT0gbnVsbCAmJiAhZWwuaWQpIHtcbiAgICAgIGlmIChyb290LmNvbXBhcmVEb2N1bWVudFBvc2l0aW9uKGVsKSAmXG4gICAgICAgICAgTm9kZS5ET0NVTUVOVF9QT1NJVElPTl9DT05UQUlORURfQlkpIHtcbiAgICAgICAgZWwgPSBlbC5wYXJlbnRFbGVtZW50O1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdubyBmcmFnbWVudCBpZGVudGlmaWVyIGZvdW5kJyk7XG4gICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIG5ldyBGcmFnbWVudEFuY2hvcihyb290LCBlbC5pZCk7XG4gIH1cblxuICBzdGF0aWMgZnJvbVNlbGVjdG9yKHJvb3QsIHNlbGVjdG9yID0ge30pIHtcbiAgICByZXR1cm4gbmV3IEZyYWdtZW50QW5jaG9yKHJvb3QsIHNlbGVjdG9yLnZhbHVlKTtcbiAgfVxuXG4gIHRvUmFuZ2UoKSB7XG4gICAgbGV0IGVsID0gdGhpcy5yb290LnF1ZXJ5U2VsZWN0b3IoJyMnICsgdGhpcy5pZCk7XG4gICAgaWYgKGVsID09IG51bGwpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignbm8gZWxlbWVudCBmb3VuZCB3aXRoIGlkIFwiJyArIHRoaXMuaWQgKyAnXCInKTtcbiAgICB9XG5cbiAgICBsZXQgcmFuZ2UgPSB0aGlzLnJvb3Qub3duZXJEb2N1bWVudC5jcmVhdGVSYW5nZSgpO1xuICAgIHJhbmdlLnNlbGVjdE5vZGVDb250ZW50cyhlbCk7XG5cbiAgICByZXR1cm4gcmFuZ2U7XG4gIH1cblxuICB0b1NlbGVjdG9yKCkge1xuICAgIGxldCBlbCA9IHRoaXMucm9vdC5xdWVyeVNlbGVjdG9yKCcjJyArIHRoaXMuaWQpO1xuICAgIGlmIChlbCA9PSBudWxsKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ25vIGVsZW1lbnQgZm91bmQgd2l0aCBpZCBcIicgKyB0aGlzLmlkICsgJ1wiJyk7XG4gICAgfVxuXG4gICAgbGV0IGNvbmZvcm1zVG8gPSAnaHR0cHM6Ly90b29scy5pZXRmLm9yZy9odG1sL3JmYzMyMzYnO1xuICAgIGlmIChlbCBpbnN0YW5jZW9mIFNWR0VsZW1lbnQpIHtcbiAgICAgIGNvbmZvcm1zVG8gPSAnaHR0cDovL3d3dy53My5vcmcvVFIvU1ZHLyc7XG4gICAgfVxuXG4gICAgcmV0dXJuIHtcbiAgICAgIHR5cGU6ICdGcmFnbWVudFNlbGVjdG9yJyxcbiAgICAgIHZhbHVlOiB0aGlzLmlkLFxuICAgICAgY29uZm9ybXNUbzogY29uZm9ybXNUbyxcbiAgICB9O1xuICB9XG59XG4iXSwic291cmNlUm9vdCI6Ii4vIn0=
--------------------------------------------------------------------------------