WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Conversation

@ArgoZhang
Copy link
Member

@ArgoZhang ArgoZhang commented Dec 3, 2025

Link issues

fixes #772

Summary By Copilot

Regression?

  • Yes
  • No

Risk

  • High
  • Medium
  • Low

Verification

  • Manual (required)
  • Automated

Packaging changes reviewed?

  • Yes
  • No
  • N/A

☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

  • Doc is updated/provided or not needed
  • Demo is updated/provided or not needed
  • Merge the latest code from the main branch

Summary by Sourcery

Add built-in client-side download support to the PdfReader toolbar using the existing PDF URL and simplify related component parameters.

New Features:

  • Enable the PdfReader download toolbar button to trigger a direct file download based on the configured PDF URL.

Enhancements:

  • Remove the PdfReader OnDownloadAsync callback parameter in favor of the built-in download behavior.
  • Clean up PdfReader client script by removing unnecessary metadata logging.

Copilot AI review requested due to automatic review settings December 3, 2025 02:55
@bb-auto bb-auto bot added the enhancement New feature or request label Dec 3, 2025
@bb-auto bb-auto bot added this to the v9.2.0 milestone Dec 3, 2025
@sourcery-ai
Copy link

sourcery-ai bot commented Dec 3, 2025

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adds built-in client-side download support to PdfReader by wiring the existing toolbar download button directly to a JavaScript handler that downloads the current PDF from its URL, while removing the previous .NET callback-based download hook and a debug log line.

Sequence diagram for PdfReader toolbar download interaction

sequenceDiagram
    actor User
    participant Browser
    participant PdfReaderRazor as PdfReader_razor
    participant PdfReaderJs as PdfReader_razor_js

    User->>Browser: Click bb_view_download button
    Browser->>PdfReaderJs: DOM click event on .bb_view_download
    PdfReaderJs->>PdfReaderJs: EventHandler.on toolbar click handler
    alt options.url is set
        PdfReaderJs->>PdfReaderJs: Read docTitle from .bb_view_subject
        PdfReaderJs->>PdfReaderJs: Create anchorElement
        PdfReaderJs->>PdfReaderJs: Set href to options.url
        PdfReaderJs->>PdfReaderJs: Set download to docTitle
        PdfReaderJs->>Browser: anchorElement.click()
        PdfReaderJs->>PdfReaderJs: Remove anchorElement
    else options.url is not set
        PdfReaderJs-->>User: No download started
    end
Loading

Updated class diagram for PdfReader download-related members

classDiagram
    class PdfReader {
        +string Id
        +bool ShowDownload
        +bool ShowPrint
        +string MoreButtonIcon
        +Task RotateRight()
        +Task RotateLeft()
        +Task Print()
        +Task ZoomIn()
        +Task ZoomOut()
        +Task FitPage()
        +Task FitWidth()
    }

    class PdfReaderDownloadBehaviorBefore {
        +Func~Task~ OnDownloadAsync
        -Task OnDownload()
    }

    PdfReader <|-- PdfReaderDownloadBehaviorBefore
Loading

File-Level Changes

Change Details Files
Wire the download toolbar button directly to a JS-based file download instead of a .NET callback.
  • Remove the OnDownloadAsync parameter and its backing OnDownload method from the PdfReader component code-behind.
  • Stop using Blazor @OnClick on the download icon so clicks are handled purely via JavaScript.
  • Add a toolbar click handler in JavaScript that creates a temporary anchor element pointing at the current PDF url and triggers a browser download with the document title as filename.
src/components/BootstrapBlazor.PdfReader/PdfReader.razor.cs
src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js
src/components/BootstrapBlazor.PdfReader/PdfReader.razor
Minor cleanup of PDF metadata loading logic.
  • Remove a console.log of metadata from the JavaScript metadata loading helper to avoid noisy logging in production.
src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js

Assessment against linked issues

