Outline any block of content on a web page - javascript

Currently in a web cms we are building, we allow users to include any arbitrary content (be it plain text or any html content) to be displayed at any part of the web site. These blocks are called widgets (and obviously we take action to clean them up for security purpose).
I would like to be able to highlight these blocks for debugging purpose when necessary, however I cannot wrap them in any additional elements (such as div) as it may break the layout. I found several questions on this site regrading the same question yet have not any good solution yet.
It would be nice if we are allowed to use some kind of non-layout element (such as the comment tags) to wrap around block of contents to be able to do some action such as highlighting like this, but I have not found any way to do that.
I'm comfortable with any solutions (js, server based, etc) as long as it allows me to outline any block of content without breaking the layout. Please let me know if you have any suggestion.
A few example of widget contents:
Text only widget:
this is a the content of text only widget, there is also no wrapper here.
HTML widget:
<h1>Hello world </h1>
<section class="content">
The problem here is that we do not want to force the users to
always wrap their contents inside a root element.
In this example you can see that the content contains 1 h1 element
and 1 section element. To outline this whole widget we may need to
wrap it by an outer element which may break the layout.
</section>

This is not exactly what you wanted, but it might be close enough (fiddle). The script will wrap widgets with a <span class="highlight">. The span should not effect layout in any way:
.highlight {
display: block; // this will change the layout as it inserts a line break
outline: 1px dashed red;
}
When rendering the html, add a comment before and after a widget:
<!--!!!start widget!!!-->
this is a the content of text only widget, there is also no wrapper here.
<!--!!!end widget!!!-->
When you move to debug mode, run this script to add the highlight span:
var START_WIDGET = '!!!start widget!!!';
var END_WIDGET = '!!!end widget!!!';
function filter( node ) {
if ( node.nodeValue !== START_WIDGET && node.nodeValue !== END_WIDGET) { // filter all comment nodes that are not start or end widget
return( NodeFilter.FILTER_SKIP );
}
return( NodeFilter.FILTER_ACCEPT );
}
filter.acceptNode = filter;
var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_COMMENT,
filter,
false
);
var start = null;
while ( treeWalker.nextNode() ) {
if(treeWalker.currentNode.nodeValue === START_WIDGET) {
start = treeWalker.currentNode;
} else if(treeWalker.currentNode.nodeValue === END_WIDGET) {
highlight(start, treeWalker.currentNode);
start = null;
}
}
function highlight(start, end) {
var currentNode = start.nextSibling;
var span = document.createElement('span');
var temp;
span.className = 'highlight';
while(currentNode !== end) {
temp = currentNode;
currentNode = currentNode.nextSibling;
span.appendChild(temp);
}
$(span).insertAfter(start);
}
The treeWalker code is based on this article - Finding HTML Comment Nodes In The DOM Using TreeWalker

Try the style property, outline. It's like border but it's size doesn't affect layout. Wrap your widgets in <span>s as they are inline elements, which makes them perfect if you don't want to disrupt your layout. In addition to the use of outline and <span>, you can use the defaults to facilitate a smooth even layout.
CSS
/* DEFAULTS */
html {
box-sizing: border-box;
font: 400 16px/1.45'Source Code Pro';
}
*,
*:before,
*:after {
box-sizing: inherit;
margin: 0;
padding: 0;
border: 0 solid transparent;
}
/* WIDGETS */
.widget {
outline: 3px dashed orange;
}
.widget.text {
outline: 1px solid blue;
}
.widget.html {
outline: 1px solid red;
}
.widget.html > .content {
outline: 1px solid black;
background: yellow;
}
<span class="widget text">
this is a the content of text only widget, there is also no wrapper here.
</span>
<span class="widget html">
<h1>Hello world</h1>
<section class="content">
The problem here is that we do not want to force the users to
always wrap their contents inside a root element.
In this example you can see that the content contains 1 h1 element
and 1 section element. To outline this whole widget we may need to
wrap it by an outer element which may break the layout.
</section>
</span>

Related

