Creating a handler for link popup inside Quill Editor - javascript

I want to create a link using popup form which takes form data(Link title and Link url) and then add this link to the editor.
Something like Reddit editor
My starter code which does not work as I wanted it to be
class LinkBlot extends Inline {
static create(url, title) {
let node = super.create();
// Sanitize url value if desired
node.setAttribute('href', url);
// Okay to set other non-format related attributes
// These are invisible to Parchment so must be static
node.setAttribute('target', '_blank');
node.setAttribute('title', title);
return node;
}
static formats(node) {
// We will only be called with a node already
// determined to be a Link blot, so we do
// not need to check ourselves
return node.getAttribute('href') || true;
}
format(name, value) {
if (name === 'link' && value) {
this.domNode.setAttribute('href', value);
} else {
super.format(name, value);
}
}
formats() {
let formats = super.formats();
formats['link'] = LinkBlot.formats(this.domNode);
return formats;
}
}
LinkBlot.blotName = 'link';
LinkBlot.tagName = 'a';
Quill.register(LinkBlot);
document.querySelector('#link-insert-btn').addEventListener('click', function(event){
event.preventDefault();
var linkText = document.getElementById('link-text').value;
var linkURL = document.getElementById('link-url').value;
quill.format('link', linkURL, linkText);
});
Some help would be appreciated.

Related

Javascript functions not working on github pages (but working locally)

I am able to load my javascript functions on the local machine (local host). But on github pages, it is not working (but marked as successfully pushed and "deployed".)
I have included the file in tags.html as
<script>
$(document).ready(function() {
let currentTag = "";
const queryTag = getQuery().tag;
if (queryTag) {
currentTag = queryTag;
filterByTagName(currentTag)
}
//binding event
$(".tag").click((e) => {
currentTag = e.target.dataset.tag;
filterByTagName(currentTag);
updateQueryString(currentTag);
})
});
function getQuery() {
var params = {};
window.location.search.replace(/[?&]+([^=&]+)=([^&]*)/gi,
function(str, key, value) {
params[key] = value;
}
);
return params;
}
function updateQueryString(tagName) {
const path = `${location.protocol}//${location.host}${location.pathname}?tag=${tagName}`;
window.history.replaceState({ path }, '', path);
}
function filterByTagName(tagName) {
$('.hidden').removeClass('hidden');
$('.post-list').each((index, elem) => {
//elem -> element (ex. div) has data-tag1
if (!elem.hasAttribute(`data-${tagName}`)) {
$(elem).addClass('hidden');
}
});
$(`.tag`).removeClass('selected');
$(`.tag[data-tag=${tagName}]`).addClass('selected');
}
</script>
The page I need to fix is https://popla0428.github.io/tags/
The code is at https://github.com/popla0428/popla0428.github.io
--> At this tag page, I want to filter the posts by tag icon, and that's why I add the above.
Any help would be appreciated. Thanks in advance.

How to run 2 js functions

