Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add element hiding utility for snapshots #112

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
16 changes: 16 additions & 0 deletions src/common/components/reader-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import ContextMenu from './context-menu';
import LabelOverlay from './overlay/label-overlay';
import PasswordOverlay from './overlay/password-overlay';
import PrintOverlay from './overlay/print-overlay';
import HideElementPopup from "./view/hide-element-popup";


function View(props) {
Expand All @@ -26,6 +27,14 @@ function View(props) {
props.onChangeFindState(primary, params);
}

function handleHideElementClose() {
props.onToggleHideElementPopup({ primary, open: false });
}

function handleHideElementCommit(element) {
props.onHideElement(primary, element);
}

function handleFindNext() {
props.onFindNext(primary);
}
Expand Down Expand Up @@ -77,6 +86,13 @@ function View(props) {
onOpenLink={props.onOpenLink}
/>
}
{state[name + 'ViewHideElementPopup'] && !state.readOnly &&
<HideElementPopup
params={state[name + 'ViewHideElementPopup']}
onClose={handleHideElementClose}
onCommit={handleHideElementCommit}
/>
}
</div>
);
}
Expand Down
37 changes: 37 additions & 0 deletions src/common/components/view/hide-element-popup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';

function HideElementPopup(props) {
let { params, onClose, onCommit } = props;
let text;

if (params.selectedElement && params.clicked) {
let selectedElementTag = '<' + params.selectedElement.tagName.toLowerCase() + '>';
text = `Selected ${selectedElementTag} element.`;
}
else {
text = 'Click an element on the page to hide it.';
}

return (
<div className="hide-element-popup">
<div className="hide-element-status">{text}</div>
<div className="hide-element-buttons">
<button
className="toolbarButton"
onClick={() => onClose(null)}
>
Cancel
</button>
<button
className="toolbarButton"
onClick={() => onCommit(params.selectedElement)}
hidden={!params.selectedElement || !params.clicked}
>
Hide
</button>
</div>
</div>
);
}