Place a button and its div with one command

Currently, I have a button class which lets me place a clickable button inside a sentence, and a div class which lets me add content to the button which I placed at the end of the paragraph containing the sentence.
This is an example of how I use them
Try to click <button class="col">THIS</button> and see what happens.
<div class="con">nice!</div>
Did you try?
When this text is displayed on the page, the two sentences are placed inside two different paragraphs, so the div object is placed between them.
Here is a snippet with the css classes and the javascript.
( function() {
coll = document.getElementsByClassName("col");
conn = document.getElementsByClassName("con");
var i;
for (i = 0; i < coll.length; i++) {
coll[i].setAttribute('data-id', 'con' + i);
conn[i].setAttribute('id', 'con' + i);
coll[i].addEventListener("click", function() {
this.classList.toggle("active");
var content = document.getElementById(this.getAttribute('data-id'));
if (content.style.maxHeight) {
content.style.maxHeight = null;
} else {
content.style.maxHeight = content.scrollHeight + "px";
}
});
}
} )();
.col {
cursor: help;
border-radius: 0;
border: none;
outline: none;
background: none;
padding: 0;
font-size: 1em;
color: red;
}
.con {
padding: 0 1em;
max-height: 0;
overflow: hidden;
transition: .3s ease;
background-color: yellow;
}
Try to click <button class="col">THIS</button> and see what happens.
<div class="con">nice!</div>
Did you try?
I wonder if it is possible to implement a shortcut to place the two objects with one command, that is to obtain the previous example by using something like this
Try to click [[THIS|nice!]] and see what happens.
Did you try?
What I mean is that the command [[THIS|nice!]] should place the object <button class="col">THIS</button> in the same position and the object <div class="con">nice!</div> at the end of the paragraph containing the command.
Is it possible to implement such a command (or a similar one)?
EDIT
I forgot to say that the content of the button, ie what is written inside the div, should also be possible to be a wordpress shortcode, which is a shortcut/macro for a longer piece of code or text.
Using jQuery, closest() find the nearest <p> element and add <div class="con">nice!</div> after <p> element. To toggle you can use class active and add or remove .con element.
$('.col').click(function(){
let traget = $(this).closest('p');
if(traget.hasClass('active')) {
traget.removeClass('active');
traget.next('.con').remove();
} else {
traget.addClass('active');
traget.after(`<div class="con">${$(this).data('message')}</div>`);
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>Try to click <button class="col" data-message="Hello">THIS</button> and see what happens.</p>
<p>Did you try?</p>
You usually dont use div to type text. you use it to define areas or group items. you could obtain what youre asking for in a 1 sentence like this:
html
<h1> some random text <a class="btnID">button</> some more text<h1>
css
.btnID {
color: red;
}

Formatting text in CSS based on output text

I am using Awesome Tables (which I will refer to as AT) to take data from a Google Sheets document and present the data in a format I can embed into a website. I have used a HTML template in the sheets to display the data in the AT form, which utilises inline CSS formatting. The template is only for the table output and as such, only the <body> element exists for that table - i.e. I have no access to <head> section, etc.
I have a piece of data (${"Status"}) pulled in from Google Sheets that can insert one of three text outputs: Active, Delivered or Cancelled. This is called to the output by:
<div style="display:inline-block;color:rgb(87, 87, 87);font-size: 14px;padding: 10px 10px 10px 0;flex-shrink: 0;margin-right: auto;text-transform: capitalize;">
<p><b>Name:</b> ${"Name"}</p>
<p><b>Start Date:</b> ${"Start Date"}</p>
<p><b>Completed Date:</b> ${"Completed Date"}</p>
<p><b>Status:</b> ${"Status"}</p>
</div>
I would like to color format the output text of ${"Status"} so that "Active" = orange, "Cancelled" = red and "Delivered" = green but not 100% sure how to do this. I have read that I probably need to use some sort of JavaScript to achieve this, but to be honest, do not know where to start.
Any help appreciated.
So, following on from the response received, here is my first attempt of writing JavaScript after a bit of online research. Am I heading along the right track?
var jobStatus = "${"Status"}";
if (jobStatus = "Delivered") {
document.getElementById("status").style.color = "green";
} else if (jobStatus = "Active") {
document.getElementById("status").style.color = "orange";
} else {
document.getElementById("status").style.color = "red";
}
If you can edit the html template, I would suggest to use css classes to apply the colors. Therefore just apply the value also as the css class and create a bit of css to format your text (or what ever) how you want it:
<div style="display:inline-block;color:rgb(87, 87, 87);font-size: 14px;padding: 10px 10px 10px 0;flex-shrink: 0;margin-right: auto;text-transform: capitalize;">
<p><b>Name:</b> ${"Name"}</p>
<p><b>Start Date:</b> ${"Start Date"}</p>
<p><b>Completed Date:</b> ${"Completed Date"}</p>
<p class="${"Status"}"><b>Status:</b> ${"Status"}</p>
</div>
The css is static. You can put it in the header section of your document (I understood, you do not have dynamic access to that, is that assumption correct?). Otherwise some js to append it to the body would work as well.
.Active {
color: orange;
}
.Cancelled {
color: red;
}
.Delivered {
color: green;
}

HTML Elements inside Contenteditable?

I have a contenteditable tag, and I want my users to be able to type code into it. However, when I type into the contenteditable tag, my code shows up as text rather than an actual element. Is there a way for a user to create a full, working HTML element in a contenteditable box? I know it is possible for the client to insert code using javascript, but what about users who do not have access to javascript? How could users get code such as buttons inside a contenteditable box?
<p contenteditable="true">Try typing code in here as user, code will only be text...</p>
Is there a javascript way to accomplish this without JQUERY?
EDIT
I spent a long time searching for answers on Google, but nothing came up. The best solution I've gotten at this point has been #Dekel's comment on CKEditor. If there is another solution, I want to hear it. If there isn't, I'm sticking to CKEditor. I don't have much time, so I need a solution fast.
MORE EDIT =D
I recently developed my own answer to my question by looking at #Brandon's .replace answer (which only worked for client-coding, not user-coding) and modifying it to work with user-coding.
This isn't pretty, but you could make it work if you are looking to add HTML only. Otherwise an inline editor might work best.
var el = document.querySelector('p')
el.addEventListener('blur', function() {
var map = {amp: '&', lt: '<', gt: '>', quot: '"', '#039': "'"}
var html = this.innerHTML.replace(/&([^;]+);/g, (m, c) => map[c]);
this.innerHTML = html;
});
<p contenteditable="true">Try typing <b>code</b> in here as user, code will only be text...</p>
This answer is similar to #Brandon's idea, but is much more simple.
https://jsfiddle.net/azopqLe4/
<iframe width="100%" height="300" src="//jsfiddle.net/azopqLe4/embedded/js,html,result/dark/" allowfullscreen="allowfullscreen" frameborder="0"></iframe>
function convertit() {
var convet = document.getElementById("convet");
var text = convet.innerHTML;
var newtext;
newtext = text.replace(/</g, "<").replace(/>/g, ">");
convet.innerHTML = newtext;
}
//this version runs onrightclick =D
<p contenteditable="true" oncontextmenu="convertit();" id="convet">
Type some code here, then right-click... =D
</p>
In the second snippet, I typed <b>Test</b>, right-clicked it, and it became Test! My answer works through simple array replacement methods, although it is frustrating and time-wasting to keep right-clicking all the time. To prevent the actual contextmenu from popping up, just add .preventDefault().
You can't insert code, but you can insert DOMElements with JS. No need for jQuery.
var element=document.createElement("button");
element.innerHTML="Hello";
document.getElementById("yourContentEditable").append(element);
The idea with this would be to have a button to prompt for the code and insert it. Something like this:
(It is very ugly and buggy but it's just an example I just wrote)
var editorSelection=null;
function openCodePopup() {
//Store cursor position before editor loses focus
editorSelection=getEditorSelection();
//Open the popup
document.querySelector("#codePopup").style.display="block";
var ta=document.querySelector("#userCode");
ta.value="";
ta.focus();
}
function closeCodePopup() {
document.querySelector("#codePopup").style.display="none";
}
function insertCode() {
var code=document.querySelector("#userCode").value;
closeCodePopup();
if(code=="") return;
insertIntoEditor(html2dom(code));
}
function getEditorSelection() {
//TODO make crossbrowser
//TODO (VERY IMPORTANT) validate if selection is whitin the editor
var sel=window.getSelection();
if(sel.rangeCount) return sel.getRangeAt(0);
return null;
}
function insertIntoEditor(dom) {
if(editorSelection) {
editorSelection.deleteContents();
editorSelection.insertNode(dom);
} else {
//Insert at the end
document.querySelector("#editor").append(dom);
}
}
function html2dom(code) {
//A lazy way to convert html to DOMElements, you can use jQuery or any other
var foo=document.createElement('div'); //or you could use an inline element
foo.contentEditable=false;
foo.innerHTML=code;
return foo;
}
#editor {
height: 180px;
overflow: auto;
border: 1px solid #ccc;
}
#toolbar {
position: relative;
}
#codePopup {
position: absolute;
left: 10px;
top: 15px;
background-color: #fff;
border: 1px solid #ccc;
padding: 5px;
display: none;
}
#userCode {
display: block;
width: 200px;
height: 100px;
}
<div id="toolbar">
<button onclick="openCodePopup()"></></button>
<div id="codePopup">
<textarea id="userCode" placeholder="Type code here"></textarea>
<button onclick="insertCode()">Ok</button>
<button onclick="closeCodePopup()">Cancel</button>
</div>
</div>
<div contenteditable="true" id="editor"></div>
With the same idea you could create other options to convert element (example, text->link, etc.).

