When I paste URLs pointing to my ticketing system into other systems, I want them to look nice without a lot of manual reformatting. I have a userscript that works in GitHub taking <rootURL>/ticket/1234 and pasting it in GitHub as [#1234](<rootURL>/ticket/1234) which GitHub renders nicely.
I want to do the same thing for Teams. The Teams app for Windows has some programmability but I can't find what I need there so I've resorted to using Teams on the web and writing another userscript. (By all means, I'm happy to hear, "The way to do that in Teams is ...." but this is the best I've come up with so far.)
Whereas GitHub messages are composed in a textarea, Teams has a div that it updates as you type. I have a paste handler that creates what seem to be the right DOM elements but when my function is done, I see the original, unformatted URL in the Teams message.
My userscript is below. The logging tells me its doing the right thing but my new DOM elements don't show up in the UI. Any thoughts?
// ==UserScript==
// #name Teams Trac link
// #namespace http://tampermonkey.net/
// #version 0.1
// #description Reformat pasted link to Trac ticket to look pretty in Teams
// #author Chris Nelson, PE
// #match https://teams.microsoft.com/*
// #icon https://www.google.com/s2/favicons?sz=64&domain=microsoft.com
// #grant none
// ==/UserScript==
// Handle paste event by making custom link, when appropriate.
// If handled, suppress the browser handling of the event.
function handlePaste(event) {
console.log("Handling paste");
var target = event.target;
// Get the text that the user is trying to paste
var text = (event.clipboardData || window.clipboardData).getData('text');
try {
var url = new URL(text);
if (url.pathname.startsWith('/trac/ticket')) {
console.log("Pasting a Trac ticket link: " + text);
var parts = url.pathname.split('/');
var ticketNumber = parts[3];
// Don't let the browser also paste
event.preventDefault();
// Create a link
var link = document.createElement('a');
link.innerHTML = '#' + ticketNumber;
link.href = text;
link.title = 'Trac ticket #' + ticketNumber;
// FIXME - nothing from here on works to insert the link
// Get all divs
var divs = Array.from(document.querySelectorAll('div'));
// Filter to those with an ID that starts 'new-message-'
divs = divs.filter(d => d.id.startsWith('new-message-'));
var parent = divs[0];
console.log('Before adding, div has ' + parent.childNodes.length + ' children');
parent.appendChild(link);
console.log('After adding, div has ' + parent.childNodes.length + ' children');
}
}
catch (error) {
// Nothing, just let the browser do its thing.
}
}
(function() {
console.log("Attaching paste handler");
// Intercept a paste event anywhere on the page but
// only handle it if pasting into a new message.
document.body.addEventListener('paste', (event) => {
var target = event.target;
console.log("Got paste event for " + target);
if (target.tagName.toLowerCase() == 'br'
&& target.dataset.ckeFiller == 'true') {
handlePaste(event);
}
}, false);
})();
Related
The Oracle Application Express code editor is just plain back text on white background. No Code highlighting. Also I can't press "tab" without the textfield loosing focus.
I am using firefox 31 (can't upgrade, rescricted by Admin at work here) Also I can't install plugins. I know you can change css on specific sites using a special folder in firefox ("chrome"-folder / userContent.css). I already used this to change die default size of the textfield, because it was frickin small everytime I opened the edit page.
So do you know any framework or script I can use in Apex ? (I could copy that shit to jsfiddle.net every time but that sucks
(I also found the scratchpad in Firefox, which can run js and jquery. Does that help ?)
[SOLVED]
since you can't use
<script src = "">
etc. in plain js, I had to use loadScript. For css files it was even more complicated, but I got it all working.
This is my code, I run it in scratchpad (firefox). It uses ACE to change a div to an editor with highlighting. When clicking apply I revert the editor-changes in the DOM but keep the text/code.
// Load Ace js
loadScript("http://cdnjs.cloudflare.com/ajax/libs/ace/1.1.01/ace.js", function(){
//initialization code
});
// Load Ace css
var cssId = 'myCss'; // you could encode the css path itself to generate id..
if (!document.getElementById(cssId)){
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.id = cssId;
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = 'http://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.0/css/bootstrap.min.css';
link.media = 'all';
head.appendChild(link);
}
// change textarea to div
var editorRegion = document.getElementById('F4000_P4651_PLUG_SOURCE_fieldset');
editorRegion.innerHTML = editorRegion.innerHTML.replace("textarea","div");
// run ACE
highlight();
// Modify the apply Button in Apex to first revert ACE-Editor to normal, then do the usual apply.
var applyChanges = document.getElementById('B3456326662');
applyChanges.setAttribute("onclick","modifiedApply()");
function modifiedApply(){
close();
setTimeout(normalApply, 500);
}
function normalApply(){
javascript:apex.submit('Apply_Changes');
}
// Revert ACE-Changes, but keep changed text/code.
function close(){
var value = editor.getValue();
editor.destroy();
var oldDiv = editor.container;
var newDiv = oldDiv.cloneNode(false);
newDiv.textContent = value;
oldDiv.parentNode.replaceChild(newDiv, oldDiv);
newDiv.outerHTML = newDiv.outerHTML.replace("div","textarea");
var old_new_old = document.getElementById('F4000_P4651_PLUG_SOURCE');
old_new_old.textContent = old_new_old.textContent.substring(0, old_new_old.textContent.length - 6);
}
var editor;
function highlight() {
editor = ace.edit("F4000_P4651_PLUG_SOURCE");
editor.setTheme("ace/theme/monokai");
editor.getSession().setUseWorker(false);
editor.getSession().setMode("ace/mode/javascript");
document.getElementsByClassName('ace_print-margin')[0].setAttribute("style","left:1000px");
}
function loadScript(url, callback){
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState){ //IE
script.onreadystatechange = function(){
if (script.readyState == "loaded" ||
script.readyState == "complete"){
script.onreadystatechange = null;
callback();
}
};
} else { //Others
script.onload = function(){
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
it's my first time to work with Youtube API and after I got most things done, I'm having one last issue with it which i can't seem to solve, so I would appreciate if you could take a few minutes to help.
Here it goes in simple words:
I have a video slider (carousel). Under the main video, are thumbnails of other related videos (this is the site: kushtube.com)
when I click on a related video thumbnail, the main player content is loaded via AJAX
What I wanted to ultimately achieve is that when the current video ends,the next vicdo starts playing automatically.
Now after I did some work, I managed to make it work like that, BUT
it only work on page load...
If i click on one of the related video thumbnails, the content of the player loads via AJAX and I cant seem to be able to re-initialize Youtube API to target the new ajax-loaded player.
This is my current code:
<script>
// global variable for the player
var player;
function ytInit(){
// create the global player from the specific iframe (#video)
player = new YT.Player('test', {
events: {
// call this function when player is ready to use
'onReady': onPlayerReady,
'onStateChange': onPlayerStatusChange
}
});
}
// this function gets called when API is ready to use
function onYouTubeIframeAPIReady() {
ytInit();
}
jQuery(document).ready( function($){
jQuery('ul.carousel-list li').click( function(){
ytInit();
jQuery('ul.carousel-list li.active').removeClass('active');
});
});
function onPlayerStatusChange(event) {
/* video status
----------------
-1 (unstarted)
0 (ended)
1 (playing)
2 (paused)
3 (buffering)
5 (video cued)
*/
console.log( event );
if( event.data == 0 ){
var thisLI = jQuery('ul.carousel-list li.active'); //get active slider item
var thisLINext = jQuery('ul.carousel-list li.active').next(); //get next slider item
var nextVideoCode = thisLINext.attr('video_code'); //get next video code
var nextVideoTitle = thisLINext.attr('video_title'); //get next video code
var nextVideoUrl = thisLINext.attr('video_url'); //get next video code
thisLINext.addClass('active'); //activate next slider item
thisLI.removeClass('active'); //deactivate current cslider item
//load next video
player.loadVideoById(nextVideoCode);
//update video title in header
jQuery('.entry-header h1.entry-title a').attr('href', decodeURIComponent(nextVideoUrl)).text(decodeURIComponent(nextVideoTitle));
}
}
function onPlayerReady(event) {
// bind events
var playButton = document.getElementById("play-button");
playButton.addEventListener("click", function() {
player.playVideo();
});
var pauseButton = document.getElementById("pause-button");
pauseButton.addEventListener("click", function() {
player.pauseVideo();
});
}
// Inject YouTube API script
var tag = document.createElement('script');
tag.src = "//www.youtube.com/player_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
</script>
Do you have any helpful suggestions? I would really appreciate.
Calling ytInit() when you click the <li> creates a new player object. I think that's not quite what you want.
Have you tried:
jQuery('ul.carousel-list li').click( function(){
// ytInit();
// just load the video from the "video_code" attr
player.loadVideoById(this.getAttribute('video_code'));
jQuery('ul.carousel-list li.active').removeClass('active');
});
If that doesn't autoplay, then you may have to wait for the YT.PlayerState.CUED (i.e. 5) event in your player status change event listener to start the player.
I'm starting to work using servicenow, and I've an issue with TimeLines and the doubleClick events.
I configured the schedule page and ScriptInclude (code as pseudo-code):
Schedule Page
glideTimeline.setReadOnly(true); glideTimeline.showLeftPane(true);
glideTimeline.registerEvent("getItems", "MyTimelineScriptInclude");
function doubleClickCustomFunction(evt) {
try {
alert('double click: ' + "evt: " + evt + ', target: ' + target );
action.setRedirectURL( 'my_application.do?sys_id=' + target );
catch (exception) {
gs.log(exception);
}
},
MyTimelineScriptInclude
var MyTimelineScriptInclude = Class.create();
MyTimelineScriptInclude.prototype = Object.extendsObject(AbstractTimelineSchedulePage, {
_getTickets: function(){
tickets = foo();
return tickets;
}
getItems: function() {
try {
var ticket_list = this._getTickets();
for (var ticket in ticket_list) {
this._representTicket(ticket_list[ticket].sys_id);
}
} catch(exception) {
this._debugLog(exception, "getItemsException");
}
},
_representTicket: function(sys_id) {
// ticket Object;
ticket_object = getTicket(sys_id);
var timelineItem = new TimelineItem('my_application' , ticket_object.sys_id);
_representSpans( timelineItem , ticket_object );
this.add(timelineItem);
},
_representSpans: function( timelineItem , ticket_object ) {
var timelineItemSpan1 = timelineItem.createTimelineSpan(''); // I'm not including any value into the span creator.
timelineItemSpan1.setTimeSpan( ticket_object.startDateTime1.getNumericValue() , ticket_object.endDateTime1.getNumericValue() );
timelineItemSpan1.setSpanText(ticket_object.spanText);
timelineItemSpan1.setSpanColor(ticket_object.spanColor);
timelineItemSpan1.setTooltip(ticket_object.spanTooltip);
var timelineItemSpan2 = timelineItem.createTimelineSpan(''); // I'm not including any value into the span creator.
timelineItemSpan2.setTimeSpan( ticket_object.startDateTime2.getNumericValue() , ticket_object.endDateTime2.getNumericValue() );
timelineItemSpan2.setSpanText(ticket_object.spanText);
timelineItemSpan2.setSpanColor(ticket_object.spanColor);
timelineItemSpan2.setTooltip(ticket_object.spanTooltip);
var timelineItemSpan3 = timelineItem.createTimelineSpan(''); // I'm not including any value into the span creator.
timelineItemSpan3.setTimeSpan( ticket_object.startDateTime2.getNumericValue() , ticket_object.endDateTime2.getNumericValue() );
timelineItemSpan3.setSpanText(ticket_object.spanText);
timelineItemSpan3.setSpanColor(ticket_object.spanColor);
timelineItemSpan3.setTooltip(ticket_object.spanTooltip);
},
});
The problem is when I double click on a timeline row, it triggers the doubleClickCustomFunction, but, it isn't able to get any evt data, so, It doesn't performs the redirection.
Best regards
Schedule Pages in ServiceNow use client-side script, so if the doubleClickCustomFunction is part of the schedule page client script, the server-side calls (action.setRedirect and gs.log) will fail.
The default double click function contains the following parameters: event, this, strRecordSysID, strUserSysID
I haven't used a custom doubleclick override, so I'm not sure if these parameters are automatically available. However, this is the case for other custom overrides written within the script include, such as elementMoveX
Other than that, you might try calling window.event within the function if it is a part of the client script
Can we do copy(Ctrl+C) and paste(Ctrl+V) the image from User system(desktop/any folder) to canvas using fabric.js. I have seen the copy and paste program inside the canvas, I have found this Example while searching google but didnt find any relevant example for desktop to canvas. Here is the snippet for copy and paste
function onKeyDownHandler(event) {
//event.preventDefault();
var key;
if(window.event){
key = window.event.keyCode;
}
else{
key = event.keyCode;
}
switch(key){
//////////////
// Shortcuts
//////////////
// Copy (Ctrl+C)
case 67: // Ctrl+C
if(ableToShortcut()){
if(event.ctrlKey){
event.preventDefault();
copy();
}
}
break;
// Paste (Ctrl+V)
case 86: // Ctrl+V
if(ableToShortcut()){
if(event.ctrlKey){
event.preventDefault();
paste();
}
}
break;
default:
// TODO
break;
}
}
function ableToShortcut(){
/*
TODO check all cases for this
if($("textarea").is(":focus")){
return false;
}
if($(":text").is(":focus")){
return false;
}
*/
return true;
}
function copy(){
if(canvas.getActiveGroup()){
for(var i in canvas.getActiveGroup().objects){
var object = fabric.util.object.clone(canvas.getActiveGroup().objects[i]);
object.set("top", object.top+5);
object.set("left", object.left+5);
copiedObjects[i] = object;
}
}
else if(canvas.getActiveObject()){
var object = fabric.util.object.clone(canvas.getActiveObject());
object.set("top", object.top+5);
object.set("left", object.left+5);
copiedObject = object;
copiedObjects = new Array();
}
}
function paste(){
if(copiedObjects.length > 0){
for(var i in copiedObjects){
canvas.add(copiedObjects[i]);
}
}
else if(copiedObject){
canvas.add(copiedObject);
}
canvas.renderAll();
}
Is it possible to do actually I have heard dat it's may not possible.Can anyone guide me how to do please.
If you're targeting modern browsers you can combine 2 new (but widely adopted) html5 features to accomplish your task:
You can create a dropzone on your page using the dragover and drop events.
Then you can use the FileReader API to read the image files into an image object.
Then it's back to FabricJS to load the image as usual.
Here's a tutorial describing how to do the hard bits (#1,#2): http://www.html5rocks.com/en/tutorials/file/dndfiles/
[ Added code that SOMETIMES allows cut/paste of image files ]
Most modern browsers support binding the “paste” event.
// listen for the paste event
window.addEventListener("paste",pasteImage);
But...!!
Support for non-text mime types (ie “image”) is scarce. Chrome seems to support it “off-and-on”.
…And browsers are constantly revising their cut/paste capabilities because of security concerns.
Here is code that sometimes works in Chrome.
// listen for the paste event
window.addEventListener("paste",pasteImage);
function pasteImage(event) {
// get the raw clipboardData
var cbData=event.clipboardData;
for(var i=0;i<cbData.items.length;i++){
// get the clipboard item
var cbDataItem = cbData.items[i];
var type = cbDataItem.type;
// warning: most browsers don't support image data type
if (type.indexOf("image")!=-1) {
// grab the imageData (as a blob)
var imageData = cbDataItem.getAsFile();
// format the imageData into a URL
var imageURL=window.webkitURL.createObjectURL(imageData);
// We've got an imageURL, add code to use it as needed
// the imageURL can be used as src for an Image object
}
}
}
I am writing a userscript for Greasemonkey and want to add an item to right click context menu. If I'm not mistaken, this function is called window.addEventListener("contextmenu", ...).
Can you give me example of this?
Following script was copied from http://userscripts-mirror.org/scripts/review/151097:
Important warning! This copy is unchanged and untested. There might be security issues with userscripts. GM is very powerful and if used on the wrong page the safety of your computer may be at risk.
/*
Google Image Search Context Menu
Add 'Search by Image' in browser context menu when you
right click on image to search Google with that image.
Copyright (C) 2012 LouCypher
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/
// ==UserScript==
// #name Google Image Search Context Menu
// #namespace http://userscripts.org/users/12
// #description Add 'Search by Image' in browser context menu when you right click on image to search Google with that image.
// #version 1.2
// #author LouCypher
// #license GPL
// #resource license https://raw.github.com/LouCypher/userscripts/master/licenses/GPL/LICENSE.txt
// #updateURL https://userscripts.org/scripts/source/151097.meta.js
// #include *
// #exclude file://*
// #grant GM_openInTab
// ==/UserScript==
if (!("contextMenu" in document.documentElement &&
"HTMLMenuItemElement" in window)) return;
var body = document.body;
body.addEventListener("contextmenu", initMenu, false);
var menu = body.appendChild(document.createElement("menu"));
menu.outerHTML = '<menu id="userscript-search-by-image" type="context">\
<menuitem label="Search Google with this image"\
icon="data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\
AAAK6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNXG14zYAAAEl\
SURBVDiNY/z//z8DJYCRkIKsthv/kRX9Z2BgmFalARdiIcaGKZXqcH5O+01U+ay2G3MYGBiSiXUm\
mofnsBDSjEUTMkiBe2Eq1JnZ7TcZBHhZGNythBl0lLkZODmYGX7++sdw/sZnhl3H3zF8+voHwwsY\
FkR5ijNICLMzTF31hOHnr38MHGxMDJlhMgwv3vxkWL7jJYpaJmzu0lTigWtmYGBg+PHrH8P0VU8Y\
tJV5MNRiNYCfmxmuGQZ+/PrHwMmOqRyrAX///WfgYEOV4mBjwjAUpwHHL31iyA6XgRvCwcbEkBUm\
w3DuxmcMtVgDkYONicHLVoTBSJOXgYONieHHz38Ml+98Ydh88DXDtx//CBtACmBiYGCYS4H+OYyU\
5kasgUgKAADN8WLFzlj9rgAAAABJRU5ErkJggg=="></menuitem>\
</menu>';
document.querySelector("#userscript-search-by-image menuitem")
.addEventListener("click", searchImage, false);
function initMenu(aEvent) {
// Executed when user right click on web page body
// aEvent.target is the element you right click on
var node = aEvent.target;
var item = document.querySelector("#userscript-search-by-image menuitem");
if (node.localName == "img") {
body.setAttribute("contextmenu", "userscript-search-by-image");
item.setAttribute("imageURL", node.src);
} else {
body.removeAttribute("contextmenu");
item.removeAttribute("imageURL");
}
}
function addParamsToForm(aForm, aKey, aValue) {
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", aKey);
hiddenField.setAttribute("value", aValue);
aForm.appendChild(hiddenField);
}
function searchImage(aEvent) {
// Executed when user click on menuitem
// aEvent.target is the <menuitem> element
var imageURL = aEvent.target.getAttribute("imageURL");
if (imageURL.indexOf("data:") == 0) {
var base64Offset = imageURL.indexOf(",");
if (base64Offset != -1) {
var inlineImage = imageURL.substring(base64Offset + 1)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/\./g, "=");
var form = document.createElement("form");
form.setAttribute("method", "POST");
form.setAttribute("action", "//www.google.com/searchbyimage/upload");
form.setAttribute("enctype", "multipart/form-data");
form.setAttribute("target", "_blank");
addParamsToForm(form, "image_content", inlineImage);
addParamsToForm(form, "filename", "");
addParamsToForm(form, "image_url", "");
body.appendChild(form);
form.submit();
}
} else {
GM_openInTab("https://www.google.com/searchbyimage?image_url=" +
encodeURIComponent(imageURL));
}
}
Unfortunately the <menu>+<menuitem> solution no longer works since Firefox 85 (and never worked in other browsers I believe) see here and here.
Below is an example of how to add an item to the right-click menu (tested in Tampermonkey & Firefox 91) although sadly it shows up under a 'Tampermonkey' sub-menu rather than as a top-level item in its own right.
The key part is #run-at context-menu
// ==UserScript==
// #name Name of context menu item
// #include *
// #version 0.1
// #grant GM_openInTab
// #run-at context-menu
// ==/UserScript==
(function() {
'use strict';
// If you want to do something based on the selected text
var selectedText = window.getSelection().toString();
// Construct your destination URL
var url = ...
// Open in new, foreground tab
GM_openInTab(url, false);
})();