2020-10-07 22:31:28 +00:00
|
|
|
/*
|
2022-02-03 01:43:10 +00:00
|
|
|
* Copyright (C) 2016-2022 Yomichan Authors
|
2020-10-07 22:31:28 +00:00
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2022-08-20 18:32:34 +00:00
|
|
|
/* global
|
|
|
|
* StringUtil
|
|
|
|
*/
|
|
|
|
|
2020-10-07 22:31:28 +00:00
|
|
|
class TextSourceElement {
|
|
|
|
constructor(element, fullContent=null, startOffset=0, endOffset=0) {
|
|
|
|
this._element = element;
|
|
|
|
this._fullContent = (typeof fullContent === 'string' ? fullContent : TextSourceElement.getElementContent(element));
|
|
|
|
this._startOffset = startOffset;
|
|
|
|
this._endOffset = endOffset;
|
|
|
|
this._content = this._fullContent.substring(this._startOffset, this._endOffset);
|
|
|
|
}
|
|
|
|
|
2021-09-27 23:07:28 +00:00
|
|
|
get type() {
|
|
|
|
return 'element';
|
|
|
|
}
|
|
|
|
|
2020-10-07 22:31:28 +00:00
|
|
|
get element() {
|
|
|
|
return this._element;
|
|
|
|
}
|
|
|
|
|
|
|
|
get fullContent() {
|
|
|
|
return this._fullContent;
|
|
|
|
}
|
|
|
|
|
|
|
|
get startOffset() {
|
|
|
|
return this._startOffset;
|
|
|
|
}
|
|
|
|
|
|
|
|
get endOffset() {
|
|
|
|
return this._endOffset;
|
|
|
|
}
|
|
|
|
|
2021-01-17 02:50:50 +00:00
|
|
|
get isConnected() {
|
|
|
|
return this._element.isConnected;
|
|
|
|
}
|
|
|
|
|
2020-10-07 22:31:28 +00:00
|
|
|
clone() {
|
|
|
|
return new TextSourceElement(this._element, this._fullContent, this._startOffset, this._endOffset);
|
|
|
|
}
|
|
|
|
|
|
|
|
cleanup() {
|
|
|
|
// NOP
|
|
|
|
}
|
|
|
|
|
|
|
|
text() {
|
|
|
|
return this._content;
|
|
|
|
}
|
|
|
|
|
2022-08-20 16:38:55 +00:00
|
|
|
setEndOffset(length, _layoutAwareScan, fromEnd) {
|
2022-08-20 18:32:34 +00:00
|
|
|
const offset = fromEnd ? this._endOffset : this._startOffset;
|
|
|
|
length = Math.min(this._fullContent.length - offset, length);
|
|
|
|
if (length > 0) {
|
|
|
|
length = StringUtil.readCodePointsForward(this._fullContent, offset, length).length;
|
2020-10-07 22:31:28 +00:00
|
|
|
}
|
2022-08-20 18:32:34 +00:00
|
|
|
this._endOffset = offset + length;
|
|
|
|
this._content = this._fullContent.substring(this._startOffset, this._endOffset);
|
|
|
|
return length;
|
2020-10-07 22:31:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
setStartOffset(length) {
|
2022-08-20 18:32:34 +00:00
|
|
|
length = Math.min(this._startOffset, length);
|
|
|
|
if (length > 0) {
|
|
|
|
length = StringUtil.readCodePointsBackward(this._fullContent, this._startOffset - 1, length).length;
|
|
|
|
}
|
|
|
|
this._startOffset -= length;
|
2020-10-07 22:31:28 +00:00
|
|
|
this._content = this._fullContent.substring(this._startOffset, this._endOffset);
|
2022-08-20 18:32:34 +00:00
|
|
|
return length;
|
2020-10-07 22:31:28 +00:00
|
|
|
}
|
|
|
|
|
2021-01-17 02:50:50 +00:00
|
|
|
collapse(toStart) {
|
|
|
|
if (toStart) {
|
|
|
|
this._endOffset = this._startOffset;
|
|
|
|
} else {
|
|
|
|
this._startOffset = this._endOffset;
|
|
|
|
}
|
|
|
|
this._content = '';
|
|
|
|
}
|
|
|
|
|
2020-10-07 22:31:28 +00:00
|
|
|
getRect() {
|
|
|
|
return this._element.getBoundingClientRect();
|
|
|
|
}
|
|
|
|
|
2022-05-17 01:45:22 +00:00
|
|
|
getRects() {
|
2022-08-20 15:14:46 +00:00
|
|
|
return this._element.getClientRects();
|
2022-05-17 01:45:22 +00:00
|
|
|
}
|
|
|
|
|
2020-10-07 22:31:28 +00:00
|
|
|
getWritingMode() {
|
|
|
|
return 'horizontal-tb';
|
|
|
|
}
|
|
|
|
|
|
|
|
select() {
|
|
|
|
// NOP
|
|
|
|
}
|
|
|
|
|
|
|
|
deselect() {
|
|
|
|
// NOP
|
|
|
|
}
|
|
|
|
|
2020-10-21 00:54:26 +00:00
|
|
|
hasSameStart(other) {
|
2020-10-07 22:31:28 +00:00
|
|
|
return (
|
|
|
|
typeof other === 'object' &&
|
|
|
|
other !== null &&
|
|
|
|
other instanceof TextSourceElement &&
|
|
|
|
this._element === other.element &&
|
|
|
|
this._fullContent === other.fullContent &&
|
|
|
|
this._startOffset === other.startOffset
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-10-21 00:54:26 +00:00
|
|
|
getNodesInRange() {
|
2021-01-21 02:35:09 +00:00
|
|
|
return [this._element];
|
2020-10-21 00:54:26 +00:00
|
|
|
}
|
|
|
|
|
2020-10-07 22:31:28 +00:00
|
|
|
static getElementContent(element) {
|
|
|
|
let content;
|
|
|
|
switch (element.nodeName.toUpperCase()) {
|
|
|
|
case 'BUTTON':
|
|
|
|
content = element.textContent;
|
|
|
|
break;
|
|
|
|
case 'IMG':
|
|
|
|
content = element.getAttribute('alt') || '';
|
|
|
|
break;
|
2021-02-28 18:26:23 +00:00
|
|
|
case 'SELECT':
|
|
|
|
{
|
|
|
|
const {selectedIndex, options} = element;
|
|
|
|
const option = (selectedIndex >= 0 && selectedIndex < options.length ? options[selectedIndex] : null);
|
|
|
|
content = (option !== null ? option.textContent : '');
|
|
|
|
}
|
|
|
|
break;
|
2020-10-07 22:31:28 +00:00
|
|
|
default:
|
|
|
|
content = `${element.value}`;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-03-02 03:45:03 +00:00
|
|
|
// Remove zero-width space and zero-width non-joiner
|
|
|
|
content = content.replace(/[\u200b\u200c]/g, '');
|
2020-10-07 22:31:28 +00:00
|
|
|
|
|
|
|
return content;
|
|
|
|
}
|
|
|
|
}
|