Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 55 additions & 30 deletions src/scripts/modules/LinkFormatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* mediator.exec('format:link'); // Remove link if already a link, otherwise show link toolbar flyout.
* mediator.request('format:link:active'); // returns true if selection is in or wraps a link.
*/

import Module from '../core/Module';
import commands from '../utils/commands';
import DOM from '../utils/DOM';
Expand All @@ -20,12 +20,12 @@ import inputFormTemplate from '../../templates/inputForm.html';
import linkDisplayTemplate from '../../templates/linkDisplay.html';

import inputFormStyles from '../../styles/inputForm.scss';
import linkDisplayStyles from '../../styles/linkDisplay.scss';

const LinkFormatter = Module({
name: 'LinkFormatter',
props: {
urlRegex: /[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/,
styles: null,
currentAnchor: null,
active: false,
hasMouse: false,
Expand Down Expand Up @@ -66,8 +66,8 @@ const LinkFormatter = Module({
},

appendStyles () {
const { props } = this;
props.styles = DOM.addStyles(inputFormStyles);
DOM.addStyles(inputFormStyles);
DOM.addStyles(linkDisplayStyles);
},

formatLink () {
Expand Down Expand Up @@ -121,6 +121,7 @@ const LinkFormatter = Module({

hideFlyout () {
const { props } = this;

props.hideTimeout = setTimeout(() => {
if (!this.isActive() && props.hasRendered) {
this.destroy();
Expand Down Expand Up @@ -151,12 +152,12 @@ const LinkFormatter = Module({

compileLinkDisplay (data) {
const wrapperEl = document.createElement('div');
let inputFormHTML = linkDisplayTemplate(data);
let linkDisplayHTML = linkDisplayTemplate(data);

if (typeof inputFormHTML === 'string') {
wrapperEl.innerHTML = inputFormHTML;
if (typeof linkDisplayHTML === 'string') {
wrapperEl.innerHTML = linkDisplayHTML;
} else {
wrapperEl.appendChild(inputFormHTML[0]);
wrapperEl.appendChild(linkDisplayHTML[0]);
}

return wrapperEl.childNodes[0];
Expand All @@ -180,15 +181,19 @@ const LinkFormatter = Module({

positionFlyout (opts) {
const { mediator, props } = this;
const { initialEvent, targetEl } = props;
let targetBounds, elStyles, elLineHeight, lineCount, lineStepHeight;
const { targetEl } = props;
let targetBounds;

if (targetEl) {
targetBounds = targetEl.getBoundingClientRect();
elStyles = window.getComputedStyle(targetEl);
elLineHeight = parseInt(elStyles.getPropertyValue('line-height'), 10);
lineCount = Math.ceil(targetBounds.height / elLineHeight);
lineStepHeight = targetBounds.height / lineCount;

// See reason below - Fred
// elStyles = window.getComputedStyle(targetEl);
// elLineHeight = elStyles.getPropertyValue('line-height');
// elLineHeight = elLineHeight === 'normal' ? elStyles.getPropertyValue('font-size') : elStyles.getPropertyValue('line-height');
// elLineHeight = parseInt(elLineHeight, 10);
// lineCount = Math.ceil(targetBounds.height / elLineHeight);
// lineStepHeight = targetBounds.height / lineCount;
} else {
targetBounds = mediator.get('selection:bounds');
}
Expand All @@ -197,21 +202,29 @@ const LinkFormatter = Module({
const scrollOffset = DOM.getScrollOffset();
let docRelTop, docRelCenter;

if (initialEvent) {
const topDiff = initialEvent.clientY - targetBounds.top;

docRelTop = initialEvent.clientY;
docRelCenter = initialEvent.clientX;

if (opts.flyoutPlacement === 'below') {
docRelTop = targetBounds.top + (lineStepHeight * Math.ceil(topDiff / lineStepHeight));
} else {
docRelTop = targetBounds.top + (lineStepHeight * Math.floor(topDiff / lineStepHeight));
}
} else {
docRelTop = (opts.flyoutPlacement === 'below' ? targetBounds.bottom : targetBounds.top);
docRelCenter = targetBounds.width / 2 + targetBounds.left + scrollOffset.x;
}
// Commenting this out because it is trying to do something smart (position
// the flyout close to the user's cursor when they hover over a link)
// but isn't particularly stable. And the alternative of positioning it underneath
// is acceptable and stable. Leaving this here in case the alternative
// proves to be a pain.
// - Fred
//
// const { initialEvent } = props;
// if (false && initialEvent) {
// const topDiff = initialEvent.clientY - targetBounds.top;
//
// docRelTop = initialEvent.clientY;
// docRelCenter = initialEvent.clientX;
//
// if (opts.flyoutPlacement === 'below') {
// docRelTop = targetBounds.top + (lineStepHeight * Math.ceil(topDiff / lineStepHeight));
// } else {
// docRelTop = targetBounds.top + (lineStepHeight * Math.floor(topDiff / lineStepHeight));
// }
// } else {
docRelTop = (opts.flyoutPlacement === 'below' ? targetBounds.bottom : targetBounds.top);
docRelCenter = targetBounds.width / 2 + targetBounds.left + scrollOffset.x;
// }

docRelTop += scrollOffset.y;

Expand Down Expand Up @@ -259,7 +272,9 @@ const LinkFormatter = Module({

handleClick (evnt) {
const { mediator, props } = this;
if (evnt.target.nodeName === 'A') {
const anchor = DOM.getClosest(evnt.target, 'a');

if (anchor && anchor.classList.contains('typester-link-edit')) {
evnt.preventDefault();
mediator.exec('selection:wrap:element', props.currentAnchor, { silent: true });
this.showLinkFormFlyout({ value: props.currentAnchor.href });
Expand Down Expand Up @@ -366,14 +381,24 @@ const LinkFormatter = Module({

destroy () {
const { props, mediator } = this;
const selectionAnchorNode = mediator.get('selection:anchornode');

if (props.flyout) {
this.unbindInput();
props.flyout.remove();
}

props.showing = null;
props.hasMouse = false;
props.hasRendered = null;
props.targetEl = null;

if (selectionAnchorNode) {
selectionAnchorNode.parentElement.normalize();
}

mediator.exec('selection:select:remove:pseudo');

}
}
});
Expand Down
11 changes: 9 additions & 2 deletions src/scripts/modules/Selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
* 'selection:in:or:contains': 'inOrContains',
* 'selection:range:coordinates': 'rangeCoordinates',
* 'selection:contains:node': 'containsNode',
* 'selection:spans:multiple:blocks': 'spansMultipleBlocks'
* 'selection:spans:multiple:blocks': 'spansMultipleBlocks',
* 'selection:pseudo': 'getPseudo'
* },
*
* commands: {
Expand Down Expand Up @@ -86,7 +87,8 @@ const Selection = Module({
'selection:in:or:contains': 'inOrContains',
'selection:range:coordinates': 'rangeCoordinates',
'selection:contains:node': 'containsNode',
'selection:spans:multiple:blocks': 'spansMultipleBlocks'
'selection:spans:multiple:blocks': 'spansMultipleBlocks',
'selection:pseudo': 'getPseudo'
},

commands: {
Expand Down Expand Up @@ -450,6 +452,11 @@ const Selection = Module({
}
},

getPseudo () {
const { props } = this;
return props.pseudoSelection;
},

removePseudo () {
const { props } = this;
let unwrappedNodes = [];
Expand Down
6 changes: 5 additions & 1 deletion src/scripts/utils/DOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,14 @@ const DOM = {
return null;
}

while (node.nodeType !== Node.ELEMENT_NODE) {
while (node && node.nodeType !== Node.ELEMENT_NODE) {
node = node.parentNode;
}

if (!node) {
return null;
}

switch (checkType) {
case 'attribute':
attrName = selector.match(/\[(.*?)\]/)[1];
Expand Down
9 changes: 2 additions & 7 deletions src/styles/inputForm.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@
}
}

.typester-link-display {
a {
display: block;
cursor: pointer;
line-height: 20px;
padding: 10px;
}
.typester-pseudo-selection {
background: #CCC;
}
35 changes: 35 additions & 0 deletions src/styles/linkDisplay.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.typester-link-display {
display: flex;

a {
display: block;
cursor: pointer;
line-height: 20px;
padding: 10px;
}

a[href] {
text-decoration: none;

&:hover {
text-decoration: underline;
}
}

.typester-link-edit {
display: block;
height: 20px;
text-align: center;

svg {
display: block;
width: 20px;
height: 20px;
fill: #FFF;
}

&:hover {
background: rgb(0, 174, 239);
}
}
}
7 changes: 6 additions & 1 deletion src/templates/linkDisplay.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
<div class='typester-link-display'>
<a>{{ href }}</a>
<a href='{{ href }}' target='_blank'>{{ href }}</a>
<a class='typester-link-edit'>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="768" height="768" viewBox="0 0 768 768">
<path d="M725 189.667l-71.5 71.501-146.666-146.666 71.501-71.501c14.666-14.666 40.334-14.666 55 0l91.666 91.666c14.666 14.666 14.666 40.334 0 55.001zM31.999 589.334l432.667-432.667 146.666 146.666-432.667 432.667h-146.666v-146.666z"></path>
</svg>
</a>
</div>
8 changes: 5 additions & 3 deletions test/server/html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,17 +77,19 @@ <h1>Typester test server</h1>
Sed porttitor lectus nibh. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Pellentesque in ipsum id orci porta dapibus. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec velit neque, auctor sit amet aliquam vel, ullamcorper sit amet ligula.
</p>
<p>
Praesent sapien massa, convallis a pellentesque nec, egestas non nisi. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Vivamus suscipit tortor eget felis porttitor volutpat. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Praesent sapien massa, <a href='https://typecode.com'>convallis a pellentesque nec</a>, egestas non nisi. Mauris blandit aliquet elit, eget tincidunt nibh pulvinar a. Vestibulum ac diam sit amet quam vehicula elementum sed sit amet dui. Vivamus suscipit tortor eget felis porttitor volutpat. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
<p>
Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Cras ultricies ligula sed magna dictum porta. Donec sollicitudin molestie malesuada. Pellentesque in ipsum id orci porta dapibus. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi.
<a href='https://typecode.com'>
Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Cras ultricies ligula sed magna dictum porta. Donec sollicitudin molestie malesuada. Pellentesque in ipsum id orci porta dapibus. Praesent sapien massa, convallis a pellentesque nec, egestas non nisi.
</a>
</p>
<p>
Nulla quis lorem ut libero malesuada feugiat. Donec sollicitudin molestie malesuada. Curabitur arcu erat, accumsan id imperdiet et, porttitor at sem. Sed porttitor lectus nibh. Donec sollicitudin molestie malesuada.
</p>
<ul>
<li>
Sed porttitor lectus nibh.
<a href='https://typecode.com'>Sed porttitor lectus nibh.</a>
</li>
<li>
Donec rutrum congue leo eget malesuada.
Expand Down