I am trying to format quotes the old-fashioned way. — Is there any way to go from this input:
<p>He argued that <q>the King by his proclamation or other ways cannot change any part of the common law, or statute law, or the customs of the realm</q> and concluded that…</p>
to this output (as displayed on the browser):
He argued that “the King by his procla-
“ mation or other ways cannot change any
“ part of the common law, or statute law,
“ or the customs of the realm” and con-
cluded that…
In the example above, hyphenation, alignment and line length are set arbitrarily and for illustration purpose. They aren’t of any concern.
I would like that, when a line breaks inside the <q>, each consequent lines (that are in the <q>), when displayed, be preceded by a quotation mark (so as the reader would visually isolate the quote). It is an old-fashioned way of formatting indirect speech.
The best I could come up with so far is an unsatisfactory pseudo-workaround using <blockquote> combined with text-shadow in CSS:
* {font-family: monospace; line-height: 1.4em; width: 25em; margin: 0;}
blockquote { position: relative; padding-left: 1.2em; text-indent: -1.2em; overflow-y: hidden;}
blockquote::before {content:"“"; position: absolute; top: 1.4em; left: 1.2em; text-shadow: 0 1.4em 0 #000, 0 2.8em 0 #000, 0 4.2em 0 #000;}
<p>He argued that <blockquote>“the King by his proclamation or other ways cannot change any part of the common law, or statute law, or the customs of the realm”</blockquote> and concluded that…</p>
(Which may vaguely resemble what I wanted (kinda), but ultimately does not work given the block nature of <blockquote>; furthermore, when set inline, the desired effect of the quotation marks being set to the left is lost.)
Edit: acknowledging answers.
Obviously, we are going the JS route. I’ve considered:
Find browser-computed line breaks, and replace each one with "<br>“ "(eventually leading to multiple issues in justified texts—therefore not ideal);
#A-Haworth suggestion of “[a] method to find the line breaks [with] a dummy element, gradually add[ing] the text and spot[ting] when it gets higher.”
But I just don’t know how to do any of these things. Also, there must be a solution I haven’t thought of.
Any idea?
Last Edit (Feb 16, 2022)
No one, really?
It probably can't be done in an easy way. I tried to write a small function that might help you.
The solution needs to be finalized, but you can take the basic idea. if you need a dynamic solution, use resize observer, as indicated in the example.
function quote(el, leftPad = 16) {
const txt = el.textContent.trim().split(" ");
const ctx = document.createElement("canvas").getContext("2d");
const styleList = getComputedStyle(el);
ctx.font = `${styleList.fontWeight} ${styleList.fontSize} "${styleList.fontFamily}"`;
const maxWidth = el.clientWidth;
let paragraph = [];
let line = "";
let lineWidth = 0;
let blockquote = false;
const symbolMap = new Map();
let firstLineBreak = false;
let quoteRowCount = 0;
const getWordLength = (word) => {
let wordWidth = 0;
for (let i = 0; i < word.length; i++) {
if (!symbolMap.has(word[i])) {
symbolMap.set(word[i], ctx.measureText(word[i]).width);
}
wordWidth += symbolMap.get(word[i]);
}
return wordWidth;
};
for (let i = 0; i < txt.length; i++) {
const word = txt[i] + " ";
const width = getWordLength(word);
lineWidth += width;
if (txt[i].search('"') >= 0) blockquote = !blockquote;
if (lineWidth > maxWidth) {
paragraph.push(line);
if (!firstLineBreak && blockquote) {
line = '<br><span class="newline"></span>';
firstLineBreak = true;
quoteRowCount++;
} else {
if (blockquote) quoteRowCount++;
line = "";
}
lineWidth = leftPad + width;
}
line += word;
}
if (line.trim() !== "") paragraph.push(line);
el.innerHTML = paragraph.join("<wbr>");
const newLineElement = el.querySelector(".newline");
if (newLineElement) newLineElement.style.height = `${quoteRowCount}em`;
}
const el = document.querySelector("p");
quote(el);
// or if you need ResizeObserver =>
//new ResizeObserver(() => quote(el)).observe(el);
p {
width: 220px;
font-size: 16px;
font-family: Arial;
padding: 0;
box-sizing: content-box;
}
.newline {
display: block;
width: 1em;
padding-right: 1em;
float: left;
overflow: hidden;
box-sizing: border-box;
word-break: break-word;
}
.newline:before {
content: '""""""""""""""""""""""""';
}
<p>
He argued that "the King by his proclamation or other ways cannot change any part of the common law, or statute law, or the customs of the realm" and concluded that…
</p>
Interesting question - how much can be done by CSS?
While I think for a totally general answer will need JS - slowly adding text to the paragraph and looking to see whether the height has changed or not - here's a snippet that does it mainly by CSS using pseudo element. However, as the browser uses the pseudo element to put in the quotation marks itself, these have to be placed in the text if they are still required.
It requires a way of getting the text that is in the paragraph before the q element into the content of the pseudo element also. Here this is done with a data attribute. Space is reserved for the column of double quotes by doing a text-indent on the paragraph.
So, not a totally general solution, but may point to a way of making it general using a bit of JS to do the content setting up initially.
p {
margin: 50px;
padding-left: 20px;
position: relative;
text-indent: -10px;
}
q {
position: relative;
}
q::before {
content: attr(data-start) '\a " \a " \a " \a " \a " \a " \a " \a " \a " \a " \a " \a " \a " \a " \a " ';
position: absolute;
top: 0;
left: 0;
transform: translateX(-100%);
height: 100%;
width: auto;
z-index: -1;
text-indent: 0;
white-space: pre;
padding-left: 20px;
overflow: hidden;
}
<p>He argued that <q data-start="He argued that ">"the King by his proclamation or other ways cannot change any part of the common law, or statute law, or the customs of the realm"</q> and concluded that…<br>some more text here for test</p>
I have a block with a few inline-blocks inside. These all have varying heights and widths. I have line wrap enabled.
What I'm trying to do is if I have a reference to one of the inline-blocks to
find out what line it is in and
how high that line is. (I can't change the HTML)
2 has the obvious solution of getting the bounding boxes of all blocks in the line and using the highest height as the height of the line. Here I'm just wondering if there is a more performant solution for that.
My main interest lies in a). My idea of an incredibly imperformant solution would be doing b) for all lines in the parent block and then comparing the offset-top of the searched element to the line-heights. I was hoping maybe someone can come up with a better idea?
edit:
relevant part of the html, css and js i'm using:
<div>
<div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>
</div>
div {
display: block;
background-color: yellow;
height: 600px;
width: 2000px;
}
div > div {
display: inline-block;
padding: 10px;
margin: 5px;
}
document.addEventListener("DOMContentLoaded", function() {
const nodes = document.querySelectorAll("div > div");
for(let i = 0; i < nodes.length; i++) {
nodes[i].style.width = `${5 + Math.random() * 200}px`;
nodes[i].style.height = `${5 + Math.random() * 200}px`;
nodes[i].style.backgroundColor = `hsl(${(Math.random() * 360)}, 100%, 50%)`;
}
const nodeiwanttogetdescribeddataof = nodes[4];
//code to get the data
});
i did not implement the ideas described as i was hoping to not need to. the purpose of the question was to find out whether there is some easy way to do what i need, for example (pseudocode) let theline = node.line.
So, I've just started developing a twitch event list with Streamlabs' kinda API and probably got some trouble accessing the style trough jQuery. What I am trying to accomplish is creating .widget-EventList li:nth-child-s depending on the value of {max__events} and besides, reduce the amount of opacity. Sure, I could add those classes manually to the CSS, and I did first, but there was missing a lot of dynamic and user-friendliness. There are two functions I can write code in:
That's the problematic function, it's called on widget load:
document.addEventListener('onLoad', function(obj) {
$('.widget-EventList .message').css("background", "black"); // experimental, doesn't work either, tho
var opMax = 1 / {max__events};
for(var i = {max__events}; i <= 1; i++){
let temp1 = 1 - opMax;
$('.widget-EventList li:nth-child(' + i + ') div:last-child {}').insertAfter('.widget-EventList .message');
$('.widget-EventList li:nth-child(' + i + ') div:last-child').css("color", "{font_color}");
$('.widget-EventList li:nth-child(' + i + ') div:last-child').css("opacity", temp1);
opMax++;
}
});
Here's the additional function I can write into, it's called on widget event receive:
document.addEventListener('onEventReceived', function(obj) {
$('.widget-EventList .message').css("background", "black"); // experimental, and works perfectly fine
});
That's the position it is supposed to be in:
.widget-EventList .message {
float: middle;
white-space: nowrap;
text-overflow: ellipsis;
margin: 4.5 0.25em;
display: inline-block;
}
/*style list-elements <<-----HERE----- */
Maybe document.addEventListener('onLoad', function(obj)) is loading way before the css even got loaded? What am I missing here?
I've been trying to make a simple marquee using velocity.js that will scroll multiple messages across the screen one after the other.
I've put together a working example but I'm experiencing issues with the text sporadically 'stuttering' as it moves.
Using the developer tools in Chrome I can see that I get a drop in framerate when this occurs but I can't figure out why:
There doesn't seem to be anything unusual such as high memory usage and the computer is doing little else.
The issue also occurs in both Internet Explorer and Mozilla Firefox so it's not a browser issue.
How can I diagnose/resolve the issue with stuttering animations, or is this situation to be expected?
The sample below should recreate the issue although as the stuttering is random you'd need to watch it carefully to see what I mean.
$(document).ready(function() {
// WINDOW WIDTH
var width = $(window).width();
// MESSAGE QUEUE
var messages = [
"This is the first message",
"This is the second message",
"This is the last message"
];
// APPEND EACH MESSAGE TO CONTAINER AND HIDE OFF RIGHT OF SCREEN
$.each(messages, function(index, value) {
$("#container").append( "<div id=\"message" + index + "\" class=\"message\">" + value + "</div>" );
$("#message" + index).css({left: width});
});
// START ANIMATION WITH FIRST MESSAGE
AnimateNext(0, messages.length - 1);
});
function AnimateNext(current) {
// GET THE CURRENT MESSAGE
var message = $("#message" + current);
// MOVE MESSAGE TO THE RIGHT OFF SCREEN POSITION
message.css({left: $(window).width()});
// MOVE MESSAGE TO THE LEFT AND OFF SCREEN
message.velocity({left: -message.width()}, 10000, "linear", function() {
// INCREASE CURRENT INDEX
current >= 2 ? current = 0 : current = current + 1;
// ANIMATE NEXT ITEM
AnimateNext(current);
});
};
html, body {
font-family: Helvetica, Arial, "Times New Roman";
height: 100%;
width: 100%;
padding: 0;
margin: 0;
background-color: black;
overflow: hidden;
}
#container {
height: 200px;
width: 100%;
background-color: darkred;
}
.message {
position: absolute;
color: white;
font-size: 100px;
white-space: nowrap;
}
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.5.0/velocity.min.js"></script>
<div id="container">
</div>
JSFiddle in case anyone wants to fork it
I am looking for a Javascript autocomplete implementation which includes the following:
Can be used in a HTML textarea
Allows for typing regular text without invoking autocomplete
Detects the # character and starts autocomplete when it is typed
Loads list of options through AJAX
I believe that this is similar to what Twitter is doing when tagging in a tweet, but I can't find a nice, reusable implementation.
A solution with jQuery would be perfect.
Thanks.
Another great library which solves this problem At.js (deprecated)
Source
Demo
They are now suggesting the Tribute library
https://github.com/zurb/tribute
Example
I'm sure your problem is long since solved, but jquery-textcomplete looks like it would do the job.
Have you tried this
GITHUB: https://github.com/podio/jquery-mentions-input
DEMO/CONFIG: http://podio.github.io/jquery-mentions-input/
It is pretty simple to implement.
I've created a Meteor package for this purpose. Meteor's data model allows for fast multi-rule searching with custom rendered lists. If you're not using Meteor for your web app, (I believe) you unfortunately won't find anything this awesome for autocompletion.
Autocompleting users with #, where online users are shown in green:
In the same line, autocompleting something else with metadata and bootstrap icons:
Fork, pull, and improve:
https://github.com/mizzao/meteor-autocomplete
Try this:
(function($){
$.widget("ui.tagging", {
// default options
options: {
source: [],
maxItemDisplay: 3,
autosize: true,
animateResize: false,
animateDuration: 50
},
_create: function() {
var self = this;
this.activeSearch = false;
this.searchTerm = "";
this.beginFrom = 0;
this.wrapper = $("<div>")
.addClass("ui-tagging-wrap");
this.highlight = $("<div></div>");
this.highlightWrapper = $("<span></span>")
.addClass("ui-corner-all");
this.highlightContainer = $("<div>")
.addClass("ui-tagging-highlight")
.append(this.highlight);
this.meta = $("<input>")
.attr("type", "hidden")
.addClass("ui-tagging-meta");
this.container = $("<div></div>")
.width(this.element.width())
.insertBefore(this.element)
.addClass("ui-tagging")
.append(
this.highlightContainer,
this.element.wrap(this.wrapper).parent(),
this.meta
);
var initialHeight = this.element.height();
this.element.height(this.element.css('lineHeight'));
this.element.keypress(function(e) {
// activate on #
if (e.which == 64 && !self.activeSearch) {
self.activeSearch = true;
self.beginFrom = e.target.selectionStart + 1;
}
// deactivate on space
if (e.which == 32 && self.activeSearch) {
self.activeSearch = false;
}
}).bind("expand keyup keydown change", function(e) {
var cur = self.highlight.find("span"),
val = self.element.val(),
prevHeight = self.element.height(),
rowHeight = self.element.css('lineHeight'),
newHeight = 0;
cur.each(function(i) {
var s = $(this);
val = val.replace(s.text(), $("<div>").append(s).html());
});
self.highlight.html(val);
newHeight = self.element.height(rowHeight)[0].scrollHeight;
self.element.height(prevHeight);
if (newHeight < initialHeight) {
newHeight = initialHeight;
}
if (!$.browser.mozilla) {
if (self.element.css('paddingBottom') || self.element.css('paddingTop')) {
var padInt =
parseInt(self.element.css('paddingBottom').replace('px', '')) +
parseInt(self.element.css('paddingTop').replace('px', ''));
newHeight -= padInt;
}
}
self.options.animateResize ?
self.element.stop(true, true).animate({
height: newHeight
}, self.options.animateDuration) :
self.element.height(newHeight);
var widget = self.element.autocomplete("widget");
widget.position({
my: "left top",
at: "left bottom",
of: self.container
}).width(self.container.width()-4);
}).autocomplete({
minLength: 0,
delay: 0,
maxDisplay: this.options.maxItemDisplay,
open: function(event, ui) {
var widget = $(this).autocomplete("widget");
widget.position({
my: "left top",
at: "left bottom",
of: self.container
}).width(self.container.width()-4);
},
source: function(request, response) {
if (self.activeSearch) {
self.searchTerm = request.term.substring(self.beginFrom);
if (request.term.substring(self.beginFrom - 1, self.beginFrom) != "#") {
self.activeSearch = false;
self.beginFrom = 0;
self.searchTerm = "";
}
if (self.searchTerm != "") {
if ($.type(self.options.source) == "function") {
self.options.source(request, response);
} else {
var re = new RegExp("^" + escape(self.searchTerm) + ".+", "i");
var matches = [];
$.each(self.options.source, function() {
if (this.label.match(re)) {
matches.push(this);
}
});
response(matches);
}
}
}
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function(event, ui) {
self.activeSearch = false;
//console.log("#"+searchTerm, ui.item.label);
this.value = this.value.replace("#" + self.searchTerm, ui.item.label) + ' ';
self.highlight.html(
self.highlight.html()
.replace("#" + self.searchTerm,
$("<div>").append(
self.highlightWrapper
.text(ui.item.label)
.clone()
).html()+' ')
);
self.meta.val((self.meta.val() + " #[" + ui.item.value + ":]").trim());
return false;
}
});
}
});
body, html {
font-family: "lucida grande",tahoma,verdana,arial,sans-serif;
}
.ui-tagging {
position: relative;
border: 1px solid #B4BBCD;
height: auto;
}
.ui-tagging .ui-tagging-highlight {
position: absolute;
padding: 5px;
overflow: hidden;
}
.ui-tagging .ui-tagging-highlight div {
color: transparent;
font-size: 13px;
line-height: 18px;
white-space: pre-wrap;
}
.ui-tagging .ui-tagging-wrap {
position: relative;
padding: 5px;
overflow: hidden;
zoom: 1;
border: 0;
}
.ui-tagging div > span {
background-color: #D8DFEA;
font-weight: normal !important;
}
.ui-tagging textarea {
display: block;
font-family: "lucida grande",tahoma,verdana,arial,sans-serif;
background: transparent;
border-width: 0;
font-size: 13px;
height: 18px;
outline: none;
resize: none;
vertical-align: top;
width: 100%;
line-height: 18px;
overflow: hidden;
}
.ui-autocomplete {
font-size: 13px;
background-color: white;
border: 1px solid black;
margin-bottom: -5px;
width: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<textarea></textarea>
http://jsfiddle.net/mekwall/mcWnL/52/
This link will help you
I could not find any solution that matched my requirements perfectly, so I ended up with the following:
I use the jQuery keypress() event to check for the user pressing the # character.
If this is the case, a modal dialog is shown using jQuery UI. This dialog contains an autocomplete text field (many options can be used here, but I recommmend jQuery Tokeninput)
When the user selects an option in the dialog, a tag is added to the text field and the dialog is closed.
This is not the most elegant solution, but it works and it does not require extra keypresses compared to my original design.
Edit
So basically, we have our large text box where the user can enter text. He should be able to "tag" a user (this just means inserting #<userid> in the text). I attach to the jQuery keyup event and detect the # character using (e.which == 64) to show a modal with a text field for selecting the users to tag.
The meat of the solution is simply this modal dialog with a jQuery Tokeninput text box. As the user types here, the list of users is loaded through AJAX. See the examples on the website for how to use it properly. When the user closes the dialog, I insert the selected IDs into the large text box.
Recently i had to face this problem and this is how i nailed down...
Get the string index at the cursor position in the textarea by using selectionStart
slice the string from index 0 to the cursor position
Insert it into a span (since span has multiple border boxes)
Get the dimensions of the border box using element.getClientRects() relative to the view port. (here is the MDN Reference)
Calculate the top and left and feed it to the dropdown
This works in all latest browsers. haven't tested at old ones
Here is Working bin
Another plugin which provides similar functionality:
AutoSuggest
You can use it with custom triggers or you can use it without any triggers. Works with input fields, textareas and contenteditables. And jQuery is not a dependency.
I would recommend the textcomplete plugin. No jQuery dependency. You may need bootstrap.css to refer, but I recommend to write your own CSS, lighter and simple.
Follow the below steps to give it a try
npm install #textcomplete/core #textcomplete/textarea
Bind it to your input element
const editor = new TextareaEditor(inputEl);
const textcomplete = new Textcomplete(editor, strategy, options);
Set strategy(how to fetch suggestion list) and options(settings to configure the suggestions) according to your need.
JS version
Angular Version
This small extension seems to be the closest at least in presentation to what was asked. Since it's small, it can be easily understood and modified. http://lookapanda.github.io/jquery-hashtags/
THIS should work. With regards to the # kicking off the search, just add (dynamically or not) the symbol to the beginning of each possible search term.