Automatically resize text area based on content [duplicate]

This question already has answers here:
Creating a textarea with auto-resize
(50 answers)
Closed 8 years ago.
On one of my pages, I have a text area html tag for users to write a letter in. I want the content below the text area to shift down, or in other words, I want the text area to resize vertically with each line added to the text area and to have the content below simply be positioned in relation to the bottom of the text area.
What I am hoping is that javascript/jquery has a way to detect when the words wrap, or when a new line is added and based on that do a resize of the text area container.
My goal is to make the content below the text area stay the same distance from the bottom of the text no matter how much a user writes.
The text area creates a scroll bar when the text overflows.
Since I wasn't too happy with several solutions I found on the web, here's my take on it.
Respects min-height, max-height.
Avoids jumping around and flashing the scrollbar by adding a buffer to the height (currently 20, may replace by line-height). However still shows scrollbar when max-height is reached.
Avoids resetting the container scroll position by incrementally reducing the textarea height instead of setting it to 0. Will thusly also remove all deleted rows at once. Works in IE and Chrome without browser sniffing.
http://jsfiddle.net/Nd6B3/4/
<textarea id="ta"></textarea>
#ta {
width:250px;
min-height:116px;
max-height:300px;
resize:none;
}
$("#ta").keyup(function (e) {
autoheight(this);
});
function autoheight(a) {
if (!$(a).prop('scrollTop')) {
do {
var b = $(a).prop('scrollHeight');
var h = $(a).height();
$(a).height(h - 5);
}
while (b && (b != $(a).prop('scrollHeight')));
};
$(a).height($(a).prop('scrollHeight') + 20);
}
autoheight($("#ta"));
http://www.jacklmoore.com/autosize/
Download the plugin first:
Step 1: Put "jquery.autoresize.min.js" where you keep your jquery plugins.
Step 2: Link the file in HTML -> <script src="jquery.autosize.min.js" type="text/javascript" ></script> Be sure that this link comes after your jquery link, and before your own javascript/jquery code links.
Step 3: In your javascript code file simply add $('#containerToBeResized').autosize();
$('textarea').keyup(function (e) {
var rows = $(this).val().split("\n");
$(this).prop('rows', rows.length);
});
this work sample.
See this Fiddle from this answer. That increases the height of the textarea based on the number of lines.
I think that's what you're asking for.
Copied the code from the answer below:
HTML
<p>Code explanation: Textarea Auto Resize</p>
<textarea id="comments" placeholder="Type many lines of texts in here and you will see magic stuff" class="common"></textarea>
JS
/*global document:false, $:false */
var txt = $('#comments'),
hiddenDiv = $(document.createElement('div')),
content = null;
txt.addClass('txtstuff');
hiddenDiv.addClass('hiddendiv common');
$('body').append(hiddenDiv);
txt.on('keyup', function () {
content = $(this).val();
content = content.replace(/\n/g, '<br>');
hiddenDiv.html(content + '<br class="lbr">');
$(this).css('height', hiddenDiv.height());
});
CSS
body {
margin: 20px;
}
p {
margin-bottom: 14px;
}
textarea {
color: #444;
padding: 5px;
}
.txtstuff {
resize: none; /* remove this if you want the user to be able to resize it in modern browsers */
overflow: hidden;
}
.hiddendiv {
display: none;
white-space: pre-wrap;
word-wrap: break-word;
overflow-wrap: break-word; /* future version of deprecated 'word-wrap' */
}
/* the styles for 'commmon' are applied to both the textarea and the hidden clone */
/* these must be the same for both */
.common {
width: 500px;
min-height: 50px;
font-family: Arial, sans-serif;
font-size: 13px;
overflow: hidden;
}
.lbr {
line-height: 3px;
}

