Repository navigation
feat: pdf download - #7918
feat: pdf download#7918
Conversation
| </el-card> | ||
|
|
||
| <template #footer> | ||
| <div class="flex-align-center w-full gap-2 border-t pt-2"> |
There was a problem hiding this comment.
Here is the code review with identified irregularities, potential issues, and optimization suggestions:
1. Critical Logic/Performance Issue: Repeated trim() Calls
- Issue:
paragraph.document_name?.trim()is called 6 times in the template (and once in the script). This is inefficient and bad practice. Ifdocument_nameis long, this creates unnecessary function call overhead. - Fix: Compute it once or use a local variable/constant. However, since this is in a
v-forloop (implied byparagraph), you cannot create a top-level constant. The best Vue 3 approach is to ensure the data is clean before entering the template, or accept the minor overhead if strictly necessary. Ideally, trim in the backend or when fetching data. If not possible, at least reduce redundancy where feasible.
2. Accessibility (a11y) Regression
- Issue: The previous implementation used an
<a>tag, which is semantically correct for links/downloads and accessible via keyboard/keyboard navigation (tabindex). The new<el-card @click="...">is a<div>under the hood. It is not keyboard-accessible by default. Users using keyboards cannot trigger the action. - Fix: Add
@keydown.enter="openSourceDocument(...)",@keydown.space="...",tabindex="0", androle="button"to theel-card. Or, keep<a>and style it like a card.
3. Security: URL Validation Weakness
- Issue: The regex
/^https?:\/\//i.test(sourceUrl.trim())only checks the prefix. It does not validate if the URL is safe (e.g., blocksjavascript:or malformed URIs that might bypass simple prefix checks in edge cases). While better than nothing,URL.canParse()(if available) or a more robust validation is preferred. Also,.trim()is called twice onsourceUrl. - Optimization: Validate once and store safely. Consider whitelisting domains if applicable.
4. Potential Bug: Missing fileId vs sourceUrl Priority
- Issue: In
openSourceDocument, if bothfileIdandsourceUrlare present, it only downloads the file. Is this intended? Previously, the<a>tag prioritizedsource_file_idtoo, so behavior is consistent, but ensure this matches business logic. - Note: No change needed if intentional.
5. Code Quality & Readability
- Suggestion: The
getFileIconUrlcall inside the template is heavy. While not easily avoidable without refactoring the component structure, be aware that it runs on every render. - Suggestion: The
openSourceDocumentfunction mixes concerns (download logic + window.open logic). Keep it as is for now, but consider separating UI event handling from business logic if the component grows.
✅ Recommended Refactored Code Template Section:
<!-- In <script setup> -->
// Add a helper to reduce trim calls if possible, but primarily fix a11y in template.
// In <template>
<el-card
v-if="paragraph.document_name?.trim()"
shadow="never"
class="small cursor-pointer mt-2 min-w-0 focus-visible:ring-2 focus-visible:ring-blue-500"
role="button"
tabindex="0"
:aria-label="`Open ${paragraph.document_name.trim()}`"
@click="handleOpenSource(paragraph)"
@keydown.enter.prevent="handleOpenSource(paragraph)"
@keydown.space.prevent="handleOpenSource(paragraph)"
>
<div class="flex-align-center gap-2">
<img
:src="getFileIconUrl(
paragraph.document_name.trim(),
paragraph.knowledge_type,
paragraph.document_resource_type === DOCUMENT_RESOURCE_TYPE.IMAGE,
)"
alt=""
class="size-6 shrink-0"
/>
<!-- Use computed property or v-text if name is very long -->
<span class="min-w-0 truncate" :title="paragraph.document_name.trim()">
{{ paragraph.document_name.trim() }}
</span>
</div>
</el-card>✅ Recommended Refactored Script Function:
/**
* Opens source document.
* Prioritizes file download if fileId exists, otherwise opens URL.
*/
function handleOpenSource(paragraph: SearchParagraph) {
const name = paragraph.document_name?.trim() || '';
if (!name) return; // Safety check, though v-if should prevent clicking empty names
const fileId = paragraph.meta?.source_file_id;
const sourceUrl = paragraph.meta?.source_url?.trim();
if (fileId) {
const url = getFileUrl(fileId);
if (url) {
downloadByURL(url, name);
}
} else if (sourceUrl) {
/**
* Security Note: Ensure sourceUrl is trusted or validated.
* Regex prevents common malicious schemes, but doesn't protect against all phishing.
*/
if (/^https?:\/\//i.test(sourceUrl)) {
window.open(sourceUrl, '_blank', 'noopener,noreferrer');
} else {
console.warn('Invalid source URL provided:', sourceUrl);
}
}
}Summary of Changes:
- Added Keyboard Accessibility (
tabindex,role,@keydown). - Extracted handler
handleOpenSourcefor cleaner separation and reusability. - Added null safety inside the handler for
FileId/Url. - Added warning log for invalid URLs to help debugging.
- Removed redundant
trim()where logically safe (in the new handler).
| </el-card> | ||
| </template> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Here is an analysis of the code changes, covering irregularities, potential issues, and optimization suggestions.
1. Functional Behavior Change
- Shift to Explicit Download: The code changed from using standard browser behavior (opening in a new tab or downloading via
a[download]) to explicitly triggering a download via a utility functiondownloadByURL.- Pros: This ensures files are always downloaded rather than opened in the browser, which solves cross-origin file viewing issues.
- Cons: It removes the ability for users to instantly preview files (like PDFs or images) in a new tab. If this was an intentional product decision, it is fine. If not, it’s a regression.
2. Potential Issues & Risks
-
Implementation Dependency (
downloadByURL):- Browser Compatibility: Ensure
downloadByURLhandles cross-origin URLs correctly. Modern browsers block thedownloadattribute on cross-origin anchor tags. A common approach is fetch + Blob or navigating to a dedicated backend endpoint. IfdownloadByURLjust uses a hidden<a>tag similar to the previous code but forced via JS, it will fail for cross-origin files. - Performance: If
downloadByURLusesfetch()to get a Blob, large document lists will consume significant memory and bandwidth simultaneously if clicked rapidly. There is no apparent debouncing or loading state shown.
- Browser Compatibility: Ensure
-
Security/Logic Gap:
- Both templates now use
@click="f.url && downloadByURL(...)". - If
f.urlis undefined/falsy, nothing happens when clicked. - Previously, the cursor styling (
cursor-pointer) was inside the card, implying interactivity even if the link was broken. Now, you applycursor-pointerto the wrapper unconditionally, creating a "dead zone" where the UI looks clickable but does nothing if the data is malformed. Consider adding a condition to disable interaction if!f.url.
- Both templates now use
3. Irregularities / Code Style
-
Repeated Logic & Duplication:
- You have two separate loops (
data.document_listanddata.other_list) with identical markup and logic. - This violates DRY (Don't Repeat Yourself). If you need to change how a file item looks or behaves, you now have to update it in two places.
- You have two separate loops (
-
Semantic HTML Loss:
- Replacing
<a>with adiv-based element (insideel-card) removes native accessibility semantics related to links. While less critical since the action isn't navigation (it's a download), it breaks right-click functionality ("Open link in tabs") if the user expects standard web behaviors.
- Replacing
4. Optimization Suggestions
1. Extract a Component
Since both sections render exactly the same component structure: create a reusable functional component or simple SFC.
Before: Two nearly identical template blocks.
After:
<template v-for="(f, i) in data.document_list" :key="i">
<AttachmentItem
:file="f"
:download-action="handleDownload"
/>
</template>
<template v-for="(f, i) in data.other_list" :key="i">
<AttachmentItem
:file="f"
:download-action="handleDownload"
/>
</template>2. Better Event Handling
Instead of complex expressions in the template @click, define a handler in the script for cleaner separation of concerns and easier debugging (e.g., error handling if download fails).
Template:
<el-card shadow="never" class="small cursor-pointer" @click="handleDownload(f)">...Script:
const handleDownload = (file: { url?: string; name?: string }) => {
if (!file.url) return;
try {
downloadByURL(file.url, file.name || 'untitled_file');
} catch (error) {
console.error('Download failed', error);
// trigger toast notification maybe?
}
}3. Conditional Rendering Improvement
Fix the "Dead Zone" issue by conditionally binding classes or rendering:
Current (Problematic):
<el-card ... class="small cursor-pointer" @click="f.url && download...">Suggested:
<!-- Only show pointer if valid -->
<el-card
shadow="never"
:class="{ small: true, 'cursor-pointer': !!f.url }"
@click="f.url && handleDownload(f)"
>
<!-- Optionally add tooltip if url missing -->
<span v-if="!f.url">No URL provided</span>
</el-card>
feat: pdf download