Issue Objective Addressed Explanation
#772 Add built-in download support to PdfReader so that clicking the download button initiates downloading the currently viewed PDF.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Removing the OnDownloadAsync parameter is a breaking API change; consider either keeping it as an optional/obsolete path that delegates to the built-in behavior or documenting/messaging a clear migration path for consumers that relied on custom download logic.
  • In the toolbar click handler for .bb-view-download, add a null/empty check for .bb-view-subject and textContent and fall back to a safe default filename to avoid runtime errors or empty filenames when the title element is missing or not yet populated.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Removing the `OnDownloadAsync` parameter is a breaking API change; consider either keeping it as an optional/obsolete path that delegates to the built-in behavior or documenting/messaging a clear migration path for consumers that relied on custom download logic.
- In the toolbar click handler for `.bb-view-download`, add a null/empty check for `.bb-view-subject` and `textContent` and fall back to a safe default filename to avoid runtime errors or empty filenames when the title element is missing or not yet populated.

## Individual Comments

### Comment 1
<location> `src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js:440-447` </location>
<code_context>
             pdfViewer.spreadMode = 0;
         }
     });
+    EventHandler.on(toolbar, "click", ".bb-view-download", e => {
+        if (options.url) {
+            const docTitle = el.querySelector('.bb-view-subject');
+            const anchorElement = document.createElement('a');
+            anchorElement.href = options.url;
+            anchorElement.download = docTitle.textContent;
+            anchorElement.click();
+            anchorElement.remove();
+        }
+    });
</code_context>

<issue_to_address>
**issue (bug_risk):** Guard against missing `.bb-view-subject` element when deriving the download filename.

If `.bb-view-subject` is missing, `querySelector` returns `null` and `docTitle.textContent` will throw. Please handle the missing/empty case and fall back to a default filename, e.g. deriving it from `options.url` (such as `new URL(options.url).pathname.split('/').pop()`), before setting `anchorElement.download`.
</issue_to_address>

### Comment 2
<location> `src/components/BootstrapBlazor.PdfReader/PdfReader.razor.js:443-447` </location>
<code_context>
+    EventHandler.on(toolbar, "click", ".bb-view-download", e => {
+        if (options.url) {
+            const docTitle = el.querySelector('.bb-view-subject');
+            const anchorElement = document.createElement('a');
+            anchorElement.href = options.url;
+            anchorElement.download = docTitle.textContent;
+            anchorElement.click();
+            anchorElement.remove();
+        }
+    });
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Consider appending the temporary anchor to the DOM before triggering `click()` for better cross‑browser reliability.

Some browsers don’t reliably trigger `click()` on elements that aren’t in the DOM. To avoid compatibility issues, append the anchor to `document.body` before clicking and then remove it, e.g. `document.body.appendChild(anchorElement); anchorElement.click(); anchorElement.remove();`.