How can I set text to be copied to clipboard when image is copied?

I am building a web page and have run into something that would be nice to be able to do; set text to be copied to the clipboard when someone tries to copy an image, probably the same as the alt text. Is there any way with javascript/html that this can be done? If so, please explain.
Thanks for any help!
Edit: Basically, I want to let my users highlight the image, press control-c, and then have the alt text stored in their clipboard.
This is possible as Twitch.tv does this when copying emote images in chat. The trick is to use the copy event.
const parent = document.getElementById('parent');
parent.addEventListener('copy', event => {
let selection = document.getSelection(),
range = selection.getRangeAt(0),
contents = range.cloneContents(),
copiedText = '';
for (let node of contents.childNodes.values()) {
if (node.nodeType === 3) {
// text node
copiedText += node.textContent;
} else if (node.nodeType === 1 && node.nodeName === 'IMG') {
copiedText += node.dataset.copyText;
}
}
event.clipboardData.setData('text/plain', copiedText);
event.preventDefault();
console.log(`Text copied: '${copiedText}'`);
});
#parent {
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
flex-grow: 0;
}
#parent,
#pasteHere {
padding: 0.5rem;
border: 1px solid black;
}
.icon {
width: 32px;
}
#pasteHere {
margin-top: 1rem;
background: #E7E7E7;
}
<p>Copy the line below:</p>
<div id="parent">
Some text <img src="https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-icon.svg?v=f13ebeedfa9e" class="icon" data-copy-text="foo" /> some more text <img src="https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-icon.svg?v=f13ebeedfa9e"
class="icon" data-copy-text="bar" />
</div>
<div id="pasteHere" contenteditable>Paste here!</div>
add attribute alt="text" to your image
example:
<img alt="🇫🇷" src="https://twemoji.maxcdn.com/v/14.0.2/72x72/1f1eb-1f1f7.png">
I don't think you can. If you could hook keyboard events through the browser, that'd be a tremendous security issue. You could capture keystrokes and send them to a web service in a few lines of code, which would ruin some lives pretty easily.
You may be able to detect a mouse down event using onmousedown by attaching it to the image in some fashion and store that alt-text in a hidden field or cookie and DoSomething() from there.
I've seen services such as tynt do something like this. 2065880 Javascript: Hijack Copy? talks about the techniques, as does 1203082 Injecting text when content is copied from Web Page

Categories