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
31 changes: 23 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@internetarchive/icon-magnify-minus": "^1.4.0",
"@internetarchive/icon-magnify-plus": "^1.4.0",
"@internetarchive/icon-search": "^1.4.0",
"@internetarchive/icon-share": "1.4.0",
"@internetarchive/icon-toc": "^1.4.0",
"@internetarchive/icon-visual-adjustment": "^1.4.0",
"@internetarchive/modal-manager": "2.0.5",
Expand Down
19 changes: 17 additions & 2 deletions src/BookReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -1970,10 +1970,25 @@ BookReader.prototype.queryStringFromParams = function(
if (params.search && urlMode === 'history') {
newParams.set('q', params.search);
}

let textFragmentParam = '';
// Need to pull out text separately to avoid the spaces becoming encoded as +, which
// the browser seems not to handle with the text fragment
if (newParams.get('text')) {
newParams.delete('text');
textFragmentParam = `text=${this.urlPlugin.retrieveTextFragment(currQueryString)}`;
}

// https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/toString
// Note: This method returns the query string without the question mark.
const result = newParams.toString();
return result ? '?' + result : '';
let result = newParams.toString();
if (textFragmentParam) {
if (result) result += '&';
result += textFragmentParam;
}
if (result) result = '?' + result;

return result;
};

/**
Expand Down
4 changes: 1 addition & 3 deletions src/BookReader/Mode2UpLit.js
Original file line number Diff line number Diff line change
Expand Up @@ -628,9 +628,7 @@ export class Mode2UpLit extends LitElement {
progression == 'lr' ? [nextSpread.left, nextSpread.right] : [nextSpread.right, nextSpread.left]
).filter(x => x);
nextPageContainers.forEach(c => {
this.br.trigger('pageVisible', {
pageContainerEl: c.$container[0],
});
this.br.trigger('pageVisible', {pageContainerEl: c.$container[0]});
});
}

Expand Down
10 changes: 9 additions & 1 deletion src/BookReader/utils/SelectionObserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ export class SelectionObserver {
startedInSelector = false;
/** @type {HTMLElement} */
target = null;
/** @type {Node} */
lastKnownFocusNode = null;

