Improve popup position for vertical text
This commit is contained in:
parent
3bf8a9ab00
commit
68af0d86c3
@ -199,6 +199,9 @@ function optionsSetDefaults(options) {
|
||||
popupHeight: 250,
|
||||
popupHorizontalOffset: 0,
|
||||
popupVerticalOffset: 10,
|
||||
popupHorizontalOffset2: 10,
|
||||
popupVerticalOffset2: 0,
|
||||
popupVerticalTextPosition: 'before',
|
||||
showGuide: true,
|
||||
compactTags: false,
|
||||
compactGlossaries: false,
|
||||
|
@ -32,10 +32,13 @@ async function formRead() {
|
||||
optionsNew.general.showAdvanced = $('#show-advanced-options').prop('checked');
|
||||
optionsNew.general.maxResults = parseInt($('#max-displayed-results').val(), 10);
|
||||
optionsNew.general.popupDisplayMode = $('#popup-display-mode').val();
|
||||
optionsNew.general.popupVerticalTextPosition = $('#popup-vertical-text-position').val();
|
||||
optionsNew.general.popupWidth = parseInt($('#popup-width').val(), 10);
|
||||
optionsNew.general.popupHeight = parseInt($('#popup-height').val(), 10);
|
||||
optionsNew.general.popupHorizontalOffset = parseInt($('#popup-horizontal-offset').val(), 0);
|
||||
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.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked');
|
||||
@ -168,10 +171,13 @@ async function onReady() {
|
||||
$('#show-advanced-options').prop('checked', options.general.showAdvanced);
|
||||
$('#max-displayed-results').val(options.general.maxResults);
|
||||
$('#popup-display-mode').val(options.general.popupDisplayMode);
|
||||
$('#popup-vertical-text-position').val(options.general.popupVerticalTextPosition);
|
||||
$('#popup-width').val(options.general.popupWidth);
|
||||
$('#popup-height').val(options.general.popupHeight);
|
||||
$('#popup-horizontal-offset').val(options.general.popupHorizontalOffset);
|
||||
$('#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);
|
||||
|
||||
$('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse);
|
||||
|
@ -107,6 +107,17 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<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 class="form-group options-advanced">
|
||||
<label for="audio-playback-volume">Audio playback volume (percent)</label>
|
||||
<input type="number" min="0" max="100" id="audio-playback-volume" class="form-control">
|
||||
@ -133,6 +144,14 @@
|
||||
</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">
|
||||
<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>
|
||||
|
@ -26,6 +26,7 @@ iframe#yomichan-float {
|
||||
resize: both;
|
||||
visibility: hidden;
|
||||
z-index: 2147483647;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
iframe#yomichan-float.yomichan-float-full-width {
|
||||
|
@ -301,7 +301,11 @@ class Frontend {
|
||||
} catch (e) {
|
||||
if (window.yomichan_orphaned) {
|
||||
if (textSource && this.options.scanning.modifier !== 'none') {
|
||||
this.popup.showOrphaned(textSource.getRect(), this.options);
|
||||
this.popup.showOrphaned(
|
||||
textSource.getRect(),
|
||||
textSource.getWritingMode(),
|
||||
this.options
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.onError(e);
|
||||
@ -332,6 +336,7 @@ class Frontend {
|
||||
const url = window.location.href;
|
||||
this.popup.termsShow(
|
||||
textSource.getRect(),
|
||||
textSource.getWritingMode(),
|
||||
definitions,
|
||||
this.options,
|
||||
{sentence, url, focus}
|
||||
@ -357,6 +362,7 @@ class Frontend {
|
||||
const url = window.location.href;
|
||||
this.popup.kanjiShow(
|
||||
textSource.getRect(),
|
||||
textSource.getWritingMode(),
|
||||
definitions,
|
||||
this.options,
|
||||
{sentence, url, focus}
|
||||
|
@ -48,59 +48,130 @@ class Popup {
|
||||
return this.injected;
|
||||
}
|
||||
|
||||
async show(elementRect, options) {
|
||||
async show(elementRect, writingMode, options) {
|
||||
await this.inject(options);
|
||||
|
||||
const containerStyle = window.getComputedStyle(this.container);
|
||||
const containerHeight = parseInt(containerStyle.height);
|
||||
const containerWidth = parseInt(containerStyle.width);
|
||||
const optionsGeneral = options.general;
|
||||
const container = this.container;
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const getPosition = (
|
||||
writingMode === 'horizontal-tb' || optionsGeneral.popupVerticalTextPosition === 'default' ?
|
||||
Popup.getPositionForHorizontalText :
|
||||
Popup.getPositionForVerticalText
|
||||
);
|
||||
|
||||
const limitX = document.body.clientWidth;
|
||||
const limitY = window.innerHeight;
|
||||
const [x, y, width, height, below] = getPosition(
|
||||
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;
|
||||
let width = Math.max(containerWidth, options.general.popupWidth);
|
||||
const overflowX = Math.max(x + width - limitX, 0);
|
||||
container.classList.toggle('yomichan-float-full-width', optionsGeneral.popupDisplayMode === 'full-width');
|
||||
container.classList.toggle('yomichan-float-above', !below);
|
||||
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 (x >= overflowX) {
|
||||
x -= overflowX;
|
||||
} else {
|
||||
width = limitX;
|
||||
width = maxWidth;
|
||||
x = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let above = false;
|
||||
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;
|
||||
}
|
||||
const verticalOffset = optionsGeneral.popupVerticalOffset;
|
||||
const [y, h, below] = Popup.limitGeometry(
|
||||
elementRect.top - verticalOffset,
|
||||
elementRect.bottom + verticalOffset,
|
||||
height,
|
||||
maxHeight,
|
||||
true
|
||||
);
|
||||
|
||||
this.container.classList.toggle('yomichan-float-full-width', options.general.popupDisplayMode === 'full-width');
|
||||
this.container.classList.toggle('yomichan-float-above', above);
|
||||
this.container.style.left = `${x}px`;
|
||||
this.container.style.top = `${y}px`;
|
||||
this.container.style.width = `${width}px`;
|
||||
this.container.style.height = `${height}px`;
|
||||
this.container.style.visibility = 'visible';
|
||||
return [x, y, width, h, below];
|
||||
}
|
||||
|
||||
async showOrphaned(elementRect, options) {
|
||||
await this.show(elementRect, options);
|
||||
static getPositionForVerticalText(elementRect, width, height, maxWidth, maxHeight, optionsGeneral, writingMode) {
|
||||
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');
|
||||
}
|
||||
|
||||
@ -136,13 +207,13 @@ class Popup {
|
||||
return contained;
|
||||
}
|
||||
|
||||
async termsShow(elementRect, definitions, options, context) {
|
||||
await this.show(elementRect, options);
|
||||
async termsShow(elementRect, writingMode, definitions, options, context) {
|
||||
await this.show(elementRect, writingMode, options);
|
||||
this.invokeApi('termsShow', {definitions, options, context});
|
||||
}
|
||||
|
||||
async kanjiShow(elementRect, definitions, options, context) {
|
||||
await this.show(elementRect, options);
|
||||
async kanjiShow(elementRect, writingMode, definitions, options, context) {
|
||||
await this.show(elementRect, writingMode, options);
|
||||
this.invokeApi('kanjiShow', {definitions, options, context});
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,10 @@ class TextSourceRange {
|
||||
return this.range.getBoundingClientRect();
|
||||
}
|
||||
|
||||
getWritingMode() {
|
||||
return TextSourceRange.getElementWritingMode(TextSourceRange.getParentElement(this.range.startContainer));
|
||||
}
|
||||
|
||||
getPaddedRect() {
|
||||
const range = this.range.cloneRange();
|
||||
const startOffset = range.startOffset;
|
||||
@ -204,6 +208,23 @@ class TextSourceRange {
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -267,6 +288,10 @@ class TextSourceElement {
|
||||
return this.element.getBoundingClientRect();
|
||||
}
|
||||
|
||||
getWritingMode() {
|
||||
return 'horizontal-tb';
|
||||
}
|
||||
|
||||
select() {
|
||||
// NOP
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user