MOBILE-3101 core: Fix embedded files
parent
38e3e88ed6
commit
47decde520
|
@ -17,6 +17,7 @@ import {
|
|||
} from '@angular/core';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { NavController } from 'ionic-angular';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||
|
@ -49,7 +50,8 @@ export class CoreIframeComponent implements OnInit, OnChanges {
|
|||
protected navCtrl: NavController,
|
||||
protected urlUtils: CoreUrlUtilsProvider,
|
||||
protected utils: CoreUtilsProvider,
|
||||
@Optional() protected svComponent: CoreSplitViewComponent) {
|
||||
@Optional() protected svComponent: CoreSplitViewComponent,
|
||||
protected fileProvider: CoreFileProvider) {
|
||||
|
||||
this.logger = logger.getInstance('CoreIframe');
|
||||
this.loaded = new EventEmitter<HTMLIFrameElement>();
|
||||
|
@ -93,8 +95,8 @@ export class CoreIframeComponent implements OnInit, OnChanges {
|
|||
*/
|
||||
ngOnChanges(changes: {[name: string]: SimpleChange }): void {
|
||||
if (changes.src) {
|
||||
const youtubeUrl = this.urlUtils.getYoutubeEmbedUrl(changes.src.currentValue);
|
||||
this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(youtubeUrl || changes.src.currentValue);
|
||||
const url = this.urlUtils.getYoutubeEmbedUrl(changes.src.currentValue) || changes.src.currentValue;
|
||||
this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.fileProvider.convertFileSrc(url));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,5 +93,6 @@
|
|||
"statusbarbgremotetheme": "#000000",
|
||||
"statusbarlighttextremotetheme": true,
|
||||
"enableanalytics": false,
|
||||
"forceColorScheme": ""
|
||||
"forceColorScheme": "",
|
||||
"webviewscheme": "moodleappfs"
|
||||
}
|
|
@ -16,6 +16,7 @@ import { Directive, Input, AfterViewInit, ElementRef, OnChanges, SimpleChange, O
|
|||
import { Platform } from 'ionic-angular';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreFile } from '@providers/file';
|
||||
import { CoreFilepoolProvider } from '@providers/filepool';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
|
@ -52,9 +53,15 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges {
|
|||
|
||||
invalid = false;
|
||||
|
||||
constructor(element: ElementRef, logger: CoreLoggerProvider, private filepoolProvider: CoreFilepoolProvider,
|
||||
private platform: Platform, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider,
|
||||
private urlUtils: CoreUrlUtilsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider) {
|
||||
constructor(element: ElementRef,
|
||||
logger: CoreLoggerProvider,
|
||||
protected filepoolProvider: CoreFilepoolProvider,
|
||||
protected platform: Platform,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected urlUtils: CoreUrlUtilsProvider,
|
||||
protected appProvider: CoreAppProvider,
|
||||
protected utils: CoreUtilsProvider) {
|
||||
// This directive can be added dynamically. In that case, the first param is the HTMLElement.
|
||||
this.element = element.nativeElement || element;
|
||||
this.logger = logger.getInstance('CoreExternalContentDirective');
|
||||
|
@ -179,7 +186,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges {
|
|||
* @param siteId Site ID.
|
||||
* @return Promise resolved if the element is successfully treated.
|
||||
*/
|
||||
protected handleExternalContent(targetAttr: string, url: string, siteId?: string): Promise<any> {
|
||||
protected async handleExternalContent(targetAttr: string, url: string, siteId?: string): Promise<any> {
|
||||
|
||||
const tagName = this.element.tagName;
|
||||
|
||||
|
@ -214,72 +221,70 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges {
|
|||
this.addSource(url);
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
throw 'Non-downloadable URL';
|
||||
}
|
||||
|
||||
// Get the webservice pluginfile URL, we ignore failures here.
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
if (!site.canDownloadFiles() && this.urlUtils.isPluginFileUrl(url)) {
|
||||
this.element.parentElement.removeChild(this.element); // Remove element since it'll be broken.
|
||||
const site = await this.sitesProvider.getSite(siteId);
|
||||
|
||||
return Promise.reject(null);
|
||||
if (!site.canDownloadFiles() && this.urlUtils.isPluginFileUrl(url)) {
|
||||
this.element.parentElement.removeChild(this.element); // Remove element since it'll be broken.
|
||||
|
||||
throw 'Site doesn\'t allow downloading files.';
|
||||
}
|
||||
|
||||
// Download images, tracks and posters if size is unknown.
|
||||
const dwnUnknown = tagName == 'IMG' || tagName == 'TRACK' || targetAttr == 'poster';
|
||||
let finalUrl: string;
|
||||
|
||||
if (targetAttr === 'src' && tagName !== 'SOURCE' && tagName !== 'TRACK' && tagName !== 'VIDEO' && tagName !== 'AUDIO') {
|
||||
finalUrl = await this.filepoolProvider.getSrcByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown);
|
||||
} else {
|
||||
finalUrl = await this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown);
|
||||
|
||||
finalUrl = CoreFile.instance.convertFileSrc(finalUrl);
|
||||
}
|
||||
|
||||
if (finalUrl.match(/^https?:\/\//i)) {
|
||||
/* In iOS, if we use the same URL in embedded file and background download then the download only
|
||||
downloads a few bytes (cached ones). Add a hash to the URL so both URLs are different. */
|
||||
finalUrl = finalUrl + '#moodlemobile-embedded';
|
||||
}
|
||||
|
||||
this.logger.debug('Using URL ' + finalUrl + ' for ' + url);
|
||||
if (tagName === 'SOURCE') {
|
||||
// The browser does not catch changes in SRC, we need to add a new source.
|
||||
this.addSource(finalUrl);
|
||||
} else {
|
||||
if (tagName === 'IMG') {
|
||||
this.loaded = false;
|
||||
this.waitForLoad();
|
||||
}
|
||||
this.element.setAttribute(targetAttr, finalUrl);
|
||||
this.element.setAttribute('data-original-' + targetAttr, url);
|
||||
}
|
||||
|
||||
// Set events to download big files (not downloaded automatically).
|
||||
if (finalUrl.indexOf('http') === 0 && targetAttr != 'poster' &&
|
||||
(tagName == 'VIDEO' || tagName == 'AUDIO' || tagName == 'A' || tagName == 'SOURCE')) {
|
||||
const eventName = tagName == 'A' ? 'click' : 'play';
|
||||
let clickableEl = this.element;
|
||||
|
||||
if (tagName == 'SOURCE') {
|
||||
clickableEl = <HTMLElement> this.domUtils.closest(this.element, 'video,audio');
|
||||
if (!clickableEl) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Download images, tracks and posters if size is unknown.
|
||||
const dwnUnknown = tagName == 'IMG' || tagName == 'TRACK' || targetAttr == 'poster';
|
||||
let promise;
|
||||
|
||||
if (targetAttr === 'src' && tagName !== 'SOURCE' && tagName !== 'TRACK' && tagName !== 'VIDEO' &&
|
||||
tagName !== 'AUDIO') {
|
||||
promise = this.filepoolProvider.getSrcByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown);
|
||||
} else {
|
||||
promise = this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown);
|
||||
}
|
||||
|
||||
return promise.then((finalUrl) => {
|
||||
if (finalUrl.match(/^https?:\/\//i)) {
|
||||
/* In iOS, if we use the same URL in embedded file and background download then the download only
|
||||
downloads a few bytes (cached ones). Add a hash to the URL so both URLs are different. */
|
||||
finalUrl = finalUrl + '#moodlemobile-embedded';
|
||||
}
|
||||
|
||||
this.logger.debug('Using URL ' + finalUrl + ' for ' + url);
|
||||
if (tagName === 'SOURCE') {
|
||||
// The browser does not catch changes in SRC, we need to add a new source.
|
||||
this.addSource(finalUrl);
|
||||
} else {
|
||||
if (tagName === 'IMG') {
|
||||
this.loaded = false;
|
||||
this.waitForLoad();
|
||||
}
|
||||
this.element.setAttribute(targetAttr, finalUrl);
|
||||
this.element.setAttribute('data-original-' + targetAttr, url);
|
||||
}
|
||||
|
||||
// Set events to download big files (not downloaded automatically).
|
||||
if (finalUrl.indexOf('http') === 0 && targetAttr != 'poster' &&
|
||||
(tagName == 'VIDEO' || tagName == 'AUDIO' || tagName == 'A' || tagName == 'SOURCE')) {
|
||||
const eventName = tagName == 'A' ? 'click' : 'play';
|
||||
let clickableEl = this.element;
|
||||
|
||||
if (tagName == 'SOURCE') {
|
||||
clickableEl = <HTMLElement> this.domUtils.closest(this.element, 'video,audio');
|
||||
if (!clickableEl) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
clickableEl.addEventListener(eventName, () => {
|
||||
// User played media or opened a downloadable link.
|
||||
// Download the file if in wifi and it hasn't been downloaded already (for big files).
|
||||
if (this.appProvider.isWifi()) {
|
||||
// We aren't using the result, so it doesn't matter which of the 2 functions we call.
|
||||
this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, false);
|
||||
}
|
||||
});
|
||||
clickableEl.addEventListener(eventName, () => {
|
||||
// User played media or opened a downloadable link.
|
||||
// Download the file if in wifi and it hasn't been downloaded already (for big files).
|
||||
if (this.appProvider.isWifi()) {
|
||||
// We aren't using the result, so it doesn't matter which of the 2 functions we call.
|
||||
this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -74,26 +74,27 @@ export class CoreFormatTextDirective implements OnChanges {
|
|||
protected loadingChangedListener;
|
||||
|
||||
constructor(element: ElementRef,
|
||||
private sitesProvider: CoreSitesProvider,
|
||||
private domUtils: CoreDomUtilsProvider,
|
||||
private textUtils: CoreTextUtilsProvider,
|
||||
private translate: TranslateService,
|
||||
private platform: Platform,
|
||||
private utils: CoreUtilsProvider,
|
||||
private urlUtils: CoreUrlUtilsProvider,
|
||||
private loggerProvider: CoreLoggerProvider,
|
||||
private filepoolProvider: CoreFilepoolProvider,
|
||||
private appProvider: CoreAppProvider,
|
||||
private contentLinksHelper: CoreContentLinksHelperProvider,
|
||||
@Optional() private navCtrl: NavController,
|
||||
@Optional() private content: Content, @Optional()
|
||||
private svComponent: CoreSplitViewComponent,
|
||||
private iframeUtils: CoreIframeUtilsProvider,
|
||||
private eventsProvider: CoreEventsProvider,
|
||||
private filterProvider: CoreFilterProvider,
|
||||
private filterHelper: CoreFilterHelperProvider,
|
||||
private filterDelegate: CoreFilterDelegate,
|
||||
private viewContainerRef: ViewContainerRef) {
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected textUtils: CoreTextUtilsProvider,
|
||||
protected translate: TranslateService,
|
||||
protected platform: Platform,
|
||||
protected utils: CoreUtilsProvider,
|
||||
protected urlUtils: CoreUrlUtilsProvider,
|
||||
protected loggerProvider: CoreLoggerProvider,
|
||||
protected filepoolProvider: CoreFilepoolProvider,
|
||||
protected appProvider: CoreAppProvider,
|
||||
protected contentLinksHelper: CoreContentLinksHelperProvider,
|
||||
@Optional() protected navCtrl: NavController,
|
||||
@Optional() protected content: Content, @Optional()
|
||||
protected svComponent: CoreSplitViewComponent,
|
||||
protected iframeUtils: CoreIframeUtilsProvider,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected filterProvider: CoreFilterProvider,
|
||||
protected filterHelper: CoreFilterHelperProvider,
|
||||
protected filterDelegate: CoreFilterDelegate,
|
||||
protected viewContainerRef: ViewContainerRef,
|
||||
) {
|
||||
|
||||
this.element = element.nativeElement;
|
||||
this.element.classList.add('opacity-hide'); // Hide contents until they're treated.
|
||||
|
|
|
@ -92,7 +92,7 @@ export class CoreLinkDirective implements OnInit {
|
|||
protected navigate(href: string): void {
|
||||
const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link=';
|
||||
|
||||
if (href.indexOf('cdvfile://') === 0 || href.indexOf('file://') === 0 || href.indexOf('filesystem:') === 0) {
|
||||
if (this.urlUtils.isLocalFileUrl(href)) {
|
||||
// We have a local file.
|
||||
this.utils.openFile(href).catch((error) => {
|
||||
this.domUtils.showErrorModal(error);
|
||||
|
|
|
@ -20,6 +20,7 @@ import { CoreAppProvider } from './app';
|
|||
import { CoreLoggerProvider } from './logger';
|
||||
import { CoreMimetypeUtilsProvider } from './utils/mimetype';
|
||||
import { CoreTextUtilsProvider } from './utils/text';
|
||||
import { CoreConfigConstants } from '../configconstants';
|
||||
import { Zip } from '@ionic-native/zip';
|
||||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
|
||||
|
@ -946,6 +947,7 @@ export class CoreFileProvider {
|
|||
|
||||
/**
|
||||
* Get the internal URL of a file.
|
||||
* Please notice that with WKWebView these URLs no longer work in mobile. Use fileEntry.toURL() along with convertFileSrc.
|
||||
*
|
||||
* @param fileEntry File Entry.
|
||||
* @return Internal URL.
|
||||
|
@ -1270,6 +1272,31 @@ export class CoreFileProvider {
|
|||
|
||||
return window.location.href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to call Ionic WebView convertFileSrc only in the needed platforms.
|
||||
* This is needed to make files work with the Ionic WebView plugin.
|
||||
*
|
||||
* @param src Source to convert.
|
||||
* @return Converted src.
|
||||
*/
|
||||
convertFileSrc(src: string): string {
|
||||
return this.appProvider.isMobile() ? (<any> window).Ionic.WebView.convertFileSrc(src) : src;
|
||||
}
|
||||
|
||||
/**
|
||||
* Undo the conversion of convertFileSrc.
|
||||
*
|
||||
* @param src Source to unconvert.
|
||||
* @return Unconverted src.
|
||||
*/
|
||||
unconvertFileSrc(src: string): string {
|
||||
if (!this.appProvider.isMobile()) {
|
||||
return src;
|
||||
}
|
||||
|
||||
return src.replace(CoreConfigConstants.webviewscheme + '://localhost/_app_file_', 'file://');
|
||||
}
|
||||
}
|
||||
|
||||
export class CoreFile extends makeSingleton(CoreFileProvider) {}
|
||||
|
|
|
@ -412,11 +412,21 @@ export class CoreFilepoolProvider {
|
|||
protected packagesPromises = {};
|
||||
protected filePromises: { [s: string]: { [s: string]: Promise<any> } } = {};
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private appProvider: CoreAppProvider, private fileProvider: CoreFileProvider,
|
||||
private sitesProvider: CoreSitesProvider, private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider,
|
||||
private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, private urlUtils: CoreUrlUtilsProvider,
|
||||
private timeUtils: CoreTimeUtilsProvider, private eventsProvider: CoreEventsProvider, initDelegate: CoreInitDelegate,
|
||||
network: Network, private pluginFileDelegate: CorePluginFileDelegate, private domUtils: CoreDomUtilsProvider,
|
||||
constructor(logger: CoreLoggerProvider,
|
||||
protected appProvider: CoreAppProvider,
|
||||
protected fileProvider: CoreFileProvider,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected wsProvider: CoreWSProvider,
|
||||
protected textUtils: CoreTextUtilsProvider,
|
||||
protected utils: CoreUtilsProvider,
|
||||
protected mimeUtils: CoreMimetypeUtilsProvider,
|
||||
protected urlUtils: CoreUrlUtilsProvider,
|
||||
protected timeUtils: CoreTimeUtilsProvider,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
initDelegate: CoreInitDelegate,
|
||||
network: Network,
|
||||
protected pluginFileDelegate: CorePluginFileDelegate,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
zone: NgZone) {
|
||||
this.logger = logger.getInstance('CoreFilepoolProvider');
|
||||
|
||||
|
@ -1796,8 +1806,7 @@ export class CoreFilepoolProvider {
|
|||
if (this.fileProvider.isAvailable()) {
|
||||
return Promise.resolve(this.getFilePath(siteId, fileId)).then((path) => {
|
||||
return this.fileProvider.getFile(path).then((fileEntry) => {
|
||||
// We use toInternalURL so images are loaded in iOS8 using img HTML tags.
|
||||
return this.fileProvider.getInternalURL(fileEntry);
|
||||
return this.fileProvider.convertFileSrc(fileEntry.toURL());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -225,7 +225,7 @@ export class CoreIframeUtilsProvider {
|
|||
} else {
|
||||
element.setAttribute('src', url);
|
||||
}
|
||||
} else if (url.indexOf('cdvfile://') === 0 || url.indexOf('file://') === 0) {
|
||||
} else if (this.urlUtils.isLocalFileUrl(url)) {
|
||||
// It's a local file.
|
||||
this.utils.openFile(url).catch((error) => {
|
||||
this.domUtils.showErrorModal(error);
|
||||
|
@ -353,16 +353,14 @@ export class CoreIframeUtilsProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
if (scheme && scheme != 'file' && scheme != 'filesystem') {
|
||||
if (!this.urlUtils.isLocalFileUrlScheme(scheme)) {
|
||||
// Scheme suggests it's an external resource.
|
||||
event.preventDefault();
|
||||
|
||||
const frameSrc = (<HTMLFrameElement> element).src || (<HTMLObjectElement> element).data,
|
||||
frameScheme = this.urlUtils.getUrlScheme(frameSrc);
|
||||
const frameSrc = (<HTMLFrameElement> element).src || (<HTMLObjectElement> element).data;
|
||||
|
||||
// If the frame is not local, check the target to identify how to treat the link.
|
||||
if (frameScheme && frameScheme != 'file' && frameScheme != 'filesystem' &&
|
||||
(!link.target || link.target == '_self')) {
|
||||
if (!this.urlUtils.isLocalFileUrl(frameSrc) && (!link.target || link.target == '_self')) {
|
||||
// Load the link inside the frame itself.
|
||||
if (element.tagName.toLowerCase() == 'object') {
|
||||
element.setAttribute('data', link.href);
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { CoreFile } from '../file';
|
||||
import { CoreLoggerProvider } from '../logger';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreTextUtilsProvider } from './text';
|
||||
|
@ -165,7 +166,7 @@ export class CoreMimetypeUtilsProvider {
|
|||
if (this.canBeEmbedded(ext)) {
|
||||
file.embedType = this.getExtensionType(ext);
|
||||
|
||||
path = path || file.fileurl || (file.toURL && file.toURL());
|
||||
path = CoreFile.instance.convertFileSrc(path || file.fileurl || (file.toURL && file.toURL()));
|
||||
|
||||
if (file.embedType == 'image') {
|
||||
return '<img src="' + path + '">';
|
||||
|
|
|
@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
|
|||
import { CoreLangProvider } from '../lang';
|
||||
import { CoreTextUtilsProvider } from './text';
|
||||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
import { CoreConfigConstants } from '../../configconstants';
|
||||
|
||||
/*
|
||||
* "Utils" service with helper functions for URLs.
|
||||
|
@ -424,6 +425,26 @@ export class CoreUrlUtilsProvider {
|
|||
return /^https?\:\/\/.+/i.test(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an URL belongs to a local file.
|
||||
*
|
||||
* @param url URL to check.
|
||||
* @return Whether the URL belongs to a local file.
|
||||
*/
|
||||
isLocalFileUrl(url: string): boolean {
|
||||
return this.isLocalFileUrlScheme(this.getUrlScheme(url));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a URL scheme belongs to a local file.
|
||||
*
|
||||
* @param scheme Scheme to check.
|
||||
* @return Whether the scheme belongs to a local file.
|
||||
*/
|
||||
isLocalFileUrlScheme(scheme: string): boolean {
|
||||
return scheme == 'cdvfile' || scheme == 'file' || scheme == 'filesystem' || scheme == CoreConfigConstants.webviewscheme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a URL is a pluginfile URL.
|
||||
*
|
||||
|
|
|
@ -27,6 +27,7 @@ import { CoreLoggerProvider } from '../logger';
|
|||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreLangProvider } from '../lang';
|
||||
import { CoreWSProvider, CoreWSError } from '../ws';
|
||||
import { CoreFile } from '../file';
|
||||
import { makeSingleton } from '@singletons/core.singletons';
|
||||
|
||||
/**
|
||||
|
@ -863,8 +864,11 @@ export class CoreUtilsProvider {
|
|||
* @return Promise resolved when done.
|
||||
*/
|
||||
openFile(path: string): Promise<any> {
|
||||
const extension = this.mimetypeUtils.getFileExtension(path),
|
||||
mimetype = this.mimetypeUtils.getMimeType(extension);
|
||||
// Convert the path to a native path if needed.
|
||||
path = CoreFile.instance.unconvertFileSrc(path);
|
||||
|
||||
const extension = this.mimetypeUtils.getFileExtension(path);
|
||||
const mimetype = this.mimetypeUtils.getMimeType(extension);
|
||||
|
||||
// Path needs to be decoded, the file won't be opened if the path has %20 instead of spaces and so.
|
||||
try {
|
||||
|
|
Loading…
Reference in New Issue