diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 1e68fdb8..251ffc59 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -10,7 +10,7 @@ import type { AppStickersManager } from "../../lib/appManagers/appStickersManage import type { AppUsersManager } from "../../lib/appManagers/appUsersManager"; import type { AppInlineBotsManager } from "../../lib/appManagers/appInlineBotsManager"; import type { AppPhotosManager } from "../../lib/appManagers/appPhotosManager"; -import type { AppDocsManager } from "../../lib/appManagers/appDocsManager"; +import type { AppDocsManager, MyDocument } from "../../lib/appManagers/appDocsManager"; import type { AppPeersManager } from "../../lib/appManagers/appPeersManager"; import type sessionStorage from '../../lib/sessionStorage'; import type Chat from "./chat"; @@ -2427,7 +2427,7 @@ export default class ChatBubbles { lastContainer && lastContainer.append(timeSpan.cloneNode(true)); bubble.classList.remove('is-message-empty'); - messageDiv.classList.add((doc.type !== 'photo' ? doc.type || 'document' : 'document') + '-message'); + messageDiv.classList.add((!(['photo', 'pdf'] as MyDocument['type'][]).includes(doc.type) ? doc.type || 'document' : 'document') + '-message'); processingWebPage = true; break; diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts index feb00921..9cb01a1d 100644 --- a/src/components/chat/contextMenu.ts +++ b/src/components/chat/contextMenu.ts @@ -24,6 +24,7 @@ import { cancelEvent } from "../../helpers/dom/cancelEvent"; import cancelSelection from "../../helpers/dom/cancelSelection"; import { attachClickEvent } from "../../helpers/dom/clickEvent"; import isSelectionEmpty from "../../helpers/dom/isSelectionEmpty"; +import appDocsManager, { MyDocument } from "../../lib/appManagers/appDocsManager"; export default class ChatContextMenu { private buttons: (ButtonMenuItemOptions & {verify: () => boolean, notDirect?: () => boolean, withSelection?: true})[]; @@ -241,6 +242,16 @@ export default class ChatContextMenu { text: 'Message.Context.Unpin', onClick: this.onUnpinClick, verify: () => this.message.pFlags.pinned && this.appPeersManager.canPinMessage(this.peerId), + }, { + icon: 'download', + text: 'MediaViewer.Context.Download', + onClick: () => { + appDocsManager.saveDocFile(this.message.media.document); + }, + verify: () => { + const doc: MyDocument = this.message.media?.document; + return doc && doc.type && !(['gif', 'photo', 'video', 'sticker'] as MyDocument['type'][]).includes(doc.type); + } }, { icon: 'checkretract', text: 'Chat.Poll.Unvote', diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index b13a390a..1d9c40f0 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -37,7 +37,7 @@ import { animateSingle } from '../helpers/animation'; import renderImageFromUrl from '../helpers/dom/renderImageFromUrl'; import sequentialDom from '../helpers/sequentialDom'; import { fastRaf } from '../helpers/schedulers'; -import appDownloadManager from '../lib/appManagers/appDownloadManager'; +import appDownloadManager, { DownloadBlob } from '../lib/appManagers/appDownloadManager'; import appStickersManager from '../lib/appManagers/appStickersManager'; import { cancelEvent } from '../helpers/dom/cancelEvent'; import { attachClickEvent } from '../helpers/dom/clickEvent'; @@ -546,7 +546,16 @@ export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showS const load = () => { const doc = appDocsManager.getDoc(docDiv.dataset.docId); - const download = appDocsManager.saveDocFile(doc, appImManager.chat.bubbles ? appImManager.chat.bubbles.lazyLoadQueue.queueId : 0); + let download: DownloadBlob; + if(doc.type === 'pdf') { + download = appDocsManager.downloadDoc(doc, appImManager.chat.bubbles ? appImManager.chat.bubbles.lazyLoadQueue.queueId : 0); + download.then(() => { + const cacheContext = appDownloadManager.getCacheContext(doc); + window.open(cacheContext.url); + }); + } else { + download = appDocsManager.saveDocFile(doc, appImManager.chat.bubbles ? appImManager.chat.bubbles.lazyLoadQueue.queueId : 0); + } if(downloadDiv) { download.then(onLoad); diff --git a/src/config/app.ts b/src/config/app.ts index 52c3bfdd..31ee2203 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -12,7 +12,7 @@ const App = { id: 1025907, hash: '452b0359b988148995f22ff0f4229750', - version: '0.5.2', + version: '0.5.3', langPackVersion: '0.1.6', langPack: 'macos', langPackCode: 'en', diff --git a/src/helpers/blob.ts b/src/helpers/blob.ts index 2d300d3c..62ac80d2 100644 --- a/src/helpers/blob.ts +++ b/src/helpers/blob.ts @@ -36,6 +36,7 @@ export function blobConstruct(blobParts: any, mimeType: string = ''): Blob { return blob; } +// https://www.iana.org/assignments/media-types/media-types.xhtml export function blobSafeMimeType(mimeType: string) { if([ 'image/jpeg', @@ -49,7 +50,8 @@ export function blobSafeMimeType(mimeType: string) { 'audio/ogg', 'audio/mpeg', 'audio/mp4', - 'application/json' + 'application/json', + 'application/pdf' ].indexOf(mimeType) === -1) { return 'application/octet-stream'; } diff --git a/src/layer.d.ts b/src/layer.d.ts index d14f1d88..067e7307 100644 --- a/src/layer.d.ts +++ b/src/layer.d.ts @@ -3101,7 +3101,7 @@ export namespace Document { video_thumbs?: Array, dc_id: number, attributes: Array, - type?: 'gif' | 'sticker' | 'audio' | 'voice' | 'video' | 'round' | 'photo', + type?: 'gif' | 'sticker' | 'audio' | 'voice' | 'video' | 'round' | 'photo' | 'pdf', h?: number, w?: number, file_name?: string, diff --git a/src/lib/appManagers/appDocsManager.ts b/src/lib/appManagers/appDocsManager.ts index af5caadb..9e7b51e8 100644 --- a/src/lib/appManagers/appDocsManager.ts +++ b/src/lib/appManagers/appDocsManager.ts @@ -21,6 +21,7 @@ import appPhotosManager from './appPhotosManager'; import blur from '../../helpers/blur'; import apiManager from '../mtproto/mtprotoworker'; import { MOUNT_CLASS_TO } from '../../config/debug'; +import { getFullDate } from '../../helpers/date'; export type MyDocument = Document.document; @@ -96,8 +97,7 @@ export class AppDocsManager { doc.duration = attribute.duration; doc.audioTitle = attribute.title; doc.audioPerformer = attribute.performer; - doc.type = attribute.pFlags.voice && doc.mime_type === "audio/ogg" ? 'voice' : 'audio'; - + doc.type = attribute.pFlags.voice && doc.mime_type === 'audio/ogg' ? 'voice' : 'audio'; /* if(apiDoc.type === 'audio') { apiDoc.supportsStreaming = true; } */ @@ -174,6 +174,15 @@ export class AppDocsManager { } } + if(doc.mime_type === 'application/pdf') { + doc.type = 'pdf'; + } + + if(doc.type === 'voice' || doc.type === 'round') { + // browser will identify extension + doc.file_name = doc.type + '_' + getFullDate(new Date(doc.date * 1000), {monthAsNumber: true, leadingZero: true}).replace(/[:\.]/g, '-').replace(', ', '_'); + } + if(apiManager.isServiceWorkerOnline()) { if((doc.type === 'gif' && doc.size > 8e6) || doc.type === 'audio' || doc.type === 'video') { doc.supportsStreaming = true; @@ -247,7 +256,7 @@ export class AppDocsManager { dcId: doc.dc_id, location: inputFileLocation, size: thumb ? thumb.size : doc.size, - mimeType: mimeType, + mimeType, fileName: doc.file_name, queueId, onlyCache diff --git a/src/scripts/in/schema_additional_params.json b/src/scripts/in/schema_additional_params.json index 58deea7c..adeae216 100644 --- a/src/scripts/in/schema_additional_params.json +++ b/src/scripts/in/schema_additional_params.json @@ -2,7 +2,7 @@ "predicate": "document", "params": [ {"name": "thumbs", "type": "Array"}, - {"name": "type", "type": "'gif' | 'sticker' | 'audio' | 'voice' | 'video' | 'round' | 'photo'"}, + {"name": "type", "type": "'gif' | 'sticker' | 'audio' | 'voice' | 'video' | 'round' | 'photo' | 'pdf'"}, {"name": "h", "type": "number"}, {"name": "w", "type": "number"}, {"name": "file_name", "type": "string"},