Skip to content

Commit 0f61636

Browse files
authored
[Feature] Add ability for users to split their PDF into multiple OneNote pages (#273)
Adds a new option to PDF Mode that allows a user to split their PDF into individual OneNote pages. To facilitate this: 1. Move the PDF Clip Options to the mainController 2. Add some new Saveables, notably OneNoteSaveablePdfSynchronousBatched, which sends a bunch of createPage requests in series 3. Lots of little UI tweaks, such as moving the page number overlay in PDF preview to the top left corner from the middle of the page, and adding a spinner to indicate that a page is loading
1 parent b8527b6 commit 0f61636

File tree

70 files changed

+2475
-641
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+2475
-641
lines changed

THIRD-PARTY-NOTICES.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,30 @@ limitations under the License.
5353

5454
-------------------------------------------
5555

56+
popper.js
57+
58+
Copyright © 2016 Federico Zivolo and contributors
59+
60+
Permission is hereby granted, free of charge, to any person obtaining a
61+
copy of this software and associated documentation files (the “Software”),
62+
to deal in the Software without restriction, including without limitation
63+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
64+
and/or sell copies of the Software, and to permit persons to whom the
65+
Software is furnished to do so, subject to the following conditions:
66+
67+
The above copyright notice and this permission notice shall be included
68+
in all copies or substantial portions of the Software.
69+
70+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
71+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
72+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
73+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
74+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
75+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
76+
THE SOFTWARE.
77+
78+
-------------------------------------------
79+
5680
jpgjs
5781

5882
Copyright (C) 2011 notmasteryet

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "3.2.16",
2+
"version": "3.3.0",
33
"name": "webclipper",
44
"private": true,
55
"description": "The core of the OneNote Web Clipper found at https://www.onenote.com/clipper",
@@ -17,6 +17,7 @@
1717
"@types/lodash": "4.14.37",
1818
"@types/mithril": "0.0.31",
1919
"@types/pdf": "0.0.31",
20+
"@types/popper.js": "0.4.5",
2021
"@types/qunit": "1.16.30",
2122
"@types/rangy": "0.0.27",
2223
"@types/safari-extension": "0.0.27",
@@ -52,9 +53,10 @@
5253
"mithril": "0.2.5",
5354
"node-qunit-phantomjs": "1.4.0",
5455
"node-static": "0.7.9",
55-
"onenoteapi": "1.0.6",
56+
"onenoteapi": "1.1.0",
5657
"onenotepicker": "1.0.6",
5758
"pdfjs-dist": "1.6.319",
59+
"popper.js": "0.6.4",
5860
"qunitjs": "1.20.0",
5961
"rangy": "1.3.0",
6062
"run-sequence": "1.2.2",

src/images/warning.png

1.98 KB
Loading

src/scripts/arrayUtils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ export module ArrayUtils {
55
* items.
66
*/
77
export function createEvenBuckets(numItems: number, maxPerBucket: number): number[] {
8+
if (numItems < 1) {
9+
return [];
10+
}
11+
12+
if (maxPerBucket < 1) {
13+
throw new Error("maxPerBucket cannot be less than 1 but was: " + maxPerBucket);
14+
}
15+
816
if (numItems <= maxPerBucket) {
917
return [numItems];
1018
}
@@ -28,6 +36,10 @@ export module ArrayUtils {
2836
* Also retains the ordering of the items when partitions are flattened.
2937
*/
3038
export function partition(items: any[], maxPerBucket: number): any[][] {
39+
if (items.length === 0) {
40+
return [];
41+
}
42+
3143
let bucketCounts = ArrayUtils.createEvenBuckets(items.length, maxPerBucket);
3244
let partitions: any[][] = [];
3345
let sliceStart = 0;

src/scripts/clipperUI/animatedTooltip.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export class AnimatedTooltipClass extends ComponentBase<AnimatedTooltipState, An
5757
render() {
5858
// We have to make the renderablePanel undefined on the collapse for the vertical shrink animation to function correctly
5959
let renderablePanel = (
60-
<div className={Constants.Classes.heightAnimator} {...this.onElementDraw(this.onHeightAnimatorDraw) }>
60+
<div className={Constants.Classes.heightAnimator + " " + Constants.Classes.clearfix} {...this.onElementDraw(this.onHeightAnimatorDraw) }>
6161
{this.state.uiExpanded ? this.props.renderablePanel : undefined}
6262
</div>
6363
);

src/scripts/clipperUI/animations/expandFromRightAnimationStrategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class ExpandFromRightAnimationStrategy extends TransitioningAnimationStra
1919
private animationTimeoutId: number;
2020
private reverseChildAnimations: boolean;
2121

22-
constructor(options?: ExpandFromRightAnimationStrategyOptions) {
22+
constructor(options: ExpandFromRightAnimationStrategyOptions) {
2323
super(500 /* animationDuration */, options);
2424
this.animationTimeout = 100;
2525
this.reverseChildAnimations = true;

src/scripts/clipperUI/animations/fadeInAnimationStrategy.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {TransitioningAnimationStrategy, TransitioningAnimationStrategyOptions} f
88
* When transitioning out, elements fade away.
99
*/
1010
export class FadeInAnimationStrategy extends TransitioningAnimationStrategy<TransitioningAnimationStrategyOptions> {
11-
1211
constructor(options?: TransitioningAnimationStrategyOptions) {
1312
super(200 /* animationDuration */, options);
1413
}

src/scripts/clipperUI/animations/slideContentInFromTopAnimationStrategy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class SlideContentInFromTopAnimationStrategy extends TransitioningAnimati
3030
private animateOutSlideUpDelta: number;
3131
private contentToAnimate: ContentToAnimate[];
3232

33-
constructor(options?: SlideContentInFromTopAnimationStrategyOptions) {
33+
constructor(options: SlideContentInFromTopAnimationStrategyOptions) {
3434
super(undefined /* animationDuration */, options, options.currentAnimationState);
3535

3636
this.animateInDuration = 367;

src/scripts/clipperUI/animations/slideFromRightAnimationStrategy.ts

Lines changed: 0 additions & 63 deletions
This file was deleted.

src/scripts/clipperUI/animations/transitioningAnimationStrategy.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,11 @@ export abstract class TransitioningAnimationStrategy<TOptions extends Transition
5353
}
5454

5555
protected doAnimate(el: HTMLElement): Promise<void> {
56-
return new Promise<void>((resolve) => {
57-
if (this.options.extShouldAnimateIn() && this.intShouldAnimateIn(el)) {
58-
this.animateIn(el).then(() => {
59-
resolve();
60-
});
61-
} else if (this.options.extShouldAnimateOut() && this.intShouldAnimateOut(el)) {
62-
this.animateOut(el).then(() => {
63-
resolve();
64-
});
65-
}
66-
});
56+
if (this.options.extShouldAnimateIn() && this.intShouldAnimateIn(el)) {
57+
return this.animateIn(el);
58+
} else if (this.options.extShouldAnimateOut() && this.intShouldAnimateOut(el)) {
59+
return this.animateOut(el);
60+
}
6761
}
6862

6963
private animateIn(el: HTMLElement): Promise<void> {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export interface ClipSaveStatus {
2+
numItemsCompleted: number;
3+
numItemsTotal: number;
4+
}

src/scripts/clipperUI/clipper.tsx

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {ClientInfo} from "../clientInfo";
44
import {ClientType} from "../clientType";
55
import {Constants} from "../constants";
66
import {ObjectUtils} from "../objectUtils";
7+
78
import {PageInfo} from "../pageInfo";
89
import {Polyfills} from "../polyfills";
910
import {PreviewGlobalInfo, PreviewInfo} from "../previewInfo";
@@ -98,6 +99,12 @@ class ClipperClass extends ComponentBase<ClipperState, {}> {
9899
isLocalFileAndNotAllowed: true,
99100
selectedPageRange: "",
100101
shouldAttachPdf: false,
102+
shouldDistributePages: false,
103+
shouldShowPopover: false
104+
},
105+
clipSaveStatus: {
106+
numItemsTotal: undefined,
107+
numItemsCompleted: undefined
101108
},
102109

103110
reset: () => {
@@ -534,10 +541,6 @@ class ClipperClass extends ComponentBase<ClipperState, {}> {
534541
}
535542

536543
private getDefaultClipMode(): ClipMode {
537-
if (this.state && this.state.pageInfo && this.state.pageInfo.contentType === OneNoteApi.ContentType.EnhancedUrl) {
538-
return ClipMode.Pdf;
539-
}
540-
541544
if (this.state && this.state.invokeOptions) {
542545
switch (this.state.invokeOptions.invokeMode) {
543546
case InvokeMode.ContextImage:
@@ -553,11 +556,15 @@ class ClipperClass extends ComponentBase<ClipperState, {}> {
553556
}
554557
}
555558

556-
if (this.state && this.state.pageInfo && UrlUtils.onWhitelistedDomain(this.state.pageInfo.rawUrl)) {
557-
return ClipMode.Augmentation;
558-
} else {
559-
return ClipMode.FullPage;
559+
if (this.state && this.state.pageInfo) {
560+
if (UrlUtils.onWhitelistedDomain(this.state.pageInfo.rawUrl)) {
561+
return ClipMode.Augmentation;
562+
} else if (this.state.pageInfo.contentType === OneNoteApi.ContentType.EnhancedUrl) {
563+
return ClipMode.Pdf;
564+
}
560565
}
566+
567+
return ClipMode.FullPage;
561568
}
562569

563570
private updateFrameHeight(newContainerHeight: number) {
@@ -608,7 +615,7 @@ class ClipperClass extends ComponentBase<ClipperState, {}> {
608615
}});
609616
}
610617

611-
private handleSignOut(authType: string) {
618+
private handleSignOut(authType: string): void {
612619
this.state.setState(this.getSignOutState());
613620
Clipper.getExtensionCommunicator().callRemoteFunction(Constants.FunctionKeys.signOutUser, { param: AuthType[authType] });
614621

@@ -624,7 +631,7 @@ class ClipperClass extends ComponentBase<ClipperState, {}> {
624631
return signOutState;
625632
}
626633

627-
private handleStartClip() {
634+
private handleStartClip(): void {
628635
Clipper.logger.logUserFunnel(Log.Funnel.Label.ClipAttempted);
629636

630637
this.state.setState({ userResult: { status: Status.InProgress, data: this.state.userResult.data } });
@@ -661,11 +668,13 @@ class ClipperClass extends ComponentBase<ClipperState, {}> {
661668

662669
let clipEvent = new Log.Event.PromiseEvent(Log.Event.Label.ClipToOneNoteAction);
663670

664-
OneNoteSaveableFactory.getSaveable(this.state).then((saveable) => {
671+
(new OneNoteSaveableFactory(this.state)).getSaveable().then((saveable) => {
665672
let saveOptions: SaveToOneNoteOptions = {
666673
page: saveable,
667-
saveLocation: this.state.saveLocation
674+
saveLocation: this.state.saveLocation,
675+
progressCallback: this.updateClipSaveProgress.bind(this)
668676
};
677+
669678
let saveToOneNote = new SaveToOneNote(this.state.userResult.data.user.accessToken);
670679
saveToOneNote.save(saveOptions).then((responsePackage: OneNoteApi.ResponsePackage<any>) => {
671680
let createPageResponse = Array.isArray(responsePackage) ? responsePackage[0] : responsePackage;
@@ -687,6 +696,15 @@ class ClipperClass extends ComponentBase<ClipperState, {}> {
687696
});
688697
}
689698

699+
private updateClipSaveProgress(numItemsCompleted: number, numItemsTotal: number): void {
700+
this.state.setState({
701+
clipSaveStatus: {
702+
numItemsCompleted: numItemsCompleted,
703+
numItemsTotal: numItemsTotal
704+
}
705+
});
706+
}
707+
690708
private storeLastClippedInformation() {
691709
Clipper.storeValue(ClipperStorageKeys.lastClippedDate, Date.now().toString());
692710

src/scripts/clipperUI/clipperState.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {InvokeOptions} from "../extensions/invokeOptions";
1616

1717
import {ClipMode} from "./clipMode";
1818
import {Status} from "./status";
19+
import {ClipSaveStatus} from "./clipSaveStatus";
1920

2021
export interface DataResult<T> {
2122
data?: T;
@@ -61,6 +62,7 @@ export interface ClipperState {
6162

6263
// Save to OneNote status
6364
oneNoteApiResult?: DataResult<OneNoteApi.Page | OneNoteApi.RequestError>;
65+
clipSaveStatus?: ClipSaveStatus;
6466

6567
// Should be set when the Web Clipper enters a state that can not be recovered this session
6668
badState?: boolean;

src/scripts/clipperUI/clipperStateUtilities.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {StringUtils} from "../stringUtils";
2+
import {ObjectUtils} from "../objectUtils";
23

34
import {ClipMode} from "./clipMode";
45
import {ClipperState} from "./clipperState";
@@ -19,11 +20,13 @@ export module ClipperStateUtilities {
1920
return false;
2021
} else if (clipperState.pdfPreviewInfo.allPages) {
2122
return true;
23+
} else if (!clipperState.pdfPreviewInfo.allPages && ObjectUtils.isNullOrUndefined(clipperState.pdfPreviewInfo.selectedPageRange)) {
24+
return false;
2225
}
23-
// We know at this point that the status is succeeded and the user has a specific page range, so if
24-
// it is unparseable or empty, we disallow the clip
25-
let pages = StringUtils.parsePageRange(clipperState.pdfPreviewInfo.selectedPageRange, clipperState.pdfResult.data.get().pdf.numPages());
26-
return !!pages && pages.length > 0;
26+
27+
// If the user has an invalidPageRange, the clipButton is still enabled,
28+
// but when the user clips, we short circuit it and display a message instead
29+
return true;
2730
case ClipMode.FullPage:
2831
let fullPageScreenshotResult = clipperState.fullPageResult;
2932
return fullPageScreenshotResult.status === Status.Succeeded;

src/scripts/clipperUI/components/modeButtonSelector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,12 @@ class ModeButtonSelectorClass extends ComponentBase<{}, ClipperStateProp> {
115115

116116
return (
117117
<div style={Localization.getFontFamilyAsStyle(Localization.FontFamily.Semilight) }>
118-
{ this.getPdfModeButton(currentMode) }
119118
{ this.getFullPageModeButton(currentMode) }
120119
{ this.getRegionModeButton(currentMode) }
121120
{ this.getAugmentationModeButton(currentMode) }
122121
{ this.getSelectionModeButton(currentMode) }
123122
{ this.getBookmarkModeButton(currentMode) }
123+
{ this.getPdfModeButton(currentMode) }
124124
</div>
125125
);
126126
}

0 commit comments

Comments
 (0)