Last active
December 15, 2015 03:39
-
-
Save jonghwanhyeon/5195598 to your computer and use it in GitHub Desktop.
Drag to open link in new tab
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
chrome.extension.onConnect.addListener(function (port) { | |
port.onMessage.addListener(openLink); | |
}); | |
var openLink = (function () { | |
var openers = { | |
tab: function (information ) { | |
chrome.tabs.create({ | |
url: information.url, | |
active: false | |
}); | |
}, | |
window: function (information) { | |
chrome.windows.create({ | |
url: information.url, | |
focused: true | |
}); | |
} | |
}; | |
return function (information) { | |
var opener = openers[information.opensIn]; | |
if (!opener) return ; | |
opener(information); | |
}; | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"manifest_version": 2, | |
"version": "1.0", | |
"manifest_version": 2, | |
"name": "Drag to open link in new tab", | |
"description": "Drag to open link in new tab", | |
"background": { | |
"scripts": ["background.js"], | |
"persistent": false | |
}, | |
"content_scripts": [ | |
{ | |
"matches": ["*://*/*"], | |
"js": ["observer.js"] | |
} | |
], | |
"permissions": [ | |
"tabs" | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* jQuery-like DOM manipulator */ | |
var $ = (function () { | |
var toString = Object.prototype.toString; | |
var slice = Array.prototype.slice; | |
var $ = function (object) { | |
var usesNewOperator = (this instanceof $); | |
if (!usesNewOperator) return new $(object); | |
if ($.isString(object)) { | |
return $.query(object); | |
} | |
if (object instanceof $) { | |
object = object.context; | |
} else if ($.isNodeList(object)) { | |
object = slice.call(object, 0); | |
} else if (!$.isArray(object)) { | |
object = [object]; | |
} | |
this.context = object; | |
}; | |
$.prototype.each = function (callback) { | |
for (var i = 0, length = this.context.length; i < length; i += 1) { | |
var stopsIteration = (callback.call(this.context[i], i, this.context[i]) === false); | |
if (stopsIteration) break; | |
} | |
return this; | |
}; | |
$.prototype.html = function (html) { | |
this.each(function () { | |
this.innerHTML = html; | |
}); | |
return this; | |
}; | |
$.prototype.style = function (object) { | |
if ($.isString(object)) { | |
var key = object; | |
return window.getComputedStyle(this.context[0])[key]; | |
} | |
var properties = object; | |
this.each(function () { | |
var element = this; | |
Object.keys(properties).forEach(function (key) { | |
element.style[key] = properties[key]; | |
}); | |
}); | |
return this; | |
}; | |
$.prototype.append = function (object) { | |
var target = this.context[0]; | |
$(object).each(function () { | |
target.appendChild(this); | |
}); | |
return this; | |
}; | |
$.prototype.appendTo = function (target) { | |
$(target).append(this); | |
return this; | |
}; | |
$.prototype.remove = function () { | |
this.each(function () { | |
if (this.parentNode) { | |
this.parentNode.removeChild(this); | |
} | |
}); | |
return this; | |
}; | |
$.prototype.size = function () { | |
return { | |
width: this.context[0].offsetWidth, | |
height: this.context[0].offsetHeight | |
}; | |
}; | |
$.prototype.offset = function () { | |
var offset = { | |
x: 0, | |
y: 0 | |
}; | |
var element = this.context[0]; | |
while (element) { | |
offset.x += element.offsetLeft; | |
offset.y += element.offsetTop; | |
element = offset.offsetParent; | |
} | |
return offset; | |
} | |
$.prototype.on = function (type, tagName, callback) { | |
if (arguments.length == 2) { | |
callback = selector; | |
tagName = undefined; | |
} else { | |
tagName = tagName.toLowerCase(); | |
} | |
var needsTagNameMatch = (tagName != undefined); | |
this.each(function () { | |
this.addEventListener(type, function (event) { | |
if (needsTagNameMatch) { | |
var tagNameIsMatched = (event.target.tagName.toLowerCase() === tagName); | |
if (!tagNameIsMatched) return ; | |
} | |
callback.call(event.target, event); | |
}, false); | |
}); | |
return this; | |
}; | |
$.prototype.show = function () { | |
this.each(function () { | |
$(this).style({ | |
display: 'block' | |
}); | |
}); | |
return this; | |
}; | |
$.prototype.hide = function () { | |
this.each(function () { | |
$(this).style({ | |
display: 'none' | |
}); | |
}); | |
return this; | |
}; | |
$.prototype.toggle = function () { | |
this.each(function () { | |
var isShown = (this.style('display') === 'block'); | |
if (isShown) { | |
$(this).hide(); | |
} else { | |
$(this).show(); | |
} | |
}); | |
return this; | |
}; | |
$.query = function (selector) { | |
return $(document.querySelectorAll(selector)); | |
}; | |
$.isString = function (object) { | |
return ((typeof object) === 'string'); | |
}; | |
(function () { | |
var types = ['Array', 'NodeList']; | |
var typesToString = types.map(function (type) { | |
return '[object ' + type + ']'; | |
}); | |
types.forEach(function (type, index) { | |
$['is' + type] = function (object) { | |
return (toString.call(object) == typesToString[index]); | |
}; | |
}); | |
})(); | |
return $; | |
})(); | |
/* utilities */ | |
var defer = function (task) { | |
window.setTimeout(task, 0); | |
}; | |
/* constants */ | |
var ACCEPTABLE_DISTANCE = 140; | |
var ACCEPTANCE_COLOR = '#00FF00'; | |
var NOT_ACCEPTANCE_COLOR = '#FF0000'; | |
var MESSAGE_FONT_SIZE = 32; | |
var MESSAGE_FOR_DEFAULT = 'Drag it'; | |
var MESSAGE_FOR_NEW_TAB = 'Open link<br />in new tab'; | |
var MESSAGE_FOR_NEW_WINDOW = 'Open link<br />in new window'; | |
var MESSAGE_FOR_CANCEL = 'Cancel'; | |
var INDICATOR_WIDTH = ACCEPTABLE_DISTANCE * 2; | |
var INDICATOR_HEIGHT = ACCEPTABLE_DISTANCE * 2; | |
/* utilities - point, distance */ | |
var getPoint = function (event, type) { | |
if (type === undefined) type = 'page'; | |
return { | |
x: event[type + 'X'], | |
y: event[type + 'Y'] | |
}; | |
}; | |
var pointIsOnTheRightSideOf = function (currentPoint, basisPoint) { | |
return ((currentPoint.x - basisPoint.x) >= 0); | |
}; | |
var calculateDistance = function (from, to) { | |
var differnce = { | |
x: from.x - to.x, | |
y: from.y - to.y | |
}; | |
return Math.sqrt((differnce.x * differnce.x) + (differnce.y * differnce.y)); | |
}; | |
/* view factory */ | |
var $view = { | |
create: (function () { | |
var creators = { | |
indicator: function (settings) { | |
var $indicator = $(document.createElement('div')).style({ | |
position: 'absolute', | |
left: (settings.point.x - (settings.width / 2)) + 'px', | |
top: (settings.point.y - (settings.height / 2)) + 'px', | |
width: settings.width + 'px', | |
height: settings.height + 'px', | |
borderWidth: '5px', | |
borderStyle: 'solid', | |
borderColor: settings.borderColor, | |
borderRadius: '50%', | |
backgroundColor: 'white', | |
opacity: '0.4', | |
zIndex: '9999', | |
display: 'none' | |
}); | |
$indicator.$message = $view.create('message', { | |
width: settings.width, | |
height: settings.height, | |
fontSize: settings.fontSize, | |
message: settings.message | |
}).appendTo($indicator); | |
return $indicator; | |
}, | |
message: function (settings) { | |
var $message = $(document.createElement('span')).style({ | |
width: settings.width + 'px', | |
height: settings.height + 'px', | |
fontFamily: 'Tahoma', | |
fontSize: settings.fontSize + 'px', | |
lineHeight: '1.3', | |
textAlign: 'center', | |
verticalAlign: 'middle', | |
display: 'table-cell' | |
}); | |
$message.html(settings.message); | |
return $message; | |
} | |
}; | |
return function (type, settings) { | |
var creator = creators[type]; | |
if (!creator) return null; | |
return creator(settings); | |
}; | |
})() | |
}; | |
/* dragging checker */ | |
var draggingChecker = { | |
check: function (settings) { | |
var distance = calculateDistance(settings.initialPoint, settings.currentPoint); | |
var isAcceptableDistance = (distance <= settings.acceptableDistance); | |
var opensInNewTab = pointIsOnTheRightSideOf(settings.currentPoint, settings.initialPoint); | |
return { | |
isAcceptableDistance: isAcceptableDistance, | |
opensInNewTab: opensInNewTab | |
}; | |
} | |
}; | |
/* drag handling routine */ | |
var $indicator = null; | |
var initialPoint = { x: null, y: null }; | |
$(document).on('dragstart', 'a', function (event) { | |
initialPoint = getPoint(event); | |
$indicator = $view.create('indicator', { | |
point: initialPoint, | |
width: INDICATOR_WIDTH, | |
height: INDICATOR_HEIGHT, | |
borderColor: ACCEPTANCE_COLOR, | |
fontSize: MESSAGE_FONT_SIZE, | |
message: MESSAGE_FOR_DEFAULT | |
}).appendTo('body'); | |
// deferring to show $indicator, because if not, it will cause blocking drag events | |
defer(function () { | |
$indicator.show(); | |
}); | |
}).on('drag', 'a', function (event) { | |
// drag event is up to end, Chrome fires drag event with zero client point. | |
var shouldBeIgnored = ((event.clientX === 0) && (event.clientY === 0)); | |
if (shouldBeIgnored) return ; | |
var information = draggingChecker.check({ | |
initialPoint: initialPoint, | |
currentPoint: getPoint(event), | |
acceptableDistance: ACCEPTABLE_DISTANCE | |
}); | |
var color, mesage; | |
if (information.isAcceptableDistance) { | |
color = ACCEPTANCE_COLOR; | |
message = (information.opensInNewTab ? MESSAGE_FOR_NEW_TAB : MESSAGE_FOR_NEW_WINDOW); | |
} else { | |
color = NOT_ACCEPTANCE_COLOR; | |
message = MESSAGE_FOR_CANCEL; | |
} | |
$indicator.style({ | |
borderColor: color | |
}).$message.html(message); | |
}).on('dragend', 'a', function (event) { | |
var information = draggingChecker.check({ | |
initialPoint: initialPoint, | |
currentPoint: getPoint(event), | |
acceptableDistance: ACCEPTABLE_DISTANCE | |
}); | |
if (information.isAcceptableDistance) { | |
var information = { | |
opensIn: (information.opensInNewTab ? 'tab' : 'window'), | |
url: event.target.href | |
}; | |
chrome.extension.connect().postMessage(information); | |
} | |
$indicator.remove(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment