From 4a2892b463106e1ce1c08deb6cd5c55b1b19284e Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 29 Jul 2021 19:32:02 -0600 Subject: [PATCH 01/12] Show simple file name and size on images/videos Fixes https://github.com/vector-im/element-web/issues/18197 --- res/css/views/messages/_MImageBody.scss | 22 ++++++++++++++++++++ res/css/views/messages/_MVideoBody.scss | 4 +++- src/components/views/messages/MImageBody.tsx | 19 +++++++++++++---- src/components/views/messages/MVideoBody.tsx | 15 ++++++++++--- 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index a748435cd8a..af986724a17 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -16,6 +16,28 @@ limitations under the License. $timelineImageBorderRadius: 4px; +.mx_MImageBody_banner { + position: absolute; + top: 0; + left: 0; + line-height: 20px; + padding: 4px; + + // Trying to match the width of the image is surprisingly difficult, so arbitrarily + // break it off early. + max-width: min(100%, 350px); + + border-top-left-radius: $timelineImageBorderRadius; + + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + // Hardcoded colours because it's the same on all themes + background-color: #1A1D2399; + color: #ffffff; +} + .mx_MImageBody_thumbnail { object-fit: contain; border-radius: $timelineImageBorderRadius; diff --git a/res/css/views/messages/_MVideoBody.scss b/res/css/views/messages/_MVideoBody.scss index ac3491bc8ff..199ec047698 100644 --- a/res/css/views/messages/_MVideoBody.scss +++ b/res/css/views/messages/_MVideoBody.scss @@ -14,7 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -span.mx_MVideoBody { +div.mx_MVideoBody { + position: relative; + video.mx_MVideoBody { max-width: 100%; height: auto; diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 538b524f0f8..3a62b1de23e 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -31,6 +31,7 @@ import { IMediaEventContent } from '../../../customisations/models/IMediaEventCo import ImageView from '../elements/ImageView'; import { SyncState } from 'matrix-js-sdk/src/sync.api'; import { IBodyProps } from "./IBodyProps"; +import { presentableTextForFile } from "../../../utils/FileUtils"; interface IState { decryptedUrl?: string; @@ -365,6 +366,15 @@ export default class MImageBody extends React.Component { gifLabel =

GIF

