Merge pull request #196 from toasted-nutbread/vertical-text-popup-position
Vertical text popup position
This commit is contained in:
commit
3c9f7ba152
@ -199,6 +199,10 @@ function optionsSetDefaults(options) {
|
|||||||
popupHeight: 250,
|
popupHeight: 250,
|
||||||
popupHorizontalOffset: 0,
|
popupHorizontalOffset: 0,
|
||||||
popupVerticalOffset: 10,
|
popupVerticalOffset: 10,
|
||||||
|
popupHorizontalOffset2: 10,
|
||||||
|
popupVerticalOffset2: 0,
|
||||||
|
popupHorizontalTextPosition: 'below',
|
||||||
|
popupVerticalTextPosition: 'before',
|
||||||
showGuide: true,
|
showGuide: true,
|
||||||
compactTags: false,
|
compactTags: false,
|
||||||
compactGlossaries: false,
|
compactGlossaries: false,
|
||||||
|
@ -32,10 +32,14 @@ async function formRead() {
|
|||||||
optionsNew.general.showAdvanced = $('#show-advanced-options').prop('checked');
|
optionsNew.general.showAdvanced = $('#show-advanced-options').prop('checked');
|
||||||
optionsNew.general.maxResults = parseInt($('#max-displayed-results').val(), 10);
|
optionsNew.general.maxResults = parseInt($('#max-displayed-results').val(), 10);
|
||||||
optionsNew.general.popupDisplayMode = $('#popup-display-mode').val();
|
optionsNew.general.popupDisplayMode = $('#popup-display-mode').val();
|
||||||
|
optionsNew.general.popupHorizontalTextPosition = $('#popup-horizontal-text-position').val();
|
||||||
|
optionsNew.general.popupVerticalTextPosition = $('#popup-vertical-text-position').val();
|
||||||
optionsNew.general.popupWidth = parseInt($('#popup-width').val(), 10);
|
optionsNew.general.popupWidth = parseInt($('#popup-width').val(), 10);
|
||||||
optionsNew.general.popupHeight = parseInt($('#popup-height').val(), 10);
|
optionsNew.general.popupHeight = parseInt($('#popup-height').val(), 10);
|
||||||
optionsNew.general.popupHorizontalOffset = parseInt($('#popup-horizontal-offset').val(), 0);
|
optionsNew.general.popupHorizontalOffset = parseInt($('#popup-horizontal-offset').val(), 0);
|
||||||
optionsNew.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10);
|
optionsNew.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10);
|
||||||
|
optionsNew.general.popupHorizontalOffset2 = parseInt($('#popup-horizontal-offset2').val(), 0);
|
||||||
|
optionsNew.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10);
|
||||||
optionsNew.general.customPopupCss = $('#custom-popup-css').val();
|
optionsNew.general.customPopupCss = $('#custom-popup-css').val();
|
||||||
|
|
||||||
optionsNew.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked');
|
optionsNew.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked');
|
||||||
@ -168,10 +172,14 @@ async function onReady() {
|
|||||||
$('#show-advanced-options').prop('checked', options.general.showAdvanced);
|
$('#show-advanced-options').prop('checked', options.general.showAdvanced);
|
||||||
$('#max-displayed-results').val(options.general.maxResults);
|
$('#max-displayed-results').val(options.general.maxResults);
|
||||||
$('#popup-display-mode').val(options.general.popupDisplayMode);
|
$('#popup-display-mode').val(options.general.popupDisplayMode);
|
||||||
|
$('#popup-horizontal-text-position').val(options.general.popupHorizontalTextPosition);
|
||||||
|
$('#popup-vertical-text-position').val(options.general.popupVerticalTextPosition);
|
||||||
$('#popup-width').val(options.general.popupWidth);
|
$('#popup-width').val(options.general.popupWidth);
|
||||||
$('#popup-height').val(options.general.popupHeight);
|
$('#popup-height').val(options.general.popupHeight);
|
||||||
$('#popup-horizontal-offset').val(options.general.popupHorizontalOffset);
|
$('#popup-horizontal-offset').val(options.general.popupHorizontalOffset);
|
||||||
$('#popup-vertical-offset').val(options.general.popupVerticalOffset);
|
$('#popup-vertical-offset').val(options.general.popupVerticalOffset);
|
||||||
|
$('#popup-horizontal-offset2').val(options.general.popupHorizontalOffset2);
|
||||||
|
$('#popup-vertical-offset2').val(options.general.popupVerticalOffset2);
|
||||||
$('#custom-popup-css').val(options.general.customPopupCss);
|
$('#custom-popup-css').val(options.general.customPopupCss);
|
||||||
|
|
||||||
$('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse);
|
$('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse);
|
||||||
|
@ -107,6 +107,28 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<label for="popup-display-mode">Popup position for horizontal text</label>
|
||||||
|
<select class="form-control" id="popup-horizontal-text-position">
|
||||||
|
<option value="below">Below text</option>
|
||||||
|
<option value="above">Above text</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6">
|
||||||
|
<label for="popup-display-mode">Popup position for vertical text</label>
|
||||||
|
<select class="form-control" id="popup-vertical-text-position">
|
||||||
|
<option value="default">Same as for horizontal text</option>
|
||||||
|
<option value="before">Before text reading direction</option>
|
||||||
|
<option value="after">After text reading direction</option>
|
||||||
|
<option value="left">Left of text</option>
|
||||||
|
<option value="right">Right of text</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group options-advanced">
|
<div class="form-group options-advanced">
|
||||||
<label for="audio-playback-volume">Audio playback volume (percent)</label>
|
<label for="audio-playback-volume">Audio playback volume (percent)</label>
|
||||||
<input type="number" min="0" max="100" id="audio-playback-volume" class="form-control">
|
<input type="number" min="0" max="100" id="audio-playback-volume" class="form-control">
|
||||||
@ -133,6 +155,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group options-advanced">
|
||||||
|
<label>Popup offset for vertical text (horizontal, vertical; in pixels)</label>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-6"><input type="number" min="0" id="popup-horizontal-offset2" class="form-control"></div>
|
||||||
|
<div class="col-xs-6"><input type="number" min="0" id="popup-vertical-offset2" class="form-control"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group options-advanced">
|
<div class="form-group options-advanced">
|
||||||
<label for="custom-popup-css">Custom popup CSS</label>
|
<label for="custom-popup-css">Custom popup CSS</label>
|
||||||
<div><textarea autocomplete="off" spellcheck="false" wrap="soft" id="custom-popup-css" class="form-control"></textarea></div>
|
<div><textarea autocomplete="off" spellcheck="false" wrap="soft" id="custom-popup-css" class="form-control"></textarea></div>
|
||||||
|
@ -26,6 +26,7 @@ iframe#yomichan-float {
|
|||||||
resize: both;
|
resize: both;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
z-index: 2147483647;
|
z-index: 2147483647;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe#yomichan-float.yomichan-float-full-width {
|
iframe#yomichan-float.yomichan-float-full-width {
|
||||||
|
@ -301,7 +301,11 @@ class Frontend {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (window.yomichan_orphaned) {
|
if (window.yomichan_orphaned) {
|
||||||
if (textSource && this.options.scanning.modifier !== 'none') {
|
if (textSource && this.options.scanning.modifier !== 'none') {
|
||||||
this.popup.showOrphaned(textSource.getRect(), this.options);
|
this.popup.showOrphaned(
|
||||||
|
textSource.getRect(),
|
||||||
|
textSource.getWritingMode(),
|
||||||
|
this.options
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.onError(e);
|
this.onError(e);
|
||||||
@ -333,6 +337,7 @@ class Frontend {
|
|||||||
const url = window.location.href;
|
const url = window.location.href;
|
||||||
this.popup.termsShow(
|
this.popup.termsShow(
|
||||||
textSource.getRect(),
|
textSource.getRect(),
|
||||||
|
textSource.getWritingMode(),
|
||||||
definitions,
|
definitions,
|
||||||
this.options,
|
this.options,
|
||||||
{sentence, url, focus}
|
{sentence, url, focus}
|
||||||
@ -358,6 +363,7 @@ class Frontend {
|
|||||||
const url = window.location.href;
|
const url = window.location.href;
|
||||||
this.popup.kanjiShow(
|
this.popup.kanjiShow(
|
||||||
textSource.getRect(),
|
textSource.getRect(),
|
||||||
|
textSource.getWritingMode(),
|
||||||
definitions,
|
definitions,
|
||||||
this.options,
|
this.options,
|
||||||
{sentence, url, focus}
|
{sentence, url, focus}
|
||||||
|
@ -48,59 +48,132 @@ class Popup {
|
|||||||
return this.injected;
|
return this.injected;
|
||||||
}
|
}
|
||||||
|
|
||||||
async show(elementRect, options) {
|
async show(elementRect, writingMode, options) {
|
||||||
await this.inject(options);
|
await this.inject(options);
|
||||||
|
|
||||||
const containerStyle = window.getComputedStyle(this.container);
|
const optionsGeneral = options.general;
|
||||||
const containerHeight = parseInt(containerStyle.height);
|
const container = this.container;
|
||||||
const containerWidth = parseInt(containerStyle.width);
|
const containerRect = container.getBoundingClientRect();
|
||||||
|
const getPosition = (
|
||||||
|
writingMode === 'horizontal-tb' || optionsGeneral.popupVerticalTextPosition === 'default' ?
|
||||||
|
Popup.getPositionForHorizontalText :
|
||||||
|
Popup.getPositionForVerticalText
|
||||||
|
);
|
||||||
|
|
||||||
const limitX = document.body.clientWidth;
|
const [x, y, width, height, below] = getPosition(
|
||||||
const limitY = window.innerHeight;
|
elementRect,
|
||||||
|
Math.max(containerRect.width, optionsGeneral.popupWidth),
|
||||||
|
Math.max(containerRect.height, optionsGeneral.popupHeight),
|
||||||
|
document.body.clientWidth,
|
||||||
|
window.innerHeight,
|
||||||
|
optionsGeneral,
|
||||||
|
writingMode
|
||||||
|
);
|
||||||
|
|
||||||
let x = elementRect.left + options.general.popupHorizontalOffset;
|
container.classList.toggle('yomichan-float-full-width', optionsGeneral.popupDisplayMode === 'full-width');
|
||||||
let width = Math.max(containerWidth, options.general.popupWidth);
|
container.classList.toggle('yomichan-float-above', !below);
|
||||||
const overflowX = Math.max(x + width - limitX, 0);
|
container.style.left = `${x}px`;
|
||||||
|
container.style.top = `${y}px`;
|
||||||
|
container.style.width = `${width}px`;
|
||||||
|
container.style.height = `${height}px`;
|
||||||
|
container.style.visibility = 'visible';
|
||||||
|
}
|
||||||
|
|
||||||
|
static getPositionForHorizontalText(elementRect, width, height, maxWidth, maxHeight, optionsGeneral) {
|
||||||
|
let x = elementRect.left + optionsGeneral.popupHorizontalOffset;
|
||||||
|
const overflowX = Math.max(x + width - maxWidth, 0);
|
||||||
if (overflowX > 0) {
|
if (overflowX > 0) {
|
||||||
if (x >= overflowX) {
|
if (x >= overflowX) {
|
||||||
x -= overflowX;
|
x -= overflowX;
|
||||||
} else {
|
} else {
|
||||||
width = limitX;
|
width = maxWidth;
|
||||||
x = 0;
|
x = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let above = false;
|
const preferBelow = (optionsGeneral.popupHorizontalTextPosition === 'below');
|
||||||
let y = 0;
|
|
||||||
let height = Math.max(containerHeight, options.general.popupHeight);
|
|
||||||
const yBelow = elementRect.bottom + options.general.popupVerticalOffset;
|
|
||||||
const yAbove = elementRect.top - options.general.popupVerticalOffset;
|
|
||||||
const overflowBelow = Math.max(yBelow + height - limitY, 0);
|
|
||||||
const overflowAbove = Math.max(height - yAbove, 0);
|
|
||||||
if (overflowBelow > 0 || overflowAbove > 0) {
|
|
||||||
if (overflowBelow < overflowAbove) {
|
|
||||||
height = Math.max(height - overflowBelow, 0);
|
|
||||||
y = yBelow;
|
|
||||||
} else {
|
|
||||||
height = Math.max(height - overflowAbove, 0);
|
|
||||||
y = Math.max(yAbove - height, 0);
|
|
||||||
above = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
y = yBelow;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.container.classList.toggle('yomichan-float-full-width', options.general.popupDisplayMode === 'full-width');
|
const verticalOffset = optionsGeneral.popupVerticalOffset;
|
||||||
this.container.classList.toggle('yomichan-float-above', above);
|
const [y, h, below] = Popup.limitGeometry(
|
||||||
this.container.style.left = `${x}px`;
|
elementRect.top - verticalOffset,
|
||||||
this.container.style.top = `${y}px`;
|
elementRect.bottom + verticalOffset,
|
||||||
this.container.style.width = `${width}px`;
|
height,
|
||||||
this.container.style.height = `${height}px`;
|
maxHeight,
|
||||||
this.container.style.visibility = 'visible';
|
preferBelow
|
||||||
|
);
|
||||||
|
|
||||||
|
return [x, y, width, h, below];
|
||||||
}
|
}
|
||||||
|
|
||||||
async showOrphaned(elementRect, options) {
|
static getPositionForVerticalText(elementRect, width, height, maxWidth, maxHeight, optionsGeneral, writingMode) {
|
||||||
await this.show(elementRect, options);
|
const preferRight = Popup.isVerticalTextPopupOnRight(optionsGeneral.popupVerticalTextPosition, writingMode);
|
||||||
|
const horizontalOffset = optionsGeneral.popupHorizontalOffset2;
|
||||||
|
const verticalOffset = optionsGeneral.popupVerticalOffset2;
|
||||||
|
|
||||||
|
const [x, w] = Popup.limitGeometry(
|
||||||
|
elementRect.left - horizontalOffset,
|
||||||
|
elementRect.right + horizontalOffset,
|
||||||
|
width,
|
||||||
|
maxWidth,
|
||||||
|
preferRight
|
||||||
|
);
|
||||||
|
const [y, h, below] = Popup.limitGeometry(
|
||||||
|
elementRect.bottom - verticalOffset,
|
||||||
|
elementRect.top + verticalOffset,
|
||||||
|
height,
|
||||||
|
maxHeight,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
return [x, y, w, h, below];
|
||||||
|
}
|
||||||
|
|
||||||
|
static isVerticalTextPopupOnRight(positionPreference, writingMode) {
|
||||||
|
switch (positionPreference) {
|
||||||
|
case 'before':
|
||||||
|
return !Popup.isWritingModeLeftToRight(writingMode);
|
||||||
|
case 'after':
|
||||||
|
return Popup.isWritingModeLeftToRight(writingMode);
|
||||||
|
case 'left':
|
||||||
|
return false;
|
||||||
|
case 'right':
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static isWritingModeLeftToRight(writingMode) {
|
||||||
|
switch (writingMode) {
|
||||||
|
case 'vertical-lr':
|
||||||
|
case 'sideways-lr':
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static limitGeometry(positionBefore, positionAfter, size, limit, preferAfter) {
|
||||||
|
let after = preferAfter;
|
||||||
|
let position = 0;
|
||||||
|
const overflowBefore = Math.max(0, size - positionBefore);
|
||||||
|
const overflowAfter = Math.max(0, positionAfter + size - limit);
|
||||||
|
if (overflowAfter > 0 || overflowBefore > 0) {
|
||||||
|
if (overflowAfter < overflowBefore) {
|
||||||
|
size = Math.max(0, size - overflowAfter);
|
||||||
|
position = positionAfter;
|
||||||
|
after = true;
|
||||||
|
} else {
|
||||||
|
size = Math.max(0, size - overflowBefore);
|
||||||
|
position = Math.max(0, positionBefore - size);
|
||||||
|
after = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
position = preferAfter ? positionAfter : positionBefore - size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [position, size, after];
|
||||||
|
}
|
||||||
|
|
||||||
|
async showOrphaned(elementRect, writingMode, options) {
|
||||||
|
await this.show(elementRect, writingMode, options);
|
||||||
this.invokeApi('orphaned');
|
this.invokeApi('orphaned');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,13 +209,13 @@ class Popup {
|
|||||||
return contained;
|
return contained;
|
||||||
}
|
}
|
||||||
|
|
||||||
async termsShow(elementRect, definitions, options, context) {
|
async termsShow(elementRect, writingMode, definitions, options, context) {
|
||||||
await this.show(elementRect, options);
|
await this.show(elementRect, writingMode, options);
|
||||||
this.invokeApi('termsShow', {definitions, options, context});
|
this.invokeApi('termsShow', {definitions, options, context});
|
||||||
}
|
}
|
||||||
|
|
||||||
async kanjiShow(elementRect, definitions, options, context) {
|
async kanjiShow(elementRect, writingMode, definitions, options, context) {
|
||||||
await this.show(elementRect, options);
|
await this.show(elementRect, writingMode, options);
|
||||||
this.invokeApi('kanjiShow', {definitions, options, context});
|
this.invokeApi('kanjiShow', {definitions, options, context});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,6 +68,10 @@ class TextSourceRange {
|
|||||||
return this.range.getBoundingClientRect();
|
return this.range.getBoundingClientRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWritingMode() {
|
||||||
|
return TextSourceRange.getElementWritingMode(TextSourceRange.getParentElement(this.range.startContainer));
|
||||||
|
}
|
||||||
|
|
||||||
getPaddedRect() {
|
getPaddedRect() {
|
||||||
const range = this.range.cloneRange();
|
const range = this.range.cloneRange();
|
||||||
const startOffset = range.startOffset;
|
const startOffset = range.startOffset;
|
||||||
@ -211,6 +215,23 @@ class TextSourceRange {
|
|||||||
|
|
||||||
return state.remainder > 0;
|
return state.remainder > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getParentElement(node) {
|
||||||
|
while (node !== null && node.nodeType !== Node.ELEMENT_NODE) {
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getElementWritingMode(element) {
|
||||||
|
if (element === null) {
|
||||||
|
return 'horizontal-tb';
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = window.getComputedStyle(element);
|
||||||
|
const writingMode = style.writingMode;
|
||||||
|
return typeof writingMode === 'string' ? writingMode : 'horizontal-tb';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -278,6 +299,10 @@ class TextSourceElement {
|
|||||||
return this.element.getBoundingClientRect();
|
return this.element.getBoundingClientRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWritingMode() {
|
||||||
|
return 'horizontal-tb';
|
||||||
|
}
|
||||||
|
|
||||||
select() {
|
select() {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user