MOBILE-4059 core: Separate error details in alerts
parent
238dc458fc
commit
11fea266e9
|
@ -1,4 +1,5 @@
|
|||
module.exports = {
|
||||
framework: '@storybook/angular',
|
||||
addons: ['@storybook/addon-controls'],
|
||||
stories: ['../src/**/*.stories.ts'],
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import '!style-loader!css-loader!sass-loader!../src/theme/theme.design-system.scss';
|
||||
import '!style-loader!css-loader!sass-loader!./styles.scss';
|
||||
|
||||
export const parameters = {
|
||||
layout: 'centered',
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
.core-error-info {
|
||||
max-width: 300px;
|
||||
}
|
|
@ -13,7 +13,10 @@ module.exports = {
|
|||
'^.+\\.(ts|html)$': 'ts-jest',
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!@ionic-native|@ionic)'],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/src/' }),
|
||||
moduleNameMapper: {
|
||||
...pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/src/' }),
|
||||
'^!raw-loader!.*': 'jest-raw-loader',
|
||||
},
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsConfig: './tsconfig.test.json',
|
||||
|
|
|
@ -5677,6 +5677,22 @@
|
|||
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.11.0.tgz",
|
||||
"integrity": "sha512-/IubCWhVXCguyMUp/3zGrg3c882+RJNg/zpiKfyfJL3kRCOwe+/MD8OoAXVGdd+xAohZKIi1Ik+EHFlsptsjLg=="
|
||||
},
|
||||
"@storybook/addon-controls": {
|
||||
"version": "6.1.21",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addon-controls/-/addon-controls-6.1.21.tgz",
|
||||
"integrity": "sha512-IJgZWD2E9eLKj8DJLA9lT63N4jPfVneFJ05gnPco01ZJCEiDAo7babP5Ns2UTJDUaQEtX0m04UoIkidcteWKsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@storybook/addons": "6.1.21",
|
||||
"@storybook/api": "6.1.21",
|
||||
"@storybook/client-api": "6.1.21",
|
||||
"@storybook/components": "6.1.21",
|
||||
"@storybook/node-logger": "6.1.21",
|
||||
"@storybook/theming": "6.1.21",
|
||||
"core-js": "^3.0.1",
|
||||
"ts-dedent": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@storybook/addons": {
|
||||
"version": "6.1.21",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.1.21.tgz",
|
||||
|
@ -21473,6 +21489,12 @@
|
|||
"ts-jest": "26.x"
|
||||
}
|
||||
},
|
||||
"jest-raw-loader": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-raw-loader/-/jest-raw-loader-1.0.1.tgz",
|
||||
"integrity": "sha512-g9oaAjeC4/rIJk1Wd3RxVbOfMizowM7LSjEJqa4R9qDX0OjQNABXOhH+GaznUp+DjTGVPi2vPPbQXyX87DOnYg==",
|
||||
"dev": true
|
||||
},
|
||||
"jest-regex-util": {
|
||||
"version": "26.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz",
|
||||
|
|
|
@ -141,6 +141,7 @@
|
|||
"@angular/language-service": "~10.0.14",
|
||||
"@ionic/angular-toolkit": "^2.3.3",
|
||||
"@ionic/cli": "^6.19.0",
|
||||
"@storybook/addon-controls": "~6.1",
|
||||
"@storybook/angular": "~6.1",
|
||||
"@types/faker": "^5.1.3",
|
||||
"@types/node": "^12.12.64",
|
||||
|
@ -172,6 +173,7 @@
|
|||
"gulp-slash": "^1.1.3",
|
||||
"jest": "^26.5.2",
|
||||
"jest-preset-angular": "^8.3.1",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"jsonc-parser": "^2.3.1",
|
||||
"minimatch": "^5.1.0",
|
||||
"native-run": "^1.4.0",
|
||||
|
|
|
@ -1472,6 +1472,7 @@
|
|||
"core.cancel": "moodle",
|
||||
"core.cannotconnect": "local_moodlemobileapp",
|
||||
"core.cannotconnecttrouble": "local_moodlemobileapp",
|
||||
"core.cannotconnecttroublewithoutsupport": "local_moodlemobileapp",
|
||||
"core.cannotconnectverify": "local_moodlemobileapp",
|
||||
"core.cannotdownloadfiles": "local_moodlemobileapp",
|
||||
"core.cannotinstallapk": "local_moodlemobileapp",
|
||||
|
@ -1697,7 +1698,10 @@
|
|||
"core.endonesteptour": "tool_usertours",
|
||||
"core.error": "moodle",
|
||||
"core.errorchangecompletion": "local_moodlemobileapp",
|
||||
"core.errorcode": "local_moodlemobileapp",
|
||||
"core.errordeletefile": "local_moodlemobileapp",
|
||||
"core.errordetailshide": "local_moodlemobileapp",
|
||||
"core.errordetailsshow": "local_moodlemobileapp",
|
||||
"core.errordownloading": "local_moodlemobileapp",
|
||||
"core.errordownloadingsomefiles": "local_moodlemobileapp",
|
||||
"core.errorfileexistssamename": "local_moodlemobileapp",
|
||||
|
|
|
@ -29,7 +29,7 @@ export class CoreSiteError extends CoreError {
|
|||
siteConfig?: CoreSitePublicConfigResponse;
|
||||
|
||||
constructor(options: CoreSiteErrorOptions) {
|
||||
super(options.message);
|
||||
super(getErrorMessage(options));
|
||||
|
||||
this.errorcode = options.errorcode;
|
||||
this.errorDetails = options.errorDetails;
|
||||
|
@ -67,8 +67,26 @@ export class CoreSiteError extends CoreError {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get message to use in the error.
|
||||
*
|
||||
* @param options Error options.
|
||||
* @returns Error message.
|
||||
*/
|
||||
function getErrorMessage(options: CoreSiteErrorOptions): string {
|
||||
if (
|
||||
options.contactSupport &&
|
||||
(!options.siteConfig || !CoreUserSupport.canContactSupport(options.siteConfig))
|
||||
) {
|
||||
return options.fallbackMessage ?? options.message;
|
||||
}
|
||||
|
||||
return options.message;
|
||||
}
|
||||
|
||||
export type CoreSiteErrorOptions = {
|
||||
message: string;
|
||||
fallbackMessage?: string; // Message to use if contacting support was intended but isn't possible.
|
||||
errorcode?: string;
|
||||
errorDetails?: string;
|
||||
critical?: boolean; // Whether the error is important enough to abort the operation.
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<!--
|
||||
The markup for this component is rendered dynamically using the static render() method
|
||||
instead of using Angular's engine. The reason for using this approach is that this
|
||||
allows injecting this component into HTML directly, rather than requiring Angular
|
||||
to control its lifecycle.
|
||||
-->
|
|
@ -0,0 +1,88 @@
|
|||
.core-error-info {
|
||||
background: var(--gray-200);
|
||||
border-radius: var(--small-radius);
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--gray-900);
|
||||
|
||||
p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.core-error-info--content {
|
||||
padding: var(--spacing-2) var(--spacing-2) 0 var(--spacing-2);
|
||||
|
||||
.core-error-info--code {
|
||||
font-size: var(--font-size-normal);
|
||||
}
|
||||
|
||||
.core-error-info--details {
|
||||
color: var(--gray-500);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.core-error-info--checkbox {
|
||||
display: none;
|
||||
|
||||
& + .core-error-info--content {
|
||||
max-height: calc(var(--font-size-sm) + 2 * var(--spacing-2));
|
||||
overflow: hidden;
|
||||
transition: max-height 600ms ease-in-out;
|
||||
|
||||
& + .core-error-info--toggle {
|
||||
display: flex;
|
||||
padding: var(--spacing-2);
|
||||
min-height: var(--a11y-min-target-size);
|
||||
align-items: center;
|
||||
|
||||
span {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
width: 11px;
|
||||
}
|
||||
|
||||
.core-error-info--hide-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&:checked + .core-error-info--content {
|
||||
max-height: 150px;
|
||||
|
||||
& + .core-error-info--toggle .core-error-info--hide-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
& + .core-error-info--toggle .core-error-info--show-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.has-error-code .core-error-info--checkbox {
|
||||
|
||||
& + .core-error-info--content {
|
||||
max-height: calc(var(--font-size-normal) + 2 * var(--spacing-2));
|
||||
}
|
||||
|
||||
&:checked + .core-error-info--content {
|
||||
max-height: 170px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
// (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 { Component, ElementRef, Input, OnChanges, OnInit } from '@angular/core';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreForms } from '@singletons/form';
|
||||
import ChevronUpSVG from '!raw-loader!ionicons/dist/svg/chevron-up.svg';
|
||||
import ChevronDownSVG from '!raw-loader!ionicons/dist/svg/chevron-down.svg';
|
||||
|
||||
/**
|
||||
* Component to show error details.
|
||||
*
|
||||
* Given that this component has to be injected dynamically in some situations (for example, error alerts),
|
||||
* it can be rendered using the static render() method to get the raw HTML.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-error-info',
|
||||
templateUrl: 'core-error-info.html',
|
||||
styleUrls: ['error-info.scss'],
|
||||
})
|
||||
export class CoreErrorInfoComponent implements OnInit, OnChanges {
|
||||
|
||||
/**
|
||||
* Render an instance of the component into an HTML string.
|
||||
*
|
||||
* @param errorDetails Error details.
|
||||
* @param errorCode Error code.
|
||||
* @returns Component HTML.
|
||||
*/
|
||||
static render(errorDetails: string, errorCode?: string): string {
|
||||
const toggleId = CoreForms.uniqueId('error-info-toggle');
|
||||
const errorCodeLabel = Translate.instant('core.errorcode');
|
||||
const hideDetailsLabel = Translate.instant('core.errordetailshide');
|
||||
const showDetailsLabel = Translate.instant('core.errordetailsshow');
|
||||
|
||||
return `
|
||||
<div class="core-error-info ${errorCode ? 'has-error-code' : ''}">
|
||||
<input id="${toggleId}" type="checkbox" class="core-error-info--checkbox" />
|
||||
<div class="core-error-info--content">
|
||||
${errorCode ? `<p class="core-error-info--code"><strong>${errorCodeLabel}: ${errorCode}</strong></p>` : ''}
|
||||
<p class="core-error-info--details">${errorDetails}</p>
|
||||
</div>
|
||||
<label for="${toggleId}" class="core-error-info--toggle" aria-hidden="true">
|
||||
<span class="core-error-info--hide-content">
|
||||
${hideDetailsLabel}
|
||||
${ChevronUpSVG}
|
||||
</span>
|
||||
<span class="core-error-info--show-content">
|
||||
${showDetailsLabel}
|
||||
${ChevronDownSVG}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@Input() errorDetails!: string;
|
||||
@Input() errorCode?: string;
|
||||
|
||||
constructor(private element: ElementRef) {}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnChanges(): void {
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render component html in the element created by Angular.
|
||||
*/
|
||||
private render(): void {
|
||||
this.element.nativeElement.innerHTML = CoreErrorInfoComponent.render(this.errorDetails, this.errorCode);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// (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 { Meta, moduleMetadata, Story } from '@storybook/angular';
|
||||
|
||||
import { story } from '@/storybook/utils/helpers';
|
||||
import { StorybookModule } from '@/storybook/storybook.module';
|
||||
|
||||
import { CoreErrorInfoComponent } from '@components/error-info/error-info';
|
||||
|
||||
interface Args {
|
||||
errorCode: string;
|
||||
errorDetails: string;
|
||||
}
|
||||
|
||||
export default <Meta<Args>> {
|
||||
title: 'Core/Error Info',
|
||||
component: CoreErrorInfoComponent,
|
||||
decorators: [
|
||||
moduleMetadata({
|
||||
declarations: [CoreErrorInfoComponent],
|
||||
imports: [StorybookModule],
|
||||
}),
|
||||
],
|
||||
args: {
|
||||
errorCode: '',
|
||||
errorDetails:
|
||||
'AJAX endpoint not found. ' +
|
||||
'This can happen if the Moodle site is too old or it blocks access to this endpoint. ' +
|
||||
'The Moodle app only supports Moodle systems 3.5 onwards.',
|
||||
},
|
||||
};
|
||||
|
||||
const Template: Story<Args> = (args) => ({
|
||||
component: CoreErrorInfoComponent,
|
||||
props: args,
|
||||
});
|
||||
|
||||
export const Primary = story<Args>(Template);
|
|
@ -42,6 +42,7 @@ import { CoreForms } from '@singletons/form';
|
|||
import { AlertButton } from '@ionic/core';
|
||||
import { CoreSiteError } from '@classes/errors/siteerror';
|
||||
import { CoreUserSupport } from '@features/user/services/support';
|
||||
import { CoreErrorInfoComponent } from '@components/error-info/error-info';
|
||||
|
||||
/**
|
||||
* Site (url) chooser when adding a new site.
|
||||
|
@ -382,7 +383,7 @@ export class CoreLoginSitePage implements OnInit {
|
|||
* @param url The URL the user was trying to connect to.
|
||||
* @param error Error to display.
|
||||
*/
|
||||
protected showLoginIssue(url: string | null, error: CoreError): void {
|
||||
protected async showLoginIssue(url: string | null, error: CoreError): Promise<void> {
|
||||
let errorMessage = CoreDomUtils.getErrorMessage(error);
|
||||
let siteExists = false;
|
||||
let supportPageUrl: string | null = null;
|
||||
|
@ -396,7 +397,12 @@ export class CoreLoginSitePage implements OnInit {
|
|||
errorCode = error.errorcode;
|
||||
}
|
||||
|
||||
if (errorMessage == Translate.instant('core.cannotconnecttrouble')) {
|
||||
if (
|
||||
!siteExists && (
|
||||
errorMessage === Translate.instant('core.cannotconnecttrouble') ||
|
||||
errorMessage === Translate.instant('core.cannotconnecttroublewithoutsupport')
|
||||
)
|
||||
) {
|
||||
const found = this.sites.find((site) => site.url == url);
|
||||
|
||||
if (!found) {
|
||||
|
@ -404,10 +410,14 @@ export class CoreLoginSitePage implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
let message = '<p>' + errorMessage + '</p>';
|
||||
errorMessage = '<p>' + errorMessage + '</p>';
|
||||
if (!siteExists && url) {
|
||||
const fullUrl = CoreUrlUtils.isAbsoluteURL(url) ? url : 'https://' + url;
|
||||
message += '<p padding><a href="' + fullUrl + '" core-link>' + url + '</a></p>';
|
||||
errorMessage += '<p padding><a href="' + fullUrl + '" core-link>' + url + '</a></p>';
|
||||
}
|
||||
|
||||
if (errorDetails) {
|
||||
errorMessage += '<div class="core-error-info-container"></div>';
|
||||
}
|
||||
|
||||
const buttons: AlertButton[] = [
|
||||
|
@ -432,11 +442,19 @@ export class CoreLoginSitePage implements OnInit {
|
|||
];
|
||||
|
||||
// @TODO: Remove CoreSite.MINIMUM_MOODLE_VERSION, not used on translations since 3.9.0.
|
||||
CoreDomUtils.showAlertWithOptions({
|
||||
const alertElement = await CoreDomUtils.showAlertWithOptions({
|
||||
header: Translate.instant('core.cannotconnect', { $a: CoreSite.MINIMUM_MOODLE_VERSION }),
|
||||
message,
|
||||
message: errorMessage,
|
||||
buttons,
|
||||
});
|
||||
|
||||
if (errorDetails) {
|
||||
const containerElement = alertElement.querySelector('.core-error-info-container');
|
||||
|
||||
if (containerElement) {
|
||||
containerElement.innerHTML = CoreErrorInfoComponent.render(errorDetails, errorCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"cancel": "Cancel",
|
||||
"cannotconnect": "Cannot connect",
|
||||
"cannotconnecttrouble": "We're having trouble connecting to your site.",
|
||||
"cannotconnecttroublewithoutsupport": "We're having trouble connecting to your site, please contact your institution.",
|
||||
"cannotconnectverify": "<strong>Please check the address is correct.</strong>",
|
||||
"cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.",
|
||||
"cannotinstallapk": "For security reasons, you can't install unknown apps on your device from this app. Please open the file using a browser.",
|
||||
|
@ -101,7 +102,10 @@
|
|||
"endonesteptour": "Got it",
|
||||
"error": "Error",
|
||||
"errorchangecompletion": "An error occurred while changing the completion status. Please try again.",
|
||||
"errorcode": "Error code",
|
||||
"errordeletefile": "Error deleting the file. Please try again.",
|
||||
"errordetailshide": "Hide error details",
|
||||
"errordetailsshow": "Show error details",
|
||||
"errordownloading": "Error downloading file.",
|
||||
"errordownloadingsomefiles": "Error downloading files. Some files might be missing.",
|
||||
"errorfileexistssamename": "A file with this name already exists.",
|
||||
|
|
|
@ -342,6 +342,7 @@ export class CoreSitesProvider {
|
|||
errorDetails,
|
||||
siteConfig,
|
||||
message: Translate.instant('core.cannotconnecttrouble'),
|
||||
fallbackMessage: Translate.instant('core.cannotconnecttroublewithoutsupport'),
|
||||
critical: true,
|
||||
contactSupport: true,
|
||||
});
|
||||
|
|
|
@ -20,6 +20,8 @@ import { CoreEventFormAction, CoreEvents } from '@singletons/events';
|
|||
*/
|
||||
export class CoreForms {
|
||||
|
||||
private static formIds: Record<string, number> = {};
|
||||
|
||||
/**
|
||||
* Get the data from a form. It will only collect elements that have a name.
|
||||
*
|
||||
|
@ -93,6 +95,18 @@ export class CoreForms {
|
|||
}, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique id for a form input using the given name.
|
||||
*
|
||||
* @param name Form input name.
|
||||
* @returns Unique id.
|
||||
*/
|
||||
static uniqueId(name: string): string {
|
||||
const count = this.formIds[name] ?? 0;
|
||||
|
||||
return `${name}-${this.formIds[name] = count + 1}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type CoreFormFields<T = unknown> = Record<string, T>;
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
html {
|
||||
|
||||
// Spacing
|
||||
@for $i from 0 to 13 {
|
||||
--spacing-#{$i}: #{$i*4}px;
|
||||
}
|
||||
|
||||
// Font sizes
|
||||
--font-size-sm: 12px;
|
||||
--font-size-normal: 14px;
|
||||
|
||||
// Radiuses
|
||||
--small-radius: 4px;
|
||||
--medium-radius: 8px;
|
||||
--big-radius: 16px;
|
||||
--huge-radius: 24px;
|
||||
|
||||
// A11y
|
||||
--a11y-min-target-size: 44px;
|
||||
|
||||
}
|
|
@ -48,18 +48,12 @@ html {
|
|||
}
|
||||
|
||||
// Accessibility vars.
|
||||
--a11y-min-target-size: 44px;
|
||||
--a11y-focus-color: var(--primary);
|
||||
--a11y-focus-width: 2px;
|
||||
--zoom-level: 100%;
|
||||
|
||||
--small-radius: 4px;
|
||||
--medium-radius: 8px;
|
||||
--big-radius: 16px;
|
||||
--huge-radius: 24px;
|
||||
|
||||
--text-color: #{$text-color};
|
||||
--text-size: 14px;
|
||||
--text-size: var(--font-size-normal);
|
||||
--background-color: #{$background-color};
|
||||
--stroke: var(--gray-300);
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
@import "./theme.light.scss";
|
||||
@import "./theme.dark.scss";
|
||||
@import "./theme.custom.scss";
|
||||
@import "./theme.design-system.scss";
|
||||
@import "./theme.base.scss";
|
||||
|
||||
/* Components */
|
||||
|
@ -24,6 +25,7 @@
|
|||
@import "./components/format-text.scss";
|
||||
@import "./components/rubrics.scss";
|
||||
@import "./components/mod-label.scss";
|
||||
@import "../core/components/error-info/error-info.scss";
|
||||
|
||||
/* Some styles from 3rd party libraries. */
|
||||
@import "./bootstrap.scss";
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
// (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.
|
||||
|
||||
declare module '!raw-loader!*' {
|
||||
const contents: string;
|
||||
|
||||
export = contents;
|
||||
}
|
Loading…
Reference in New Issue