I have 2 function that I am trying to run, one after another. For some reason they both run at the same time, but the second one does not load properly. Is there a way to run the first function wait then run the second function?:
//run this first
$('#abc').click(function() {
$('.test1').show();
return false;
});
//run this second
(function ($) {
"use strict";
// A nice closure for our definitions
function getjQueryObject(string) {
// Make string a vaild jQuery thing
var jqObj = $("");
try {
jqObj = $(string)
.clone();
} catch (e) {
jqObj = $("<span />")
.html(string);
}
return jqObj;
}
function printFrame(frameWindow, content, options) {
// Print the selected window/iframe
var def = $.Deferred();
try {
frameWindow = frameWindow.contentWindow || frameWindow.contentDocument || frameWindow;
var wdoc = frameWindow.document || frameWindow.contentDocument || frameWindow;
if(options.doctype) {
wdoc.write(options.doctype);
}
wdoc.write(content);
wdoc.close();
var printed = false;
var callPrint = function () {
if(printed) {
return;
}
// Fix for IE : Allow it to render the iframe
frameWindow.focus();
try {
// Fix for IE11 - printng the whole page instead of the iframe content
if (!frameWindow.document.execCommand('print', false, null)) {
// document.execCommand returns false if it failed -http://stackoverflow.com/a/21336448/937891
frameWindow.print();
}
// focus body as it is losing focus in iPad and content not getting printed
$('body').focus();
} catch (e) {
frameWindow.print();
}
frameWindow.close();
printed = true;
def.resolve();
}
// Print once the frame window loads - seems to work for the new-window option but unreliable for the iframe
$(frameWindow).on("load", callPrint);
// Fallback to printing directly if the frame doesn't fire the load event for whatever reason
setTimeout(callPrint, options.timeout);
} catch (err) {
def.reject(err);
}
return def;
}
function printContentInIFrame(content, options) {
var $iframe = $(options.iframe + "");
var iframeCount = $iframe.length;
if (iframeCount === 0) {
// Create a new iFrame if none is given
$iframe = $('<iframe height="0" width="0" border="0" wmode="Opaque"/>')
.prependTo('body')
.css({
"position": "absolute",
"top": -999,
"left": -999
});
}
var frameWindow = $iframe.get(0);
return printFrame(frameWindow, content, options)
.done(function () {
// Success
setTimeout(function () {
// Wait for IE
if (iframeCount === 0) {
// Destroy the iframe if created here
$iframe.remove();
}
}, 1000);
})
.fail(function (err) {
// Use the pop-up method if iframe fails for some reason
console.error("Failed to print from iframe", err);
printContentInNewWindow(content, options);
})
.always(function () {
try {
options.deferred.resolve();
} catch (err) {
console.warn('Error notifying deferred', err);
}
});
}
function printContentInNewWindow(content, options) {
// Open a new window and print selected content
var frameWindow = window.open();
return printFrame(frameWindow, content, options)
.always(function () {
try {
options.deferred.resolve();
} catch (err) {
console.warn('Error notifying deferred', err);
}
});
}
function isNode(o) {
/* http://stackoverflow.com/a/384380/937891 */
return !!(typeof Node === "object" ? o instanceof Node : o && typeof o === "object" && typeof o.nodeType === "number" && typeof o.nodeName === "string");
}
$.print = $.fn.print = function () {
// Print a given set of elements
var options, $this, self = this;
// console.log("Printing", this, arguments);
if (self instanceof $) {
// Get the node if it is a jQuery object
self = self.get(0);
}
if (isNode(self)) {
// If `this` is a HTML element, i.e. for
// $(selector).print()
$this = $(self);
if (arguments.length > 0) {
options = arguments[0];
}
} else {
if (arguments.length > 0) {
// $.print(selector,options)
$this = $(arguments[0]);
if (isNode($this[0])) {
if (arguments.length > 1) {
options = arguments[1];
}
} else {
// $.print(options)
options = arguments[0];
$this = $("html");
}
} else {
// $.print()
$this = $("html");
}
}
// Default options
var defaults = {
globalStyles: true,
mediaPrint: false,
stylesheet: null,
noPrintSelector: ".no-print",
iframe: true,
append: null,
prepend: null,
manuallyCopyFormValues: true,
deferred: $.Deferred(),
timeout: 750,
title: null,
doctype: '<!doctype html>'
};
// Merge with user-options
options = $.extend({}, defaults, (options || {}));
var $styles = $("");
if (options.globalStyles) {
// Apply the stlyes from the current sheet to the printed page
$styles = $("style, link, meta, base, title");
} else if (options.mediaPrint) {
// Apply the media-print stylesheet
$styles = $("link[media=print]");
}
if (options.stylesheet) {
// Add a custom stylesheet if given
$styles = $.merge($styles, $('<link rel="stylesheet" href="' + options.stylesheet + '">'));
}
// Create a copy of the element to print
var copy = $this.clone();
// Wrap it in a span to get the HTML markup string
copy = $("<span/>")
.append(copy);
// Remove unwanted elements
copy.find(options.noPrintSelector)
.remove();
// Add in the styles
copy.append($styles.clone());
// Update title
if (options.title) {
var title = $("title", copy);
if (title.length === 0) {
title = $("<title />");
copy.append(title);
}
title.text(options.title);
}
// Appedned content
copy.append(getjQueryObject(options.append));
// Prepended content
copy.prepend(getjQueryObject(options.prepend));
if (options.manuallyCopyFormValues) {
// Manually copy form values into the HTML for printing user-modified input fields
// http://stackoverflow.com/a/26707753
copy.find("input")
.each(function () {
var $field = $(this);
if ($field.is("[type='radio']") || $field.is("[type='checkbox']")) {
if ($field.prop("checked")) {
$field.attr("checked", "checked");
}
} else {
$field.attr("value", $field.val());
}
});
copy.find("select").each(function () {
var $field = $(this);
$field.find(":selected").attr("selected", "selected");
});
copy.find("textarea").each(function () {
// Fix for https://github.com/DoersGuild/jQuery.print/issues/18#issuecomment-96451589
var $field = $(this);
$field.text($field.val());
});
}
// Get the HTML markup string
var content = copy.html();
// Notify with generated markup & cloned elements - useful for logging, etc
try {
options.deferred.notify('generated_markup', content, copy);
} catch (err) {
console.warn('Error notifying deferred', err);
}
// Destroy the copy
copy.remove();
if (options.iframe) {
// Use an iframe for printing
try {
printContentInIFrame(content, options);
} catch (e) {
// Use the pop-up method if iframe fails for some reason
console.error("Failed to print from iframe", e.stack, e.message);
printContentInNewWindow(content, options);
}
} else {
// Use a new window for printing
printContentInNewWindow(content, options);
}
return this;
};
})(jQuery);
How would I run the first one wait 5 or so seconds and then run the jquery print? I'm having a hard time with this. So the id would run first and then the print would run adter the id="abc" Here is an example of the code in use:
<div id="test">
<button id="abc" class="btn" onclick="jQuery.print(#test1)"></button>
</div>
If I understand your problem correctly, you want the jQuery click function to be run first, making a div with id="test1" visible and then, once it's visible, you want to run the onclick code which calls jQuery.print.
The very first thing I will suggest is that you don't have two different places where you are handling the click implementation, that can make your code hard to follow.
I would replace your $('#abc').click with the following:
function printDiv(selector) {
$(selector).show();
window.setTimeout(function () {
jQuery.print(selector);
}, 1);
}
This function, when called, will call jQuery.show on the passed selector, wait 1ms and then call jQuery.print. If you need the timeout to be longer, just change the 1 to whatever you need. To use the function, update your example html to the following:
<div id="test">
<button id="abc" class="btn" onclick="printDiv('#test1')"</button>
</div>
When the button is clicked, it will now call the previously mentioned function and pass it the ID of the object that you want to print.
As far as your second function goes, where you have the comment **//run this second**, you should leave that alone. All it does is extend you jQuery object with the print functionality. You need it to run straight away and it currently does.

