278 lines
9.6 KiB
TypeScript
278 lines
9.6 KiB
TypeScript
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { Directive, Input, OnInit, ElementRef, Optional, SecurityContext } from '@angular/core';
|
|
import { SafeUrl } from '@angular/platform-browser';
|
|
import { IonContent } from '@ionic/angular';
|
|
|
|
import { CoreFileHelper } from '@services/file-helper';
|
|
import { CoreSites } from '@services/sites';
|
|
import { CoreDomUtils } from '@services/utils/dom';
|
|
import { CoreUrlUtils } from '@services/utils/url';
|
|
import { CoreUtils } from '@services/utils/utils';
|
|
import { CoreTextUtils } from '@services/utils/text';
|
|
import { CoreConstants } from '@/core/constants';
|
|
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
|
|
import { CoreCustomURLSchemes } from '@services/urlschemes';
|
|
import { DomSanitizer } from '@singletons';
|
|
import { CoreFilepool } from '@services/filepool';
|
|
|
|
/**
|
|
* Directive to open a link in external browser or in the app.
|
|
*/
|
|
@Directive({
|
|
selector: '[core-link]',
|
|
})
|
|
export class CoreLinkDirective implements OnInit {
|
|
|
|
@Input() href?: string | SafeUrl; // Link URL.
|
|
@Input() capture?: boolean | string; // If the link needs to be captured by the app.
|
|
@Input() inApp?: boolean | string; // True to open in embedded browser, false to open in system browser.
|
|
/* Whether the link should be opened with auto-login. Accepts the following values:
|
|
"yes" -> Always auto-login.
|
|
"no" -> Never auto-login.
|
|
"check" -> Auto-login only if it points to the current site. Default value. */
|
|
@Input() autoLogin = 'check';
|
|
@Input() showBrowserWarning = true; // Whether to show a warning before opening browser. Defaults to true.
|
|
|
|
protected element: Element;
|
|
|
|
constructor(
|
|
element: ElementRef,
|
|
@Optional() protected content: IonContent,
|
|
) {
|
|
this.element = element.nativeElement;
|
|
}
|
|
|
|
/**
|
|
* Function executed when the component is initialized.
|
|
*/
|
|
ngOnInit(): void {
|
|
this.inApp = this.inApp === undefined ? this.inApp : CoreUtils.isTrueOrOne(this.inApp);
|
|
|
|
if (this.element.tagName != 'BUTTON' && this.element.tagName != 'A') {
|
|
this.element.setAttribute('tabindex', '0');
|
|
this.element.setAttribute('role', 'button');
|
|
}
|
|
|
|
this.element.addEventListener('click', async (event) => {
|
|
this.performAction(event);
|
|
});
|
|
|
|
this.element.addEventListener('keydown', (event: KeyboardEvent) => {
|
|
if ((event.key == ' ' || event.key == 'Enter')) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}
|
|
});
|
|
|
|
this.element.addEventListener('keyup', (event: KeyboardEvent) => {
|
|
if ((event.key == ' ' || event.key == 'Enter')) {
|
|
this.performAction(event);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Perform "click" action.
|
|
*
|
|
* @param event Event.
|
|
* @returns Resolved when done.
|
|
*/
|
|
protected async performAction(event: Event): Promise<void> {
|
|
if (event.defaultPrevented) {
|
|
return; // Link already treated, stop.
|
|
}
|
|
|
|
let href: string | null = null;
|
|
if (this.href) {
|
|
// Convert the URL back to string if needed.
|
|
href = typeof this.href === 'string' ? this.href : DomSanitizer.sanitize(SecurityContext.URL, this.href);
|
|
}
|
|
|
|
href = href || this.element.getAttribute('href') || this.element.getAttribute('xlink:href');
|
|
|
|
if (!href || CoreUrlUtils.getUrlScheme(href) == 'javascript') {
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
const openIn = this.element.getAttribute('data-open-in');
|
|
|
|
if (CoreUtils.isTrueOrOne(this.capture)) {
|
|
href = CoreTextUtils.decodeURI(href);
|
|
|
|
const treated = await CoreContentLinksHelper.handleLink(href, undefined, true, true);
|
|
|
|
if (!treated) {
|
|
this.navigate(href, openIn);
|
|
}
|
|
} else {
|
|
this.navigate(href, openIn);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convenience function to correctly navigate, open file or url in the browser.
|
|
*
|
|
* @param href HREF to be opened.
|
|
* @param openIn Open In App value coming from data-open-in attribute.
|
|
* @return Promise resolved when done.
|
|
*/
|
|
protected async navigate(href: string, openIn?: string | null): Promise<void> {
|
|
|
|
if (CoreUrlUtils.isLocalFileUrl(href)) {
|
|
return this.openLocalFile(href);
|
|
}
|
|
|
|
if (href.charAt(0) == '#') {
|
|
// Look for id or name.
|
|
href = href.substring(1);
|
|
CoreDomUtils.scrollToElementBySelector(
|
|
this.element.closest('ion-content'),
|
|
this.content,
|
|
`#${href}, [name='${href}']`,
|
|
);
|
|
|
|
return;
|
|
}
|
|
|
|
if (CoreCustomURLSchemes.isCustomURL(href)) {
|
|
try {
|
|
await CoreCustomURLSchemes.handleCustomURL(href);
|
|
} catch (error) {
|
|
CoreCustomURLSchemes.treatHandleCustomURLError(error);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
return this.openExternalLink(href, openIn);
|
|
}
|
|
|
|
/**
|
|
* Open a local file.
|
|
*
|
|
* @param path Path to the file.
|
|
* @return Promise resolved when done.
|
|
*/
|
|
protected async openLocalFile(path: string): Promise<void> {
|
|
const filename = path.substring(path.lastIndexOf('/') + 1);
|
|
|
|
if (!CoreFileHelper.isOpenableInApp({ filename })) {
|
|
try {
|
|
await CoreFileHelper.showConfirmOpenUnsupportedFile();
|
|
} catch (error) {
|
|
return; // Cancelled, stop.
|
|
}
|
|
}
|
|
|
|
try {
|
|
await CoreUtils.openFile(path);
|
|
} catch (error) {
|
|
CoreDomUtils.showErrorModal(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Open an external link in the app or in browser.
|
|
*
|
|
* @param href HREF to be opened.
|
|
* @param openIn Open In App value coming from data-open-in attribute.
|
|
* @return Promise resolved when done.
|
|
*/
|
|
protected async openExternalLink(href: string, openIn?: string | null): Promise<void> {
|
|
// It's an external link, we will open with browser. Check if we need to auto-login.
|
|
if (!CoreSites.isLoggedIn()) {
|
|
// Not logged in, cannot auto-login.
|
|
if (this.inApp) {
|
|
CoreUtils.openInApp(href);
|
|
} else {
|
|
CoreUtils.openInBrowser(href, { showBrowserWarning: this.showBrowserWarning });
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
const currentSite = CoreSites.getRequiredCurrentSite();
|
|
|
|
// Check if URL does not have any protocol, so it's a relative URL.
|
|
if (!CoreUrlUtils.isAbsoluteURL(href)) {
|
|
// Add the site URL at the begining.
|
|
if (href.charAt(0) == '/') {
|
|
href = currentSite.getURL() + href;
|
|
} else {
|
|
href = currentSite.getURL() + '/' + href;
|
|
}
|
|
}
|
|
|
|
if (currentSite.isSitePluginFileUrl(href)) {
|
|
// It's a site file. Check if it's being downloaded right now.
|
|
const isDownloading = await CoreFilepool.isFileDownloadingByUrl(currentSite.getId(), href);
|
|
|
|
if (isDownloading) {
|
|
// Wait for the download to finish before opening the file to prevent downloading it twice.
|
|
const modal = await CoreDomUtils.showModalLoading();
|
|
|
|
try {
|
|
const path = await CoreFilepool.downloadUrl(currentSite.getId(), href);
|
|
|
|
return this.openLocalFile(path);
|
|
} catch {
|
|
// Error downloading, just open the original URL.
|
|
} finally {
|
|
modal.dismiss();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.autoLogin == 'yes') {
|
|
if (this.inApp) {
|
|
await currentSite.openInAppWithAutoLogin(href);
|
|
} else {
|
|
await currentSite.openInBrowserWithAutoLogin(href, undefined, { showBrowserWarning: this.showBrowserWarning });
|
|
}
|
|
} else if (this.autoLogin == 'no') {
|
|
if (this.inApp) {
|
|
CoreUtils.openInApp(href);
|
|
} else {
|
|
CoreUtils.openInBrowser(href, { showBrowserWarning: this.showBrowserWarning });
|
|
}
|
|
} else {
|
|
// Priority order is: core-link inApp attribute > forceOpenLinksIn setting > data-open-in HTML attribute.
|
|
let openInApp = this.inApp;
|
|
if (this.inApp === undefined) {
|
|
if (CoreConstants.CONFIG.forceOpenLinksIn == 'browser') {
|
|
openInApp = false;
|
|
} else if (CoreConstants.CONFIG.forceOpenLinksIn == 'app' || openIn == 'app') {
|
|
openInApp = true;
|
|
}
|
|
}
|
|
|
|
if (openInApp) {
|
|
await currentSite.openInAppWithAutoLoginIfSameSite(href);
|
|
} else {
|
|
await currentSite.openInBrowserWithAutoLoginIfSameSite(
|
|
href,
|
|
undefined,
|
|
{ showBrowserWarning: this.showBrowserWarning },
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|