```suggestion
            const anchorElement = document.createElement('a');
            anchorElement.href = options.url;
            anchorElement.download = docTitle.textContent;
            document.body.appendChild(anchorElement);
            anchorElement.click();
            anchorElement.remove();
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@ArgoZhang ArgoZhang merged commit 3715d31 into master Dec 3, 2025
2 checks passed
@ArgoZhang ArgoZhang deleted the feat-pdf branch December 3, 2025 03:00
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements client-side PDF download functionality by replacing the previous callback-based approach with a direct JavaScript implementation. The change removes the C# OnDownloadAsync parameter and implements the download directly in the browser using a dynamically created anchor element.

Key Changes:

  • Removed OnDownloadAsync parameter and OnDownload() method from C# code
  • Added JavaScript event handler for .bb-view-download button that creates an anchor element with the PDF URL and triggers download
  • Cleaned up debug console.log statement from metadata loading function

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
PdfReader.razor.js Added click handler for download button that creates anchor element with download attribute; removed debug console.log
PdfReader.razor.cs Removed OnDownloadAsync parameter and OnDownload() callback method (breaking change)
PdfReader.razor Removed @onclick binding from download button div to use JavaScript event handler instead

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const docTitle = el.querySelector('.bb-view-subject');
const anchorElement = document.createElement('a');
anchorElement.href = options.url;
anchorElement.download = docTitle.textContent;
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docTitle element should be null-checked before accessing its textContent property. If the element doesn't exist, this will throw a runtime error. Consider adding a null check:

const docTitle = el.querySelector('.bb-view-subject');
if (docTitle && docTitle.textContent) {
    anchorElement.download = docTitle.textContent;
}
Suggested change
anchorElement.download = docTitle.textContent;
anchorElement.download = (docTitle && docTitle.textContent) ? docTitle.textContent : '';

Copilot uses AI. Check for mistakes.
Comment on lines +440 to +447
EventHandler.on(toolbar, "click", ".bb-view-download", e => {
if (options.url) {
const docTitle = el.querySelector('.bb-view-subject');
const anchorElement = document.createElement('a');
anchorElement.href = options.url;
anchorElement.download = docTitle.textContent;
anchorElement.click();
anchorElement.remove();
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The download implementation using anchor element with download attribute will not work for cross-origin URLs due to CORS restrictions. When the PDF URL is from a different origin, the browser will navigate to the URL instead of downloading it. This is different from the print functionality which uses an iframe. Consider:

  1. Documenting this limitation in the component documentation, or
  2. Implementing a server-side proxy download mechanism similar to how the previous OnDownloadAsync callback allowed, or
  3. Using a fetch + Blob approach for same-origin URLs:
if (options.url) {
    const docTitle = el.querySelector('.bb-view-subject');
    try {
        const response = await fetch(options.url);
        const blob = await response.blob();
        const url = window.URL.createObjectURL(blob);
        const anchorElement = document.createElement('a');
        anchorElement.href = url;
        anchorElement.download = docTitle?.textContent || 'document.pdf';
        anchorElement.click();
        window.URL.revokeObjectURL(url);
        anchorElement.remove();
    } catch (error) {
        // Fallback to direct link for cross-origin URLs
        const anchorElement = document.createElement('a');
        anchorElement.href = options.url;
        anchorElement.download = docTitle?.textContent || 'document.pdf';
        anchorElement.click();
        anchorElement.remove();
    }
}
Suggested change
EventHandler.on(toolbar, "click", ".bb-view-download", e => {
if (options.url) {
const docTitle = el.querySelector('.bb-view-subject');
const anchorElement = document.createElement('a');
anchorElement.href = options.url;
anchorElement.download = docTitle.textContent;
anchorElement.click();
anchorElement.remove();
EventHandler.on(toolbar, "click", ".bb-view-download", async e => {
if (options.url) {
const docTitle = el.querySelector('.bb-view-subject');
try {
const response = await fetch(options.url);
if (!response.ok) throw new Error("Network response was not ok");
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download = docTitle?.textContent || 'document.pdf';
document.body.appendChild(anchorElement);
anchorElement.click();
window.URL.revokeObjectURL(url);
anchorElement.remove();
} catch (error) {
// Fallback to direct link for cross-origin URLs
const anchorElement = document.createElement('a');
anchorElement.href = options.url;
anchorElement.download = docTitle?.textContent || 'document.pdf';
document.body.appendChild(anchorElement);
anchorElement.click();
anchorElement.remove();
}

Copilot uses AI. Check for mistakes.
Comment on lines 121 to 122
[Parameter]
public string? MoreButtonIcon { get; set; }
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the OnDownloadAsync parameter is a breaking API change that will affect existing consumers of this component. Users who were relying on this callback to implement custom download logic (e.g., logging, authentication checks, custom file sources) will no longer be able to do so.

Consider:

  1. Deprecating this parameter instead of removing it immediately, marking it as [Obsolete] in this version
  2. Supporting both the new client-side download and the callback-based approach, allowing the callback to override the default behavior
  3. Documenting this as a breaking change in the release notes

If this breaking change is intentional, ensure it's clearly documented in the changelog and migration guide.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(PdfReader): support download

2 participants