Kendo Grid: Retaining navigational cell after a datasource sync

this is similar to a previous post where I wanted to sync my data source when the user changes rows in the grid (exactly as Access saves out a record)
In the post above am am shown how to do this when the user tabs into a new cell as follows...
function refreshFix1() {
kendo.ui.Grid.fn.refresh = (function (refresh) {
return function (e) {
this._refreshing = true;
refresh.call(this, e);
this._refreshing = false;
}
})(kendo.ui.Grid.fn.refresh);
kendo.ui.Grid.fn.current = (function (current) {
return function (element) {
// assuming element is td element, i.e. cell selection
if (!this._refreshing && element) {
this._lastFocusedCellIndex = $(element).index();
this._lastFocusedUid = $(element).closest("tr").data("uid");
// Added this For navigation mode
this._lastNavigationCell = this.tbody.find("tr:last td:last");
}
return current.call(this, element);
}
})(kendo.ui.Grid.fn.current);
kendo.ui.Grid.fn.refocusLastEditedCell = function () {
if (this._lastFocusedUid) {
var row = $(this.tbody).find("tr[data-uid='" + this._lastFocusedUid + "']");
var cell = $(row).children().eq(this._lastFocusedCellIndex);
this.editCell(cell);
}
};
The above gives us a function we can call (refocusLastEditedCell) after we sync the data source, and seems to work great.
I now want to do the same for when the grid is in Navigation mode. Following the above example, and the doco here , I added the following...
// Call this to go back to a cell in *navigation* mode
kendo.ui.Grid.fn.refocusLastNavigatedCell = function () {
var self = this;
if (this._lastNavigationCell) {
// try see if calling "async" using setTimeout will help
setTimeout (function(){
console.log("going back to navigation cell");
self.current(this._lastNavigationCell);
self.table.focus();
}, 10)
}
};
I then have the following code to call sync on the datasource...
vm.gridData.sync();
if (vm.editMode){
/ Go back to edit cell
grid.refocusLastEditedCell()
} else{
// Go back to navigation cell
grid.refocusLastNavigatedCell();
};
}
(full example here)
Unfortunately I do not seem to be going back to the same cell, it again just jumps to the top left cell.
Any ideas how to get it to work in this situation would be greatly appreciated.
Thanks in advance for any help!
You need to use the row uid and cell index as done in the original code; the <td> element you're trying to focus doesn't exist anymore once the grid gets rerendered. So you could do this:
kendo.ui.Grid.fn.current = (function (current) {
return function (element) {
// assuming element is td element, i.e. cell selection
if (!this._refreshing && element) {
this._lastFocusedCellIndex = $(element).index();
this._lastFocusedUid = $(element).closest("tr").data("uid");
// For navigation mode
var cell = current.call(this, element);
this._lastNavigationCellIndex = $(cell).index();
this._lastNavigationCellUid = $(cell).closest("tr").data("uid");
}
return current.call(this, element);
}
})(kendo.ui.Grid.fn.current);
and use it:
kendo.ui.Grid.fn.refocusLastNavigatedCell = function () {
if (this._lastNavigationCellUid) {
var row = $(this.tbody).find("tr[data-uid='" +
this._lastNavigationCellUid + "']");
var cell = $(row).children().eq(this._lastNavigationCellIndex);
this.current(cell);
}
};
You've got so many customizations now, you may want to extend the grid itself instead of replacing its methods one by one.
By the way, you could probably integrate all of this in the refresh method, so you don't have to explicitly call it:
kendo.ui.Grid.fn.refresh = (function (refresh) {
return function (e) {
this._refreshing = true;
refresh.call(this, e);
this._refreshing = false;
if (!this.options.editable) {
this.refocusLastNavigatedCell();
} else {
this.refocusLastEditedCell()
}
}
})(kendo.ui.Grid.fn.refresh);
I didn't understand why you want to refocus
this.tbody.find("tr:last td:last");
so I changed it for the (demo).