export default HideElementPopup;
6 changes: 5 additions & 1 deletion src/common/context-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ export function createViewContextMenu(reader, params) {
label: reader._getString('general.copy'),
disabled: !reader.canCopy,
onCommand: () => reader.copy()
}
},
reader._type === 'snapshot' && {
label: reader._getString('pdfReader.hideElement'),
onCommand: () => reader.toggleHideElementPopup({ open: true })
},
],
[
{
Expand Down
55 changes: 53 additions & 2 deletions src/common/reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Reader {
this._onDeletePages = options.onDeletePages;
// Only used on Zotero client, sets text/plain and text/html values from Note Markdown and Note HTML translators
this._onSetDataTransferAnnotations = options.onSetDataTransferAnnotations;
this._onUpdateSnapshotHTML = options.onUpdateSnapshotHTML;

this._localizedStrings = options.localizedStrings;

Expand Down Expand Up @@ -159,6 +160,7 @@ class Reader {
entireWord: false,
result: null,
},
primaryViewHideElementPopup: null,
secondaryViewState: null,
secondaryViewStats: {},
secondaryViewAnnotationPopup: null,
Expand All @@ -172,7 +174,8 @@ class Reader {
caseSensitive: false,
entireWord: false,
result: null
}
},
secondaryViewHideElementPopup: null,
};

if (options.secondaryViewState) {
Expand Down Expand Up @@ -261,6 +264,9 @@ class Reader {
onFindPrevious={this.findPrevious.bind(this)}
onToggleFindPopup={this.toggleFindPopup.bind(this)}

onToggleHideElementPopup={this.toggleHideElementPopup.bind(this)}
onHideElement={this._handleHideElement.bind(this)}

ref={this._readerRef}
/>
</ReaderContext.Provider>
Expand Down Expand Up @@ -364,6 +370,15 @@ class Reader {
this._secondaryView?.setFindState(this._state.secondaryViewFindState);
}

if (this._type === 'snapshot') {
if (this._state.primaryViewHideElementPopup !== previousState.primaryViewHideElementPopup) {
this._primaryView?.setHideElementPopup(this._state.primaryViewHideElementPopup);
}
if (this._state.secondaryViewHideElementPopup !== previousState.secondaryViewHideElementPopup) {
this._secondaryView?.setHideElementPopup(this._state.secondaryViewHideElementPopup);
}
}

if (this._type === 'epub' && this._state.fontFamily !== previousState.fontFamily) {
this._primaryView?.setFontFamily(this._state.fontFamily);
this._secondaryView?.setFontFamily(this._state.fontFamily);
Expand Down Expand Up @@ -584,6 +599,28 @@ class Reader {
this._updateState({ [key]: findState });
}

toggleHideElementPopup({ primary, open } = {}) {
this._ensureType('snapshot');
if (primary === undefined) {
primary = this._lastViewPrimary;
}
let key = primary ? 'primaryViewHideElementPopup' : 'secondaryViewHideElementPopup';
if (open === undefined) {
open = !this._state[key];
}
if (open) {
this._updateState({ [key]: {} });
}
else {
this._updateState({ [key]: null });
}
}

_handleHideElement(primary, element) {
this._updateState({ [primary ? 'primaryViewHideElementPopup' : 'secondaryViewHideElementPopup']: null });
(primary ? this._primaryView : this._secondaryView).hideElement(element);
}

_sidebarScrollAnnotationIntoViev(id) {
this._readerRef.current.sidebarScrollAnnotationIntoView(id);
}
Expand Down Expand Up @@ -773,8 +810,21 @@ class Reader {
fontFamily: this._state.fontFamily
});
} else if (this._type === 'snapshot') {
let onSetHideElementPopup = (hideElementPopup) => {
this._updateState({ [primary ? 'primaryViewHideElementPopup' : 'secondaryViewHideElementPopup']: hideElementPopup });
};

let onUpdateSnapshotHTML = async (html) => {
let data = await this._onUpdateSnapshotHTML(html);
if (data) {
this.reload(data);
}
};

view = new SnapshotView({
...common
...common,
onSetHideElementPopup,
onUpdateSnapshotHTML
});
}

Expand Down Expand Up @@ -1101,6 +1151,7 @@ class Reader {
reload(data) {
this._data = data;
this._primaryViewContainer.replaceChildren();
this._primaryView = null; // Prevent reusing data from old _primaryView
this._primaryView = this._createView(true);
if (this._state.splitType) {
this._secondaryViewContainer.replaceChildren();
Expand Down
20 changes: 20 additions & 0 deletions src/common/stylesheets/components/_hide-element-popup.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.hide-element-popup {
display: flex;
flex-direction: column;
gap: 10px;

position: absolute;
right: 15px;
bottom: 15px;
width: 200px;
padding: 15px;
z-index: $z-index-findbar;
background-color: $findbar-bg;
border-radius: $findbar-border-radius;
box-shadow: $findbar-box-shadow;

.hide-element-buttons {
display: flex;
justify-content: flex-end;
}
}
1 change: 1 addition & 0 deletions src/common/stylesheets/darwin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ $mode: "production" !default;
@import "components/overlay";
@import "components/outline-view";
@import "components/thumbnails-view";
@import "components/hide-element-popup";

6 changes: 5 additions & 1 deletion src/dom/common/dom-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -278,12 +278,16 @@ abstract class DOMView<State extends DOMViewState, Data> {
this._options.onSetOverlayPopup();
}

protected get _shouldRenderAnnotations() {
return this._showAnnotations;
}

protected _renderAnnotations() {
if (!this._iframeDocument) {
return;
}
let container = this._iframeDocument.body.querySelector(':scope > #annotation-overlay');
if (!this._showAnnotations) {
if (!this._shouldRenderAnnotations) {
if (container) {
container.remove();
}
Expand Down
73 changes: 70 additions & 3 deletions src/dom/snapshot/snapshot-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
textPositionToRange
} from "../common/lib/selector";
import DOMView, {
DOMViewOptions,
DOMViewState,
NavigateOptions
} from "../common/dom-view";
Expand All @@ -35,6 +36,8 @@ import {
import contentCSS from '!!raw-loader!./stylesheets/content.css';

class SnapshotView extends DOMView<SnapshotViewState, SnapshotViewData> {
protected override readonly _options: SnapshotViewOptions;

private readonly _navStack = new NavStack<[number, number]>();

protected _find: DefaultFindProcessor | null = null;
Expand All @@ -43,6 +46,13 @@ class SnapshotView extends DOMView<SnapshotViewState, SnapshotViewData> {

private _scale!: number;

private _hideElementPopup?: HideElementPopupParams;

constructor(options: SnapshotViewOptions) {
super(options);
this._options = options; // Make the compiler happy
}

protected async _getSrcDoc() {
if (this._options.data.srcDoc) {
return this._options.data.srcDoc;
Expand Down Expand Up @@ -81,15 +91,19 @@ class SnapshotView extends DOMView<SnapshotViewState, SnapshotViewData> {
noscript.remove();
}

let doctype = doc.doctype ? new XMLSerializer().serializeToString(doc.doctype) : '';
let html = doc.documentElement.outerHTML;
return doctype + html;
return this._getDocumentHTML(doc);
}
else {
throw new Error('buf, url, or srcDoc is required');
}
}

_getDocumentHTML(doc: Document) {
let doctype = doc.doctype ? new XMLSerializer().serializeToString(doc.doctype) : '';
let html = doc.documentElement.outerHTML;
return doctype + html;
}

getData() {
return {
srcDoc: this._iframe.srcdoc,
Expand Down Expand Up @@ -275,6 +289,10 @@ class SnapshotView extends DOMView<SnapshotViewState, SnapshotViewData> {
}
}

protected override get _shouldRenderAnnotations() {
return !this._hideElementPopup && super._shouldRenderAnnotations;
}

private _getSearchContext() {
if (!this._searchContext) {
this._searchContext = createSearchContext(getVisibleTextNodes(this._iframeDocument.body));
Expand Down Expand Up @@ -350,6 +368,28 @@ class SnapshotView extends DOMView<SnapshotViewState, SnapshotViewData> {
this._updateViewState();
}

protected override _handlePointerOver(event: PointerEvent) {
if (this._hideElementPopup && !this._hideElementPopup.clicked) {
this._options.onSetHideElementPopup({ selectedElement: event.target as Element });
return;
}

super._handlePointerOver(event);
}

protected override _handlePointerDown(event: PointerEvent) {
if (this._hideElementPopup && this._hideElementPopup.selectedElement && !this._hideElementPopup.clicked) {
event.preventDefault();
this._options.onSetHideElementPopup({
...this._hideElementPopup,
clicked: true
});
return;
}

super._handlePointerDown(event);
}

// ***
// Setters that get called once there are changes in reader._state
// ***
Expand Down Expand Up @@ -387,6 +427,23 @@ class SnapshotView extends DOMView<SnapshotViewState, SnapshotViewData> {
}
}

setHideElementPopup(params?: HideElementPopupParams) {
if (this._hideElementPopup?.selectedElement) {
this._hideElementPopup.selectedElement.classList.remove('hide-element-current-target');
}
if (params?.selectedElement) {
params.selectedElement.classList.add('hide-element-current-target');
}
this._hideElementPopup = params;
this._renderAnnotations();
}

hideElement(element: Element) {
this._options.onSetHideElementPopup();
element.classList.add('hide-element-was-hidden');
this._options.onUpdateSnapshotHTML(this._getDocumentHTML(this._iframeDocument));
}

// ***
// Public methods to control the view from the outside
// ***
Expand Down Expand Up @@ -482,6 +539,16 @@ class SnapshotView extends DOMView<SnapshotViewState, SnapshotViewData> {
}
}

export interface HideElementPopupParams {
selectedElement?: Element;
clicked?: boolean;
}

export interface SnapshotViewOptions extends DOMViewOptions<SnapshotViewState, SnapshotViewData> {
onSetHideElementPopup: (params?: HideElementPopupParams) => void;
onUpdateSnapshotHTML: (html: string) => void;
}

export interface SnapshotViewState extends DOMViewState {
scrollYPercent?: number;
}
Expand Down
Loading