/**
* @param {string} selector
* @param {function('started' | 'cleared', HTMLElement): any} handler
* @param {function('started' | 'cleared' | 'focusChanged', HTMLElement): any} handler
*/
constructor(selector, handler) {
this.selector = selector;
Expand All @@ -34,9 +36,15 @@ export class SelectionObserver {
if (!target) return;
this.target = target;
this.selecting = true;
this.lastKnownFocusNode = sel.focusNode;
this.handler('started', this.target);
}

if (this.selecting && (this.lastKnownFocusNode != sel.focusNode || sel.toString() && !sel.isCollapsed)) {
this.lastKnownFocusNode = sel.focusNode;
this.handler('focusChanged', this.target);
}

if (this.selecting && (sel.isCollapsed || !sel.toString() || !$(sel.anchorNode).closest(this.selector)[0])) {
this.selecting = false;
this.handler('cleared', this.target);
Expand Down
59 changes: 59 additions & 0 deletions src/css/_TextSelection.scss
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,63 @@

.BRtranslateLayer .BRparagraphElement.BRtranslateHidden {
display: none;
}

.br-select-menu__root {
display: none;
border-radius: 20px;
background-color: #333;
color-scheme: dark;
padding: 2px;
overflow: hidden;
transition: border-radius 0.2s;
}
.br-select-menu__root:hover {
border-radius: 8px;
}

.br-select-menu__root:hover .br-select-menu__option {
border-radius: 6px;
}

.br-select-menu__option {
--iconWidth: 15px;
--iconHeight: 15px;
--iconFillColor: currentColor;
background: transparent;
display: flex;
align-items: center;
border-radius: 20px;
font-family: inherit;
border: 0;
transition: background-color 0.2s, border-radius 0.2s;
cursor: pointer;
text-wrap: nowrap;
padding: 4px;
}

.br-select-menu__option:hover {
background-color: rgba(255, 255, 255, 0.1);
}

.br-select-menu__icon {
display: block;
flex-shrink: 0;
width: 17px;
height: 17px;
}

.br-select-menu__label {
width: 0px;
opacity: 0;
interpolate-size: allow-keywords;
transition: width 0.2s;
font-size: 12px;
}


.br-select-menu__root:hover .br-select-menu__label {
width: auto;
margin-left: 4px;
opacity: 1;
}
24 changes: 21 additions & 3 deletions src/plugins/plugin.experiments.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,27 @@ export class ExperimentsPlugin extends BookReaderPlugin {
localStorageKey: 'BrExperiments',

/** The experiments that should be shown in the experiments panel */
enabledExperiments: ['translate'],
enabledExperiments: ['translate', 'copyLinkToHighlight'],
}

/** @type {ExperimentModel[]} */
allExperiments = [
new class extends ExperimentModel {
name = 'copyLinkToHighlight';
title = 'Copy to Selection URL';
description = 'Share text selection via URL';
learnMore = 'none';
icon = null;
enabled = false;
async enable ({ manual = false }) {
this.br.plugins.textSelection.enableSelectionMenu();
}
async disable() {
sleep(0).then(() => {
window.location.reload();
});
}
}(),
new class extends ExperimentModel {
name = 'translate';
title = 'Translate Plugin';
Expand Down Expand Up @@ -123,7 +139,9 @@ export class ExperimentsPlugin extends BookReaderPlugin {
for (const experiment of this.allExperiments) {
// TODO: imagesBaseURL should be replaced with assetRoot everywhere
experiment.assetRoot = this.br.options.imagesBaseURL.replace(/images\/$/, '');
experiment.icon = experiment.buildAssetPath(experiment.icon);
if (experiment.icon) {
experiment.icon = experiment.buildAssetPath(experiment.icon);
}
experiment.br = this.br;
}

Expand Down Expand Up @@ -269,7 +287,7 @@ export class BrExperimentToggle extends LitElement {
return html`
<div class="experiment-card" style="margin-bottom: 10px;">
<div style="display: flex; align-items: center; gap: 10px;">
<img src="${this.icon}" style="width: 20px; height: 20px;" alt="" />
${this.icon ? html`<img src="${this.icon}" style="width: 20px; height: 20px;" alt="" />` : ''}
<div style="flex-grow: 1; font-weight: bold;">${this.title}</div>
</div>
<p style="opacity: 0.9">
Expand Down
17 changes: 17 additions & 0 deletions src/plugins/plugin.text_selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,21 @@ export class TextSelectionPlugin extends BookReaderPlugin {
init() {
if (!this.options.enabled) return;

this.br.on('pageVisible', (_, {pageContainerEl}) => {
if (pageContainerEl.querySelector('.BRtextLayer')) {
this.br.trigger('textLayerVisible', {pageContainerEl});
}
});

this.loadData();
this.textSelectionManager.init();
}

enableSelectionMenu() {
this.textSelectionManager.selectionMenuEnabled = true;
this.textSelectionManager.renderSelectionMenu();
}

/**
* @override
* @param {PageContainer} pageContainer
Expand Down Expand Up @@ -188,13 +199,19 @@ export class TextSelectionPlugin extends BookReaderPlugin {
paragEl.style.marginTop = `${newTop}px`;
yAdded += newTop;
textLayer.appendChild(paragEl);
textLayer.appendChild(document.createTextNode('\n'));
}
$container.append(textLayer);
this.textSelectionManager.stopPageFlip($container);
this.br.trigger('textLayerRendered', {
pageIndex,
pageContainer,
});

// Check if page is visible
if ($container.hasClass('BRpage-visible')) {
this.br.trigger('textLayerVisible', {pageContainerEl: $container[0]});
}
}

/**
Expand Down
26 changes: 23 additions & 3 deletions src/plugins/url/UrlPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,15 +161,15 @@ export class UrlPlugin {
* If it was changeed, update the urlState
*/
listenForHashChanges() {
this.oldLocationHash = window.location.hash.substr(1);
this.oldLocationHash = this.getHash();
if (this.urlLocationPollId) {
clearInterval(this.urlLocationPollId);
this.urlLocationPollId = null;
}

// check if the URL changes
const updateHash = () => {
const newFragment = window.location.hash.substr(1);
const newFragment = this.getHash();
const hasFragmentChange = newFragment != this.oldLocationHash;

if (!hasFragmentChange) { return; }
Expand All @@ -182,10 +182,30 @@ export class UrlPlugin {
/**
* Will read either the hash or URL and return the bookreader fragment
*/
pullFromAddressBar (location = window.location) {
pullFromAddressBar(location = window.location) {
const path = this.urlMode === 'history'
? (location.pathname.substr(this.urlHistoryBasePath.length) + location.search)
: location.hash.substr(1);
this.urlState = this.urlStringToUrlState(path);
}

/**
* Get the hash out of the current URL. Also augments it with the text
* from the main part of the URL, since that is not readable by JS
* from the actual hash
* @returns
*/
getHash() {
const text = this.retrieveTextFragment(window.location.search);
const textFragment = text ? `:~:text=${text[0]}` : '';
return `${window.location.hash.slice(1)}${textFragment}`;
}

/**
* @param {string} urlString
* @returns {string}
*/
retrieveTextFragment(urlString) {
return urlString.match(/(?<=[&?]?text=)[^&]*/);
}
}
Loading
Loading