; } + let banner; + if (this.state.showImage) { + banner = ( + + { presentableTextForFile(content, _t("Image"), true) } + + ); + } + const thumbnail = (
{ showPlaceholder && @@ -382,6 +392,7 @@ export default class MImageBody extends React.Component {
{ img } { gifLabel } + { banner }
{ this.state.hover && this.getTooltip() } @@ -391,14 +402,14 @@ export default class MImageBody extends React.Component { return this.wrapImage(contentUrl, thumbnail); } - // Overidden by MStickerBody + // Overridden by MStickerBody protected wrapImage(contentUrl: string, children: JSX.Element): JSX.Element { return { children } ; } - // Overidden by MStickerBody + // Overridden by MStickerBody protected getPlaceholder(width: number, height: number): JSX.Element { const blurhash = this.props.mxEvent.getContent().info[BLURHASH_FIELD]; if (blurhash) return ; @@ -407,12 +418,12 @@ export default class MImageBody extends React.Component { ); } - // Overidden by MStickerBody + // Overridden by MStickerBody protected getTooltip(): JSX.Element { return null; } - // Overidden by MStickerBody + // Overridden by MStickerBody protected getFileBody(): string | JSX.Element { // We only ever need the download bar if we're appearing outside of the timeline if (this.props.tileShape) { diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 77c7ebacdad..8127273c7ae 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -26,6 +26,7 @@ import { BLURHASH_FIELD } from "../../../ContentMessages"; import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent"; import { IBodyProps } from "./IBodyProps"; import MFileBody from "./MFileBody"; +import { presentableTextForFile } from "../../../utils/FileUtils"; interface IState { decryptedUrl?: string; @@ -208,7 +209,7 @@ export default class MVideoBody extends React.PureComponent }; render() { - const content = this.props.mxEvent.getContent(); + const content = this.props.mxEvent.getContent(); const autoplay = SettingsStore.getValue("autoplayGifsAndVideos"); if (this.state.error !== null) { @@ -234,6 +235,13 @@ export default class MVideoBody extends React.PureComponent ); } + const banner = ( + // XXX: Class abuse (so we can have context on the border radius) + + { presentableTextForFile(content, _t("Video"), true) } + + ); + const contentUrl = this.getContentUrl(); const thumbUrl = this.getThumbUrl(); let height = null; @@ -253,7 +261,7 @@ export default class MVideoBody extends React.PureComponent } } return ( - +
); } } From bf72f237db058093b5be5db2dcc9d7bd1893a608 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 29 Jul 2021 19:36:41 -0600 Subject: [PATCH 02/12] i18n --- src/i18n/strings/en_EN.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 11d1f2140ac..31ee741b06c 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1908,10 +1908,10 @@ "Decrypt %(text)s": "Decrypt %(text)s", "Download %(text)s": "Download %(text)s", "Invalid file%(extra)s": "Invalid file%(extra)s", + "Image": "Image", "Error decrypting image": "Error decrypting image", "Show image": "Show image", "Sticker": "Sticker", - "Image": "Image", "Join the conference at the top of this room": "Join the conference at the top of this room", "Join the conference from the room information card on the right": "Join the conference from the room information card on the right", "Video conference ended by %(senderName)s": "Video conference ended by %(senderName)s", @@ -1932,6 +1932,7 @@ "%(name)s wants to verify": "%(name)s wants to verify", "You sent a verification request": "You sent a verification request", "Error decrypting video": "Error decrypting video", + "Video": "Video", "Error processing voice message": "Error processing voice message", "Add reaction": "Add reaction", "Show all": "Show all", From 1f54488aae409f934ff964c4bf195086e9787147 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Oct 2021 23:34:59 -0600 Subject: [PATCH 03/12] Fix bad merge --- src/components/views/messages/MVideoBody.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 214bbc5f022..8cabeeb3861 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -222,7 +222,7 @@ export default class MVideoBody extends React.PureComponent }; render() { - const content = this.props.mxEvent.getContent(); + const content = this.props.mxEvent.getContent(); const autoplay = SettingsStore.getValue("autoplayVideo"); if (this.state.error !== null) { @@ -293,7 +293,7 @@ export default class MVideoBody extends React.PureComponent /> { fileBody } { banner } -
+
); } } From 3ef691aacb09a208044c6f6b3cc41ee15768ebda Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Oct 2021 23:39:17 -0600 Subject: [PATCH 04/12] Add hover state tracking --- src/components/views/messages/MImageBody.tsx | 2 +- src/components/views/messages/MVideoBody.tsx | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index bf720d4a786..7f365d21917 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -421,7 +421,7 @@ export default class MImageBody extends React.Component { } let banner; - if (this.state.showImage) { + if (this.state.showImage && this.state.hover) { banner = ( { presentableTextForFile(content, _t("Image"), true) } diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 8cabeeb3861..4f3a48e4d9c 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -38,6 +38,7 @@ interface IState { fetchingData: boolean; posterLoading: boolean; blurhashUrl: string; + hover?: boolean; } @replaceableComponent("views.messages.MVideoBody") @@ -248,12 +249,14 @@ export default class MVideoBody extends React.PureComponent ); } - const banner = ( - // XXX: Class abuse (so we can have context on the border radius) - - { presentableTextForFile(content, _t("Video"), true) } - - ); + const banner = this.state.hover + ? ( + // XXX: Class abuse (so we can have context on the border radius) + + { presentableTextForFile(content, _t("Video"), true) } + + ) + : null; const contentUrl = this.getContentUrl(); const thumbUrl = this.getThumbUrl(); @@ -290,6 +293,8 @@ export default class MVideoBody extends React.PureComponent width={width} poster={poster} onPlay={this.videoOnPlay} + onMouseEnter={() => this.setState({ hover: true })} + onMouseLeave={() => this.setState({ hover: false })} /> { fileBody } { banner } From 771f640c2910db9f66eb8beba249667e679c4f10 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Oct 2021 23:49:02 -0600 Subject: [PATCH 05/12] Only show on timeline-like objects --- src/components/views/messages/MImageBody.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 7f365d21917..767a00a69f5 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -36,6 +36,7 @@ import { CSSTransition, SwitchTransition } from 'react-transition-group'; import { presentableTextForFile } from "../../../utils/FileUtils"; import { logger } from "matrix-js-sdk/src/logger"; +import { TileShape } from "../rooms/EventTile"; interface IState { decryptedUrl?: string; @@ -421,7 +422,10 @@ export default class MImageBody extends React.Component { } let banner; - if (this.state.showImage && this.state.hover) { + const isTimeline = !this.props.tileShape + || this.props.tileShape === TileShape.FileGrid + || this.props.tileShape === TileShape.Notif; + if (this.state.showImage && this.state.hover && isTimeline) { banner = ( { presentableTextForFile(content, _t("Image"), true) } From 29859a82a2915fa002314e0168eb59ef748ab047 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 21 Oct 2021 23:49:14 -0600 Subject: [PATCH 06/12] Match new design requirements --- res/css/views/messages/_MImageBody.scss | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index cdf19633a9f..f36c9c0851d 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -18,17 +18,16 @@ $timelineImageBorderRadius: 4px; .mx_MImageBody_banner { position: absolute; - top: 0; - left: 0; + bottom: 4px; + left: 4px; line-height: 20px; padding: 4px; + border-radius: $timelineImageBorderRadius; // Trying to match the width of the image is surprisingly difficult, so arbitrarily // break it off early. max-width: min(100%, 350px); - border-top-left-radius: $timelineImageBorderRadius; - text-overflow: ellipsis; white-space: nowrap; overflow: hidden; From bb6f330396c47da63892e86ced2b72a121f20c40 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Oct 2021 12:56:44 -0600 Subject: [PATCH 07/12] Remove video support (deemed not needed) --- src/components/views/messages/MVideoBody.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 4f3a48e4d9c..6c675e24732 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -26,7 +26,6 @@ import { BLURHASH_FIELD } from "../../../ContentMessages"; import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent"; import { IBodyProps } from "./IBodyProps"; import MFileBody from "./MFileBody"; -import { presentableTextForFile } from "../../../utils/FileUtils"; import { logger } from "matrix-js-sdk/src/logger"; @@ -38,7 +37,6 @@ interface IState { fetchingData: boolean; posterLoading: boolean; blurhashUrl: string; - hover?: boolean; } @replaceableComponent("views.messages.MVideoBody") @@ -249,15 +247,6 @@ export default class MVideoBody extends React.PureComponent ); } - const banner = this.state.hover - ? ( - // XXX: Class abuse (so we can have context on the border radius) - - { presentableTextForFile(content, _t("Video"), true) } - - ) - : null; - const contentUrl = this.getContentUrl(); const thumbUrl = this.getThumbUrl(); let height = null; @@ -293,11 +282,8 @@ export default class MVideoBody extends React.PureComponent width={width} poster={poster} onPlay={this.videoOnPlay} - onMouseEnter={() => this.setState({ hover: true })} - onMouseLeave={() => this.setState({ hover: false })} /> { fileBody } - { banner } ); } From 2af16ac2d183a6fc1b311f86125cdf9213e35d91 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Oct 2021 13:05:37 -0600 Subject: [PATCH 08/12] Colouring and sizing from design --- res/css/views/messages/_MImageBody.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index f36c9c0851d..e6bc20f4c11 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -20,9 +20,11 @@ $timelineImageBorderRadius: 4px; position: absolute; bottom: 4px; left: 4px; - line-height: 20px; padding: 4px; border-radius: $timelineImageBorderRadius; + font-size: $font-15px; + + pointer-events: none; // let the cursor go through to the media underneath // Trying to match the width of the image is surprisingly difficult, so arbitrarily // break it off early. @@ -33,7 +35,7 @@ $timelineImageBorderRadius: 4px; overflow: hidden; // Hardcoded colours because it's the same on all themes - background-color: #1A1D2399; + background-color: rgba(0, 0, 0, 0.6); color: #ffffff; } From 16cb18dd73a4f04f4bc1a3ba660806f83504307b Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Oct 2021 13:23:02 -0600 Subject: [PATCH 09/12] Include file name in lightbox --- res/css/views/elements/_ImageView.scss | 5 +++++ src/components/views/elements/ImageView.tsx | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/res/css/views/elements/_ImageView.scss b/res/css/views/elements/_ImageView.scss index 787d33ddc22..e0fb9144471 100644 --- a/res/css/views/elements/_ImageView.scss +++ b/res/css/views/elements/_ImageView.scss @@ -79,6 +79,11 @@ $button-gap: 24px; font-weight: bold; } +.mx_ImageView_title { + color: $lightbox-fg-color; + font-size: $font-12px; +} + .mx_ImageView_toolbar { padding-right: 16px; pointer-events: initial; diff --git a/src/components/views/elements/ImageView.tsx b/src/components/views/elements/ImageView.tsx index 44ff6644d75..9aaa268c93c 100644 --- a/src/components/views/elements/ImageView.tsx +++ b/src/components/views/elements/ImageView.tsx @@ -35,6 +35,7 @@ import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { normalizeWheelEvent } from "../../../utils/Mouse"; import { IDialogProps } from '../dialogs/IDialogProps'; import UIStore from '../../../stores/UIStore'; +import { presentableTextForFile } from "../../../utils/FileUtils"; // Max scale to keep gaps around the image const MAX_SCALE = 0.95; @@ -540,6 +541,9 @@ export default class ImageView extends React.Component { >
{ info } +
+ { presentableTextForFile(this.props.mxEvent.getContent(), _t("Image"), true) } +
{ zoomOutButton } { zoomInButton } From ed906c7759d6d3479712d1747a08dc7c55159847 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Oct 2021 13:25:46 -0600 Subject: [PATCH 10/12] Revert changes to videos since we don't need them --- res/css/views/messages/_MVideoBody.scss | 4 +--- src/components/views/messages/MVideoBody.tsx | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/res/css/views/messages/_MVideoBody.scss b/res/css/views/messages/_MVideoBody.scss index 199ec047698..ac3491bc8ff 100644 --- a/res/css/views/messages/_MVideoBody.scss +++ b/res/css/views/messages/_MVideoBody.scss @@ -14,9 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -div.mx_MVideoBody { - position: relative; - +span.mx_MVideoBody { video.mx_MVideoBody { max-width: 100%; height: auto; diff --git a/src/components/views/messages/MVideoBody.tsx b/src/components/views/messages/MVideoBody.tsx index 6c675e24732..b2e587e51ab 100644 --- a/src/components/views/messages/MVideoBody.tsx +++ b/src/components/views/messages/MVideoBody.tsx @@ -221,7 +221,7 @@ export default class MVideoBody extends React.PureComponent }; render() { - const content = this.props.mxEvent.getContent(); + const content = this.props.mxEvent.getContent(); const autoplay = SettingsStore.getValue("autoplayVideo"); if (this.state.error !== null) { @@ -268,7 +268,7 @@ export default class MVideoBody extends React.PureComponent const fileBody = this.getFileBody(); return ( -
+
+ ); } } From 0caa62105b321377498347970976ba0190b40c00 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 22 Oct 2021 13:26:10 -0600 Subject: [PATCH 11/12] i18n --- src/i18n/strings/en_EN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2fd5716426b..321080f1ac2 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2007,7 +2007,6 @@ "%(name)s wants to verify": "%(name)s wants to verify", "You sent a verification request": "You sent a verification request", "Error decrypting video": "Error decrypting video", - "Video": "Video", "Error processing voice message": "Error processing voice message", "Add reaction": "Add reaction", "Show all": "Show all", From 98cc6fa1cbf43017c5fe3254889c7b0bdf3209e5 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Fri, 13 May 2022 16:26:31 +0100 Subject: [PATCH 12/12] Iterate PR --- res/css/views/messages/_MImageBody.scss | 5 ++--- src/components/views/messages/MFileBody.tsx | 2 +- src/components/views/messages/MImageBody.tsx | 15 +++++++++------ src/i18n/strings/en_EN.json | 2 +- src/utils/FileUtils.ts | 4 ++-- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/res/css/views/messages/_MImageBody.scss b/res/css/views/messages/_MImageBody.scss index 8f3ae07dccc..87a44d741bf 100644 --- a/res/css/views/messages/_MImageBody.scss +++ b/res/css/views/messages/_MImageBody.scss @@ -22,13 +22,12 @@ $timeline-image-border-radius: 8px; bottom: 4px; left: 4px; padding: 4px; - border-radius: $timelineImageBorderRadius; + border-radius: $timeline-image-border-radius; font-size: $font-15px; pointer-events: none; // let the cursor go through to the media underneath - // Trying to match the width of the image is surprisingly difficult, so arbitrarily - // break it off early. + // Trying to match the width of the image is surprisingly difficult, so arbitrarily break it off early. max-width: min(100%, 350px); text-overflow: ellipsis; diff --git a/src/components/views/messages/MFileBody.tsx b/src/components/views/messages/MFileBody.tsx index 2556616b7a5..1b2eaeeb501 100644 --- a/src/components/views/messages/MFileBody.tsx +++ b/src/components/views/messages/MFileBody.tsx @@ -344,7 +344,7 @@ export default class MFileBody extends React.Component { { this.context.timelineRenderingType === TimelineRenderingType.File && (
- { this.content.info && this.content.info.size ? filesize(this.content.info.size) : "" } + { this.content.info?.size ? filesize(this.content.info.size) : "" }
) }
} diff --git a/src/components/views/messages/MImageBody.tsx b/src/components/views/messages/MImageBody.tsx index 1cd036e83d4..9ab05a320ba 100644 --- a/src/components/views/messages/MImageBody.tsx +++ b/src/components/views/messages/MImageBody.tsx @@ -447,14 +447,17 @@ export default class MImageBody extends React.Component { gifLabel =

GIF

; } - let banner; - const isTimeline = !this.props.tileShape - || this.props.tileShape === TileShape.FileGrid - || this.props.tileShape === TileShape.Notif; + let banner: JSX.Element; + const isTimeline = [ + TimelineRenderingType.Room, + TimelineRenderingType.Search, + TimelineRenderingType.Thread, + TimelineRenderingType.Notification, + ].includes(this.context.timelineRenderingType); if (this.state.showImage && this.state.hover && isTimeline) { banner = ( - - { presentableTextForFile(content, _t("Image"), true) } + + { presentableTextForFile(content, _t("Image"), true, true) } ); } diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 6f051db4813..ede1176ef68 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2134,9 +2134,9 @@ "Error decrypting attachment": "Error decrypting attachment", "Decrypt %(text)s": "Decrypt %(text)s", "Invalid file%(extra)s": "Invalid file%(extra)s", + "Image": "Image", "Error decrypting image": "Error decrypting image", "Show image": "Show image", - "Image": "Image", "Join the conference at the top of this room": "Join the conference at the top of this room", "Join the conference from the room information card on the right": "Join the conference from the room information card on the right", "Video conference ended by %(senderName)s": "Video conference ended by %(senderName)s", diff --git a/src/utils/FileUtils.ts b/src/utils/FileUtils.ts index e934389152e..948f023d7e7 100644 --- a/src/utils/FileUtils.ts +++ b/src/utils/FileUtils.ts @@ -37,7 +37,7 @@ export function presentableTextForFile( shortened = false, ): string { let text = fallbackText; - if (content.body && content.body.length > 0) { + if (content.body?.length > 0) { // The content body should be the name of the file including a // file extension. text = content.body; @@ -58,7 +58,7 @@ export function presentableTextForFile( text = `${fileName}...${extension}`; } - if (content.info && content.info.size && withSize) { + if (content.info?.size && withSize) { // If we know the size of the file then add it as human readable // string to the end of the link text so that the user knows how // big a file they are downloading.