diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 4182b822deeb..4479935607b9 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -68,12 +68,16 @@
/core/templates/services/request-interceptor.service*.ts @oppia/angular-migration-reviewers
/core/templates/services/UpgradedServices.ts @oppia/angular-migration-reviewers
/core/templates/hybrid-router-module-provider*.ts @oppia/angular-migration-reviewers
+/core/templates/utility/hashes.ts @oppia/angular-migration-reviewers
/core/templates/utility/string-utility*.ts @oppia/angular-migration-reviewers
+/proxy.conf.json @oppia/angular-migration-reviewers
+/angular-template-style-url-replacer.webpack-loader.js @oppia/angular-migration-reviewers
+/angular.json @oppia/angular-migration-reviewers
+/src @oppia/frontend-infrastructure-reviewers
# TS typing
/typings/ @oppia/data-and-stability-reviewers
-/tsconfig.json @oppia/data-and-stability-reviewers
-/tsconfig-strict.json @oppia/data-and-stability-reviewers
+/tsconfig*.json @oppia/data-and-stability-reviewers
/scripts/typescript_checks*.py @oppia/data-and-stability-reviewers
@@ -573,6 +577,7 @@
/core/templates/components/ck-editor-helpers/ck-editor-copy-content.service*.ts @oppia/interaction-and-rte-reviewers
/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component.html @oppia/interaction-and-rte-reviewers
/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component*.ts @oppia/interaction-and-rte-reviewers
+/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.module.ts @oppia/interaction-and-rte-reviewers
/core/templates/services/autoplayed-videos.service*.ts @oppia/interaction-and-rte-reviewers
/core/templates/services/external-save.service*.ts @oppia/angular-migration-reviewers
/core/templates/services/external-rte-save.service*.ts @oppia/angular-migration-reviewers
diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml
index 56685545f7dc..7f1d05f9bc73 100644
--- a/.github/workflows/e2e_tests.yml
+++ b/.github/workflows/e2e_tests.yml
@@ -59,7 +59,7 @@ jobs:
# We avoid using ../ or absolute paths because unzip treats these as
# security issues and will refuse to follow them.
run: |
- zip -rqy build_files.zip oppia/third_party oppia_tools oppia/build oppia/webpack_bundles oppia/proto_files oppia/app.yaml oppia/assets/hashes.json oppia/proto_files oppia/extensions/classifiers/proto/* oppia/backend_prod_files
+ zip -rqy build_files.zip oppia/third_party oppia_tools oppia/build oppia/webpack_bundles oppia/proto_files oppia/app.yaml oppia/assets/hashes.json oppia/proto_files oppia/extensions/classifiers/proto/* oppia/backend_prod_files oppia/dist
working-directory: /home/runner/work/oppia
- name: Upload build files artifact
uses: actions/upload-artifact@v3
diff --git a/.gitignore b/.gitignore
index b48eaba4f15b..9e67b269c21d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,10 @@ core/templates/prod/*
webpack_bundles/*
core/tests/.browserstack.env
node_modules/*
+# .angular is temp folder used by angular to put logs and other artefacts.
+.angular/*
+# dist is our angular build folder output.
+dist/*
.coverage*
!.coveragerc
coverage.xml
diff --git a/angular-template-style-url-replacer.webpack-loader.js b/angular-template-style-url-replacer.webpack-loader.js
new file mode 100644
index 000000000000..23ba52203417
--- /dev/null
+++ b/angular-template-style-url-replacer.webpack-loader.js
@@ -0,0 +1,105 @@
+// Copyright 2021 The Oppia Authors. All Rights Reserved.
+//
+// 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.
+
+/**
+ * @fileoverview This is a webpack loader that replaces templateUrl: './html'
+ * with template: require('./html'). This is needed for our webpack based
+ * compilation and not the angular compiler. Angular compiler parses the html
+ * and converts it to js instructions. For the style urls, the angular compiler
+ * uses styleUrls while webpack uses imports. Hence, currently we put the
+ * stylesheet as import and as a styleUrl in the component. Once we have moved
+ * away from separate rtl css files, we will remove the import statements and
+ * just keep styleUrls. Until then, for webpack, we need remove styleUrls for
+ * webpack compilation.
+ */
+
+/**
+ * The regexes are trying to find the templateUrl from the component decorator
+ * Eg:
+ * @Component({
+ * selector: 'oppia-base-content',
+ * templateUrl: './base-content.component.html',
+ * styleUrls: ['./base-content.component.css']
+ * })
+ *
+ * From the above we need to get './base-content.component.html' and
+ * ['./base-content.component.css'].
+ *
+ * After modifications, it will look like:
+ * @Component({
+ * selector: 'oppia-base-content',
+ * template: require('./base-content.component.html'),
+ * styleUrls: []
+ * })
+ * Templates can be found using the regex:
+ * templateUrl[any number of spaces]:[any number of spaces]
+ * [any of '"` that starts a string in javascript]
+ * [match all characters between the quotes][End quotes '"`]
+ * [any number of spaces]
+ * [
+ * ends with a comma or a closing curly bracket depending or wether there are
+ * more items in the decorator or not
+ * ]
+ */
+const TEMPLATE_URL_REGEX = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*([,}]))/gm;
+const STYLES_URL_REGEX = /styleUrls *:(\s*\[[^\]]*?\])/g;
+const VALUE_REGEX = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
+
+/**
+ * This function is only used for templateUrl modifications. From a string this
+ * function extracts the first value inside quotes ('"`).
+ * Example: For a string like: "templateUrl: './base-content.component.html',"
+ * The VALUE_REGEX will match "'./base-content.component.html'" and the first
+ * group is the quote ("'") and the second group is
+ * ""./base-content.component.html"
+ * @param {string} str
+ * @returns Relative url
+ */
+const replaceStringsWithRequiresStatement = (str) => {
+ return str.replace(VALUE_REGEX, function(_, __, url) {
+ return "require('" + url + "')";
+ });
+};
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+const loader = (sourceString) => {
+ // https://webpack.js.org/api/loaders/#thiscacheable
+ // Cacheable is an interface provided by webpack and is used to speed up
+ // the build by caching results.
+ this.cacheable && this.cacheable();
+ /**
+ * The templateURL regex will match something like:
+ * "templateUrl: './base-content.component.html',"
+ */
+ const newSourceString = sourceString.replace(
+ TEMPLATE_URL_REGEX, (_, url) => {
+ return 'template:' + replaceStringsWithRequiresStatement(url);
+ }
+ ).replace(STYLES_URL_REGEX, () => {
+ /**
+ * For the style urls, the angular compiler
+ * uses styleUrls while webpack uses imports. Hence, currently we put the
+ * stylesheet as import and as a styleUrl in the component. Once we have
+ * moved away from separate rtl css files, we will remove the import
+ * statements and just keep styleUrls. Until then, for webpack, we need
+ * remove styleUrls property for webpack compilation.
+ */
+ return 'styleUrl: []';
+ });
+
+ return newSourceString;
+};
+
+
+module.exports = loader;
diff --git a/angular.json b/angular.json
new file mode 100644
index 000000000000..f35e89db6fd4
--- /dev/null
+++ b/angular.json
@@ -0,0 +1,133 @@
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "oppia-angular": {
+ "projectType": "application",
+ "schematics": {
+ "@schematics/angular:component": {
+ "style": "scss"
+ }
+ },
+ "root": "",
+ "sourceRoot": "src",
+ "prefix": "app",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:browser",
+ "options": {
+ "allowedCommonJsDependencies": ["path", "stream", "zlib"],
+ "outputPath": "dist/oppia-angular",
+ "index": "src/index.html",
+ "main": "src/main.ts",
+ "polyfills": "core/templates/Polyfills.ts",
+ "tsConfig": "tsconfig.json",
+ "assets": [
+ "src/favicon.ico",
+ "src/assets"
+ ],
+ "styles": [
+ "core/templates/css/oppia.css",
+ "core/templates/css/oppia-material.css"
+ ],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ],
+ "index": {
+ "input": "src/index.prod.html",
+ "output": "index.html"
+ },
+ "baseHref": "/dist/oppia-angular-prod/",
+ "outputPath": "dist/oppia-angular-prod",
+ "aot": true,
+ "optimization": true,
+ "outputHashing": "all",
+ "sourceMap": false,
+ "namedChunks": false,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true,
+ "budgets": [
+ {
+ "type": "initial",
+ "maximumWarning": "500kb",
+ "maximumError": "2mb"
+ },
+ {
+ "type": "anyComponentStyle",
+ "maximumWarning": "6kb",
+ "maximumError": "28kb"
+ }
+ ]
+ }
+ }
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "options": {
+ "browserTarget": "oppia-angular:build"
+ },
+ "configurations": {
+ "production": {
+ "browserTarget": "oppia-angular:build:production"
+ }
+ }
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "oppia-angular:build"
+ }
+ },
+ "test": {
+ "builder": "@angular-devkit/build-angular:karma",
+ "options": {
+ "main": "src/test.ts",
+ "polyfills": "core/templates/Polyfills.ts",
+ "tsConfig": "tsconfig.spec.json",
+ "karmaConfig": "karma.conf.js",
+ "assets": [
+ "src/favicon.ico",
+ "assets"
+ ],
+ "styles": [
+ ],
+ "scripts": []
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "tsconfig.json"
+ ],
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ },
+ "e2e": {
+ "builder": "@angular-devkit/build-angular:protractor",
+ "options": {
+ "protractorConfig": "e2e/protractor.conf.js",
+ "devServerTarget": "oppia-angular:serve"
+ },
+ "configurations": {
+ "production": {
+ "devServerTarget": "oppia-angular:serve:production"
+ }
+ }
+ }
+ }
+ }
+ },
+ "defaultProject": "oppia-angular"
+}
diff --git a/app_dev.yaml b/app_dev.yaml
index 4db8887d5302..6acabd1322b8 100644
--- a/app_dev.yaml
+++ b/app_dev.yaml
@@ -47,6 +47,11 @@ handlers:
static_dir: webpack_bundles
secure: always
expiration: "0"
+# /dist is the build output folder for angular.
+- url: /dist
+ static_dir: dist/
+ secure: always
+ expiration: "0"
- url: /assets
static_dir: assets
secure: always
diff --git a/core/controllers/base.py b/core/controllers/base.py
index 186a55fde199..3ccbe4e1794b 100755
--- a/core/controllers/base.py
+++ b/core/controllers/base.py
@@ -79,16 +79,26 @@ class ResponseValueDict(TypedDict):
@functools.lru_cache(maxsize=128)
-def load_template(filename: str) -> str:
+def load_template(
+ filename: str, *, template_is_aot_compiled: bool
+) -> str:
"""Return the HTML file contents at filepath.
Args:
filename: str. Name of the requested HTML file.
+ template_is_aot_compiled: bool. Used to determine which bundle to use.
Returns:
str. The HTML file content.
"""
- filepath = os.path.join(feconf.FRONTEND_TEMPLATES_DIR, filename)
+ filepath = os.path.join(
+ (
+ feconf.FRONTEND_AOT_DIR
+ if template_is_aot_compiled
+ else feconf.FRONTEND_TEMPLATES_DIR
+ ),
+ filename
+ )
with utils.open_file(filepath, 'r') as f:
html_text = f.read()
return html_text
@@ -647,7 +657,11 @@ def render_downloadable_file(
super(webapp2.Response, self.response).write(file.getvalue()) # type: ignore[misc] # pylint: disable=bad-super-call
def render_template(
- self, filepath: str, iframe_restriction: Optional[str] = 'DENY'
+ self,
+ filepath: str,
+ iframe_restriction: Optional[str] = 'DENY',
+ *,
+ template_is_aot_compiled: bool = False
) -> None:
"""Prepares an HTML response to be sent to the client.
@@ -659,6 +673,8 @@ def render_template(
DENY: Strictly prevents the template to load in an iframe.
SAMEORIGIN: The template can only be displayed in a frame
on the same origin as the page itself.
+ template_is_aot_compiled: bool. False by default. Use
+ True when the template is compiled by angular AoT compiler.
Raises:
Exception. Invalid X-Frame-Options.
@@ -683,8 +699,9 @@ def render_template(
self.response.expires = 'Mon, 01 Jan 1990 00:00:00 GMT'
self.response.pragma = 'no-cache'
-
- self.response.write(load_template(filepath))
+ self.response.write(load_template(
+ filepath, template_is_aot_compiled=template_is_aot_compiled
+ ))
def _render_exception_json_or_html(
self, return_type: str, values: ResponseValueDict
diff --git a/core/controllers/base_test.py b/core/controllers/base_test.py
index 9c4c1a46b93b..cac453e6e1da 100644
--- a/core/controllers/base_test.py
+++ b/core/controllers/base_test.py
@@ -73,7 +73,11 @@ def test_load_template(self) -> None:
with self.swap(feconf, 'FRONTEND_TEMPLATES_DIR', oppia_root_path):
self.assertIn(
'"Loading | Oppia"',
- base.load_template('oppia-root.mainpage.html'))
+ base.load_template(
+ 'oppia-root.mainpage.html',
+ template_is_aot_compiled=False
+ )
+ )
class UniqueTemplateNamesTests(test_utils.GenericTestBase):
diff --git a/core/controllers/oppia_root.py b/core/controllers/oppia_root.py
index b5d2dcba23c1..ff6b3572ea50 100644
--- a/core/controllers/oppia_root.py
+++ b/core/controllers/oppia_root.py
@@ -53,4 +53,25 @@ class OppiaLightweightRootPage(
@acl_decorators.open_access
def get(self, **kwargs: Dict[str, str]) -> None:
"""Handles GET requests."""
- self.render_template('lightweight-oppia-root.mainpage.html')
+ # The following logic determines which bundle to return. Currently the
+ # AoT bundle doesn't support rtl languages yet. So we switch between
+ # AoT and webpack bundle based on language direction.
+ # The order of preference to determine the language direction is:
+ # 1. Cookies
+ # 2. Url params
+ # In the case we don't find a language direction from the above two,
+ # we default to AoT bundle.
+ # TODO(#16300): Refactor the RTL css generation to add RTL CSS to the
+ # original CSS files instead of creating a new rtl CSS file
+ # NOTE: After the aforementioned issue is solved, the AoT bundle will be
+ # the only bundle that is returned.
+ if self.request.cookies.get('dir') == 'rtl':
+ self.render_template('lightweight-oppia-root.mainpage.html')
+ return
+ if self.request.cookies.get('dir') == 'ltr':
+ self.render_template('index.html', template_is_aot_compiled=True)
+ return
+ if self.request.get('dir') == 'rtl':
+ self.render_template('lightweight-oppia-root.mainpage.html')
+ return
+ self.render_template('index.html', template_is_aot_compiled=True)
diff --git a/core/controllers/oppia_root_test.py b/core/controllers/oppia_root_test.py
index 102a61501e8a..9b46c20d1bd7 100644
--- a/core/controllers/oppia_root_test.py
+++ b/core/controllers/oppia_root_test.py
@@ -33,3 +33,89 @@ def test_oppia_root_page(self) -> None:
'')
else:
response.mustcontain('')
+
+
+class OppiaLightweightRootPageTests(test_utils.GenericTestBase):
+
+ def test_oppia_lightweight_root_page(self) -> None:
+ response = self.get_html_response('/', expected_status_int=200)
+ response.mustcontain(
+ '',
+ '
Loading | Oppia'
+ )
+
+ def test_oppia_lightweight_root_page_with_rtl_lang_param(self) -> None:
+ response = self.get_html_response('/?dir=rtl', expected_status_int=200)
+ response.mustcontain(
+ '',
+ no='Loading | Oppia'
+ )
+
+ def test_oppia_lightweight_root_page_with_ltr_lang_param(self) -> None:
+ response = self.get_html_response('/?dir=ltr', expected_status_int=200)
+ response.mustcontain(
+ '',
+ 'Loading | Oppia'
+ )
+
+ def test_oppia_lightweight_root_page_with_rtl_dir_cookie(self) -> None:
+ self.testapp.set_cookie('dir', 'rtl')
+ response = self.get_html_response('/', expected_status_int=200)
+ response.mustcontain(
+ '',
+ no='Loading | Oppia'
+ )
+
+ def test_oppia_lightweight_root_page_with_ltr_dir_cookie(self) -> None:
+ self.testapp.set_cookie('dir', 'ltr')
+ response = self.get_html_response('/', expected_status_int=200)
+ response.mustcontain(
+ '',
+ 'Loading | Oppia'
+ )
+
+ def test_return_bundle_modifier_precedence(self) -> None:
+ # In case of conflicting cookie and url param values for dir, cookie
+ # is preferred.
+ self.testapp.set_cookie('dir', 'ltr')
+ response = self.get_html_response('/?dir=rtl', expected_status_int=200)
+ response.mustcontain(
+ '',
+ 'Loading | Oppia'
+ )
+
+ self.testapp.set_cookie('dir', 'rtl')
+ response = self.get_html_response('/?dir=ltr', expected_status_int=200)
+ response.mustcontain(
+ '',
+ no='Loading | Oppia'
+ )
+
+ def test_invalid_bundle_modifier_values(self) -> None:
+ # In case of invalid values in cookie but valid query param respect the
+ # param value for dir.
+ self.testapp.set_cookie('dir', 'new_hacker_in_the_block')
+ response = self.get_html_response('/?dir=rtl', expected_status_int=200)
+ response.mustcontain(
+ '',
+ no='Loading | Oppia'
+ )
+
+ self.testapp.set_cookie('dir', 'new_hacker_in_the_block')
+ response = self.get_html_response('/?dir=ltr', expected_status_int=200)
+ response.mustcontain(
+ '',
+ 'Loading | Oppia'
+ )
+ # The bundle modifier precedence guarantees that a valid cookie dir
+ # value will return the correct bundle.
+
+ # When both modifiers are invalid, default to AoT bundle.
+ self.testapp.set_cookie('dir', 'new_hacker_in_the_block')
+ response = self.get_html_response(
+ '/?dir=is_trying_out', expected_status_int=200
+ )
+ response.mustcontain(
+ '',
+ 'Loading | Oppia'
+ )
diff --git a/core/feconf.py b/core/feconf.py
index b47483b4b94c..ec36d072494b 100644
--- a/core/feconf.py
+++ b/core/feconf.py
@@ -112,6 +112,10 @@ def check_dev_mode_is_true() -> None:
FRONTEND_TEMPLATES_DIR = (
os.path.join('webpack_bundles') if constants.DEV_MODE else
os.path.join('build', 'webpack_bundles'))
+# To know more about AOT visit https://angular.io/guide/glossary#aot
+FRONTEND_AOT_DIR = (
+ os.path.join('dist', 'oppia-angular') if constants.DEV_MODE else
+ os.path.join('dist', 'oppia-angular-prod'))
DEPENDENCIES_TEMPLATES_DIR = (
os.path.join(EXTENSIONS_DIR_PREFIX, 'extensions', 'dependencies'))
diff --git a/core/templates/base-components/base-content.component.ts b/core/templates/base-components/base-content.component.ts
index afd1ddca9b37..be81871eccef 100644
--- a/core/templates/base-components/base-content.component.ts
+++ b/core/templates/base-components/base-content.component.ts
@@ -31,13 +31,12 @@ import { SidebarStatusService } from 'services/sidebar-status.service';
import { BackgroundMaskService } from 'services/stateful/background-mask.service';
import { I18nLanguageCodeService } from 'services/i18n-language-code.service';
import { NavigationEnd, Router } from '@angular/router';
-
import './base-content.component.css';
-
@Component({
selector: 'oppia-base-content',
- templateUrl: './base-content.component.html'
+ templateUrl: './base-content.component.html',
+ styleUrls: ['./base-content.component.css']
})
export class BaseContentComponent {
loadingMessage: string = '';
diff --git a/core/templates/base-components/base.module.ts b/core/templates/base-components/base.module.ts
index d131e91e227a..95bd8cfb5024 100644
--- a/core/templates/base-components/base.module.ts
+++ b/core/templates/base-components/base.module.ts
@@ -48,6 +48,7 @@ import {
// Miscellaneous.
import { SmartRouterModule } from 'hybrid-router-module-provider';
import { OppiaAngularRootComponent } from 'components/oppia-angular-root.component';
+import { NgBootstrapModule } from 'modules/ng-boostrap.module';
@NgModule({
imports: [
@@ -55,6 +56,7 @@ import { OppiaAngularRootComponent } from 'components/oppia-angular-root.compone
CookieModule.forChild(),
DirectivesModule,
I18nModule,
+ NgBootstrapModule,
// TODO(#13443): Remove smart router module provider once all pages are
// migrated to angular router.
SmartRouterModule,
diff --git a/core/templates/base-components/oppia-footer.component.ts b/core/templates/base-components/oppia-footer.component.ts
index c453ba8ba1fa..64bc64e1a9a8 100644
--- a/core/templates/base-components/oppia-footer.component.ts
+++ b/core/templates/base-components/oppia-footer.component.ts
@@ -21,13 +21,13 @@ import { downgradeComponent } from '@angular/upgrade/static';
import { PlatformFeatureService } from 'services/platform-feature.service';
import { AppConstants } from 'app.constants';
-
import './oppia-footer.component.css';
@Component({
selector: 'oppia-footer',
- templateUrl: './oppia-footer.component.html'
+ templateUrl: './oppia-footer.component.html',
+ styleUrls: ['./oppia-footer.component.css']
})
export class OppiaFooterComponent {
siteFeedbackFormUrl: string = AppConstants.SITE_FEEDBACK_FORM_URL;
diff --git a/core/templates/combined-tests.spec.ts b/core/templates/combined-tests.spec.ts
index d9bf586c2ddc..b7524998023a 100644
--- a/core/templates/combined-tests.spec.ts
+++ b/core/templates/combined-tests.spec.ts
@@ -43,8 +43,8 @@ import {
// https://webpack.js.org/guides/dependency-management/#context-module-api
// as a reference.
interface RequireContext {
- context(
- directory: string, useSubdirectories: boolean, regExp: RegExp): Context;
+ context: (
+ directory: string, useSubdirectories: boolean, regExp: RegExp) => Context;
}
interface Context {
diff --git a/core/templates/components/button-directives/hint-and-solution-buttons.component.ts b/core/templates/components/button-directives/hint-and-solution-buttons.component.ts
index 49447ddd6b2a..f2a0a10a5d8a 100644
--- a/core/templates/components/button-directives/hint-and-solution-buttons.component.ts
+++ b/core/templates/components/button-directives/hint-and-solution-buttons.component.ts
@@ -34,7 +34,8 @@ import './hint-and-solution-buttons.component.css';
@Component({
selector: 'oppia-hint-and-solution-buttons',
- templateUrl: './hint-and-solution-buttons.component.html'
+ templateUrl: './hint-and-solution-buttons.component.html',
+ styleUrls: ['./hint-and-solution-buttons.component.css']
})
export class HintAndSolutionButtonsComponent implements OnInit, OnDestroy {
directiveSubscriptions = new Subscription();
diff --git a/core/templates/components/button-directives/social-buttons.component.ts b/core/templates/components/button-directives/social-buttons.component.ts
index 2a16d468246e..ed73584b452f 100644
--- a/core/templates/components/button-directives/social-buttons.component.ts
+++ b/core/templates/components/button-directives/social-buttons.component.ts
@@ -26,7 +26,7 @@ import './social-buttons.component.css';
@Component({
selector: 'oppia-social-buttons',
templateUrl: './social-buttons.component.html',
- styleUrls: []
+ styleUrls: ['./social-buttons.component.css']
})
export class SocialButtonsComponent {
androidAppButtonIsShown = (
diff --git a/core/templates/components/checkpoint-celebration-modal/checkpoint-celebration-modal.component.ts b/core/templates/components/checkpoint-celebration-modal/checkpoint-celebration-modal.component.ts
index 699350bed95a..9514c7a83cb0 100644
--- a/core/templates/components/checkpoint-celebration-modal/checkpoint-celebration-modal.component.ts
+++ b/core/templates/components/checkpoint-celebration-modal/checkpoint-celebration-modal.component.ts
@@ -44,6 +44,7 @@ const SCREEN_WIDTH_FOR_STANDARD_SIZED_MESSAGE_MODAL_CUTOFF_PX = 1370;
@Component({
selector: 'oppia-checkpoint-celebration-modal',
templateUrl: './checkpoint-celebration-modal.component.html',
+ styleUrls: ['./checkpoint-celebration-modal.component.css']
})
export class CheckpointCelebrationModalComponent implements OnInit, OnDestroy {
@ViewChild('checkpointCelebrationModalTimer') checkpointTimerTemplateRef!:
diff --git a/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component.ts b/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component.ts
index ce1d6afeff7e..f75071e777eb 100644
--- a/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component.ts
+++ b/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.component.ts
@@ -30,7 +30,7 @@ import { CkEditorCopyContentService } from
})
export class CkEditorCopyToolbarComponent {
constructor(
- private ckEditorCopyContentService: CkEditorCopyContentService,
+ public ckEditorCopyContentService: CkEditorCopyContentService,
@Inject(DOCUMENT) private document: Document
) {
ckEditorCopyContentService.copyModeActive = false;
diff --git a/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.module.ts b/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.module.ts
new file mode 100644
index 000000000000..0cac61265997
--- /dev/null
+++ b/core/templates/components/ck-editor-helpers/ck-editor-copy-toolbar/ck-editor-copy-toolbar.module.ts
@@ -0,0 +1,41 @@
+// Copyright 2021 The Oppia Authors. All Rights Reserved.
+//
+// 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.
+
+/**
+ * @fileoverview Module for the CkEditor4 components.
+ */
+
+import 'core-js/es7/reflect';
+import 'zone.js';
+
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { CkEditorCopyToolbarComponent } from './ck-editor-copy-toolbar.component';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ ],
+ declarations: [
+ CkEditorCopyToolbarComponent
+ ],
+ entryComponents: [
+ CkEditorCopyToolbarComponent
+ ],
+ exports: [
+ CkEditorCopyToolbarComponent
+ ],
+})
+
+export class OppiaCkEditorCopyToolBarModule { }
diff --git a/core/templates/components/common-layout-directives/common-elements/alert-message.component.ts b/core/templates/components/common-layout-directives/common-elements/alert-message.component.ts
index 8eb9da984ce2..372cdef1162a 100644
--- a/core/templates/components/common-layout-directives/common-elements/alert-message.component.ts
+++ b/core/templates/components/common-layout-directives/common-elements/alert-message.component.ts
@@ -20,7 +20,6 @@ import { Component, Input } from '@angular/core';
import { downgradeComponent } from '@angular/upgrade/static';
import { ToastrService } from 'ngx-toastr';
import { AlertsService } from 'services/alerts.service';
-require('ngx-toastr/toastr.css');
export interface MessageObject {
type: string;
diff --git a/core/templates/components/common-layout-directives/common-elements/attribution-guide.component.ts b/core/templates/components/common-layout-directives/common-elements/attribution-guide.component.ts
index a6154504bf4c..34dff15cf189 100644
--- a/core/templates/components/common-layout-directives/common-elements/attribution-guide.component.ts
+++ b/core/templates/components/common-layout-directives/common-elements/attribution-guide.component.ts
@@ -33,7 +33,7 @@ import './attribution-guide.component.css';
@Component({
selector: 'attribution-guide',
templateUrl: './attribution-guide.component.html',
- styleUrls: []
+ styleUrls: ['./attribution-guide.component.css']
})
export class AttributionGuideComponent implements OnInit {
deviceUsedIsMobile: boolean = false;
diff --git a/core/templates/components/common-layout-directives/common-elements/confirm-or-cancel-modal.component.ts b/core/templates/components/common-layout-directives/common-elements/confirm-or-cancel-modal.component.ts
index f6e4a39d0313..4d1a47a54d36 100644
--- a/core/templates/components/common-layout-directives/common-elements/confirm-or-cancel-modal.component.ts
+++ b/core/templates/components/common-layout-directives/common-elements/confirm-or-cancel-modal.component.ts
@@ -22,7 +22,13 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
export class ConfirmOrCancelModal {
constructor(protected modalInstance: NgbActiveModal) {}
- confirm(value: T): void {
+ /**
+ * Function called upon an affirmative user action.
+ * @param value: Value with which the user confirms the action.
+ * Some actions don't require a value when confirming hence this arg is
+ * optional.
+ */
+ confirm(value?: T): void {
this.modalInstance.close(value);
}
diff --git a/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.css b/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.css
index 41238a8dc87d..40d7954a5ad1 100644
--- a/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.css
+++ b/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.css
@@ -3,61 +3,61 @@
compilation, here are sme additional rules that can be added to the CSS files:
https://rtlcss.com/learn/usage-guide/control-directives .
*/
-oppia-top-navigation-bar .oppia-navbar-tab:focus {
+.oppia-top-navigation-bar .oppia-navbar-tab:focus {
background-color: #fff;
color: #00645c;
}
-oppia-top-navigation-bar .arrow-forward {
+.oppia-top-navigation-bar .arrow-forward {
color: #00645c;
font-size: 14px;
margin-left: 4px;
}
-oppia-top-navigation-bar .launch {
+.oppia-top-navigation-bar .launch {
font-size: 12px;
margin-left: 90px;
}
-oppia-top-navigation-bar .underline:hover,
-oppia-top-navigation-bar .underline:focus {
+.oppia-top-navigation-bar .underline:hover,
+.oppia-top-navigation-bar .underline:focus {
border-bottom: 2px solid #333;
transition: 50ms;
}
-oppia-top-navigation-bar .get-involved-dropdown a {
+.oppia-top-navigation-bar .get-involved-dropdown a {
text-decoration: none;
}
-oppia-top-navigation-bar .heading a {
+.oppia-top-navigation-bar .heading a {
color: #212121;
}
-oppia-top-navigation-bar .nav-item-links a {
+.oppia-top-navigation-bar .nav-item-links a {
color: #212121;
text-decoration: none;
}
-oppia-top-navigation-bar .learn-dropdown-menu a,
-oppia-top-navigation-bar .about-dropdown-menu a,
-oppia-top-navigation-bar .get-involved-dropdown a {
+.oppia-top-navigation-bar .learn-dropdown-menu a,
+.oppia-top-navigation-bar .about-dropdown-menu a,
+.oppia-top-navigation-bar .get-involved-dropdown a {
text-decoration: none;
}
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-top-link a {
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-top-link a {
color: #333;
font-family: 'Roboto', sans-serif;
font-size: 16px;
font-weight: 500;
line-height: 18.75px;
}
-oppia-top-navigation-bar .dropdown-menu .innvolved-item:hover a {
+.oppia-top-navigation-bar .dropdown-menu .innvolved-item:hover a {
border-bottom: #333 solid 2px;
transition: 50ms;
}
-oppia-top-navigation-bar .get-involved-dropdown {
+.oppia-top-navigation-bar .get-involved-dropdown {
min-width: 574px;
padding: 0;
}
-oppia-top-navigation-bar .get-involved-dropdown .nav-item {
+.oppia-top-navigation-bar .get-involved-dropdown .nav-item {
width: 100%;
}
-oppia-top-navigation-bar .get-involved-dropdown .heading {
+.oppia-top-navigation-bar .get-involved-dropdown .heading {
color: #333;
font-family: 'Roboto', sans-serif;
font-size: 16px;
@@ -65,23 +65,23 @@ oppia-top-navigation-bar .get-involved-dropdown .heading {
line-height: 18.75px;
padding: 0;
}
-oppia-top-navigation-bar .get-involved-dropdown .language {
+.oppia-top-navigation-bar .get-involved-dropdown .language {
color: #f2994a;
font-size: 18px;
}
-oppia-top-navigation-bar .get-involved-dropdown .volunteer {
+.oppia-top-navigation-bar .get-involved-dropdown .volunteer {
color: #2d9cdb;
font-size: 18px;
}
-oppia-top-navigation-bar .get-involved-dropdown .contact {
+.oppia-top-navigation-bar .get-involved-dropdown .contact {
color: #b4bbc3;
font-size: 18px;
}
-oppia-top-navigation-bar .get-involved-dropdown .fav {
+.oppia-top-navigation-bar .get-involved-dropdown .fav {
color: #eb5757;
font-size: 18px;
}
-oppia-top-navigation-bar .get-involved-dropdown .des {
+.oppia-top-navigation-bar .get-involved-dropdown .des {
color: #767676;
font-family: 'Roboto', sans-serif;
font-size: 14px;
@@ -91,50 +91,50 @@ oppia-top-navigation-bar .get-involved-dropdown .des {
padding-left: 36px;
text-overflow: clip;
}
-oppia-top-navigation-bar .item-border {
+.oppia-top-navigation-bar .item-border {
border-right: 1px solid rgba(33, 33, 33, 0.102);
}
-oppia-top-navigation-bar .top-section {
+.oppia-top-navigation-bar .top-section {
border-bottom: 1px solid rgba(33, 33, 33, 0.102);
}
-oppia-top-navigation-bar .top-section .heading {
+.oppia-top-navigation-bar .top-section .heading {
align-items: center;
display: flex;
}
-oppia-top-navigation-bar .bottom-section .heading {
+.oppia-top-navigation-bar .bottom-section .heading {
align-items: center;
display: flex;
}
-oppia-top-navigation-bar .top-section .heading-text {
+.oppia-top-navigation-bar .top-section .heading-text {
display: inline-block;
margin-left: 7px;
}
-oppia-top-navigation-bar .bottom-section .heading-text {
+.oppia-top-navigation-bar .bottom-section .heading-text {
margin-left: 9px;
}
-oppia-top-navigation-bar .item {
+.oppia-top-navigation-bar .item {
padding: 32px;
width: 287px;
}
-oppia-top-navigation-bar .item:hover,
-oppia-top-navigation-bar .item:focus {
+.oppia-top-navigation-bar .item:hover,
+.oppia-top-navigation-bar .item:focus {
background-color: rgba(33, 33, 33, 0.027);
}
-oppia-top-navigation-bar .dropdown-menu .signin-dropdown {
+.oppia-top-navigation-bar .dropdown-menu .signin-dropdown {
border-radius: 1px;
height: 41px;
width: 200px;
}
-oppia-top-navigation-bar .top-section .item {
+.oppia-top-navigation-bar .top-section .item {
padding-bottom: 27px;
}
-oppia-top-navigation-bar .bottom-section .item {
+.oppia-top-navigation-bar .bottom-section .item {
padding-top: 27px;
}
@@ -146,33 +146,33 @@ oppia-top-navigation-bar .bottom-section .item {
margin-left: 25px;
}
-oppia-top-navigation-bar .dropdown-toggle::after {
+.oppia-top-navigation-bar .dropdown-toggle::after {
margin-left: 0.255em;
margin-right: 0.255em;
}
-oppia-top-navigation-bar .learn-dropdown-menu {
+.oppia-top-navigation-bar .learn-dropdown-menu {
border-radius: 3px;
padding: 0;
}
-oppia-top-navigation-bar .classroom-enabled {
+.oppia-top-navigation-bar .classroom-enabled {
min-width: 688px;
}
-oppia-top-navigation-bar .classroom-disabled {
+.oppia-top-navigation-bar .classroom-disabled {
min-width: 300px;
}
-oppia-top-navigation-bar .language-dropdown {
+.oppia-top-navigation-bar .language-dropdown {
max-height: 60vh;
overflow-y: auto;
}
-oppia-top-navigation-bar .learn {
+.oppia-top-navigation-bar .learn {
height: 66.5px;
}
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-top-link {
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-top-link {
padding: 0;
padding-bottom: 8px;
}
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right-head {
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right-head {
color: #333;
font-family: 'Roboto', sans-serif;
font-size: 12px;
@@ -183,7 +183,7 @@ oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right-head {
padding-bottom: 8px;
text-transform: uppercase;
}
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-links {
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-links {
color: #333;
font-family: 'Roboto', sans-serif;
font-size: 14px;
@@ -194,39 +194,39 @@ oppia-top-navigation-bar .learn-dropdown-menu .nav-item-links {
text-transform: capitalize;
}
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left {
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left {
border-right: 1px solid rgba(0, 0, 0, 0.051);
padding: 32px;
width: 388px;
}
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right {
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right {
min-width: 300px;
padding: 32px;
}
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left:hover,
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right:hover,
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left:focus,
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right:focus {
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left:hover,
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right:hover,
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-left:focus,
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-right:focus {
background-color: rgba(33, 33, 33, 0.027);
}
-oppia-top-navigation-bar .about-dropdown-menu .nav-item a:hover,
-oppia-top-navigation-bar .about-dropdown-menu .nav-item a:focus {
+.oppia-top-navigation-bar .about-dropdown-menu .nav-item a:hover,
+.oppia-top-navigation-bar .about-dropdown-menu .nav-item a:focus {
background-color: rgba(33, 33, 33, 0.027);
}
-oppia-top-navigation-bar .about-dropdown-menu .about-link:hover,
-oppia-top-navigation-bar .about-dropdown-menu .about-link:focus {
+.oppia-top-navigation-bar .about-dropdown-menu .about-link:hover,
+.oppia-top-navigation-bar .about-dropdown-menu .about-link:focus {
color: #009688 !important;
}
-oppia-top-navigation-bar .nav-item-left-head {
+.oppia-top-navigation-bar .nav-item-left-head {
color: #333;
font-family: 'Roboto', sans-serif;
font-size: 16px;
font-weight: 500;
line-height: 18.75px;
}
-oppia-top-navigation-bar .learn-dropdown-menu .nav-content {
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-content {
color: #767676;
font-family: 'Roboto', sans-serif;
font-size: 14px;
@@ -234,7 +234,7 @@ oppia-top-navigation-bar .learn-dropdown-menu .nav-content {
line-height: 20px;
width: 224px;
}
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link {
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link {
color: #00645c;
font-family: 'Inter', 'Roboto', sans-serif;
font-size: 14px;
@@ -243,7 +243,7 @@ oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link {
padding: 0;
padding-top: 16px;
}
-oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link-logged {
+.oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link-logged {
color: #00645c;
font-family: 'Inter', 'Roboto', sans-serif;
font-size: 14px;
@@ -251,7 +251,7 @@ oppia-top-navigation-bar .learn-dropdown-menu .nav-item-bottom-link-logged {
line-height: 16.41px;
padding: 0;
}
-oppia-top-navigation-bar .oppia-navbar-nav .dropdown-menu {
+.oppia-top-navigation-bar .oppia-navbar-nav .dropdown-menu {
border-radius: 3px;
border-top-left-radius: 0;
border-top-right-radius: 0;
@@ -260,15 +260,15 @@ oppia-top-navigation-bar .oppia-navbar-nav .dropdown-menu {
text-align: left;
}
-oppia-top-navigation-bar .language-icon {
+.oppia-top-navigation-bar .language-icon {
font-size: 20px;
vertical-align: text-bottom;
}
-oppia-top-navigation-bar .language-padding-left {
+.oppia-top-navigation-bar .language-padding-left {
padding-left: 26px;
}
-oppia-top-navigation-bar .oppia-nav-right-dropdown {
+.oppia-top-navigation-bar .oppia-nav-right-dropdown {
background-color: #fff;
border-radius: 5px;
border-width: 0;
@@ -284,44 +284,44 @@ oppia-top-navigation-bar .oppia-nav-right-dropdown {
white-space: nowrap;
}
-oppia-top-navigation-bar .oppia-navbar-brand-name:focus {
+.oppia-top-navigation-bar .oppia-navbar-brand-name:focus {
outline: 1px dotted #fff;
outline: auto 5px -webkit-focus-ring-color;
}
-oppia-top-navigation-bar .language-element {
+.oppia-top-navigation-bar .language-element {
border-radius: 0;
box-shadow: 0 0;
color: #34665c;
padding: 10px 0;
}
-oppia-top-navigation-bar .language-element:hover {
+.oppia-top-navigation-bar .language-element:hover {
background-color: rgb(52, 102, 92, 0.1);
}
-oppia-top-navigation-bar .language-element-selected {
+.oppia-top-navigation-bar .language-element-selected {
background-color: rgb(52, 102, 92, 0.1);
}
-oppia-top-navigation-bar .oppia-nav-right-dropdown:focus,
-oppia-top-navigation-bar .oppia-nav-right-dropdown:hover {
+.oppia-top-navigation-bar .oppia-nav-right-dropdown:focus,
+.oppia-top-navigation-bar .oppia-nav-right-dropdown:hover {
box-shadow: 1px 4px 5px 1px rgba(0,0,0,0.1);
}
-oppia-top-navigation-bar .oppia-nav-right-dropdown:active {
+.oppia-top-navigation-bar .oppia-nav-right-dropdown:active {
background-color: #e5e5e5;
box-shadow: none;
transition-duration: 10ms;
}
-oppia-top-navigation-bar .oppia-navbar-button-container.dropdown.oppia-navbar-button-container-extra-info {
+.oppia-top-navigation-bar .oppia-navbar-button-container.dropdown.oppia-navbar-button-container-extra-info {
margin-left: 10px;
}
-oppia-top-navigation-bar .oppia-navbar-button-container .btn.oppia-navbar-button {
+.oppia-top-navigation-bar .oppia-navbar-button-container .btn.oppia-navbar-button {
margin-left: 5px;
margin-right: 0;
}
-oppia-top-navigation-bar .oppia-signin-g-icon {
+.oppia-top-navigation-bar .oppia-signin-g-icon {
float: left;
padding: 10px 15px 10px 0;
}
-oppia-top-navigation-bar .oppia-navbar-dashboard-indicator-text {
+.oppia-top-navigation-bar .oppia-navbar-dashboard-indicator-text {
bottom: 0;
color: white;
font-size: 1.12rem;
@@ -329,7 +329,7 @@ oppia-top-navigation-bar .oppia-navbar-dashboard-indicator-text {
position: absolute;
right: 4px;
}
-oppia-top-navigation-bar .oppia-navbar-role-text {
+.oppia-top-navigation-bar .oppia-navbar-role-text {
bottom: 0;
color: white;
font-size: 11px;
@@ -337,7 +337,7 @@ oppia-top-navigation-bar .oppia-navbar-role-text {
position: absolute;
right: 3px;
}
-oppia-top-navigation-bar .oppia-navbar-dashboard-indicator {
+.oppia-top-navigation-bar .oppia-navbar-dashboard-indicator {
background-color: #f7a541;
border-radius: 20px;
height: 15px;
@@ -346,16 +346,16 @@ oppia-top-navigation-bar .oppia-navbar-dashboard-indicator {
top: 8px;
width: 15px;
}
-oppia-top-navigation-bar .oppia-navbar-menu {
+.oppia-top-navigation-bar .oppia-navbar-menu {
margin-left: 10px;
opacity: 0.9;
outline-color: transparent;
padding-top: 20px;
}
-oppia-top-navigation-bar .oppia-navbar-profile {
+.oppia-top-navigation-bar .oppia-navbar-profile {
float: right;
}
-oppia-top-navigation-bar .oppia-navbar-role-indicator {
+.oppia-top-navigation-bar .oppia-navbar-role-indicator {
background-color: #f7a541;
border-radius: 20px;
bottom: 10px;
@@ -364,95 +364,95 @@ oppia-top-navigation-bar .oppia-navbar-role-indicator {
right: 25px;
width: 15px;
}
-oppia-top-navigation-bar .oppia-navbar-close-icon {
+.oppia-top-navigation-bar .oppia-navbar-close-icon {
color: #fff;
font-size: 22px;
margin-right: 4px;
margin-top: -5px;
}
-oppia-top-navigation-bar .oppia-launch-icon {
+.oppia-top-navigation-bar .oppia-launch-icon {
color: #009688;
font-size: 22px;
}
-.oppia-user-avatar-icon {
+.oppia-top-navigation-bar .oppia-user-avatar-icon {
font-size: 36px;
}
-oppia-top-navigation-bar .oppia-admin-text {
+.oppia-top-navigation-bar .oppia-admin-text {
right: 4px;
}
-oppia-top-navigation-bar .oppia-navbar-button-container-extra-info {
+.oppia-top-navigation-bar .oppia-navbar-button-container-extra-info {
margin-right: 10px;
}
-oppia-top-navigation-bar .oppia-navbar-dropdown-menu {
+.oppia-top-navigation-bar .oppia-navbar-dropdown-menu {
margin-right: 15px;
padding: 0;
}
-oppia-top-navigation-bar .oppia-navbar-dropdown {
+.oppia-top-navigation-bar .oppia-navbar-dropdown {
border: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
margin-top: 0;
}
-oppia-top-navigation-bar .oppia-navbar-profile-picture-container .caret {
+.oppia-top-navigation-bar .oppia-navbar-profile-picture-container .caret {
margin: 4px;
margin-left: 10px;
}
-oppia-top-navigation-bar .oppia-navbar-profile-picture-container {
+.oppia-top-navigation-bar .oppia-navbar-profile-picture-container {
margin-left: 10px;
text-align: right;
}
-oppia-top-navigation-bar .oppia-navbar-dropdown .nav-link {
+.oppia-top-navigation-bar .oppia-navbar-dropdown .nav-link {
color: #009688;
}
-oppia-top-navigation-bar .oppia-navbar-dropdown .nav-link:hover {
+.oppia-top-navigation-bar .oppia-navbar-dropdown .nav-link:hover {
color: #888;
}
-oppia-top-navigation-bar .oppia-signin-text {
+.oppia-top-navigation-bar .oppia-signin-text {
font-family: Roboto, arial, sans-serif;
font-size: 14px;
font-weight: 500;
letter-spacing: .21px;
line-height: 41px;
}
-oppia-top-navigation-bar .oppia-navbar-menu-icon {
+.oppia-top-navigation-bar .oppia-navbar-menu-icon {
color: #fff;
font-size: 22px;
margin-top: -5px;
}
-oppia-top-navigation-bar .nav-mobile-header-text-container {
+.oppia-top-navigation-bar .nav-mobile-header-text-container {
display: none;
}
-oppia-top-navigation-bar .oppia-navbar-clickable-dropdown:hover ul.dropdown-menu {
+.oppia-top-navigation-bar .oppia-navbar-clickable-dropdown:hover ul.dropdown-menu {
display: block;
}
-oppia-top-navigation-bar .oppia-navbar-menu:hover {
+.oppia-top-navigation-bar .oppia-navbar-menu:hover {
opacity: 1;
}
-oppia-top-navigation-bar .oppia-navbar-menu:focus {
+.oppia-top-navigation-bar .oppia-navbar-menu:focus {
outline: 1px dotted #212121;
outline: 5px -webkit-focus-ring-color;
}
-oppia-top-navigation-bar .oppia-nav-link {
+.oppia-top-navigation-bar .oppia-nav-link {
padding: 0;
width: 190px;
}
-oppia-top-navigation-bar .oppia-navbar-dropdown-item {
+.oppia-top-navigation-bar .oppia-navbar-dropdown-item {
display: flex;
flex-direction: column;
gap: 2px;
padding: 30px;
}
-oppia-top-navigation-bar .oppia-navbar-tab-content-heading {
+.oppia-top-navigation-bar .oppia-navbar-tab-content-heading {
color: #212121;
font-size: 16px;
font-weight: 500;
padding: 0;
}
-oppia-top-navigation-bar .oppia-navbar-tab-content-heading:after {
+.oppia-top-navigation-bar .oppia-navbar-tab-content-heading:after {
border-style: solid;
border-width: 0 0 1.59px;
bottom: 1px;
@@ -462,45 +462,45 @@ oppia-top-navigation-bar .oppia-navbar-tab-content-heading:after {
width: 50.5px;
}
-oppia-top-navigation-bar .oppia-navbar-dropdown-heart-icon {
+.oppia-top-navigation-bar .oppia-navbar-dropdown-heart-icon {
color: #eb5757;
font-size: 22px;
padding-right: 6px;
}
-oppia-top-navigation-bar .oppia-navbar-tab-content-desc {
+.oppia-top-navigation-bar .oppia-navbar-tab-content-desc {
color: #767676;
line-height: 20px;
margin-left: 28px;
opacity: 0.9;
}
-oppia-top-navigation-bar .oppia-navbar-dropdown-right {
+.oppia-top-navigation-bar .oppia-navbar-dropdown-right {
left: 0;
min-width: 285px;
padding: 0;
right: auto;
}
-oppia-top-navigation-bar .oppia-navbar-nav.oppia-navbar-profile .profile-dropdown:hover {
+.oppia-top-navigation-bar .oppia-navbar-nav.oppia-navbar-profile .profile-dropdown:hover {
background-color: #fff;
}
-oppia-top-navigation-bar .oppia-navbar-arrow-right {
+.oppia-top-navigation-bar .oppia-navbar-arrow-right {
font-size: 12px;
padding-left: 8px;
padding-top: 1.5px;
}
-oppia-top-navigation-bar .oppia-tick-mark {
+.oppia-top-navigation-bar .oppia-tick-mark {
font-size: 15px;
margin-right: 5px;
padding-left: 5px;
}
@media screen and (max-width: 880px) {
- oppia-top-navigation-bar .oppia-logo-wide {
+ .oppia-top-navigation-bar .oppia-logo-wide {
margin-left: 0;
}
}
@media screen and (max-width: 840px) {
- oppia-top-navigation-bar .desktop-view-language-code {
+ .oppia-top-navigation-bar .desktop-view-language-code {
display: none;
}
}
@@ -513,7 +513,7 @@ oppia-top-navigation-bar .oppia-tick-mark {
}
@media screen and (min-width: 300px) and (max-width: 768px) {
- .nav-mobile-header-text-container {
+ .oppia-top-navigation-bar .nav-mobile-header-text-container {
display: inline-block;
font-size: 1px;
margin-top: 2px;
@@ -521,13 +521,13 @@ oppia-top-navigation-bar .oppia-tick-mark {
text-overflow: ellipsis;
width: 50%;
}
- oppia-top-navigation-bar .nav-mobile-header-editor {
+ .oppia-top-navigation-bar .nav-mobile-header-editor {
color: #fff;
display: block;
font-family: Roboto, arial, sans-serif;
font-size: 19px;
}
- oppia-top-navigation-bar .nav-mobile-header-text {
+ .oppia-top-navigation-bar .nav-mobile-header-text {
color: #fff;
display: inline-block;
font-size: 15px;
@@ -537,4 +537,11 @@ oppia-top-navigation-bar .oppia-tick-mark {
white-space: nowrap;
width: 100%;
}
+ @media screen and (max-width: 600px) {
+ .oppia-top-navigation-bar .oppia-logo {
+ left: 0;
+ margin-left: 60px;
+ position: absolute;
+ }
+ }
}
diff --git a/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.html b/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.html
index d511abd3d6ba..6167fc9028bb 100644
--- a/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.html
+++ b/core/templates/components/common-layout-directives/navigation-bars/top-navigation-bar.component.html
@@ -30,7 +30,7 @@
-