Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
87a73a0
Add deduping, check scrollWidth and Height in coords
fchasen Apr 20, 2017
ce9665b
Add className option for highlight
fchasen Apr 20, 2017
cac9538
Add attributes for highlight
fchasen Apr 20, 2017
b25b08f
Create README.md
fchasen Apr 21, 2017
a5d1861
Add data attr
fchasen Apr 21, 2017
58ca982
npm name and prepublish command
fchasen Apr 25, 2017
3c4fe18
Update coords to use scrollHeight & scrollWidth
fchasen Jun 17, 2017
14ca703
pane width is 100%
fchasen Aug 1, 2017
db46f9e
bump to v1.0.2
fchasen Aug 1, 2017
e935200
Fixes for iframe, restore setting width and height
fchasen Sep 26, 2017
43d9258
version bump to 1.0.3
fchasen Sep 26, 2017
42958b5
Add touch events
fchasen Sep 27, 2017
b51335c
Check that touches exists
fchasen Sep 27, 2017
1bd7889
Add container left and top to rectangle SVG x and y for Underline.
Sep 27, 2017
d948884
FIXES transpile es6 code for modules
kimpers Dec 28, 2017
56fd727
Merge pull request #2 from kimpers/transpile-module-code
fchasen Mar 16, 2018
9fb050b
Update dependencies, point to transpiled lib
fchasen Mar 16, 2018
1c1fc53
Merge pull request #1 from bdauria/master
fchasen Mar 16, 2018
6286acc
version bump to 1.0.7
fchasen Mar 16, 2018
79a63dc
Add babelrc to npmignore
fchasen Jun 7, 2018
4839b87
Add important to svg container coords
Jul 16, 2018
289a967
Merge pull request #3 from mikkelvp/master
fchasen Jul 17, 2018
2f5bb3c
Version bump to 1.0.9
fchasen Jul 17, 2018
b937da5
properly mark sub elements
sbatson5 Nov 29, 2021
8c7877a
Merge pull request #5 from Upstatement/highlight-sub-elements
fchasen Jan 28, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "presets": ["es2015"] }
2 changes: 1 addition & 1 deletion .npmignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
src/
test/
Makefile
karma.conf.js
index.html
.babelrc
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# marks
A proof-of-concept for SVG-based unobtrusive highlighting on web pages.
40 changes: 24 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
{
"name": "marks",
"version": "1.0.0",
"description": "A library for drawing marks on web page text",
"homepage": "https://github.com/nickstenning/marks",
"name": "marks-pane",
"version": "1.0.9",
"description": "A library for drawing marks on a pane",
"homepage": "https://github.com/fchasen/marks",
"bugs": {
"url": "https://github.com/nickstenning/marks/issues"
"url": "https://github.com/fchasen/marks/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/nickstenning/marks"
"url": "https://github.com/fchasen/marks"
},
"author": "Nick Stenning",
"author": "Fred Chasen",
"license": "MIT",
"main": "lib/marks.js",
"dependencies": {},
"module": "lib/marks.js",
"devDependencies": {
"babel": "^4.3.0",
"babelify": "^5.0.3",
"karma": "^0.12.31",
"karma-browserify": "^3.0.2",
"karma-mocha": "^0.1.10",
"karma-phantomjs-launcher": "^0.1.4",
"babel-core": "^6.22.1",
"babel-loader": "^7.1.4",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-transform-runtime": "^6.22.0",
"babel-polyfill": "^6.22.0",
"babel-preset-env": "1.6.1",
"babel-preset-es2015": "^6.22.0",
"babili-webpack-plugin": "0.1.2",
"babelify": "^8.0.0",
"karma": "^2.0.0",
"karma-browserify": "^5.2.0",
"karma-mocha": "^1.3.0",
"karma-phantomjs-launcher": "^1.0.4",
"karma-referee": "^0.4.2",
"mocha": "^2.1.0",
"mocha": "^5.0.4",
"referee": "^1.1.1",
"referee-sinon": "^1.0.2",
"sinon": "^1.12.2"
"sinon": "^4.4.6",
"babel-cli": "^6.22.2"
},
"scripts": {
"prepublish": "make clean default",
Expand Down
42 changes: 34 additions & 8 deletions src/events.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'babelify/polyfill'; // needed for Object.assign
// import 'babelify/polyfill'; // needed for Object.assign

export default {
proxyMouse: proxyMouse
Expand Down Expand Up @@ -26,8 +26,15 @@ export function proxyMouse(target, tracked) {
// earlier ones.
for (var i = tracked.length - 1; i >= 0; i--) {
var t = tracked[i];
var x = e.clientX
var y = e.clientY;

if (!contains(t, e.clientX, e.clientY)) {
if (e.touches && e.touches.length) {
x = e.touches[0].clientX;
y = e.touches[0].clientY;
}

if (!contains(t, target, x, y)) {
continue;
}

Expand All @@ -38,9 +45,23 @@ export function proxyMouse(target, tracked) {
}
}

for (var ev of ['mouseup', 'mousedown', 'click']) {
target.addEventListener(ev, (e) => dispatch(e), false);
if (target.nodeName === "iframe" || target.nodeName === "IFRAME") {

try {
// Try to get the contents if same domain
this.target = target.contentDocument;
} catch(err){
this.target = target;
}

} else {
this.target = target;
}

for (var ev of ['mouseup', 'mousedown', 'click', 'touchstart']) {
this.target.addEventListener(ev, (e) => dispatch(e), false);
}

}


Expand Down Expand Up @@ -74,11 +95,16 @@ export function clone(e) {
* @param y {Number}
* @returns {Boolean}
*/
function contains(item, x, y) {
function contains(item, target, x, y) {
// offset
var offset = target.getBoundingClientRect();

function rectContains(r, x, y) {
var bottom = r.top + r.height;
var right = r.left + r.width;
return (r.top <= y && r.left <= x && bottom > y && right > x);
var top = r.top - offset.top;
var left = r.left - offset.left;
var bottom = top + r.height;
var right = left + r.width;
return (top <= y && left <= x && bottom > y && right > x);
}

// Check overall bounding box first
Expand Down
145 changes: 119 additions & 26 deletions src/marks.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import svg from './svg';
import events from './events';


export class Pane {
constructor(target, container = document.body) {
this.target = target;
Expand All @@ -16,15 +15,16 @@ export class Pane {
// Set up mouse event proxying between the target element and the marks
events.proxyMouse(this.target, this.marks);

container.appendChild(this.element);
this.container = container;
this.container.appendChild(this.element);

this.render();
}

addMark(mark) {
var g = svg.createElement('g');
this.element.appendChild(g);
mark.bind(g);
mark.bind(g, this.container);

this.marks.push(mark);

Expand All @@ -43,7 +43,7 @@ export class Pane {
}

render() {
setCoords(this.element, coords(this.target));
setCoords(this.element, coords(this.target, this.container));
for (var m of this.marks) {
m.render();
}
Expand All @@ -56,8 +56,9 @@ export class Mark {
this.element = null;
}

bind(element) {
bind(element, container) {
this.element = element;
this.container = container;
}

unbind() {
Expand Down Expand Up @@ -86,19 +87,48 @@ export class Mark {
}
return rects;
}
}

filteredRanges() {
if (!this.range) {
return [];
}

// De-duplicate the boxes
const rects = Array.from(this.range.getClientRects());
const stringRects = rects.map((r) => JSON.stringify(r));
const setRects = new Set(stringRects);
return Array.from(setRects).map((sr) => JSON.parse(sr));
}

}

export class Highlight extends Mark {
constructor(range) {
constructor(range, className, data, attributes) {
super();
this.range = range;
this.className = className;
this.data = data || {};
this.attributes = attributes || {};
}

bind(element) {
super.bind(element);
this.element.setAttribute('fill', 'rgb(255, 10, 10)');
this.element.setAttribute('fill-opacity', '0.3');
bind(element, container) {
super.bind(element, container);

for (var attr in this.data) {
if (this.data.hasOwnProperty(attr)) {
this.element.dataset[attr] = this.data[attr];
}
}

for (var attr in this.attributes) {
if (this.attributes.hasOwnProperty(attr)) {
this.element.setAttribute(attr, this.attributes[attr]);
}
}

if (this.className) {
this.element.classList.add(this.className);
}
}

render() {
Expand All @@ -107,36 +137,99 @@ export class Highlight extends Mark {
this.element.removeChild(this.element.firstChild);
}

var rects = this.range.getClientRects();
var docFrag = this.element.ownerDocument.createDocumentFragment();
var filtered = this.filteredRanges();
var offset = this.element.getBoundingClientRect();
var container = this.container.getBoundingClientRect();

for (var i = 0, len = rects.length; i < len; i++) {
var r = rects[i];
for (var i = 0, len = filtered.length; i < len; i++) {
var r = filtered[i];
var el = svg.createElement('rect');
el.setAttribute('x', r.left - offset.left);
el.setAttribute('y', r.top - offset.top);
el.setAttribute('x', r.left - offset.left + container.left);
el.setAttribute('y', r.top - offset.top + container.top);
el.setAttribute('height', r.height);
el.setAttribute('width', r.width);
this.element.appendChild(el);
docFrag.appendChild(el);
}

this.element.appendChild(docFrag);

}
}

export class Underline extends Highlight {
constructor(range, className, data, attributes) {
super(range, className, data, attributes);
}

render() {
// Empty element
while (this.element.firstChild) {
this.element.removeChild(this.element.firstChild);
}

var docFrag = this.element.ownerDocument.createDocumentFragment();
var filtered = this.filteredRanges();
var offset = this.element.getBoundingClientRect();
var container = this.container.getBoundingClientRect();

for (var i = 0, len = filtered.length; i < len; i++) {
var r = filtered[i];

var rect = svg.createElement('rect');
rect.setAttribute('x', r.left - offset.left + container.left);
rect.setAttribute('y', r.top - offset.top + container.top);
rect.setAttribute('height', r.height);
rect.setAttribute('width', r.width);
rect.setAttribute('fill', 'none');


var line = svg.createElement('line');
line.setAttribute('x1', r.left - offset.left + container.left);
line.setAttribute('x2', r.left - offset.left + container.left + r.width);
line.setAttribute('y1', r.top - offset.top + container.top + r.height - 1);
line.setAttribute('y2', r.top - offset.top + container.top + r.height - 1);

line.setAttribute('stroke-width', 1);
line.setAttribute('stroke', 'black'); //TODO: match text color?
line.setAttribute('stroke-linecap', 'square');

docFrag.appendChild(rect);

docFrag.appendChild(line);
}

this.element.appendChild(docFrag);

}
}


function coords(el) {
function coords(el, container) {
var offset = container.getBoundingClientRect();
var rect = el.getBoundingClientRect();

return {
top: rect.top + document.body.scrollTop,
left: rect.left + document.body.scrollLeft,
height: rect.height,
width: rect.width
top: rect.top - offset.top,
left: rect.left - offset.left,
height: el.scrollHeight,
width: el.scrollWidth
};
}


function setCoords(el, coords) {
el.style.top = `${coords.top}px`;
el.style.left = `${coords.left}px`;
el.style.height = `${coords.height}px`;
el.style.width = `${coords.width}px`;
el.style.setProperty('top', `${coords.top}px`, 'important');
el.style.setProperty('left', `${coords.left}px`, 'important');
el.style.setProperty('height', `${coords.height}px`, 'important');
el.style.setProperty('width', `${coords.width}px`, 'important');
}

function contains(rect1, rect2) {
return (
(rect2.right <= rect1.right) &&
(rect2.left >= rect1.left) &&
(rect2.top >= rect1.top) &&
(rect2.bottom <= rect1.bottom)
);
}