jquery: can find element, but unable to remove it

I've got stuck here with jQuery remove method. Here's a piece of code ...
clearTemplate : function(contentContainer) {
var container = $('body').find('#' + contentContainer);
if (container.length !== 0) {
console.log(container.length);
container.remove();
console.log(container.length);
}
}
Script can easily find '#'+contentContainer in DOM but is unable to remove it. It has no problem with removing children elements of container object as well.
Console.log returns (obviously) : 1 and 1
Container is also dynamically loaded into DOM.
Here's a bigger piece ...
var TemplateClass = {
mainDiv : $('<div>').attr('id',contentContainer),
setData : function(result, images, template, link) {
this.result = result;
this.images = images;
this.template = template;
this.link = link;
},
readyTemplate : function() {
var that = this;
$.each(this.result, function () {
data = that.result[0];
$(that.mainDiv).loadTemplate(that.template, data, {
append: true
});
});
return this.mainDiv;
},
clearTemplate : function(contentContainer) {
var container = $('body').find('#' + contentContainer);
if (container.length !== 0) {
console.log(container.length);
container.remove();
console.log(container.length);
}
}
}
I'm able to live without removing this object, but it's just not right.

Has anyone found a way to handle code in WYSIHTML5 pleasantly?

I'm using WYSIHTML5 Bootstrap ( http://jhollingworth.github.com/bootstrap-wysihtml5 ), based on WYSIHTML5 ( https://github.com/xing/wysihtml5 ) which is absolutely fantastic at cleaning up HTML when copy pasting from websites.
I'd like to be able to handle code into the editor, and then highlight the syntax with HighlightJS.
I've created a new button and replicated the method used in wysihtml5.js to toggle bold <b> on and off, using <pre> instead:
(function(wysihtml5) {
var undef;
wysihtml5.commands.pre = {
exec: function(composer, command) {
return wysihtml5.commands.formatInline.exec(composer, command, "pre");
},
state: function(composer, command, color) {
return wysihtml5.commands.formatInline.state(composer, command, "pre");
},
value: function() {
return undef;
}
};
})(wysihtml5)
But that's not enough. The editor hides the tags when editing. I need to be able to wrap my content in both <pre>and <code>ie. <pre><code></code></pre>.
This means writing a different function than the one used by wysihtml5, and I don't know how... Could anyone help me with that?
Here's the code for the formatInline function in wysihtml5:
wysihtml5.commands.formatInline = {
exec: function(composer, command, tagName, className, classRegExp) {
var range = composer.selection.getRange();
if (!range) {
return false;
}
_getApplier(tagName, className, classRegExp).toggleRange(range);
composer.selection.setSelection(range);
},
state: function(composer, command, tagName, className, classRegExp) {
var doc = composer.doc,
aliasTagName = ALIAS_MAPPING[tagName] || tagName,
range;
// Check whether the document contains a node with the desired tagName
if (!wysihtml5.dom.hasElementWithTagName(doc, tagName) &&
!wysihtml5.dom.hasElementWithTagName(doc, aliasTagName)) {
return false;
}
// Check whether the document contains a node with the desired className
if (className && !wysihtml5.dom.hasElementWithClassName(doc, className)) {
return false;
}
range = composer.selection.getRange();
if (!range) {
return false;
}
return _getApplier(tagName, className, classRegExp).isAppliedToRange(range);
},
value: function() {
return undef;
}
};
})(wysihtml5);
Got the answer from Christopher, the developer of wysihtml5:
wysihtml5.commands.formatCode = function() {
exec: function(composer) {
var pre = this.state(composer);
if (pre) {
// caret is already within a <pre><code>...</code></pre>
composer.selection.executeAndRestore(function() {
var code = pre.querySelector("code");
wysihtml5.dom.replaceWithChildNodes(pre);
if (code) {
wysihtml5.dom.replaceWithChildNodes(pre);
}
});
} else {
// Wrap in <pre><code>...</code></pre>
var range = composer.selection.getRange(),
selectedNodes = range.extractContents(),
pre = composer.doc.createElement("pre"),
code = composer.doc.createElement("code");
pre.appendChild(code);
code.appendChild(selectedNodes);
range.insertNode(pre);
composer.selection.selectNode(pre);
}
},
state: function(composer) {
var selectedNode = composer.selection.getSelectedNode();
return wysihtml5.dom.getParentElement(selectedNode, { nodeName: "CODE" }) && wysihtml5.dom.getParentElement(selectedNode, { nodeName: "PRE" });
}
};
... and add this to your toolbar:
<a data-wysihtml5-command="formatCode">highlight code</a>
Many thanks Christopher!!
I fork today bootstrap-wysihtml5 project and add code highlighting support using Highlight.js.
You can check demo at http://evereq.github.com/bootstrap-wysihtml5 and review source code https://github.com/evereq/bootstrap-wysihtml5. It's basically almost same code as from Christopher, together with UI changes and embeded inside bootstrap version of editor itself.

Categories