From f5cfda53a59eec1e429686c96c5e091506f86094 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 11:05:15 +0200 Subject: [PATCH 01/23] MOBILE-2201 tag: List component --- scripts/langindex.json | 1 + src/app/app.module.ts | 2 + src/assets/lang/en.json | 1 + src/core/tag/components/components.module.ts | 40 +++++++++++ .../tag/components/list/core-tag-list.html | 3 + src/core/tag/components/list/list.scss | 7 ++ src/core/tag/components/list/list.ts | 45 ++++++++++++ src/core/tag/lang/en.json | 3 + src/core/tag/providers/tag.ts | 70 +++++++++++++++++++ src/core/tag/tag.module.ts | 28 ++++++++ 10 files changed, 200 insertions(+) create mode 100644 src/core/tag/components/components.module.ts create mode 100644 src/core/tag/components/list/core-tag-list.html create mode 100644 src/core/tag/components/list/list.scss create mode 100644 src/core/tag/components/list/list.ts create mode 100644 src/core/tag/lang/en.json create mode 100644 src/core/tag/providers/tag.ts create mode 100644 src/core/tag/tag.module.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index 5351ef509..89bf47b41 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1793,6 +1793,7 @@ "core.submit": "moodle", "core.success": "moodle", "core.tablet": "local_moodlemobileapp", + "core.tag.tags": "moodle", "core.teachers": "moodle", "core.thereisdatatosync": "local_moodlemobileapp", "core.thisdirection": "langconfig", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 6969e01bc..f908025de 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -81,6 +81,7 @@ import { CoreQuestionModule } from '@core/question/question.module'; import { CoreCommentsModule } from '@core/comments/comments.module'; import { CoreBlockModule } from '@core/block/block.module'; import { CoreRatingModule } from '@core/rating/rating.module'; +import { CoreTagModule } from '@core/tag/tag.module'; // Addon modules. import { AddonBadgesModule } from '@addon/badges/badges.module'; @@ -223,6 +224,7 @@ export const CORE_PROVIDERS: any[] = [ CoreBlockModule, CoreRatingModule, CorePushNotificationsModule, + CoreTagModule, AddonBadgesModule, AddonBlogModule, AddonCalendarModule, diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 21810db95..0dc92ff13 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1793,6 +1793,7 @@ "core.submit": "Submit", "core.success": "Success", "core.tablet": "Tablet", + "core.tag.tags": "Tags", "core.teachers": "Teachers", "core.thereisdatatosync": "There are offline {{$a}} to be synchronised.", "core.thisdirection": "ltr", diff --git a/src/core/tag/components/components.module.ts b/src/core/tag/components/components.module.ts new file mode 100644 index 000000000..c2e07f85b --- /dev/null +++ b/src/core/tag/components/components.module.ts @@ -0,0 +1,40 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreTagListComponent } from './list/list'; +import { CoreDirectivesModule } from '@directives/directives.module'; + +@NgModule({ + declarations: [ + CoreTagListComponent + ], + imports: [ + CommonModule, + IonicModule, + CoreDirectivesModule, + TranslateModule.forChild() + ], + providers: [ + ], + exports: [ + CoreTagListComponent + ], + entryComponents: [ + ] +}) +export class CoreTagComponentsModule {} diff --git a/src/core/tag/components/list/core-tag-list.html b/src/core/tag/components/list/core-tag-list.html new file mode 100644 index 000000000..7e6372e20 --- /dev/null +++ b/src/core/tag/components/list/core-tag-list.html @@ -0,0 +1,3 @@ + + {{ tag.rawname }} + diff --git a/src/core/tag/components/list/list.scss b/src/core/tag/components/list/list.scss new file mode 100644 index 000000000..569d645d6 --- /dev/null +++ b/src/core/tag/components/list/list.scss @@ -0,0 +1,7 @@ +ion-app.app-root core-tag-list { + line-height: 1.6; + + ion-badge { + cursor: pointer; + } +} diff --git a/src/core/tag/components/list/list.ts b/src/core/tag/components/list/list.ts new file mode 100644 index 000000000..6abbf3d7f --- /dev/null +++ b/src/core/tag/components/list/list.ts @@ -0,0 +1,45 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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, Input, Optional } from '@angular/core'; +import { NavController } from 'ionic-angular'; +import { CoreTagItem } from '@core/tag/providers/tag'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; + +/** + * Component that displays the list of tags of an item. + */ +@Component({ + selector: 'core-tag-list', + templateUrl: 'core-tag-list.html' +}) +export class CoreTagListComponent { + @Input() tags: CoreTagItem[]; + + constructor(private navCtrl: NavController, @Optional() private svComponent: CoreSplitViewComponent) {} + + /** + * Go to tag index page. + */ + openTag(tag: CoreTagItem): void { + const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl; + const params = { + tagId: tag.id, + tagName: tag.rawname, + collectionId: tag.tagcollid, + fromContextId: tag.taginstancecontextid + }; + navCtrl.push('CoreTagIndexPage', params); + } +} diff --git a/src/core/tag/lang/en.json b/src/core/tag/lang/en.json new file mode 100644 index 000000000..fec56bb36 --- /dev/null +++ b/src/core/tag/lang/en.json @@ -0,0 +1,3 @@ +{ + "tags": "Tags" +} diff --git a/src/core/tag/providers/tag.ts b/src/core/tag/providers/tag.ts new file mode 100644 index 000000000..fdcdaf189 --- /dev/null +++ b/src/core/tag/providers/tag.ts @@ -0,0 +1,70 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable } from '@angular/core'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreSite } from '@classes/site'; + +/** + * Structure of a tag item returned by WS. + */ +export interface CoreTagItem { + id: number; + name: string; + rawname: string; + isstandard: boolean; + tagcollid: number; + taginstanceid: number; + taginstancecontextid: number; + itemid: number; + ordering: number; + flag: number; +} + +/** + * Service to handle tags. + */ +@Injectable() +export class CoreTagProvider { + + constructor(private sitesProvider: CoreSitesProvider) {} + + /** + * Check whether tags are available in a certain site. + * + * @param {string} [siteId] Site Id. If not defined, use current site. + * @return {Promise} Promise resolved with true if available, resolved with false otherwise. + * @since 3.7 + */ + areTagsAvailable(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return this.areTagsAvailableInSite(site); + }); + } + + /** + * Check whether tags are available in a certain site. + * + * @param {CoreSite} [site] Site. If not defined, use current site. + * @return {boolean} True if available. + */ + areTagsAvailableInSite(site?: CoreSite): boolean { + site = site || this.sitesProvider.getCurrentSite(); + + return site.wsAvailable('core_tag_get_tagindex_per_area') && + site.wsAvailable('core_tag_get_tag_cloud') && + site.wsAvailable('core_tag_get_tag_collections') && + !site.isFeatureDisabled('NoDelegate_CoreTag'); + } +} diff --git a/src/core/tag/tag.module.ts b/src/core/tag/tag.module.ts new file mode 100644 index 000000000..eaa2f9e81 --- /dev/null +++ b/src/core/tag/tag.module.ts @@ -0,0 +1,28 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { NgModule } from '@angular/core'; +import { CoreTagProvider } from './providers/tag'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + CoreTagProvider, + ] +}) +export class CoreTagModule { +} From 2ea97b0840beb4b0d27304863e20f67829a2d002 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 11:13:50 +0200 Subject: [PATCH 02/23] MOBILE-2201 blog: Display tags in blog posts --- src/addon/blog/components/components.module.ts | 4 +++- src/addon/blog/components/entries/addon-blog-entries.html | 4 ++++ src/addon/blog/components/entries/entries.ts | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/addon/blog/components/components.module.ts b/src/addon/blog/components/components.module.ts index 0e56fcc3f..efba08d7d 100644 --- a/src/addon/blog/components/components.module.ts +++ b/src/addon/blog/components/components.module.ts @@ -20,6 +20,7 @@ import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; import { CoreCommentsComponentsModule } from '@core/comments/components/components.module'; +import { CoreTagComponentsModule } from '@core/tag/components/components.module'; import { AddonBlogEntriesComponent } from './entries/entries'; @NgModule({ @@ -33,7 +34,8 @@ import { AddonBlogEntriesComponent } from './entries/entries'; CoreComponentsModule, CoreDirectivesModule, CorePipesModule, - CoreCommentsComponentsModule + CoreCommentsComponentsModule, + CoreTagComponentsModule ], providers: [ ], diff --git a/src/addon/blog/components/entries/addon-blog-entries.html b/src/addon/blog/components/entries/addon-blog-entries.html index 670e4d276..690930091 100644 --- a/src/addon/blog/components/entries/addon-blog-entries.html +++ b/src/addon/blog/components/entries/addon-blog-entries.html @@ -29,6 +29,10 @@ + +
{{ 'core.tag.tags' | translate }}:
+ +
diff --git a/src/addon/blog/components/entries/entries.ts b/src/addon/blog/components/entries/entries.ts index b66db02a3..a0e4bd3da 100644 --- a/src/addon/blog/components/entries/entries.ts +++ b/src/addon/blog/components/entries/entries.ts @@ -19,6 +19,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreUserProvider } from '@core/user/providers/user'; import { AddonBlogProvider } from '../../providers/blog'; import { CoreCommentsProvider } from '@core/comments/providers/comments'; +import { CoreTagProvider } from '@core/tag/providers/tag'; /** * Component that displays the blog entries. @@ -49,10 +50,11 @@ export class AddonBlogEntriesComponent implements OnInit { onlyMyEntries = false; component = AddonBlogProvider.COMPONENT; commentsEnabled: boolean; + tagsEnabled: boolean; constructor(protected blogProvider: AddonBlogProvider, protected domUtils: CoreDomUtilsProvider, protected userProvider: CoreUserProvider, sitesProvider: CoreSitesProvider, - protected commentsProvider: CoreCommentsProvider) { + protected commentsProvider: CoreCommentsProvider, private tagProvider: CoreTagProvider) { this.currentUserId = sitesProvider.getCurrentSiteUserId(); } @@ -85,6 +87,7 @@ export class AddonBlogEntriesComponent implements OnInit { } this.commentsEnabled = !this.commentsProvider.areCommentsDisabledInSite(); + this.tagsEnabled = this.tagProvider.areTagsAvailableInSite(); this.fetchEntries().then(() => { this.blogProvider.logView(this.filter).catch(() => { From 85d214edba9d420198217354f99546a08034736f Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 15:09:36 +0200 Subject: [PATCH 03/23] MOBILE-2201 book: Display tags in book chapters --- src/addon/mod/book/components/components.module.ts | 4 +++- .../book/components/index/addon-mod-book-index.html | 4 ++++ src/addon/mod/book/components/index/index.ts | 6 +++++- src/addon/mod/book/providers/book.ts | 12 ++++++++++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/addon/mod/book/components/components.module.ts b/src/addon/mod/book/components/components.module.ts index 54e83ef50..4cc338b6e 100644 --- a/src/addon/mod/book/components/components.module.ts +++ b/src/addon/mod/book/components/components.module.ts @@ -20,6 +20,7 @@ import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CoreCourseComponentsModule } from '@core/course/components/components.module'; import { AddonModBookIndexComponent } from './index/index'; +import { CoreTagComponentsModule } from '@core/tag/components/components.module'; @NgModule({ declarations: [ @@ -31,7 +32,8 @@ import { AddonModBookIndexComponent } from './index/index'; TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, - CoreCourseComponentsModule + CoreCourseComponentsModule, + CoreTagComponentsModule ], providers: [ ], diff --git a/src/addon/mod/book/components/index/addon-mod-book-index.html b/src/addon/mod/book/components/index/addon-mod-book-index.html index 6f4e0be3a..13cc19b7e 100644 --- a/src/addon/mod/book/components/index/addon-mod-book-index.html +++ b/src/addon/mod/book/components/index/addon-mod-book-index.html @@ -21,6 +21,10 @@
+
+ {{ 'core.tag.tags' | translate }}: + +
diff --git a/src/addon/mod/book/components/index/index.ts b/src/addon/mod/book/components/index/index.ts index 1b154d373..569060aa3 100644 --- a/src/addon/mod/book/components/index/index.ts +++ b/src/addon/mod/book/components/index/index.ts @@ -19,6 +19,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component'; import { AddonModBookProvider, AddonModBookContentsMap, AddonModBookTocChapter } from '../../providers/book'; import { AddonModBookPrefetchHandler } from '../../providers/prefetch-handler'; +import { CoreTagProvider } from '@core/tag/providers/tag'; /** * Component that displays a book. @@ -34,6 +35,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp chapterContent: string; previousChapter: string; nextChapter: string; + tagsEnabled: boolean; protected chapters: AddonModBookTocChapter[]; protected currentChapter: string; @@ -41,7 +43,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp constructor(injector: Injector, private bookProvider: AddonModBookProvider, private courseProvider: CoreCourseProvider, private appProvider: CoreAppProvider, private prefetchDelegate: AddonModBookPrefetchHandler, - private modalCtrl: ModalController, @Optional() private content: Content) { + private modalCtrl: ModalController, private tagProvider: CoreTagProvider, @Optional() private content: Content) { super(injector); } @@ -51,6 +53,8 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp ngOnInit(): void { super.ngOnInit(); + this.tagsEnabled = this.tagProvider.areTagsAvailableInSite(); + this.loadContent(); } diff --git a/src/addon/mod/book/providers/book.ts b/src/addon/mod/book/providers/book.ts index 1655ed164..74fd32706 100644 --- a/src/addon/mod/book/providers/book.ts +++ b/src/addon/mod/book/providers/book.ts @@ -24,6 +24,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSite } from '@classes/site'; +import { CoreTagItem } from '@core/tag/providers/tag'; /** * A book chapter inside the toc list. @@ -52,7 +53,13 @@ export interface AddonModBookTocChapter { * Map of book contents. For each chapter it has its index URL and the list of paths of the files the chapter has. Each path * is identified by the relative path in the book, and the value is the URL of the file. */ -export type AddonModBookContentsMap = {[chapter: string]: {indexUrl?: string, paths: {[path: string]: string}}}; +export type AddonModBookContentsMap = { + [chapter: string]: { + indexUrl?: string, + paths: {[path: string]: string}, + tags?: CoreTagItem[] + } +}; /** * Service that provides some features for books. @@ -203,8 +210,9 @@ export class AddonModBookProvider { map[chapter] = map[chapter] || { paths: {} }; if (content.filename == 'index.html' && filepathIsChapter) { - // Index of the chapter, set indexUrl of the chapter. + // Index of the chapter, set indexUrl and tags of the chapter. map[chapter].indexUrl = content.fileurl; + map[chapter].tags = content.tags; } else { if (filepathIsChapter) { // It's a file in the root folder OR the WS isn't returning the filepath as it should (MDL-53671). From c07eb58568508fe9f5f27b6392321ed4e9207a92 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 11:19:05 +0200 Subject: [PATCH 04/23] MOBILE-2201 data: Display tags in database entries --- src/addon/mod/data/components/action/action.ts | 6 +++++- .../mod/data/components/action/addon-mod-data-action.html | 2 ++ src/addon/mod/data/components/components.module.ts | 4 +++- src/addon/mod/data/providers/helper.ts | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/addon/mod/data/components/action/action.ts b/src/addon/mod/data/components/action/action.ts index 9e96c3d1a..b96dc078d 100644 --- a/src/addon/mod/data/components/action/action.ts +++ b/src/addon/mod/data/components/action/action.ts @@ -20,6 +20,7 @@ import { AddonModDataOfflineProvider } from '../../providers/offline'; import { CoreSitesProvider } from '@providers/sites'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreUserProvider } from '@core/user/providers/user'; +import { CoreTagProvider } from '@core/tag/providers/tag'; /** * Component that displays a database action. @@ -41,13 +42,16 @@ export class AddonModDataActionComponent implements OnInit { rootUrl: string; url: string; userPicture: string; + tagsEnabled: boolean; constructor(protected injector: Injector, protected dataProvider: AddonModDataProvider, protected dataOffline: AddonModDataOfflineProvider, protected eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, protected userProvider: CoreUserProvider, private navCtrl: NavController, - protected linkHelper: CoreContentLinksHelperProvider, private dataHelper: AddonModDataHelperProvider) { + protected linkHelper: CoreContentLinksHelperProvider, private dataHelper: AddonModDataHelperProvider, + private tagProvider: CoreTagProvider) { this.rootUrl = sitesProvider.getCurrentSite().getURL(); this.siteId = sitesProvider.getCurrentSiteId(); + this.tagsEnabled = this.tagProvider.areTagsAvailableInSite(); } /** diff --git a/src/addon/mod/data/components/action/addon-mod-data-action.html b/src/addon/mod/data/components/action/addon-mod-data-action.html index 41a44e5fa..b6c9e9924 100644 --- a/src/addon/mod/data/components/action/addon-mod-data-action.html +++ b/src/addon/mod/data/components/action/addon-mod-data-action.html @@ -32,3 +32,5 @@ {{entry.fullname}} + + diff --git a/src/addon/mod/data/components/components.module.ts b/src/addon/mod/data/components/components.module.ts index 3470ae872..ef12a46b3 100644 --- a/src/addon/mod/data/components/components.module.ts +++ b/src/addon/mod/data/components/components.module.ts @@ -25,6 +25,7 @@ import { AddonModDataFieldPluginComponent } from './field-plugin/field-plugin'; import { AddonModDataActionComponent } from './action/action'; import { CoreCompileHtmlComponentModule } from '@core/compile/components/compile-html/compile-html.module'; import { CoreCommentsComponentsModule } from '@core/comments/components/components.module'; +import { CoreTagComponentsModule } from '@core/tag/components/components.module'; @NgModule({ declarations: [ @@ -41,7 +42,8 @@ import { CoreCommentsComponentsModule } from '@core/comments/components/componen CorePipesModule, CoreCourseComponentsModule, CoreCompileHtmlComponentModule, - CoreCommentsComponentsModule + CoreCommentsComponentsModule, + CoreTagComponentsModule ], providers: [ ], diff --git a/src/addon/mod/data/providers/helper.ts b/src/addon/mod/data/providers/helper.ts index b5aaadcec..477eed3fc 100644 --- a/src/addon/mod/data/providers/helper.ts +++ b/src/addon/mod/data/providers/helper.ts @@ -367,6 +367,7 @@ export class AddonModDataHelperProvider { userpicture: true, timeadded: true, timemodified: true, + tags: true, edit: record.canmanageentry && !record.deleted, // This already checks capabilities and readonly period. delete: record.canmanageentry, @@ -377,7 +378,6 @@ export class AddonModDataHelperProvider { comments: database.comments, // Unsupported actions. - tags: false, delcheck: false, export: false }; From c00cbb9b8ddb8734c72e533f21a7bd6590ad547b Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 11:21:48 +0200 Subject: [PATCH 05/23] MOBILE-2201 data: Display message in pages where tags are not supported --- scripts/langindex.json | 2 ++ src/addon/mod/data/lang/en.json | 2 ++ src/addon/mod/data/pages/edit/edit.ts | 9 ++++++++- src/addon/mod/data/pages/search/search.ts | 9 ++++++--- src/assets/lang/en.json | 2 ++ 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 89bf47b41..d9741522c 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -431,6 +431,7 @@ "addon.mod_data.confirmdeleterecord": "data", "addon.mod_data.descending": "data", "addon.mod_data.disapprove": "data", + "addon.mod_data.edittagsnotsupported": "local_moodlemobileapp", "addon.mod_data.emptyaddform": "data", "addon.mod_data.entrieslefttoadd": "data", "addon.mod_data.entrieslefttoaddtoview": "data", @@ -455,6 +456,7 @@ "addon.mod_data.recorddisapproved": "data", "addon.mod_data.resetsettings": "data", "addon.mod_data.search": "data", + "addon.mod_data.searchbytagsnotsupported": "local_moodlemobileapp", "addon.mod_data.selectedrequired": "data", "addon.mod_data.single": "data", "addon.mod_data.timeadded": "data", diff --git a/src/addon/mod/data/lang/en.json b/src/addon/mod/data/lang/en.json index f358c48a8..f7c0005dc 100644 --- a/src/addon/mod/data/lang/en.json +++ b/src/addon/mod/data/lang/en.json @@ -10,6 +10,7 @@ "confirmdeleterecord": "Are you sure you want to delete this entry?", "descending": "Descending", "disapprove": "Undo approval", + "edittagsnotsupported": "Sorry, editing tags is not supported by the app.", "emptyaddform": "You did not fill out any fields!", "entrieslefttoadd": "You must add {{$a.entriesleft}} more entry/entries in order to complete this activity", "entrieslefttoaddtoview": "You must add {{$a.entrieslefttoview}} more entry/entries before you can view other participants' entries.", @@ -34,6 +35,7 @@ "recorddisapproved": "Entry unapproved", "resetsettings": "Reset filters", "search": "Search", + "searchbytagsnotsupported": "Sorry, searching by tags is not supported by the app.", "selectedrequired": "All selected required", "single": "View single", "timeadded": "Time added", diff --git a/src/addon/mod/data/pages/edit/edit.ts b/src/addon/mod/data/pages/edit/edit.ts index 35e74cd06..68d6cdada 100644 --- a/src/addon/mod/data/pages/edit/edit.ts +++ b/src/addon/mod/data/pages/edit/edit.ts @@ -28,6 +28,7 @@ import { AddonModDataHelperProvider } from '../../providers/helper'; import { AddonModDataOfflineProvider } from '../../providers/offline'; import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate'; import { AddonModDataComponentsModule } from '../../components/components.module'; +import { CoreTagProvider } from '@core/tag/providers/tag'; /** * Page that displays the view edit page. @@ -68,7 +69,8 @@ export class AddonModDataEditPage { protected courseProvider: CoreCourseProvider, protected dataProvider: AddonModDataProvider, protected dataOffline: AddonModDataOfflineProvider, protected dataHelper: AddonModDataHelperProvider, sitesProvider: CoreSitesProvider, protected navCtrl: NavController, protected translate: TranslateService, - protected eventsProvider: CoreEventsProvider, protected fileUploaderProvider: CoreFileUploaderProvider) { + protected eventsProvider: CoreEventsProvider, protected fileUploaderProvider: CoreFileUploaderProvider, + private tagProvider: CoreTagProvider) { this.module = params.get('module') || {}; this.entryId = params.get('entryId') || null; this.courseId = params.get('courseId'); @@ -309,6 +311,11 @@ export class AddonModDataEditPage { template = template.replace(replace, 'field_' + field.id); }); + // Editing tags is not supported. + replace = new RegExp('##tags##', 'gi'); + const message = '

{{ \'addon.mod_data.edittagsnotsupported\' | translate }}

'; + template = template.replace(replace, this.tagProvider.areTagsAvailableInSite() ? message : ''); + return template; } diff --git a/src/addon/mod/data/pages/search/search.ts b/src/addon/mod/data/pages/search/search.ts index 4ca20b5d0..fb0a16495 100644 --- a/src/addon/mod/data/pages/search/search.ts +++ b/src/addon/mod/data/pages/search/search.ts @@ -21,6 +21,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { AddonModDataComponentsModule } from '../../components/components.module'; import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate'; import { AddonModDataHelperProvider } from '../../providers/helper'; +import { CoreTagProvider } from '@core/tag/providers/tag'; /** * Page that displays the search modal. @@ -42,7 +43,8 @@ export class AddonModDataSearchPage { constructor(params: NavParams, private viewCtrl: ViewController, fb: FormBuilder, protected utils: CoreUtilsProvider, protected domUtils: CoreDomUtilsProvider, protected fieldsDelegate: AddonModDataFieldsDelegate, - protected textUtils: CoreTextUtilsProvider, protected dataHelper: AddonModDataHelperProvider) { + protected textUtils: CoreTextUtilsProvider, protected dataHelper: AddonModDataHelperProvider, + private tagProvider: CoreTagProvider) { this.search = params.get('search'); this.fields = params.get('fields'); this.data = params.get('data'); @@ -117,9 +119,10 @@ export class AddonModDataSearchPage { [placeholder]="\'addon.mod_data.authorlastname\' | translate" formControlName="lastname">'; template = template.replace(replace, render); - // Tags are unsupported right now. + // Searching by tags is not supported. replace = new RegExp('##tags##', 'gi'); - template = template.replace(replace, ''); + const message = '

{{ \'addon.mod_data.searchbytagsnotsupported\' | translate }}

'; + template = template.replace(replace, this.tagProvider.areTagsAvailableInSite() ? message : ''); return template; } diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 0dc92ff13..b75231f80 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -431,6 +431,7 @@ "addon.mod_data.confirmdeleterecord": "Are you sure you want to delete this entry?", "addon.mod_data.descending": "Descending", "addon.mod_data.disapprove": "Undo approval", + "addon.mod_data.edittagsnotsupported": "Sorry, editing tags is not supported by the app.", "addon.mod_data.emptyaddform": "You did not fill out any fields!", "addon.mod_data.entrieslefttoadd": "You must add {{$a.entriesleft}} more entry/entries in order to complete this activity", "addon.mod_data.entrieslefttoaddtoview": "You must add {{$a.entrieslefttoview}} more entry/entries before you can view other participants' entries.", @@ -455,6 +456,7 @@ "addon.mod_data.recorddisapproved": "Entry unapproved", "addon.mod_data.resetsettings": "Reset filters", "addon.mod_data.search": "Search", + "addon.mod_data.searchbytagsnotsupported": "Sorry, searching by tags is not supported by the app.", "addon.mod_data.selectedrequired": "All selected required", "addon.mod_data.single": "View single", "addon.mod_data.timeadded": "Time added", From 1226a17d81491c29d87bd1856b4a1aa2e6924389 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 11:24:17 +0200 Subject: [PATCH 06/23] MOBILE-2201 forum: Display tags in forum posts --- src/addon/mod/forum/components/components.module.ts | 4 +++- src/addon/mod/forum/components/post/addon-mod-forum-post.html | 4 ++++ src/addon/mod/forum/components/post/post.ts | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/addon/mod/forum/components/components.module.ts b/src/addon/mod/forum/components/components.module.ts index 0f3bf1b10..06cdabd01 100644 --- a/src/addon/mod/forum/components/components.module.ts +++ b/src/addon/mod/forum/components/components.module.ts @@ -21,6 +21,7 @@ import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; import { CoreCourseComponentsModule } from '@core/course/components/components.module'; import { CoreRatingComponentsModule } from '@core/rating/components/components.module'; +import { CoreTagComponentsModule } from '@core/tag/components/components.module'; import { AddonModForumIndexComponent } from './index/index'; import { AddonModForumPostComponent } from './post/post'; @@ -37,7 +38,8 @@ import { AddonModForumPostComponent } from './post/post'; CoreDirectivesModule, CorePipesModule, CoreCourseComponentsModule, - CoreRatingComponentsModule + CoreRatingComponentsModule, + CoreTagComponentsModule ], providers: [ ], diff --git a/src/addon/mod/forum/components/post/addon-mod-forum-post.html b/src/addon/mod/forum/components/post/addon-mod-forum-post.html index 9119c2bed..81107408c 100644 --- a/src/addon/mod/forum/components/post/addon-mod-forum-post.html +++ b/src/addon/mod/forum/components/post/addon-mod-forum-post.html @@ -30,6 +30,10 @@
+ +
{{ 'core.tag.tags' | translate }}:
+ +
diff --git a/src/addon/mod/forum/components/post/post.ts b/src/addon/mod/forum/components/post/post.ts index a8d010a9e..d6a9ca54d 100644 --- a/src/addon/mod/forum/components/post/post.ts +++ b/src/addon/mod/forum/components/post/post.ts @@ -25,6 +25,7 @@ import { AddonModForumHelperProvider } from '../../providers/helper'; import { AddonModForumOfflineProvider } from '../../providers/offline'; import { AddonModForumSyncProvider } from '../../providers/sync'; import { CoreRatingInfo } from '@core/rating/providers/rating'; +import { CoreTagProvider } from '@core/tag/providers/tag'; /** * Components that shows a discussion post, its attachments and the action buttons allowed (reply, etc.). @@ -52,6 +53,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { uniqueId: string; advanced = false; // Display all form fields. + tagsEnabled: boolean; protected syncId: string; @@ -65,8 +67,10 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { private forumHelper: AddonModForumHelperProvider, private forumOffline: AddonModForumOfflineProvider, private forumSync: AddonModForumSyncProvider, + private tagProvider: CoreTagProvider, @Optional() private content: Content) { this.onPostChange = new EventEmitter(); + this.tagsEnabled = this.tagProvider.areTagsAvailableInSite(); } /** From 4400d99638449f0e5480f3740c45950d3ed3c35f Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 11:28:04 +0200 Subject: [PATCH 07/23] MOBILE-2201 glossary: Display tags in glossary entries --- src/addon/mod/glossary/pages/entry/entry.html | 4 ++++ src/addon/mod/glossary/pages/entry/entry.module.ts | 4 +++- src/addon/mod/glossary/pages/entry/entry.ts | 6 +++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/addon/mod/glossary/pages/entry/entry.html b/src/addon/mod/glossary/pages/entry/entry.html index f34e46d27..5954398ff 100644 --- a/src/addon/mod/glossary/pages/entry/entry.html +++ b/src/addon/mod/glossary/pages/entry/entry.html @@ -28,6 +28,10 @@ + +
{{ 'core.tag.tags' | translate }}:
+ +

{{ 'addon.mod_glossary.entrypendingapproval' | translate }}

diff --git a/src/addon/mod/glossary/pages/entry/entry.module.ts b/src/addon/mod/glossary/pages/entry/entry.module.ts index cc69e9dc4..730943091 100644 --- a/src/addon/mod/glossary/pages/entry/entry.module.ts +++ b/src/addon/mod/glossary/pages/entry/entry.module.ts @@ -19,6 +19,7 @@ import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; import { CoreRatingComponentsModule } from '@core/rating/components/components.module'; +import { CoreTagComponentsModule } from '@core/tag/components/components.module'; import { AddonModGlossaryEntryPage } from './entry'; @NgModule({ @@ -31,7 +32,8 @@ import { AddonModGlossaryEntryPage } from './entry'; CorePipesModule, IonicPageModule.forChild(AddonModGlossaryEntryPage), TranslateModule.forChild(), - CoreRatingComponentsModule + CoreRatingComponentsModule, + CoreTagComponentsModule ], }) export class AddonModForumDiscussionPageModule {} diff --git a/src/addon/mod/glossary/pages/entry/entry.ts b/src/addon/mod/glossary/pages/entry/entry.ts index 7bbf4f0bf..a5b38641d 100644 --- a/src/addon/mod/glossary/pages/entry/entry.ts +++ b/src/addon/mod/glossary/pages/entry/entry.ts @@ -16,6 +16,7 @@ import { Component } from '@angular/core'; import { IonicPage, NavParams } from 'ionic-angular'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreRatingInfo } from '@core/rating/providers/rating'; +import { CoreTagProvider } from '@core/tag/providers/tag'; import { AddonModGlossaryProvider } from '../../providers/glossary'; /** @@ -35,15 +36,18 @@ export class AddonModGlossaryEntryPage { showAuthor = false; showDate = false; ratingInfo: CoreRatingInfo; + tagsEnabled: boolean; protected courseId: number; protected entryId: number; constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, - private glossaryProvider: AddonModGlossaryProvider) { + private glossaryProvider: AddonModGlossaryProvider, + private tagProvider: CoreTagProvider) { this.courseId = navParams.get('courseId'); this.entryId = navParams.get('entryId'); + this.tagsEnabled = this.tagProvider.areTagsAvailableInSite(); } /** From 75929f6b4f8fb797774b2043f50bc47145cc5367 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 11:29:04 +0200 Subject: [PATCH 08/23] MOBILE-2201 wiki: Display tags in wiki pages --- src/addon/mod/wiki/components/components.module.ts | 4 +++- .../mod/wiki/components/index/addon-mod-wiki-index.html | 5 +++++ src/addon/mod/wiki/components/index/index.ts | 6 +++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/addon/mod/wiki/components/components.module.ts b/src/addon/mod/wiki/components/components.module.ts index 39372cfe2..638be75aa 100644 --- a/src/addon/mod/wiki/components/components.module.ts +++ b/src/addon/mod/wiki/components/components.module.ts @@ -19,6 +19,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CoreCourseComponentsModule } from '@core/course/components/components.module'; +import { CoreTagComponentsModule } from '@core/tag/components/components.module'; import { AddonModWikiIndexComponent } from './index/index'; import { AddonModWikiSubwikiPickerComponent } from './subwiki-picker/subwiki-picker'; @@ -33,7 +34,8 @@ import { AddonModWikiSubwikiPickerComponent } from './subwiki-picker/subwiki-pic TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, - CoreCourseComponentsModule + CoreCourseComponentsModule, + CoreTagComponentsModule ], providers: [ ], diff --git a/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html b/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html index 3d8834595..2ee9b5e8d 100644 --- a/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html +++ b/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html @@ -50,6 +50,11 @@ + +
+ {{ 'core.tag.tags' | translate }}: + +
diff --git a/src/addon/mod/wiki/components/index/index.ts b/src/addon/mod/wiki/components/index/index.ts index 941417a68..7b4a69f16 100644 --- a/src/addon/mod/wiki/components/index/index.ts +++ b/src/addon/mod/wiki/components/index/index.ts @@ -23,6 +23,7 @@ import { AddonModWikiOfflineProvider } from '../../providers/wiki-offline'; import { AddonModWikiSyncProvider } from '../../providers/wiki-sync'; import { CoreTabsComponent } from '@components/tabs/tabs'; import { AddonModWikiSubwikiPickerComponent } from '../../components/subwiki-picker/subwiki-picker'; +import { CoreTagProvider } from '@core/tag/providers/tag'; /** * Component that displays a wiki entry page. @@ -64,6 +65,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp subwikis: [], count: 0 }; + tagsEnabled: boolean; protected syncEventName = AddonModWikiSyncProvider.AUTO_SYNCED; protected currentSubwiki: any; // Current selected subwiki. @@ -81,10 +83,12 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp constructor(injector: Injector, protected wikiProvider: AddonModWikiProvider, @Optional() protected content: Content, protected wikiOffline: AddonModWikiOfflineProvider, protected wikiSync: AddonModWikiSyncProvider, protected navCtrl: NavController, protected utils: CoreUtilsProvider, protected groupsProvider: CoreGroupsProvider, - protected userProvider: CoreUserProvider, private popoverCtrl: PopoverController) { + protected userProvider: CoreUserProvider, private popoverCtrl: PopoverController, + private tagProvider: CoreTagProvider) { super(injector, content); this.pageStr = this.translate.instant('addon.mod_wiki.wikipage'); + this.tagsEnabled = this.tagProvider.areTagsAvailableInSite(); } /** From 7546ac9e28f200db80fdf9ea532332065957a024 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 11:48:52 +0200 Subject: [PATCH 09/23] MOBILE-2201 tag: Area delegate and helpers for handlers --- src/core/tag/components/components.module.ts | 4 + .../tag/components/feed/core-tag-feed.html | 8 ++ src/core/tag/components/feed/feed.ts | 26 +++++ src/core/tag/providers/area-delegate.ts | 98 +++++++++++++++++++ src/core/tag/providers/helper.ts | 81 +++++++++++++++ src/core/tag/tag.module.ts | 4 + 6 files changed, 221 insertions(+) create mode 100644 src/core/tag/components/feed/core-tag-feed.html create mode 100644 src/core/tag/components/feed/feed.ts create mode 100644 src/core/tag/providers/area-delegate.ts create mode 100644 src/core/tag/providers/helper.ts diff --git a/src/core/tag/components/components.module.ts b/src/core/tag/components/components.module.ts index c2e07f85b..8960002cd 100644 --- a/src/core/tag/components/components.module.ts +++ b/src/core/tag/components/components.module.ts @@ -16,11 +16,13 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { IonicModule } from 'ionic-angular'; import { TranslateModule } from '@ngx-translate/core'; +import { CoreTagFeedComponent } from './feed/feed'; import { CoreTagListComponent } from './list/list'; import { CoreDirectivesModule } from '@directives/directives.module'; @NgModule({ declarations: [ + CoreTagFeedComponent, CoreTagListComponent ], imports: [ @@ -32,9 +34,11 @@ import { CoreDirectivesModule } from '@directives/directives.module'; providers: [ ], exports: [ + CoreTagFeedComponent, CoreTagListComponent ], entryComponents: [ + CoreTagFeedComponent ] }) export class CoreTagComponentsModule {} diff --git a/src/core/tag/components/feed/core-tag-feed.html b/src/core/tag/components/feed/core-tag-feed.html new file mode 100644 index 000000000..fe4a02e21 --- /dev/null +++ b/src/core/tag/components/feed/core-tag-feed.html @@ -0,0 +1,8 @@ + + + + + +

{{ item.heading }}

+

{{ text }}

+
diff --git a/src/core/tag/components/feed/feed.ts b/src/core/tag/components/feed/feed.ts new file mode 100644 index 000000000..5c554f7c3 --- /dev/null +++ b/src/core/tag/components/feed/feed.ts @@ -0,0 +1,26 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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, Input } from '@angular/core'; + +/** + * Component to render a tag area that uses the "core_tag/tagfeed" web template. + */ +@Component({ + selector: 'core-tag-feed', + templateUrl: 'core-tag-feed.html' +}) +export class CoreTagFeedComponent { + @Input() items: any[]; // Area items to render. +} diff --git a/src/core/tag/providers/area-delegate.ts b/src/core/tag/providers/area-delegate.ts new file mode 100644 index 000000000..2b0e2ffb8 --- /dev/null +++ b/src/core/tag/providers/area-delegate.ts @@ -0,0 +1,98 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable, Injector } from '@angular/core'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; + +/** + * Interface that all tag area handlers must implement. + */ +export interface CoreTagAreaHandler extends CoreDelegateHandler { + /** + * Component and item type separated by a slash. E.g. 'core/course_modules'. + * @type {string} + */ + type: string; + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param {string} content Rendered content. + * @return {any[]|Promise} Area items (or promise resolved with the items). + */ + parseContent(content: string): any[] | Promise; + + /** + * Get the component to use to display items. + * + * @param {Injector} injector Injector. + * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(injector: Injector): any | Promise; +} + +/** + * Delegate to register tag area handlers. + */ +@Injectable() +export class CoreTagAreaDelegate extends CoreDelegate { + + protected handlerNameProperty = 'type'; + + constructor(logger: CoreLoggerProvider, sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider) { + super('CoreTagAreaDelegate', logger, sitesProvider, eventsProvider); + } + + /** + * Returns the display name string for this area. + * + * @param {string} component Component name. + * @param {string} itemType Item type. + * @return {string} String key. + */ + getDisplayNameKey(component: string, itemType: string): string { + return (component == 'core' ? 'core.tag' : 'addon.' + component) + '.tagarea_' + itemType; + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param {string} component Component name. + * @param {string} itemType Item type. + * @param {string} content Rendered content. + * @return {Promise} Promise resolved with the area items, or undefined if not found. + */ + parseContent(component: string, itemType: string, content: string): Promise { + const type = component + '/' + itemType; + + return Promise.resolve(this.executeFunctionOnEnabled(type, 'parseContent', [content])); + } + + /** + * Get the component to use to display an area item. + * + * @param {string} component Component name. + * @param {string} itemType Item type. + * @param {Injector} injector Injector. + * @return {Promise} The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(component: string, itemType: string, injector: Injector): Promise { + const type = component + '/' + itemType; + + return Promise.resolve(this.executeFunctionOnEnabled(type, 'getComponent', [injector])); + } +} diff --git a/src/core/tag/providers/helper.ts b/src/core/tag/providers/helper.ts new file mode 100644 index 000000000..38c097b79 --- /dev/null +++ b/src/core/tag/providers/helper.ts @@ -0,0 +1,81 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable } from '@angular/core'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; + +/** + * Service with helper functions for tags. + */ +@Injectable() +export class CoreTagHelperProvider { + + constructor(protected domUtils: CoreDomUtilsProvider) {} + + /** + * Parses the rendered content of the "core_tag/tagfeed" web template and returns the items. + * + * @param {string} content Rendered content. + * @return {any[]} Area items. + */ + parseFeedContent(content: string): any[] { + const items = []; + const element = this.domUtils.convertToElement(content); + + Array.from(element.querySelectorAll('ul.tag_feed > li.media')).forEach((itemElement) => { + const item: any = { details: [] }; + + Array.from(itemElement.querySelectorAll('div.media-body > div')).forEach((div: HTMLElement) => { + if (div.classList.contains('media-heading')) { + item.heading = div.innerText.trim(); + const link = div.querySelector('a'); + if (link) { + item.url = link.getAttribute('href'); + } + } else { + // Separate details by lines. + const lines = ['']; + Array.from(div.childNodes).forEach((childNode: Node) => { + if (childNode.nodeType == Node.TEXT_NODE) { + lines[lines.length - 1] += childNode.textContent; + } else if (childNode.nodeType == Node.ELEMENT_NODE) { + const childElement = childNode as HTMLElement; + if (childElement.tagName == 'BR') { + lines.push(''); + } else { + lines[lines.length - 1] += childElement.innerText; + } + } + }); + item.details.push(...lines.map((line) => line.trim()).filter((line) => line != '')); + } + }); + + const image = itemElement.querySelector('div.itemimage img'); + if (image) { + if (image.classList.contains('userpicture')) { + item.avatarUrl = image.getAttribute('src'); + } else { + item.iconUrl = image.getAttribute('src'); + } + } + + if (item.heading && item.url) { + items.push(item); + } + }); + + return items; + } +} diff --git a/src/core/tag/tag.module.ts b/src/core/tag/tag.module.ts index eaa2f9e81..45d4d69af 100644 --- a/src/core/tag/tag.module.ts +++ b/src/core/tag/tag.module.ts @@ -14,6 +14,8 @@ import { NgModule } from '@angular/core'; import { CoreTagProvider } from './providers/tag'; +import { CoreTagHelperProvider } from './providers/helper'; +import { CoreTagAreaDelegate } from './providers/area-delegate'; @NgModule({ declarations: [ @@ -22,6 +24,8 @@ import { CoreTagProvider } from './providers/tag'; ], providers: [ CoreTagProvider, + CoreTagHelperProvider, + CoreTagAreaDelegate ] }) export class CoreTagModule { From eed78c8b6f56a85750149ec1186470c1e3153de2 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:02:33 +0200 Subject: [PATCH 10/23] MOBILE-2201 course: Tag area handler for courses --- scripts/langindex.json | 1 + src/assets/lang/en.json | 1 + .../course/components/components.module.ts | 6 +- .../tag-area/core-course-tag-area.html | 5 ++ .../course/components/tag-area/tag-area.ts | 43 +++++++++++ src/core/course/course.module.ts | 9 ++- .../providers/course-tag-area-handler.ts | 74 +++++++++++++++++++ src/core/tag/lang/en.json | 1 + 8 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 src/core/course/components/tag-area/core-course-tag-area.html create mode 100644 src/core/course/components/tag-area/tag-area.ts create mode 100644 src/core/course/providers/course-tag-area-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index d9741522c..b9db7a280 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1795,6 +1795,7 @@ "core.submit": "moodle", "core.success": "moodle", "core.tablet": "local_moodlemobileapp", + "core.tag.tagarea_course": "moodle", "core.tag.tags": "moodle", "core.teachers": "moodle", "core.thereisdatatosync": "local_moodlemobileapp", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index b75231f80..1174b5e6a 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1795,6 +1795,7 @@ "core.submit": "Submit", "core.success": "Success", "core.tablet": "Tablet", + "core.tag.tagarea_course": "Courses", "core.tag.tags": "Tags", "core.teachers": "Teachers", "core.thereisdatatosync": "There are offline {{$a}} to be synchronised.", diff --git a/src/core/course/components/components.module.ts b/src/core/course/components/components.module.ts index a56f920d8..4b55708e2 100644 --- a/src/core/course/components/components.module.ts +++ b/src/core/course/components/components.module.ts @@ -22,6 +22,7 @@ import { CoreCourseFormatComponent } from './format/format'; import { CoreCourseModuleComponent } from './module/module'; import { CoreCourseModuleCompletionComponent } from './module-completion/module-completion'; import { CoreCourseModuleDescriptionComponent } from './module-description/module-description'; +import { CoreCourseTagAreaComponent } from './tag-area/tag-area'; import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsupported-module'; @NgModule({ @@ -30,6 +31,7 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup CoreCourseModuleComponent, CoreCourseModuleCompletionComponent, CoreCourseModuleDescriptionComponent, + CoreCourseTagAreaComponent, CoreCourseUnsupportedModuleComponent ], imports: [ @@ -46,10 +48,12 @@ import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsup CoreCourseModuleComponent, CoreCourseModuleCompletionComponent, CoreCourseModuleDescriptionComponent, + CoreCourseTagAreaComponent, CoreCourseUnsupportedModuleComponent ], entryComponents: [ - CoreCourseUnsupportedModuleComponent + CoreCourseUnsupportedModuleComponent, + CoreCourseTagAreaComponent ] }) export class CoreCourseComponentsModule {} diff --git a/src/core/course/components/tag-area/core-course-tag-area.html b/src/core/course/components/tag-area/core-course-tag-area.html new file mode 100644 index 000000000..b372fdf09 --- /dev/null +++ b/src/core/course/components/tag-area/core-course-tag-area.html @@ -0,0 +1,5 @@ + + +

{{ item.courseName }}

+

{{ 'core.category' | translate }}: {{ item.categoryName }}

+
diff --git a/src/core/course/components/tag-area/tag-area.ts b/src/core/course/components/tag-area/tag-area.ts new file mode 100644 index 000000000..07d34c21c --- /dev/null +++ b/src/core/course/components/tag-area/tag-area.ts @@ -0,0 +1,43 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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, Input, Optional } from '@angular/core'; +import { NavController } from 'ionic-angular'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; + +/** + * Component that renders the course tag area. + */ +@Component({ + selector: 'core-course-tag-area', + templateUrl: 'core-course-tag-area.html' +}) +export class CoreCourseTagAreaComponent { + @Input() items: any[]; // Area items to render. + + constructor(private navCtrl: NavController, @Optional() private splitviewCtrl: CoreSplitViewComponent, + private courseHelper: CoreCourseHelperProvider) {} + + /** + * Open a course. + * + * @param {number} courseId The course to open. + */ + openCourse(courseId: number): void { + // If this component is inside a split view, use the master nav to open it. + const navCtrl = this.splitviewCtrl ? this.splitviewCtrl.getMasterNav() : this.navCtrl; + this.courseHelper.getAndOpenCourse(navCtrl, courseId); + } +} diff --git a/src/core/course/course.module.ts b/src/core/course/course.module.ts index 5293eda63..4e8206abf 100644 --- a/src/core/course/course.module.ts +++ b/src/core/course/course.module.ts @@ -33,6 +33,8 @@ import { CoreCourseFormatWeeksModule } from './formats/weeks/weeks.module'; import { CoreCourseSyncProvider } from './providers/sync'; import { CoreCourseSyncCronHandler } from './providers/sync-cron-handler'; import { CoreCourseLogCronHandler } from './providers/log-cron-handler'; +import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; +import { CoreCourseTagAreaHandler } from './providers/course-tag-area-handler'; // List of providers (without handlers). export const CORE_COURSE_PROVIDERS: any[] = [ @@ -68,15 +70,18 @@ export const CORE_COURSE_PROVIDERS: any[] = [ CoreCourseFormatDefaultHandler, CoreCourseModuleDefaultHandler, CoreCourseSyncCronHandler, - CoreCourseLogCronHandler + CoreCourseLogCronHandler, + CoreCourseTagAreaHandler ], exports: [] }) export class CoreCourseModule { constructor(cronDelegate: CoreCronDelegate, syncHandler: CoreCourseSyncCronHandler, logHandler: CoreCourseLogCronHandler, - platform: Platform, eventsProvider: CoreEventsProvider) { + platform: Platform, eventsProvider: CoreEventsProvider, tagAreaDelegate: CoreTagAreaDelegate, + courseTagAreaHandler: CoreCourseTagAreaHandler) { cronDelegate.register(syncHandler); cronDelegate.register(logHandler); + tagAreaDelegate.registerHandler(courseTagAreaHandler); platform.resume.subscribe(() => { // Log the app is open to keep user in online status. diff --git a/src/core/course/providers/course-tag-area-handler.ts b/src/core/course/providers/course-tag-area-handler.ts new file mode 100644 index 000000000..066754de6 --- /dev/null +++ b/src/core/course/providers/course-tag-area-handler.ts @@ -0,0 +1,74 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable, Injector } from '@angular/core'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreTagAreaHandler } from '@core/tag/providers/area-delegate'; +import { CoreCourseTagAreaComponent } from '../components/tag-area/tag-area'; + +/** + * Handler to support tags. + */ +@Injectable() +export class CoreCourseTagAreaHandler implements CoreTagAreaHandler { + name = 'CoreCourseTagAreaHandler'; + type = 'core/course'; + + constructor(private domUtils: CoreDomUtilsProvider) {} + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return true; + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param {string} content Rendered content. + * @return {any[]|Promise} Area items (or promise resolved with the items). + */ + parseContent(content: string): any[] | Promise { + const items = []; + const element = this.domUtils.convertToElement(content); + + Array.from(element.querySelectorAll('div.coursebox')).forEach((coursebox) => { + const courseId = parseInt(coursebox.getAttribute('data-courseid'), 10); + const courseLink = coursebox.querySelector('.coursename > a'); + const categoryLink = coursebox.querySelector('.coursecat > a'); + + if (courseId > 0 && courseLink) { + items.push({ + courseId, + courseName: courseLink.innerHTML, + categoryName: categoryLink ? categoryLink.innerHTML : null + }); + } + }); + + return items; + } + + /** + * Get the component to use to display items. + * + * @param {Injector} injector Injector. + * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(injector: Injector): any | Promise { + return CoreCourseTagAreaComponent; + } +} diff --git a/src/core/tag/lang/en.json b/src/core/tag/lang/en.json index fec56bb36..c8303131c 100644 --- a/src/core/tag/lang/en.json +++ b/src/core/tag/lang/en.json @@ -1,3 +1,4 @@ { + "tagarea_course": "Courses", "tags": "Tags" } From fdae95d2946f593b120d78f46880d24a2a63655d Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:07:18 +0200 Subject: [PATCH 11/23] MOBILE-2201 course: Tag area handler for activities and resources --- scripts/langindex.json | 1 + src/assets/lang/en.json | 1 + src/core/course/course.module.ts | 7 ++- .../providers/modules-tag-area-handler.ts | 57 +++++++++++++++++++ src/core/tag/lang/en.json | 1 + 5 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/core/course/providers/modules-tag-area-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index b9db7a280..f36b31fdf 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1796,6 +1796,7 @@ "core.success": "moodle", "core.tablet": "local_moodlemobileapp", "core.tag.tagarea_course": "moodle", + "core.tag.tagarea_course_modules": "moodle", "core.tag.tags": "moodle", "core.teachers": "moodle", "core.thereisdatatosync": "local_moodlemobileapp", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 1174b5e6a..3dc88e71c 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1796,6 +1796,7 @@ "core.success": "Success", "core.tablet": "Tablet", "core.tag.tagarea_course": "Courses", + "core.tag.tagarea_course_modules": "Activities and resources", "core.tag.tags": "Tags", "core.teachers": "Teachers", "core.thereisdatatosync": "There are offline {{$a}} to be synchronised.", diff --git a/src/core/course/course.module.ts b/src/core/course/course.module.ts index 4e8206abf..d14844582 100644 --- a/src/core/course/course.module.ts +++ b/src/core/course/course.module.ts @@ -35,6 +35,7 @@ import { CoreCourseSyncCronHandler } from './providers/sync-cron-handler'; import { CoreCourseLogCronHandler } from './providers/log-cron-handler'; import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; import { CoreCourseTagAreaHandler } from './providers/course-tag-area-handler'; +import { CoreCourseModulesTagAreaHandler } from './providers/modules-tag-area-handler'; // List of providers (without handlers). export const CORE_COURSE_PROVIDERS: any[] = [ @@ -71,17 +72,19 @@ export const CORE_COURSE_PROVIDERS: any[] = [ CoreCourseModuleDefaultHandler, CoreCourseSyncCronHandler, CoreCourseLogCronHandler, - CoreCourseTagAreaHandler + CoreCourseTagAreaHandler, + CoreCourseModulesTagAreaHandler ], exports: [] }) export class CoreCourseModule { constructor(cronDelegate: CoreCronDelegate, syncHandler: CoreCourseSyncCronHandler, logHandler: CoreCourseLogCronHandler, platform: Platform, eventsProvider: CoreEventsProvider, tagAreaDelegate: CoreTagAreaDelegate, - courseTagAreaHandler: CoreCourseTagAreaHandler) { + courseTagAreaHandler: CoreCourseTagAreaHandler, modulesTagAreaHandler: CoreCourseModulesTagAreaHandler) { cronDelegate.register(syncHandler); cronDelegate.register(logHandler); tagAreaDelegate.registerHandler(courseTagAreaHandler); + tagAreaDelegate.registerHandler(modulesTagAreaHandler); platform.resume.subscribe(() => { // Log the app is open to keep user in online status. diff --git a/src/core/course/providers/modules-tag-area-handler.ts b/src/core/course/providers/modules-tag-area-handler.ts new file mode 100644 index 000000000..4b7dcfc0a --- /dev/null +++ b/src/core/course/providers/modules-tag-area-handler.ts @@ -0,0 +1,57 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable, Injector } from '@angular/core'; +import { CoreTagAreaHandler } from '@core/tag/providers/area-delegate'; +import { CoreTagHelperProvider } from '@core/tag/providers/helper'; +import { CoreTagFeedComponent } from '@core/tag/components/feed/feed'; + +/** + * Handler to support tags. + */ +@Injectable() +export class CoreCourseModulesTagAreaHandler implements CoreTagAreaHandler { + name = 'CoreCourseModulesTagAreaHandler'; + type = 'core/course_modules'; + + constructor(protected tagHelper: CoreTagHelperProvider) {} + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return true; + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param {string} content Rendered content. + * @return {any[]|Promise} Area items (or promise resolved with the items). + */ + parseContent(content: string): any[] | Promise { + return this.tagHelper.parseFeedContent(content); + } + + /** + * Get the component to use to display items. + * + * @param {Injector} injector Injector. + * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(injector: Injector): any | Promise { + return CoreTagFeedComponent; + } +} diff --git a/src/core/tag/lang/en.json b/src/core/tag/lang/en.json index c8303131c..3275d27af 100644 --- a/src/core/tag/lang/en.json +++ b/src/core/tag/lang/en.json @@ -1,4 +1,5 @@ { "tagarea_course": "Courses", + "tagarea_course_modules": "Activities and resources", "tags": "Tags" } From df423b640e45a3115debd2e7573a0f291ece839d Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:09:57 +0200 Subject: [PATCH 12/23] MOBILE-2201 blog: Tag area handler for blog posts --- scripts/langindex.json | 1 + src/addon/blog/blog.module.ts | 9 ++- src/addon/blog/providers/tag-area-handler.ts | 58 ++++++++++++++++++++ src/assets/lang/en.json | 1 + src/core/tag/lang/en.json | 1 + 5 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/addon/blog/providers/tag-area-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index f36b31fdf..c40287ac5 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1797,6 +1797,7 @@ "core.tablet": "local_moodlemobileapp", "core.tag.tagarea_course": "moodle", "core.tag.tagarea_course_modules": "moodle", + "core.tag.tagarea_post": "moodle", "core.tag.tags": "moodle", "core.teachers": "moodle", "core.thereisdatatosync": "local_moodlemobileapp", diff --git a/src/addon/blog/blog.module.ts b/src/addon/blog/blog.module.ts index f31372745..b697ba27a 100644 --- a/src/addon/blog/blog.module.ts +++ b/src/addon/blog/blog.module.ts @@ -17,12 +17,14 @@ import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; import { CoreUserDelegate } from '@core/user/providers/user-delegate'; import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; +import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; import { AddonBlogProvider } from './providers/blog'; import { AddonBlogMainMenuHandler } from './providers/mainmenu-handler'; import { AddonBlogUserHandler } from './providers/user-handler'; import { AddonBlogCourseOptionHandler } from './providers/course-option-handler'; import { AddonBlogComponentsModule } from './components/components.module'; import { AddonBlogIndexLinkHandler } from './providers/index-link-handler'; +import { AddonBlogTagAreaHandler } from './providers/tag-area-handler'; @NgModule({ declarations: [ @@ -35,17 +37,20 @@ import { AddonBlogIndexLinkHandler } from './providers/index-link-handler'; AddonBlogMainMenuHandler, AddonBlogUserHandler, AddonBlogCourseOptionHandler, - AddonBlogIndexLinkHandler + AddonBlogIndexLinkHandler, + AddonBlogTagAreaHandler ] }) export class AddonBlogModule { constructor(mainMenuDelegate: CoreMainMenuDelegate, menuHandler: AddonBlogMainMenuHandler, userHandler: AddonBlogUserHandler, userDelegate: CoreUserDelegate, courseOptionHandler: AddonBlogCourseOptionHandler, courseOptionsDelegate: CoreCourseOptionsDelegate, - linkHandler: AddonBlogIndexLinkHandler, contentLinksDelegate: CoreContentLinksDelegate) { + linkHandler: AddonBlogIndexLinkHandler, contentLinksDelegate: CoreContentLinksDelegate, + tagAreaDelegate: CoreTagAreaDelegate, tagAreaHandler: AddonBlogTagAreaHandler) { mainMenuDelegate.registerHandler(menuHandler); userDelegate.registerHandler(userHandler); courseOptionsDelegate.registerHandler(courseOptionHandler); contentLinksDelegate.registerHandler(linkHandler); + tagAreaDelegate.registerHandler(tagAreaHandler); } } diff --git a/src/addon/blog/providers/tag-area-handler.ts b/src/addon/blog/providers/tag-area-handler.ts new file mode 100644 index 000000000..41413d824 --- /dev/null +++ b/src/addon/blog/providers/tag-area-handler.ts @@ -0,0 +1,58 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable, Injector } from '@angular/core'; +import { CoreTagAreaHandler } from '@core/tag/providers/area-delegate'; +import { CoreTagHelperProvider } from '@core/tag/providers/helper'; +import { CoreTagFeedComponent } from '@core/tag/components/feed/feed'; +import { AddonBlogProvider } from './blog'; + +/** + * Handler to support tags. + */ +@Injectable() +export class AddonBlogTagAreaHandler implements CoreTagAreaHandler { + name = 'AddonBlogTagAreaHandler'; + type = 'core/post'; + + constructor(private tagHelper: CoreTagHelperProvider, private blogProvider: AddonBlogProvider) {} + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.blogProvider.isPluginEnabled(); + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param {string} content Rendered content. + * @return {any[]|Promise} Area items (or promise resolved with the items). + */ + parseContent(content: string): any[] | Promise { + return this.tagHelper.parseFeedContent(content); + } + + /** + * Get the component to use to display items. + * + * @param {Injector} injector Injector. + * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(injector: Injector): any | Promise { + return CoreTagFeedComponent; + } +} diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 3dc88e71c..344ae3a2c 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1797,6 +1797,7 @@ "core.tablet": "Tablet", "core.tag.tagarea_course": "Courses", "core.tag.tagarea_course_modules": "Activities and resources", + "core.tag.tagarea_post": "Blog posts", "core.tag.tags": "Tags", "core.teachers": "Teachers", "core.thereisdatatosync": "There are offline {{$a}} to be synchronised.", diff --git a/src/core/tag/lang/en.json b/src/core/tag/lang/en.json index 3275d27af..ce6aec9f1 100644 --- a/src/core/tag/lang/en.json +++ b/src/core/tag/lang/en.json @@ -1,5 +1,6 @@ { "tagarea_course": "Courses", "tagarea_course_modules": "Activities and resources", + "tagarea_post": "Blog posts", "tags": "Tags" } From b0b1c2f7c9a3d62d77b2460d15f1d3577ec43c29 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:13:59 +0200 Subject: [PATCH 13/23] MOBILE-2201 user: Tag area handler for users --- scripts/langindex.json | 1 + src/assets/lang/en.json | 1 + src/core/tag/lang/en.json | 1 + src/core/user/components/components.module.ts | 10 ++- .../tag-area/core-user-tag-area.html | 4 + src/core/user/components/tag-area/tag-area.ts | 26 ++++++ src/core/user/providers/tag-area-handler.ts | 82 +++++++++++++++++++ src/core/user/user.module.ts | 6 +- 8 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 src/core/user/components/tag-area/core-user-tag-area.html create mode 100644 src/core/user/components/tag-area/tag-area.ts create mode 100644 src/core/user/providers/tag-area-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index c40287ac5..a0dbf92e1 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1798,6 +1798,7 @@ "core.tag.tagarea_course": "moodle", "core.tag.tagarea_course_modules": "moodle", "core.tag.tagarea_post": "moodle", + "core.tag.tagarea_user": "moodle", "core.tag.tags": "moodle", "core.teachers": "moodle", "core.thereisdatatosync": "local_moodlemobileapp", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 344ae3a2c..f48252c14 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1798,6 +1798,7 @@ "core.tag.tagarea_course": "Courses", "core.tag.tagarea_course_modules": "Activities and resources", "core.tag.tagarea_post": "Blog posts", + "core.tag.tagarea_user": "User interests", "core.tag.tags": "Tags", "core.teachers": "Teachers", "core.thereisdatatosync": "There are offline {{$a}} to be synchronised.", diff --git a/src/core/tag/lang/en.json b/src/core/tag/lang/en.json index ce6aec9f1..02e288849 100644 --- a/src/core/tag/lang/en.json +++ b/src/core/tag/lang/en.json @@ -2,5 +2,6 @@ "tagarea_course": "Courses", "tagarea_course_modules": "Activities and resources", "tagarea_post": "Blog posts", + "tagarea_user": "User interests", "tags": "Tags" } diff --git a/src/core/user/components/components.module.ts b/src/core/user/components/components.module.ts index 7e7427c17..e741ad964 100644 --- a/src/core/user/components/components.module.ts +++ b/src/core/user/components/components.module.ts @@ -18,6 +18,7 @@ import { IonicModule } from 'ionic-angular'; import { TranslateModule } from '@ngx-translate/core'; import { CoreUserParticipantsComponent } from './participants/participants'; import { CoreUserProfileFieldComponent } from './user-profile-field/user-profile-field'; +import { CoreUserTagAreaComponent } from './tag-area/tag-area'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; @@ -25,7 +26,8 @@ import { CorePipesModule } from '@pipes/pipes.module'; @NgModule({ declarations: [ CoreUserParticipantsComponent, - CoreUserProfileFieldComponent + CoreUserProfileFieldComponent, + CoreUserTagAreaComponent ], imports: [ CommonModule, @@ -39,10 +41,12 @@ import { CorePipesModule } from '@pipes/pipes.module'; ], exports: [ CoreUserParticipantsComponent, - CoreUserProfileFieldComponent + CoreUserProfileFieldComponent, + CoreUserTagAreaComponent ], entryComponents: [ - CoreUserParticipantsComponent + CoreUserParticipantsComponent, + CoreUserTagAreaComponent ] }) export class CoreUserComponentsModule {} diff --git a/src/core/user/components/tag-area/core-user-tag-area.html b/src/core/user/components/tag-area/core-user-tag-area.html new file mode 100644 index 000000000..8ca11b857 --- /dev/null +++ b/src/core/user/components/tag-area/core-user-tag-area.html @@ -0,0 +1,4 @@ + + +

{{ item.fullname }}

+
diff --git a/src/core/user/components/tag-area/tag-area.ts b/src/core/user/components/tag-area/tag-area.ts new file mode 100644 index 000000000..8c4f01612 --- /dev/null +++ b/src/core/user/components/tag-area/tag-area.ts @@ -0,0 +1,26 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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, Input } from '@angular/core'; + +/** + * Component to render the user tag area. + */ +@Component({ + selector: 'core-user-tag-area', + templateUrl: 'core-user-tag-area.html' +}) +export class CoreUserTagAreaComponent { + @Input() items: any[]; // Area items to render. +} diff --git a/src/core/user/providers/tag-area-handler.ts b/src/core/user/providers/tag-area-handler.ts new file mode 100644 index 000000000..ab2d167cf --- /dev/null +++ b/src/core/user/providers/tag-area-handler.ts @@ -0,0 +1,82 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable, Injector } from '@angular/core'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreTagAreaHandler } from '@core/tag/providers/area-delegate'; +import { CoreUserTagAreaComponent } from '../components/tag-area/tag-area'; + +/** + * Handler to support tags. + */ +@Injectable() +export class CoreUserTagAreaHandler implements CoreTagAreaHandler { + name = 'CoreUserTagAreaHandler'; + type = 'core/user'; + + constructor(private domUtils: CoreDomUtilsProvider) {} + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return true; + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param {string} content Rendered content. + * @return {any[]|Promise} Area items (or promise resolved with the items). + */ + parseContent(content: string): any[] | Promise { + const items = []; + const element = this.domUtils.convertToElement(content); + + Array.from(element.querySelectorAll('div.user-box')).forEach((userbox: HTMLElement) => { + const item: any = {}; + + const avatarLink = userbox.querySelector('a:first-child'); + if (!avatarLink) { + return; + } + + const profileUrl = avatarLink.getAttribute('href') || ''; + const match = profileUrl.match(/.*\/user\/(?:profile|view)\.php\?id=(\d+)/); + if (!match) { + return; + } + + item.id = parseInt(match[1], 10); + const avatarImg = avatarLink.querySelector('img.userpicture'); + item.profileimageurl = avatarImg ? avatarImg.getAttribute('src') : ''; + item.fullname = userbox.innerText; + + items.push(item); + }); + + return items; + } + + /** + * Get the component to use to display items. + * + * @param {Injector} injector Injector. + * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(injector: Injector): any | Promise { + return CoreUserTagAreaComponent; + } +} diff --git a/src/core/user/user.module.ts b/src/core/user/user.module.ts index 845f2179e..0370243a6 100644 --- a/src/core/user/user.module.ts +++ b/src/core/user/user.module.ts @@ -30,6 +30,8 @@ import { CoreCronDelegate } from '@providers/cron'; import { CoreUserOfflineProvider } from './providers/offline'; import { CoreUserSyncProvider } from './providers/sync'; import { CoreUserSyncCronHandler } from './providers/sync-cron-handler'; +import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; +import { CoreUserTagAreaHandler } from './providers/tag-area-handler'; // List of providers (without handlers). export const CORE_USER_PROVIDERS: any[] = [ @@ -59,6 +61,7 @@ export const CORE_USER_PROVIDERS: any[] = [ CoreUserParticipantsCourseOptionHandler, CoreUserParticipantsLinkHandler, CoreUserSyncCronHandler, + CoreUserTagAreaHandler ] }) export class CoreUserModule { @@ -67,13 +70,14 @@ export class CoreUserModule { contentLinksDelegate: CoreContentLinksDelegate, userLinkHandler: CoreUserProfileLinkHandler, courseOptionHandler: CoreUserParticipantsCourseOptionHandler, linkHandler: CoreUserParticipantsLinkHandler, courseOptionsDelegate: CoreCourseOptionsDelegate, cronDelegate: CoreCronDelegate, - syncHandler: CoreUserSyncCronHandler) { + syncHandler: CoreUserSyncCronHandler, tagAreaDelegate: CoreTagAreaDelegate, tagAreaHandler: CoreUserTagAreaHandler) { userDelegate.registerHandler(userProfileMailHandler); courseOptionsDelegate.registerHandler(courseOptionHandler); contentLinksDelegate.registerHandler(userLinkHandler); contentLinksDelegate.registerHandler(linkHandler); cronDelegate.register(syncHandler); + tagAreaDelegate.registerHandler(tagAreaHandler); eventsProvider.on(CoreEventsProvider.USER_DELETED, (data) => { // Search for userid in params. From 82611bcf3ad0dba4390af8799bc7950a474c0a6d Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:15:29 +0200 Subject: [PATCH 14/23] MOBILE-2201 book: Tag area handler for book chapters --- scripts/langindex.json | 1 + src/addon/mod/book/book.module.ts | 9 ++- src/addon/mod/book/lang/en.json | 1 + .../mod/book/providers/tag-area-handler.ts | 75 +++++++++++++++++++ src/assets/lang/en.json | 1 + 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/addon/mod/book/providers/tag-area-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index a0dbf92e1..57c4bf18d 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -372,6 +372,7 @@ "addon.mod_assign_submission_onlinetext.wordlimitexceeded": "assignsubmission_onlinetext", "addon.mod_book.errorchapter": "book", "addon.mod_book.modulenameplural": "book", + "addon.mod_book.tagarea_book_chapters": "book", "addon.mod_book.toc": "book", "addon.mod_chat.beep": "chat", "addon.mod_chat.chatreport": "chat", diff --git a/src/addon/mod/book/book.module.ts b/src/addon/mod/book/book.module.ts index cc06e008d..ea425d1c8 100644 --- a/src/addon/mod/book/book.module.ts +++ b/src/addon/mod/book/book.module.ts @@ -22,6 +22,8 @@ import { AddonModBookPrefetchHandler } from './providers/prefetch-handler'; import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; +import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; +import { AddonModBookTagAreaHandler } from './providers/tag-area-handler'; // List of providers (without handlers). export const ADDON_MOD_BOOK_PROVIDERS: any[] = [ @@ -39,18 +41,21 @@ export const ADDON_MOD_BOOK_PROVIDERS: any[] = [ AddonModBookModuleHandler, AddonModBookLinkHandler, AddonModBookListLinkHandler, - AddonModBookPrefetchHandler + AddonModBookPrefetchHandler, + AddonModBookTagAreaHandler ] }) export class AddonModBookModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModBookModuleHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModBookLinkHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModBookPrefetchHandler, - listLinkHandler: AddonModBookListLinkHandler) { + listLinkHandler: AddonModBookListLinkHandler, tagAreaDelegate: CoreTagAreaDelegate, + tagAreaHandler: AddonModBookTagAreaHandler) { moduleDelegate.registerHandler(moduleHandler); contentLinksDelegate.registerHandler(linkHandler); contentLinksDelegate.registerHandler(listLinkHandler); prefetchDelegate.registerHandler(prefetchHandler); + tagAreaDelegate.registerHandler(tagAreaHandler); } } diff --git a/src/addon/mod/book/lang/en.json b/src/addon/mod/book/lang/en.json index 7d1140fe4..4f8f32f54 100644 --- a/src/addon/mod/book/lang/en.json +++ b/src/addon/mod/book/lang/en.json @@ -1,5 +1,6 @@ { "errorchapter": "Error reading chapter of book.", "modulenameplural": "Books", + "tagarea_book_chapters": "Book chapters", "toc": "Table of contents" } \ No newline at end of file diff --git a/src/addon/mod/book/providers/tag-area-handler.ts b/src/addon/mod/book/providers/tag-area-handler.ts new file mode 100644 index 000000000..47c456cb6 --- /dev/null +++ b/src/addon/mod/book/providers/tag-area-handler.ts @@ -0,0 +1,75 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable, Injector } from '@angular/core'; +import { CoreTagAreaHandler } from '@core/tag/providers/area-delegate'; +import { CoreTagHelperProvider } from '@core/tag/providers/helper'; +import { CoreTagFeedComponent } from '@core/tag/components/feed/feed'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreUrlUtilsProvider } from '@providers/utils/url'; +import { AddonModBookProvider } from './book'; + +/** + * Handler to support tags. + */ +@Injectable() +export class AddonModBookTagAreaHandler implements CoreTagAreaHandler { + name = 'AddonModBookTagAreaHandler'; + type = 'mod_book/book_chapters'; + + constructor(private tagHelper: CoreTagHelperProvider, private bookProvider: AddonModBookProvider, + private courseProvider: CoreCourseProvider, private urlUtils: CoreUrlUtilsProvider) {} + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.bookProvider.isPluginEnabled(); + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param {string} content Rendered content. + * @return {any[]|Promise} Area items (or promise resolved with the items). + */ + parseContent(content: string): any[] | Promise { + const items = this.tagHelper.parseFeedContent(content); + + // Find module ids of the returned books, they are needed by the link delegate. + return Promise.all(items.map((item) => { + const params = this.urlUtils.extractUrlParams(item.url); + if (params.b && !params.id) { + const bookId = parseInt(params.b, 10); + + return this.courseProvider.getModuleBasicInfoByInstance(bookId, 'book').then((module) => { + item.url += '&id=' + module.id; + }); + } + })).then(() => { + return items; + }); + } + + /** + * Get the component to use to display items. + * + * @param {Injector} injector Injector. + * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(injector: Injector): any | Promise { + return CoreTagFeedComponent; + } +} diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index f48252c14..121bd0dba 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -372,6 +372,7 @@ "addon.mod_assign_submission_onlinetext.wordlimitexceeded": "The word limit for this assignment is {{$a.limit}} words and you are attempting to submit {{$a.count}} words. Please review your submission and try again.", "addon.mod_book.errorchapter": "Error reading chapter of book.", "addon.mod_book.modulenameplural": "Books", + "addon.mod_book.tagarea_book_chapters": "Book chapters", "addon.mod_book.toc": "Table of contents", "addon.mod_chat.beep": "Beep", "addon.mod_chat.chatreport": "Chat sessions", From e698537cf74846b13fba9aabdfbc13ac817e353a Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:16:41 +0200 Subject: [PATCH 15/23] MOBILE-2201 data: Tag area handler for database records --- scripts/langindex.json | 1 + src/addon/mod/data/data.module.ts | 9 ++- src/addon/mod/data/lang/en.json | 1 + .../mod/data/providers/tag-area-handler.ts | 58 +++++++++++++++++++ src/assets/lang/en.json | 1 + 5 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/addon/mod/data/providers/tag-area-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index 57c4bf18d..ac7fabe30 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -460,6 +460,7 @@ "addon.mod_data.searchbytagsnotsupported": "local_moodlemobileapp", "addon.mod_data.selectedrequired": "data", "addon.mod_data.single": "data", + "addon.mod_data.tagarea_data_records": "data", "addon.mod_data.timeadded": "data", "addon.mod_data.timemodified": "data", "addon.mod_data.usedate": "data", diff --git a/src/addon/mod/data/data.module.ts b/src/addon/mod/data/data.module.ts index 9babfa364..158865aec 100644 --- a/src/addon/mod/data/data.module.ts +++ b/src/addon/mod/data/data.module.ts @@ -33,6 +33,8 @@ import { AddonModDataSyncCronHandler } from './providers/sync-cron-handler'; import { AddonModDataOfflineProvider } from './providers/offline'; import { AddonModDataFieldsDelegate } from './providers/fields-delegate'; import { AddonModDataDefaultFieldHandler } from './providers/default-field-handler'; +import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; +import { AddonModDataTagAreaHandler } from './providers/tag-area-handler'; import { AddonModDataFieldModule } from './fields/field.module'; import { CoreUpdateManagerProvider } from '@providers/update-manager'; @@ -67,7 +69,8 @@ export const ADDON_MOD_DATA_PROVIDERS: any[] = [ AddonModDataEditLinkHandler, AddonModDataListLinkHandler, AddonModDataSyncCronHandler, - AddonModDataDefaultFieldHandler + AddonModDataDefaultFieldHandler, + AddonModDataTagAreaHandler ] }) export class AddonModDataModule { @@ -77,7 +80,8 @@ export class AddonModDataModule { cronDelegate: CoreCronDelegate, syncHandler: AddonModDataSyncCronHandler, updateManager: CoreUpdateManagerProvider, approveLinkHandler: AddonModDataApproveLinkHandler, deleteLinkHandler: AddonModDataDeleteLinkHandler, showLinkHandler: AddonModDataShowLinkHandler, editLinkHandler: AddonModDataEditLinkHandler, - listLinkHandler: AddonModDataListLinkHandler) { + listLinkHandler: AddonModDataListLinkHandler, tagAreaDelegate: CoreTagAreaDelegate, + tagAreaHandler: AddonModDataTagAreaHandler) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); @@ -88,6 +92,7 @@ export class AddonModDataModule { contentLinksDelegate.registerHandler(editLinkHandler); contentLinksDelegate.registerHandler(listLinkHandler); cronDelegate.register(syncHandler); + tagAreaDelegate.registerHandler(tagAreaHandler); // Allow migrating the tables from the old app to the new schema. updateManager.registerSiteTableMigration({ diff --git a/src/addon/mod/data/lang/en.json b/src/addon/mod/data/lang/en.json index f7c0005dc..219ec090c 100644 --- a/src/addon/mod/data/lang/en.json +++ b/src/addon/mod/data/lang/en.json @@ -38,6 +38,7 @@ "searchbytagsnotsupported": "Sorry, searching by tags is not supported by the app.", "selectedrequired": "All selected required", "single": "View single", + "tagarea_data_records": "Data records", "timeadded": "Time added", "timemodified": "Time modified", "usedate": "Include in search." diff --git a/src/addon/mod/data/providers/tag-area-handler.ts b/src/addon/mod/data/providers/tag-area-handler.ts new file mode 100644 index 000000000..f7fb15098 --- /dev/null +++ b/src/addon/mod/data/providers/tag-area-handler.ts @@ -0,0 +1,58 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable, Injector } from '@angular/core'; +import { CoreTagAreaHandler } from '@core/tag/providers/area-delegate'; +import { CoreTagHelperProvider } from '@core/tag/providers/helper'; +import { CoreTagFeedComponent } from '@core/tag/components/feed/feed'; +import { AddonModDataProvider } from './data'; + +/** + * Handler to support tags. + */ +@Injectable() +export class AddonModDataTagAreaHandler implements CoreTagAreaHandler { + name = 'AddonModDataTagAreaHandler'; + type = 'mod_data/data_records'; + + constructor(private tagHelper: CoreTagHelperProvider, private dataProvider: AddonModDataProvider) {} + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.dataProvider.isPluginEnabled(); + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param {string} content Rendered content. + * @return {any[]|Promise} Area items (or promise resolved with the items). + */ + parseContent(content: string): any[] | Promise { + return this.tagHelper.parseFeedContent(content); + } + + /** + * Get the component to use to display items. + * + * @param {Injector} injector Injector. + * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(injector: Injector): any | Promise { + return CoreTagFeedComponent; + } +} diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 121bd0dba..ba1bfb778 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -460,6 +460,7 @@ "addon.mod_data.searchbytagsnotsupported": "Sorry, searching by tags is not supported by the app.", "addon.mod_data.selectedrequired": "All selected required", "addon.mod_data.single": "View single", + "addon.mod_data.tagarea_data_records": "Data records", "addon.mod_data.timeadded": "Time added", "addon.mod_data.timemodified": "Time modified", "addon.mod_data.usedate": "Include in search.", From f1591b1113e74ccb415a0a448686715d720c8f4b Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:17:47 +0200 Subject: [PATCH 16/23] MOBILE-2201 forum: Tag area handler for forum posts --- scripts/langindex.json | 1 + src/addon/mod/forum/forum.module.ts | 9 ++- src/addon/mod/forum/lang/en.json | 1 + .../mod/forum/providers/tag-area-handler.ts | 57 +++++++++++++++++++ src/assets/lang/en.json | 1 + 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 src/addon/mod/forum/providers/tag-area-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index ac7fabe30..cb110da9c 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -553,6 +553,7 @@ "addon.mod_forum.reply": "forum", "addon.mod_forum.replyplaceholder": "forum", "addon.mod_forum.subject": "forum", + "addon.mod_forum.tagarea_forum_posts": "forum", "addon.mod_forum.thisforumhasduedate": "forum", "addon.mod_forum.thisforumisdue": "forum", "addon.mod_forum.unlockdiscussion": "forum", diff --git a/src/addon/mod/forum/forum.module.ts b/src/addon/mod/forum/forum.module.ts index 94fe30257..b8463c714 100644 --- a/src/addon/mod/forum/forum.module.ts +++ b/src/addon/mod/forum/forum.module.ts @@ -18,6 +18,7 @@ import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate'; +import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; import { AddonModForumProvider } from './providers/forum'; import { AddonModForumOfflineProvider } from './providers/offline'; import { AddonModForumHelperProvider } from './providers/helper'; @@ -30,6 +31,7 @@ import { AddonModForumDiscussionLinkHandler } from './providers/discussion-link- import { AddonModForumListLinkHandler } from './providers/list-link-handler'; import { AddonModForumPostLinkHandler } from './providers/post-link-handler'; import { AddonModForumPushClickHandler } from './providers/push-click-handler'; +import { AddonModForumTagAreaHandler } from './providers/tag-area-handler'; import { AddonModForumComponentsModule } from './components/components.module'; import { CoreUpdateManagerProvider } from '@providers/update-manager'; @@ -59,7 +61,8 @@ export const ADDON_MOD_FORUM_PROVIDERS: any[] = [ AddonModForumListLinkHandler, AddonModForumPostLinkHandler, AddonModForumDiscussionLinkHandler, - AddonModForumPushClickHandler + AddonModForumPushClickHandler, + AddonModForumTagAreaHandler ] }) export class AddonModForumModule { @@ -69,7 +72,8 @@ export class AddonModForumModule { indexHandler: AddonModForumIndexLinkHandler, discussionHandler: AddonModForumDiscussionLinkHandler, updateManager: CoreUpdateManagerProvider, listLinkHandler: AddonModForumListLinkHandler, pushNotificationsDelegate: CorePushNotificationsDelegate, pushClickHandler: AddonModForumPushClickHandler, - postLinkHandler: AddonModForumPostLinkHandler) { + postLinkHandler: AddonModForumPostLinkHandler, tagAreaDelegate: CoreTagAreaDelegate, + tagAreaHandler: AddonModForumTagAreaHandler) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); @@ -79,6 +83,7 @@ export class AddonModForumModule { linksDelegate.registerHandler(listLinkHandler); linksDelegate.registerHandler(postLinkHandler); pushNotificationsDelegate.registerClickHandler(pushClickHandler); + tagAreaDelegate.registerHandler(tagAreaHandler); // Allow migrating the tables from the old app to the new schema. updateManager.registerSiteTablesMigration([ diff --git a/src/addon/mod/forum/lang/en.json b/src/addon/mod/forum/lang/en.json index 93e72d2c2..dbfac5fd3 100644 --- a/src/addon/mod/forum/lang/en.json +++ b/src/addon/mod/forum/lang/en.json @@ -50,6 +50,7 @@ "reply": "Reply", "replyplaceholder": "Write your reply...", "subject": "Subject", + "tagarea_forum_posts": "Forum posts", "thisforumhasduedate": "The due date for posting to this forum is {{$a}}.", "thisforumisdue": "The due date for posting to this forum was {{$a}}.", "unlockdiscussion": "Unlock this discussion", diff --git a/src/addon/mod/forum/providers/tag-area-handler.ts b/src/addon/mod/forum/providers/tag-area-handler.ts new file mode 100644 index 000000000..02505b31c --- /dev/null +++ b/src/addon/mod/forum/providers/tag-area-handler.ts @@ -0,0 +1,57 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable, Injector } from '@angular/core'; +import { CoreTagAreaHandler } from '@core/tag/providers/area-delegate'; +import { CoreTagHelperProvider } from '@core/tag/providers/helper'; +import { CoreTagFeedComponent } from '@core/tag/components/feed/feed'; + +/** + * Handler to support tags. + */ +@Injectable() +export class AddonModForumTagAreaHandler implements CoreTagAreaHandler { + name = 'AddonModForumTagAreaHandler'; + type = 'mod_forum/forum_posts'; + + constructor(private tagHelper: CoreTagHelperProvider) {} + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return true; + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param {string} content Rendered content. + * @return {any[]|Promise} Area items (or promise resolved with the items). + */ + parseContent(content: string): any[] | Promise { + return this.tagHelper.parseFeedContent(content); + } + + /** + * Get the component to use to display items. + * + * @param {Injector} injector Injector. + * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(injector: Injector): any | Promise { + return CoreTagFeedComponent; + } +} diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index ba1bfb778..cb31d3779 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -553,6 +553,7 @@ "addon.mod_forum.reply": "Reply", "addon.mod_forum.replyplaceholder": "Write your reply...", "addon.mod_forum.subject": "Subject", + "addon.mod_forum.tagarea_forum_posts": "Forum posts", "addon.mod_forum.thisforumhasduedate": "The due date for posting to this forum is {{$a}}.", "addon.mod_forum.thisforumisdue": "The due date for posting to this forum was {{$a}}.", "addon.mod_forum.unlockdiscussion": "Unlock this discussion", From 0a770f8528c5b963594ab44e45df107f44a17389 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:18:27 +0200 Subject: [PATCH 17/23] MOBILE-2201 glossary: Tag area handler for glossary entries --- scripts/langindex.json | 1 + src/addon/mod/glossary/glossary.module.ts | 9 ++- src/addon/mod/glossary/lang/en.json | 3 +- .../glossary/providers/tag-area-handler.ts | 57 +++++++++++++++++++ src/assets/lang/en.json | 1 + 5 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 src/addon/mod/glossary/providers/tag-area-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index cb110da9c..2d1efd4f0 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -588,6 +588,7 @@ "addon.mod_glossary.modulenameplural": "glossary", "addon.mod_glossary.noentriesfound": "local_moodlemobileapp", "addon.mod_glossary.searchquery": "local_moodlemobileapp", + "addon.mod_glossary.tagarea_glossary_entries": "glossary", "addon.mod_imscp.deploymenterror": "imscp", "addon.mod_imscp.modulenameplural": "imscp", "addon.mod_imscp.showmoduledescription": "local_moodlemobileapp", diff --git a/src/addon/mod/glossary/glossary.module.ts b/src/addon/mod/glossary/glossary.module.ts index ac311c144..e3520fbe7 100644 --- a/src/addon/mod/glossary/glossary.module.ts +++ b/src/addon/mod/glossary/glossary.module.ts @@ -17,6 +17,7 @@ import { CoreCronDelegate } from '@providers/cron'; import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; +import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; import { AddonModGlossaryProvider } from './providers/glossary'; import { AddonModGlossaryOfflineProvider } from './providers/offline'; import { AddonModGlossaryHelperProvider } from './providers/helper'; @@ -28,6 +29,7 @@ import { AddonModGlossaryIndexLinkHandler } from './providers/index-link-handler import { AddonModGlossaryEntryLinkHandler } from './providers/entry-link-handler'; import { AddonModGlossaryListLinkHandler } from './providers/list-link-handler'; import { AddonModGlossaryEditLinkHandler } from './providers/edit-link-handler'; +import { AddonModGlossaryTagAreaHandler } from './providers/tag-area-handler'; import { AddonModGlossaryComponentsModule } from './components/components.module'; import { CoreUpdateManagerProvider } from '@providers/update-manager'; @@ -56,7 +58,8 @@ export const ADDON_MOD_GLOSSARY_PROVIDERS: any[] = [ AddonModGlossaryIndexLinkHandler, AddonModGlossaryEntryLinkHandler, AddonModGlossaryListLinkHandler, - AddonModGlossaryEditLinkHandler + AddonModGlossaryEditLinkHandler, + AddonModGlossaryTagAreaHandler ] }) export class AddonModGlossaryModule { @@ -65,7 +68,8 @@ export class AddonModGlossaryModule { cronDelegate: CoreCronDelegate, syncHandler: AddonModGlossarySyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModGlossaryIndexLinkHandler, discussionHandler: AddonModGlossaryEntryLinkHandler, updateManager: CoreUpdateManagerProvider, listLinkHandler: AddonModGlossaryListLinkHandler, - editLinkHandler: AddonModGlossaryEditLinkHandler) { + editLinkHandler: AddonModGlossaryEditLinkHandler, tagAreaDelegate: CoreTagAreaDelegate, + tagAreaHandler: AddonModGlossaryTagAreaHandler) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); @@ -74,6 +78,7 @@ export class AddonModGlossaryModule { linksDelegate.registerHandler(discussionHandler); linksDelegate.registerHandler(listLinkHandler); linksDelegate.registerHandler(editLinkHandler); + tagAreaDelegate.registerHandler(tagAreaHandler); // Allow migrating the tables from the old app to the new schema. updateManager.registerSiteTableMigration({ diff --git a/src/addon/mod/glossary/lang/en.json b/src/addon/mod/glossary/lang/en.json index 18e5ff7bc..ba4329f33 100644 --- a/src/addon/mod/glossary/lang/en.json +++ b/src/addon/mod/glossary/lang/en.json @@ -26,5 +26,6 @@ "linking": "Auto-linking", "modulenameplural": "Glossaries", "noentriesfound": "No entries were found.", - "searchquery": "Search query" + "searchquery": "Search query", + "tagarea_glossary_entries": "Glossary entries" } diff --git a/src/addon/mod/glossary/providers/tag-area-handler.ts b/src/addon/mod/glossary/providers/tag-area-handler.ts new file mode 100644 index 000000000..4c62f698d --- /dev/null +++ b/src/addon/mod/glossary/providers/tag-area-handler.ts @@ -0,0 +1,57 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable, Injector } from '@angular/core'; +import { CoreTagAreaHandler } from '@core/tag/providers/area-delegate'; +import { CoreTagHelperProvider } from '@core/tag/providers/helper'; +import { CoreTagFeedComponent } from '@core/tag/components/feed/feed'; + +/** + * Handler to support tags. + */ +@Injectable() +export class AddonModGlossaryTagAreaHandler implements CoreTagAreaHandler { + name = 'AddonModGlossaryTagAreaHandler'; + type = 'mod_glossary/glossary_entries'; + + constructor(private tagHelper: CoreTagHelperProvider) {} + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return true; + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param {string} content Rendered content. + * @return {any[]|Promise} Area items (or promise resolved with the items). + */ + parseContent(content: string): any[] | Promise { + return this.tagHelper.parseFeedContent(content); + } + + /** + * Get the component to use to display items. + * + * @param {Injector} injector Injector. + * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(injector: Injector): any | Promise { + return CoreTagFeedComponent; + } +} diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index cb31d3779..00a011891 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -588,6 +588,7 @@ "addon.mod_glossary.modulenameplural": "Glossaries", "addon.mod_glossary.noentriesfound": "No entries were found.", "addon.mod_glossary.searchquery": "Search query", + "addon.mod_glossary.tagarea_glossary_entries": "Glossary entries", "addon.mod_imscp.deploymenterror": "Content package error!", "addon.mod_imscp.modulenameplural": "IMS content packages", "addon.mod_imscp.showmoduledescription": "Show description", From 353c6823dbbfa2ced392f8131e99709e7cdcabd4 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:19:07 +0200 Subject: [PATCH 18/23] MOBILE-2201 wiki: Tag area handler for wiki pages --- scripts/langindex.json | 1 + src/addon/mod/wiki/lang/en.json | 1 + .../mod/wiki/providers/tag-area-handler.ts | 57 +++++++++++++++++++ src/addon/mod/wiki/wiki.module.ts | 9 ++- src/assets/lang/en.json | 1 + 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 src/addon/mod/wiki/providers/tag-area-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index 2d1efd4f0..369488b84 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -843,6 +843,7 @@ "addon.mod_wiki.pageexists": "wiki", "addon.mod_wiki.pagename": "wiki", "addon.mod_wiki.subwiki": "local_moodlemobileapp", + "addon.mod_wiki.tagarea_wiki_pages": "wiki", "addon.mod_wiki.titleshouldnotbeempty": "local_moodlemobileapp", "addon.mod_wiki.viewpage": "local_moodlemobileapp", "addon.mod_wiki.wikipage": "local_moodlemobileapp", diff --git a/src/addon/mod/wiki/lang/en.json b/src/addon/mod/wiki/lang/en.json index 29ed054ce..246965b9c 100644 --- a/src/addon/mod/wiki/lang/en.json +++ b/src/addon/mod/wiki/lang/en.json @@ -14,6 +14,7 @@ "pageexists": "This page already exists.", "pagename": "Page name", "subwiki": "Sub-wiki", + "tagarea_wiki_pages": "Wiki pages", "titleshouldnotbeempty": "The title should not be empty", "viewpage": "View page", "wikipage": "Wiki page", diff --git a/src/addon/mod/wiki/providers/tag-area-handler.ts b/src/addon/mod/wiki/providers/tag-area-handler.ts new file mode 100644 index 000000000..0f3cab521 --- /dev/null +++ b/src/addon/mod/wiki/providers/tag-area-handler.ts @@ -0,0 +1,57 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable, Injector } from '@angular/core'; +import { CoreTagAreaHandler } from '@core/tag/providers/area-delegate'; +import { CoreTagHelperProvider } from '@core/tag/providers/helper'; +import { CoreTagFeedComponent } from '@core/tag/components/feed/feed'; + +/** + * Handler to support tags. + */ +@Injectable() +export class AddonModWikiTagAreaHandler implements CoreTagAreaHandler { + name = 'AddonModWikiTagAreaHandler'; + type = 'mod_wiki/wiki_pages'; + + constructor(private tagHelper: CoreTagHelperProvider) {} + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return true; + } + + /** + * Parses the rendered content of a tag index and returns the items. + * + * @param {string} content Rendered content. + * @return {any[]|Promise} Area items (or promise resolved with the items). + */ + parseContent(content: string): any[] | Promise { + return this.tagHelper.parseFeedContent(content); + } + + /** + * Get the component to use to display items. + * + * @param {Injector} injector Injector. + * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + */ + getComponent(injector: Injector): any | Promise { + return CoreTagFeedComponent; + } +} diff --git a/src/addon/mod/wiki/wiki.module.ts b/src/addon/mod/wiki/wiki.module.ts index 539b962dd..6396cac74 100644 --- a/src/addon/mod/wiki/wiki.module.ts +++ b/src/addon/mod/wiki/wiki.module.ts @@ -17,6 +17,7 @@ import { CoreCronDelegate } from '@providers/cron'; import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; +import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; import { AddonModWikiComponentsModule } from './components/components.module'; import { AddonModWikiProvider } from './providers/wiki'; import { AddonModWikiOfflineProvider } from './providers/wiki-offline'; @@ -29,6 +30,7 @@ import { AddonModWikiPageOrMapLinkHandler } from './providers/page-or-map-link-h import { AddonModWikiCreateLinkHandler } from './providers/create-link-handler'; import { AddonModWikiEditLinkHandler } from './providers/edit-link-handler'; import { AddonModWikiListLinkHandler } from './providers/list-link-handler'; +import { AddonModWikiTagAreaHandler } from './providers/tag-area-handler'; import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). @@ -55,7 +57,8 @@ export const ADDON_MOD_WIKI_PROVIDERS: any[] = [ AddonModWikiPageOrMapLinkHandler, AddonModWikiCreateLinkHandler, AddonModWikiEditLinkHandler, - AddonModWikiListLinkHandler + AddonModWikiListLinkHandler, + AddonModWikiTagAreaHandler ] }) export class AddonModWikiModule { @@ -64,7 +67,8 @@ export class AddonModWikiModule { cronDelegate: CoreCronDelegate, syncHandler: AddonModWikiSyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModWikiIndexLinkHandler, pageOrMapHandler: AddonModWikiPageOrMapLinkHandler, createHandler: AddonModWikiCreateLinkHandler, editHandler: AddonModWikiEditLinkHandler, - updateManager: CoreUpdateManagerProvider, listLinkHandler: AddonModWikiListLinkHandler) { + updateManager: CoreUpdateManagerProvider, listLinkHandler: AddonModWikiListLinkHandler, + tagAreaDelegate: CoreTagAreaDelegate, tagAreaHandler: AddonModWikiTagAreaHandler) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); @@ -74,6 +78,7 @@ export class AddonModWikiModule { linksDelegate.registerHandler(createHandler); linksDelegate.registerHandler(editHandler); linksDelegate.registerHandler(listLinkHandler); + tagAreaDelegate.registerHandler(tagAreaHandler); // Allow migrating the tables from the old app to the new schema. updateManager.registerSiteTableMigration({ diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 00a011891..6fc2be13d 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -843,6 +843,7 @@ "addon.mod_wiki.pageexists": "This page already exists.", "addon.mod_wiki.pagename": "Page name", "addon.mod_wiki.subwiki": "Sub-wiki", + "addon.mod_wiki.tagarea_wiki_pages": "Wiki pages", "addon.mod_wiki.titleshouldnotbeempty": "The title should not be empty", "addon.mod_wiki.viewpage": "View page", "addon.mod_wiki.wikipage": "Wiki page", From b2db3774e6d909dcea3e90827a5a2a1145c1c4ff Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:32:05 +0200 Subject: [PATCH 19/23] MOBILE-2201 tag: Index page --- scripts/langindex.json | 4 + src/assets/lang/en.json | 4 + src/core/tag/lang/en.json | 6 +- src/core/tag/pages/index-area/index-area.html | 16 ++ .../tag/pages/index-area/index-area.module.ts | 33 ++++ src/core/tag/pages/index-area/index-area.ts | 150 +++++++++++++++++ src/core/tag/pages/index/index.html | 24 +++ src/core/tag/pages/index/index.module.ts | 33 ++++ src/core/tag/pages/index/index.ts | 154 ++++++++++++++++++ src/core/tag/providers/tag.ts | 117 ++++++++++++- 10 files changed, 538 insertions(+), 3 deletions(-) create mode 100644 src/core/tag/pages/index-area/index-area.html create mode 100644 src/core/tag/pages/index-area/index-area.module.ts create mode 100644 src/core/tag/pages/index-area/index-area.ts create mode 100644 src/core/tag/pages/index/index.html create mode 100644 src/core/tag/pages/index/index.module.ts create mode 100644 src/core/tag/pages/index/index.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index 369488b84..679e4d559 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1800,11 +1800,15 @@ "core.submit": "moodle", "core.success": "moodle", "core.tablet": "local_moodlemobileapp", + "core.tag.errorareanotsupported": "local_moodlemobileapp", + "core.tag.itemstaggedwith": "moodle", + "core.tag.tag": "moodle", "core.tag.tagarea_course": "moodle", "core.tag.tagarea_course_modules": "moodle", "core.tag.tagarea_post": "moodle", "core.tag.tagarea_user": "moodle", "core.tag.tags": "moodle", + "core.tag.warningareasnotsupported": "local_moodlemobileapp", "core.teachers": "moodle", "core.thereisdatatosync": "local_moodlemobileapp", "core.thisdirection": "langconfig", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 6fc2be13d..69730eab9 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1800,11 +1800,15 @@ "core.submit": "Submit", "core.success": "Success", "core.tablet": "Tablet", + "core.tag.errorareanotsupported": "This tag area is not supported by the app.", + "core.tag.itemstaggedwith": "{{$a.tagarea}} tagged with \"{{$a.tag}}\"", + "core.tag.tag": "Tag", "core.tag.tagarea_course": "Courses", "core.tag.tagarea_course_modules": "Activities and resources", "core.tag.tagarea_post": "Blog posts", "core.tag.tagarea_user": "User interests", "core.tag.tags": "Tags", + "core.tag.warningareasnotsupported": "Some of the tag areas are not displayed because they are not supported by the app.", "core.teachers": "Teachers", "core.thereisdatatosync": "There are offline {{$a}} to be synchronised.", "core.thisdirection": "ltr", diff --git a/src/core/tag/lang/en.json b/src/core/tag/lang/en.json index 02e288849..b7f8b8794 100644 --- a/src/core/tag/lang/en.json +++ b/src/core/tag/lang/en.json @@ -1,7 +1,11 @@ { + "errorareanotsupported": "This tag area is not supported by the app.", + "itemstaggedwith": "{{$a.tagarea}} tagged with \"{{$a.tag}}\"", + "tag": "Tag", "tagarea_course": "Courses", "tagarea_course_modules": "Activities and resources", "tagarea_post": "Blog posts", "tagarea_user": "User interests", - "tags": "Tags" + "tags": "Tags", + "warningareasnotsupported": "Some of the tag areas are not displayed because they are not supported by the app." } diff --git a/src/core/tag/pages/index-area/index-area.html b/src/core/tag/pages/index-area/index-area.html new file mode 100644 index 000000000..8a43d2d51 --- /dev/null +++ b/src/core/tag/pages/index-area/index-area.html @@ -0,0 +1,16 @@ + + + {{ 'core.tag.itemstaggedwith' | translate: { $a: {tagarea: areaNameKey | translate, tag: tagName} } }} + + + + + + + + + + + + + diff --git a/src/core/tag/pages/index-area/index-area.module.ts b/src/core/tag/pages/index-area/index-area.module.ts new file mode 100644 index 000000000..87a49cd7f --- /dev/null +++ b/src/core/tag/pages/index-area/index-area.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreTagIndexAreaPage } from './index-area'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; + +@NgModule({ + declarations: [ + CoreTagIndexAreaPage + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + IonicPageModule.forChild(CoreTagIndexAreaPage), + TranslateModule.forChild() + ], +}) +export class CoreTagIndexAreaPageModule {} diff --git a/src/core/tag/pages/index-area/index-area.ts b/src/core/tag/pages/index-area/index-area.ts new file mode 100644 index 000000000..9f71f0532 --- /dev/null +++ b/src/core/tag/pages/index-area/index-area.ts @@ -0,0 +1,150 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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, Injector } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { IonicPage, NavParams } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreTagProvider } from '@core/tag/providers/tag'; +import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; + +/** + * Page that displays the tag index area. + */ +@IonicPage({ segment: 'core-tag-index-area' }) +@Component({ + selector: 'page-core-tag-index-area', + templateUrl: 'index-area.html', +}) +export class CoreTagIndexAreaPage { + tagId: number; + tagName: string; + collectionId: number; + areaId: number; + fromContextId: number; + contextId: number; + recursive: boolean; + areaNameKey: string; + loaded = false; + componentName: string; + itemType: string; + items = []; + nextPage = 0; + canLoadMore = false; + areaComponent: any; + loadMoreError = false; + + constructor(navParams: NavParams, private injector: Injector, private translate: TranslateService, + private tagProvider: CoreTagProvider, private domUtils: CoreDomUtilsProvider, + private tagAreaDelegate: CoreTagAreaDelegate) { + this.tagId = navParams.get('tagId'); + this.tagName = navParams.get('tagName'); + this.collectionId = navParams.get('collectionId'); + this.areaId = navParams.get('areaId'); + this.fromContextId = navParams.get('fromContextId'); + this.contextId = navParams.get('contextId'); + this.recursive = navParams.get('recursive'); + this.areaNameKey = navParams.get('areaNameKey'); + + // Pass the the following parameters to avoid fetching the first page. + this.componentName = navParams.get('componentName'); + this.itemType = navParams.get('itemType'); + this.items = navParams.get('items') || []; + this.nextPage = navParams.get('nextPage') || 0; + this.canLoadMore = !!navParams.get('canLoadMore'); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + let promise: Promise; + if (!this.componentName || !this.itemType || !this.items.length || this.nextPage == 0) { + promise = this.fetchData(true); + } else { + promise = Promise.resolve(); + } + + promise.then(() => { + return this.tagAreaDelegate.getComponent(this.componentName, this.itemType, this.injector).then((component) => { + this.areaComponent = component; + }); + }).finally(() => { + this.loaded = true; + }); + } + + /** + * Fetch next page of the tag index area. + * + * @param {boolean} [refresh=false] Whether to refresh the data or fetch a new page. + * @return {Promise} Resolved when done. + */ + fetchData(refresh: boolean = false): Promise { + this.loadMoreError = false; + const page = refresh ? 0 : this.nextPage; + + return this.tagProvider.getTagIndexPerArea(this.tagId, this.tagName, this.collectionId, this.areaId, this.fromContextId, + this.contextId, this.recursive, page).then((areas) => { + const area = areas[0]; + + return this.tagAreaDelegate.parseContent(area.component, area.itemtype, area.content).then((items) => { + if (!items || !items.length) { + // Tag area not supported. + return Promise.reject(this.translate.instant('core.tag.errorareanotsupported')); + } + + if (page == 0) { + this.items = items; + } else { + this.items.push(...items); + } + this.componentName = area.component; + this.itemType = area.itemtype; + this.areaNameKey = this.tagAreaDelegate.getDisplayNameKey(area.component, area.itemtype); + this.canLoadMore = !!area.nextpageurl; + this.nextPage = page + 1; + }); + }).catch((error) => { + this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. + this.domUtils.showErrorModalDefault(error, 'Error loading tag index'); + }); + } + + /** + * Load more items. + * + * @param {any} infiniteComplete Infinite scroll complete function. + * @return {Promise} Resolved when done. + */ + loadMore(infiniteComplete: any): Promise { + return this.fetchData().finally(() => { + infiniteComplete(); + }); + } + + /** + * Refresh data. + * + * @param {any} refresher Refresher. + */ + refreshData(refresher: any): void { + this.tagProvider.invalidateTagIndexPerArea(this.tagId, this.tagName, this.collectionId, this.areaId, this.fromContextId, + this.contextId, this.recursive).finally(() => { + this.fetchData(true).finally(() => { + refresher.complete(); + }); + }); + } +} diff --git a/src/core/tag/pages/index/index.html b/src/core/tag/pages/index/index.html new file mode 100644 index 000000000..5174fcd7c --- /dev/null +++ b/src/core/tag/pages/index/index.html @@ -0,0 +1,24 @@ + + + {{ 'core.tag.tag' | translate }}: {{ tagName }} + + + + + + + + + + + + {{ 'core.tag.warningareasnotsupported' | translate }} + + +

{{ area.nameKey | translate }}

+ {{ area.badge }} +
+
+
+
+
diff --git a/src/core/tag/pages/index/index.module.ts b/src/core/tag/pages/index/index.module.ts new file mode 100644 index 000000000..bb3cd138d --- /dev/null +++ b/src/core/tag/pages/index/index.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreTagIndexPage } from './index'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; + +@NgModule({ + declarations: [ + CoreTagIndexPage + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + IonicPageModule.forChild(CoreTagIndexPage), + TranslateModule.forChild() + ], +}) +export class CoreTagIndexPageModule {} diff --git a/src/core/tag/pages/index/index.ts b/src/core/tag/pages/index/index.ts new file mode 100644 index 000000000..9185bae0f --- /dev/null +++ b/src/core/tag/pages/index/index.ts @@ -0,0 +1,154 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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, ViewChild } from '@angular/core'; +import { IonicPage, NavParams } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreTagProvider } from '@core/tag/providers/tag'; +import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; + +/** + * Page that displays the tag index. + */ +@IonicPage({ segment: 'core-tag-index' }) +@Component({ + selector: 'page-core-tag-index', + templateUrl: 'index.html', +}) +export class CoreTagIndexPage { + @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; + + tagId: number; + tagName: string; + collectionId: number; + areaId: number; + fromContextId: number; + contextId: number; + recursive: boolean; + loaded = false; + areas: Array<{ + id: number, + componentName: string, + itemType: string, + nameKey: string, + items: any[], + canLoadMore: boolean, + badge: string + }>; + selectedAreaId: number; + hasUnsupportedAreas = false; + + constructor(navParams: NavParams, private tagProvider: CoreTagProvider, private domUtils: CoreDomUtilsProvider, + private tagAreaDelegate: CoreTagAreaDelegate) { + this.tagId = navParams.get('tagId') || 0; + this.tagName = navParams.get('tagName') || ''; + this.collectionId = navParams.get('collectionId'); + this.areaId = navParams.get('areaId') || 0; + this.fromContextId = navParams.get('fromContextId') || 0; + this.contextId = navParams.get('contextId') || 0; + this.recursive = navParams.get('recursive') || true; + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.fetchData().then(() => { + if (this.splitviewCtrl.isOn() && this.areas && this.areas.length > 0) { + const area = this.areas.find((area) => area.id == this.areaId); + this.openArea(area || this.areas[0]); + } + }).finally(() => { + this.loaded = true; + }); + } + + /** + * Fetch first page of tag index per area. + * + * @return {Promise} Resolved when done. + */ + fetchData(): Promise { + return this.tagProvider.getTagIndexPerArea(this.tagId, this.tagName, this.collectionId, this.areaId, this.fromContextId, + this.contextId, this.recursive, 0).then((areas) => { + this.areas = []; + this.hasUnsupportedAreas = false; + + return Promise.all(areas.map((area) => { + return this.tagAreaDelegate.parseContent(area.component, area.itemtype, area.content).then((items) => { + if (!items || !items.length) { + // Tag area not supported, skip. + this.hasUnsupportedAreas = true; + + return null; + } + + return { + id: area.ta, + componentName: area.component, + itemType: area.itemtype, + nameKey: this.tagAreaDelegate.getDisplayNameKey(area.component, area.itemtype), + items, + canLoadMore: !!area.nextpageurl, + badge: items && items.length ? items.length + (area.nextpageurl ? '+' : '') : '', + }; + }); + })).then((areas) => { + this.areas = areas.filter((area) => area != null); + }); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error loading tag index'); + }); + } + + /** + * Refresh data. + * + * @param {any} refresher Refresher. + */ + refreshData(refresher: any): void { + this.tagProvider.invalidateTagIndexPerArea(this.tagId, this.tagName, this.collectionId, this.areaId, this.fromContextId, + this.contextId, this.recursive).finally(() => { + this.fetchData().finally(() => { + refresher.complete(); + }); + }); + } + + /** + * Navigate to an index area. + * + * @param {any} area Area. + */ + openArea(area: any): void { + this.selectedAreaId = area.id; + const params = { + tagId: this.tagId, + tagName: this.tagName, + collectionId: this.collectionId, + areaId: area.id, + fromContextId: this.fromContextId, + contextId: this.contextId, + recursive: this.recursive, + areaNameKey: area.nameKey, + componentName: area.component, + itemType: area.itemType, + items: area.items.slice(), + canLoadMore: area.canLoadMore, + nextPage: 1 + }; + this.splitviewCtrl.push('CoreTagIndexAreaPage', params); + } +} diff --git a/src/core/tag/providers/tag.ts b/src/core/tag/providers/tag.ts index fdcdaf189..88b739f04 100644 --- a/src/core/tag/providers/tag.ts +++ b/src/core/tag/providers/tag.ts @@ -13,8 +13,27 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '@providers/sites'; -import { CoreSite } from '@classes/site'; +import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; + +/** + * Structure of a tag index returned by WS. + */ +export interface CoreTagIndex { + tagid: number; + ta: number; + component: string; + itemtype: string; + nextpageurl: string; + prevpageurl: string; + exclusiveurl: string; + exclusivetext: string; + title: string; + content: string; + hascontent: number; + anchor: string; +} /** * Structure of a tag item returned by WS. @@ -38,7 +57,9 @@ export interface CoreTagItem { @Injectable() export class CoreTagProvider { - constructor(private sitesProvider: CoreSitesProvider) {} + protected ROOT_CACHE_KEY = 'CoreTag:'; + + constructor(private sitesProvider: CoreSitesProvider, private translate: TranslateService) {} /** * Check whether tags are available in a certain site. @@ -67,4 +88,96 @@ export class CoreTagProvider { site.wsAvailable('core_tag_get_tag_collections') && !site.isFeatureDisabled('NoDelegate_CoreTag'); } + + /** + * Fetch the tag index. + * + * @param {number} [id=0] Tag ID. + * @param {string} [name=''] Tag name. + * @param {number} [collectionId=0] Tag collection ID. + * @param {number} [areaId=0] Tag area ID. + * @param {number} [fromContextId=0] Context ID where the link was displayed. + * @param {number} [contextId=0] Context ID where to search for items. + * @param {boolean} [recursive=true] Search in the context and its children. + * @param {number} [page=0] Page number. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the tag index per area. + * @since 3.7 + */ + getTagIndexPerArea(id: number, name: string = '', collectionId: number = 0, areaId: number = 0, fromContextId: number = 0, + contextId: number = 0, recursive: boolean = true, page: number = 0, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + tagindex: { + id: id, + tag: name, + tc: collectionId, + ta: areaId, + excl: true, + from: fromContextId, + ctx: contextId, + rec: recursive, + page: page + }, + }; + const preSets: CoreSiteWSPreSets = { + updateFrequency: CoreSite.FREQUENCY_OFTEN, + cacheKey: this.getTagIndexPerAreaKey(id, name, collectionId, areaId, fromContextId, contextId, recursive) + }; + + return site.read('core_tag_get_tagindex_per_area', params, preSets).catch((error) => { + // Workaround for WS not passing parameter to error string. + if (error && error.errorcode == 'notagsfound') { + error.message = this.translate.instant('core.tag.notagsfound', {$a: name || id || ''}); + } + + return Promise.reject(error); + }).then((response) => { + if (!response || !response.length) { + return Promise.reject(null); + } + + return response; + }); + }); + } + + /** + * Invalidate tag index. + * + * @param {number} [id=0] Tag ID. + * @param {string} [name=''] Tag name. + * @param {number} [collectionId=0] Tag collection ID. + * @param {number} [areaId=0] Tag area ID. + * @param {number} [fromContextId=0] Context ID where the link was displayed. + * @param {number} [contextId=0] Context ID where to search for items. + * @param {boolean} [recursive=true] Search in the context and its children. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateTagIndexPerArea(id: number, name: string = '', collectionId: number = 0, areaId: number = 0, + fromContextId: number = 0, contextId: number = 0, recursive: boolean = true, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const key = this.getTagIndexPerAreaKey(id, name, collectionId, areaId, fromContextId, contextId, recursive); + + return site.invalidateWsCacheForKey(key); + }); + } + + /** + * Get cache key for tag index. + * + * @param {number} id Tag ID. + * @param {string} name Tag name. + * @param {number} collectionId Tag collection ID. + * @param {number} areaId Tag area ID. + * @param {number} fromContextId Context ID where the link was displayed. + * @param {number} contextId Context ID where to search for items. + * @param {boolean} [recursive=true] Search in the context and its children. + * @return {string} Cache key. + */ + protected getTagIndexPerAreaKey(id: number, name: string, collectionId: number, areaId: number, fromContextId: number, + contextId: number, recursive: boolean): string { + return this.ROOT_CACHE_KEY + 'index:' + id + ':' + name + ':' + collectionId + ':' + areaId + ':' + fromContextId + ':' + + contextId + ':' + (recursive ? 1 : 0); + } } From b5b480e226c5aaa0984a133706ff932b31ae456f Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Wed, 17 Jul 2019 16:13:00 +0200 Subject: [PATCH 20/23] MOBILE-2201 search-box: Initial search text property --- src/components/search-box/search-box.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/search-box/search-box.ts b/src/components/search-box/search-box.ts index 61922cbb3..e978908d1 100644 --- a/src/components/search-box/search-box.ts +++ b/src/components/search-box/search-box.ts @@ -39,6 +39,7 @@ export class CoreSearchBoxComponent implements OnInit { @Input() lengthCheck = 3; // Check value length before submit. If 0, any string will be submitted. @Input() showClear = true; // Show/hide clear button. @Input() disabled = false; // Disables the input text. + @Input() initialSearch: string; // Initial search text. @Output() onSubmit: EventEmitter; // Send data when submitting the search form. @Output() onClear: EventEmitter; // Send event when clearing the search form. @@ -55,6 +56,7 @@ export class CoreSearchBoxComponent implements OnInit { this.placeholder = this.placeholder || this.translate.instant('core.search'); this.spellcheck = this.utils.isTrueOrOne(this.spellcheck); this.showClear = this.utils.isTrueOrOne(this.showClear); + this.searchText = this.initialSearch || ''; } /** From d00d40189417fcc1156cc3b75749c848468edaa2 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:35:47 +0200 Subject: [PATCH 21/23] MOBILE-2201 tag: Search page --- scripts/langindex.json | 5 + src/assets/lang/en.json | 5 + src/core/tag/lang/en.json | 5 + src/core/tag/pages/search/search.html | 37 +++++ src/core/tag/pages/search/search.module.ts | 33 +++++ src/core/tag/pages/search/search.scss | 95 ++++++++++++ src/core/tag/pages/search/search.ts | 135 +++++++++++++++++ src/core/tag/providers/mainmenu-handler.ts | 59 ++++++++ src/core/tag/providers/tag.ts | 162 +++++++++++++++++++++ src/core/tag/tag.module.ts | 9 +- 10 files changed, 544 insertions(+), 1 deletion(-) create mode 100644 src/core/tag/pages/search/search.html create mode 100644 src/core/tag/pages/search/search.module.ts create mode 100644 src/core/tag/pages/search/search.scss create mode 100644 src/core/tag/pages/search/search.ts create mode 100644 src/core/tag/providers/mainmenu-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index 679e4d559..10ac601e4 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1800,8 +1800,13 @@ "core.submit": "moodle", "core.success": "moodle", "core.tablet": "local_moodlemobileapp", + "core.tag.defautltagcoll": "moodle", "core.tag.errorareanotsupported": "local_moodlemobileapp", + "core.tag.inalltagcoll": "moodle", "core.tag.itemstaggedwith": "moodle", + "core.tag.notagsfound": "moodle", + "core.tag.searchtags": "moodle", + "core.tag.showingfirsttags": "moodle", "core.tag.tag": "moodle", "core.tag.tagarea_course": "moodle", "core.tag.tagarea_course_modules": "moodle", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 69730eab9..726ea14f2 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1800,8 +1800,13 @@ "core.submit": "Submit", "core.success": "Success", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Default collection", "core.tag.errorareanotsupported": "This tag area is not supported by the app.", + "core.tag.inalltagcoll": "Everywhere", "core.tag.itemstaggedwith": "{{$a.tagarea}} tagged with \"{{$a.tag}}\"", + "core.tag.notagsfound": "No tags matching \"{{$a}}\" found", + "core.tag.searchtags": "Search tags", + "core.tag.showingfirsttags": "Showing {{$a}} most popular tags", "core.tag.tag": "Tag", "core.tag.tagarea_course": "Courses", "core.tag.tagarea_course_modules": "Activities and resources", diff --git a/src/core/tag/lang/en.json b/src/core/tag/lang/en.json index b7f8b8794..c23afc5e9 100644 --- a/src/core/tag/lang/en.json +++ b/src/core/tag/lang/en.json @@ -1,6 +1,11 @@ { + "defautltagcoll": "Default collection", "errorareanotsupported": "This tag area is not supported by the app.", + "inalltagcoll": "Everywhere", "itemstaggedwith": "{{$a.tagarea}} tagged with \"{{$a.tag}}\"", + "notagsfound": "No tags matching \"{{$a}}\" found", + "searchtags": "Search tags", + "showingfirsttags": "Showing {{$a}} most popular tags", "tag": "Tag", "tagarea_course": "Courses", "tagarea_course_modules": "Activities and resources", diff --git a/src/core/tag/pages/search/search.html b/src/core/tag/pages/search/search.html new file mode 100644 index 000000000..5635d09d9 --- /dev/null +++ b/src/core/tag/pages/search/search.html @@ -0,0 +1,37 @@ + + + {{ 'core.tag.searchtags' | translate }} + + + + + + + + + + + + + + {{ 'core.tag.inalltagcoll' | translate }} + {{ collection.name }} + + + + + + + + +
+ + {{ tag.name }} + +
+

+ {{ 'core.tag.showingfirsttags' | translate: {$a: cloud.tags.length} }} +

+
+
+
diff --git a/src/core/tag/pages/search/search.module.ts b/src/core/tag/pages/search/search.module.ts new file mode 100644 index 000000000..29776ce68 --- /dev/null +++ b/src/core/tag/pages/search/search.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreTagSearchPage } from './search'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; + +@NgModule({ + declarations: [ + CoreTagSearchPage + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + IonicPageModule.forChild(CoreTagSearchPage), + TranslateModule.forChild() + ], +}) +export class CoreTagSerchPageModule {} diff --git a/src/core/tag/pages/search/search.scss b/src/core/tag/pages/search/search.scss new file mode 100644 index 000000000..cd7173445 --- /dev/null +++ b/src/core/tag/pages/search/search.scss @@ -0,0 +1,95 @@ +ion-app.app-root page-core-tag-search { + core-search-box ion-card { + width: 100% !important; + margin: 0 !important; + } + + .core-tag-cloud ion-badge { + margin: 8px; + cursor: pointer; + + .size20 { + font-size: 3.4rem; + } + + .size19 { + font-size: 3.3rem; + } + + .size18 { + font-size: 3.2rem; + } + + .size17 { + font-size: 3.1rem; + } + + .size16 { + font-size: 3rem; + } + + .size15 { + font-size: 2.9rem; + } + + .size14 { + font-size: 2.8rem; + } + + .size13 { + font-size: 2.7rem; + } + + .size12 { + font-size: 2.6rem; + } + + .size11 { + font-size: 2.5rem; + } + + .size10 { + font-size: 2.4rem; + } + + .size9 { + font-size: 2.3rem; + } + + .size8 { + font-size: 2.2rem; + } + + .size7 { + font-size: 2.1rem; + } + + .size6 { + font-size: 2rem; + } + + .size5 { + font-size: 1.9rem; + } + + .size4 { + font-size: 1.8rem; + } + + .size3 { + font-size: 1.7rem; + } + + .size2 { + font-size: 1.6rem; + } + + .size1 { + font-size: 1.5rem; + } + + .size0 { + font-size: 1.4rem; + } + } +} diff --git a/src/core/tag/pages/search/search.ts b/src/core/tag/pages/search/search.ts new file mode 100644 index 000000000..13f09bb4c --- /dev/null +++ b/src/core/tag/pages/search/search.ts @@ -0,0 +1,135 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { IonicPage, NavParams, NavController } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '@providers/app'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { CoreTagProvider, CoreTagCloud, CoreTagCollection, CoreTagCloudTag } from '@core/tag/providers/tag'; + +/** + * Page that displays most used tags and allows searching. + */ +@IonicPage({ segment: 'core-tag-search' }) +@Component({ + selector: 'page-core-tag-search', + templateUrl: 'search.html', +}) +export class CoreTagSearchPage { + collectionId: number; + query: string; + collections: CoreTagCollection[] = []; + cloud: CoreTagCloud; + loaded = false; + searching = false; + + constructor(private navCtrl: NavController, navParams: NavParams, private appProvider: CoreAppProvider, + private translate: TranslateService, private domUtils: CoreDomUtilsProvider, private utils: CoreUtilsProvider, + private textUtils: CoreTextUtilsProvider, private contentLinksHelper: CoreContentLinksHelperProvider, + private tagProvider: CoreTagProvider) { + this.collectionId = navParams.get('collectionId') || 0; + this.query = navParams.get('query') || ''; + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.fetchData().finally(() => { + this.loaded = true; + }); + } + + fetchData(): Promise { + return Promise.all([ + this.fetchCollections(), + this.fetchTags() + ]).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error loading tags.'); + }); + } + + /** + * Fetch tag collections. + * + * @return {Promise} Resolved when done. + */ + fetchCollections(): Promise { + return this.tagProvider.getTagCollections().then((collections) => { + collections.forEach((collection) => { + if (!collection.name && collection.isdefault) { + collection.name = this.translate.instant('core.tag.defautltagcoll'); + } + }); + this.collections = collections; + }); + } + + /** + * Fetch tags. + * + * @return {Promise} Resolved when done. + */ + fetchTags(): Promise { + return this.tagProvider.getTagCloud(this.collectionId, undefined, undefined, this.query).then((cloud) => { + this.cloud = cloud; + }); + } + + /** + * Go to tag index page. + */ + openTag(tag: CoreTagCloudTag): void { + const url = this.textUtils.decodeURI(tag.viewurl); + this.contentLinksHelper.handleLink(url, undefined, this.navCtrl); + } + + /** + * Refresh data. + * + * @param {any} refresher Refresher. + */ + refreshData(refresher: any): void { + this.utils.allPromises([ + this.tagProvider.invalidateTagCollections(), + this.tagProvider.invalidateTagCloud(this.collectionId, undefined, undefined, this.query), + ]).finally(() => { + return this.fetchData().finally(() => { + refresher.complete(); + }); + }); + } + + /** + * Search tags. + * + * @param {string} query Search query. + * @return {Promise} Resolved when done. + */ + searchTags(query: string): Promise { + this.searching = true; + this.query = query; + this.appProvider.closeKeyboard(); + + return this.fetchTags().catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error loading tags.'); + }).finally(() => { + this.searching = false; + }); + } +} diff --git a/src/core/tag/providers/mainmenu-handler.ts b/src/core/tag/providers/mainmenu-handler.ts new file mode 100644 index 000000000..8e676acc1 --- /dev/null +++ b/src/core/tag/providers/mainmenu-handler.ts @@ -0,0 +1,59 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable } from '@angular/core'; +import { CoreTagProvider } from './tag'; +import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/providers/delegate'; +import { CoreUtilsProvider } from '@providers/utils/utils'; + +/** + * Handler to inject an option into main menu. + */ +@Injectable() +export class CoreTagMainMenuHandler implements CoreMainMenuHandler { + name = 'CoreTag'; + priority = 300; + + constructor(private tagProvider: CoreTagProvider, private utils: CoreUtilsProvider) { } + + /** + * Check if the handler is enabled on a site level. + * + * @return {boolean | Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.tagProvider.areTagsAvailable().then((available) => { + if (!available) { + return false; + } + + // The only way to check whether tags are enabled on web is to perform a WS call. + return this.utils.promiseWorks(this.tagProvider.getTagCollections()); + }); + } + + /** + * Returns the data needed to render the handler. + * + * @return {CoreMainMenuHandlerData} Data needed to render the handler. + */ + getDisplayData(): CoreMainMenuHandlerData { + return { + icon: 'pricetags', + title: 'core.tag.tags', + page: 'CoreTagSearchPage', + class: 'core-tag-search-handler' + }; + } +} diff --git a/src/core/tag/providers/tag.ts b/src/core/tag/providers/tag.ts index 88b739f04..3a377cba9 100644 --- a/src/core/tag/providers/tag.ts +++ b/src/core/tag/providers/tag.ts @@ -17,6 +17,40 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; +/** + * Structure of a tag cloud returned by WS. + */ +export interface CoreTagCloud { + tags: CoreTagCloudTag[]; + tagscount: number; + totalcount: number; +} + +/** + * Structure of a tag cloud tag returned by WS. + */ +export interface CoreTagCloudTag { + name: string; + viewurl: string; + flag: boolean; + isstandard: boolean; + count: number; + size: number; +} + +/** + * Structure of a tag collection returned by WS. + */ +export interface CoreTagCollection { + id: number; + name: string; + isdefault: boolean; + component: string; + sortoder: number; + searchable: boolean; + customurl: string; +} + /** * Structure of a tag index returned by WS. */ @@ -57,6 +91,8 @@ export interface CoreTagItem { @Injectable() export class CoreTagProvider { + static SEARCH_LIMIT = 150; + protected ROOT_CACHE_KEY = 'CoreTag:'; constructor(private sitesProvider: CoreSitesProvider, private translate: TranslateService) {} @@ -89,6 +125,71 @@ export class CoreTagProvider { !site.isFeatureDisabled('NoDelegate_CoreTag'); } + /** + * Fetch the tag cloud. + * + * @param {number} [collectionId=0] Tag collection ID. + * @param {boolean} [isStandard=false] Whether to return only standard tags. + * @param {string} [sort='name'] Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). + * @param {string} [search=''] Search string. + * @param {number} [fromContextId=0] Context ID where this tag cloud is displayed. + * @param {number} [contextId=0] Only retrieve tag instances in this context. + * @param {boolean} [recursive=true] Retrieve tag instances in the context and its children. + * @param {number} [limit] Maximum number of tags to retrieve. Defaults to SEARCH_LIMIT. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the tag cloud. + * @since 3.7 + */ + getTagCloud(collectionId: number = 0, isStandard: boolean = false, sort: string = 'name', search: string = '', + fromContextId: number = 0, contextId: number = 0, recursive: boolean = true, limit?: number, siteId?: string): + Promise { + limit = limit || CoreTagProvider.SEARCH_LIMIT; + + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + tagcollid: collectionId, + isstandard: isStandard, + limit: limit, + sort: sort, + search: search, + fromctx: fromContextId, + ctx: contextId, + rec: recursive + }; + const preSets: CoreSiteWSPreSets = { + updateFrequency: CoreSite.FREQUENCY_SOMETIMES, + cacheKey: this.getTagCloudKey(collectionId, isStandard, sort, search, fromContextId, contextId, recursive), + getFromCache: search != '' // Try to get updated data when searching. + }; + + return site.read('core_tag_get_tag_cloud', params, preSets); + }); + } + + /** + * Fetch the tag collections. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the tag collections. + * @since 3.7 + */ + getTagCollections(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const preSets: CoreSiteWSPreSets = { + updateFrequency: CoreSite.FREQUENCY_RARELY, + cacheKey: this.getTagCollectionsKey() + }; + + return site.read('core_tag_get_tag_collections', null, preSets).then((response) => { + if (!response || !response.collections) { + return Promise.reject(null); + } + + return response.collections; + }); + }); + } + /** * Fetch the tag index. * @@ -142,6 +243,40 @@ export class CoreTagProvider { }); } + /** + * Invalidate tag cloud. + * + * @param {number} [collectionId=0] Tag collection ID. + * @param {boolean} [isStandard=false] Whether to return only standard tags. + * @param {string} [sort='name'] Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). + * @param {string} [search=''] Search string. + * @param {number} [fromContextId=0] Context ID where this tag cloud is displayed. + * @param {number} [contextId=0] Only retrieve tag instances in this context. + * @param {boolean} [recursive=true] Retrieve tag instances in the context and its children. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateTagCloud(collectionId: number = 0, isStandard: boolean = false, sort: string = 'name', search: string = '', + fromContextId: number = 0, contextId: number = 0, recursive: boolean = true, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const key = this.getTagCloudKey(collectionId, isStandard, sort, search, fromContextId, contextId, recursive); + + return site.invalidateWsCacheForKey(key); + }); + } + + /** + * Invalidate tag collections. + * + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateTagCollections(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const key = this.getTagCollectionsKey(); + + return site.invalidateWsCacheForKey(key); + }); + } + /** * Invalidate tag index. * @@ -163,6 +298,33 @@ export class CoreTagProvider { }); } + /** + * Get cache key for tag cloud. + * + * @param {number} collectionId Tag collection ID. + * @param {boolean} isStandard Whether to return only standard tags. + * @param {string} sort Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). + * @param {string} search Search string. + * @param {number} fromContextId Context ID where this tag cloud is displayed. + * @param {number} contextId Only retrieve tag instances in this context. + * @param {boolean} recursive Retrieve tag instances in the context and it's children. + * @return {string} Cache key. + */ + protected getTagCloudKey(collectionId: number, isStandard: boolean, sort: string, search: string, fromContextId: number, + contextId: number, recursive: boolean): string { + return this.ROOT_CACHE_KEY + 'cloud:' + collectionId + ':' + (isStandard ? 1 : 0) + ':' + sort + ':' + search + ':' + + fromContextId + ':' + contextId + ':' + (recursive ? 1 : 0); + } + + /** + * Get cache key for tag collections. + * + * @return {string} Cache key. + */ + protected getTagCollectionsKey(): string { + return this.ROOT_CACHE_KEY + 'collections'; + } + /** * Get cache key for tag index. * diff --git a/src/core/tag/tag.module.ts b/src/core/tag/tag.module.ts index 45d4d69af..baa55d98f 100644 --- a/src/core/tag/tag.module.ts +++ b/src/core/tag/tag.module.ts @@ -13,9 +13,11 @@ // limitations under the License. import { NgModule } from '@angular/core'; +import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; import { CoreTagProvider } from './providers/tag'; import { CoreTagHelperProvider } from './providers/helper'; import { CoreTagAreaDelegate } from './providers/area-delegate'; +import { CoreTagMainMenuHandler } from './providers/mainmenu-handler'; @NgModule({ declarations: [ @@ -25,8 +27,13 @@ import { CoreTagAreaDelegate } from './providers/area-delegate'; providers: [ CoreTagProvider, CoreTagHelperProvider, - CoreTagAreaDelegate + CoreTagAreaDelegate, + CoreTagMainMenuHandler ] }) export class CoreTagModule { + + constructor(mainMenuDelegate: CoreMainMenuDelegate, mainMenuHandler: CoreTagMainMenuHandler) { + mainMenuDelegate.registerHandler(mainMenuHandler); + } } From e4260aa92a7a698dc1ced0690c4888a3118ad1cf Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 12:37:05 +0200 Subject: [PATCH 22/23] MOBILE-2201 tag: Link handlers --- src/core/tag/providers/index-link-handler.ts | 81 +++++++++++++++++++ src/core/tag/providers/search-link-handler.ts | 70 ++++++++++++++++ src/core/tag/tag.module.ts | 13 ++- 3 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 src/core/tag/providers/index-link-handler.ts create mode 100644 src/core/tag/providers/search-link-handler.ts diff --git a/src/core/tag/providers/index-link-handler.ts b/src/core/tag/providers/index-link-handler.ts new file mode 100644 index 000000000..c8e1d7c49 --- /dev/null +++ b/src/core/tag/providers/index-link-handler.ts @@ -0,0 +1,81 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable } from '@angular/core'; +import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { CoreTagProvider } from './tag'; + +/** + * Handler to treat links to tag index. + */ +@Injectable() +export class CoreTagIndexLinkHandler extends CoreContentLinksHandlerBase { + name = 'CoreTagIndexLinkHandler'; + pattern = /\/tag\/index\.php/; + + constructor(private tagProvider: CoreTagProvider, private linkHelper: CoreContentLinksHelperProvider) { + super(); + } + + /** + * Get the list of actions for a link (url). + * + * @param {string[]} siteIds List of sites the URL belongs to. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @param {any} [data] Extra data to handle the URL. + * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: any, courseId?: number, data?: any): + CoreContentLinksAction[] | Promise { + return [{ + action: (siteId, navCtrl?): void => { + const pageParams = { + tagId: parseInt(params.id, 10) || 0, + tagName: params.tag || '', + collectionId: parseInt(params.tc, 10) || 0, + areaId: parseInt(params.ta, 10) || 0, + fromContextId: parseInt(params.from, 10) || 0, + contextId: parseInt(params.ctx, 10) || 0, + recursive: parseInt(params.rec, 10) || 1 + }; + + if (!pageParams.tagId && (!pageParams.tagName || !pageParams.collectionId)) { + this.linkHelper.goInSite(navCtrl, 'CoreTagSearchPage', {}, siteId); + } else if (pageParams.areaId) { + this.linkHelper.goInSite(navCtrl, 'CoreTagIndexAreaPage', pageParams, siteId); + } else { + this.linkHelper.goInSite(navCtrl, 'CoreTagIndexPage', pageParams, siteId); + } + } + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param {string} siteId The site ID. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + return this.tagProvider.areTagsAvailable(siteId); + } +} diff --git a/src/core/tag/providers/search-link-handler.ts b/src/core/tag/providers/search-link-handler.ts new file mode 100644 index 000000000..68ea7cb96 --- /dev/null +++ b/src/core/tag/providers/search-link-handler.ts @@ -0,0 +1,70 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { Injectable } from '@angular/core'; +import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { CoreTagProvider } from './tag'; + +/** + * Handler to treat links to tag search. + */ +@Injectable() +export class CoreTagSearchLinkHandler extends CoreContentLinksHandlerBase { + name = 'CoreTagSearchLinkHandler'; + pattern = /\/tag\/search\.php/; + + constructor(private tagProvider: CoreTagProvider, private linkHelper: CoreContentLinksHelperProvider) { + super(); + } + + /** + * Get the list of actions for a link (url). + * + * @param {string[]} siteIds List of sites the URL belongs to. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @param {any} [data] Extra data to handle the URL. + * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: any, courseId?: number, data?: any): + CoreContentLinksAction[] | Promise { + return [{ + action: (siteId, navCtrl?): void => { + const pageParams = { + collectionId: parseInt(params.tc, 10) || 0, + query: params.query || '', + }; + + this.linkHelper.goInSite(navCtrl, 'CoreTagSearchPage', pageParams, siteId); + } + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param {string} siteId The site ID. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + return this.tagProvider.areTagsAvailable(siteId); + } +} diff --git a/src/core/tag/tag.module.ts b/src/core/tag/tag.module.ts index baa55d98f..970e66e46 100644 --- a/src/core/tag/tag.module.ts +++ b/src/core/tag/tag.module.ts @@ -14,10 +14,13 @@ import { NgModule } from '@angular/core'; import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; +import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; import { CoreTagProvider } from './providers/tag'; import { CoreTagHelperProvider } from './providers/helper'; import { CoreTagAreaDelegate } from './providers/area-delegate'; import { CoreTagMainMenuHandler } from './providers/mainmenu-handler'; +import { CoreTagIndexLinkHandler } from './providers/index-link-handler'; +import { CoreTagSearchLinkHandler } from './providers/search-link-handler'; @NgModule({ declarations: [ @@ -28,12 +31,18 @@ import { CoreTagMainMenuHandler } from './providers/mainmenu-handler'; CoreTagProvider, CoreTagHelperProvider, CoreTagAreaDelegate, - CoreTagMainMenuHandler + CoreTagMainMenuHandler, + CoreTagIndexLinkHandler, + CoreTagSearchLinkHandler ] }) export class CoreTagModule { - constructor(mainMenuDelegate: CoreMainMenuDelegate, mainMenuHandler: CoreTagMainMenuHandler) { + constructor(mainMenuDelegate: CoreMainMenuDelegate, mainMenuHandler: CoreTagMainMenuHandler, + contentLinksDelegate: CoreContentLinksDelegate, indexLinkHandler: CoreTagIndexLinkHandler, + searchLinkHandler: CoreTagSearchLinkHandler) { mainMenuDelegate.registerHandler(mainMenuHandler); + contentLinksDelegate.registerHandler(indexLinkHandler); + contentLinksDelegate.registerHandler(searchLinkHandler); } } From 759f0c3624475a4547dfb85f70f553b13923167c Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 8 Jul 2019 15:30:57 +0200 Subject: [PATCH 23/23] MOBILE-2201 link: Fix URLs with escaped characters --- src/directives/format-text.ts | 2 +- src/directives/link.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index 489561674..5933f59c3 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -380,7 +380,7 @@ export class CoreFormatTextDirective implements OnChanges { anchors.forEach((anchor) => { // Angular 2 doesn't let adding directives dynamically. Create the CoreLinkDirective manually. const linkDir = new CoreLinkDirective(anchor, this.domUtils, this.utils, this.sitesProvider, this.urlUtils, - this.contentLinksHelper, this.navCtrl, this.content, this.svComponent); + this.contentLinksHelper, this.navCtrl, this.content, this.svComponent, this.textUtils); linkDir.capture = true; linkDir.ngOnInit(); diff --git a/src/directives/link.ts b/src/directives/link.ts index 0540eb83f..a10f69fde 100644 --- a/src/directives/link.ts +++ b/src/directives/link.ts @@ -21,6 +21,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreConfigConstants } from '../configconstants'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; /** * Directive to open a link in external browser. @@ -41,7 +42,8 @@ export class CoreLinkDirective implements OnInit { constructor(element: ElementRef, private domUtils: CoreDomUtilsProvider, private utils: CoreUtilsProvider, private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider, private contentLinksHelper: CoreContentLinksHelperProvider, @Optional() private navCtrl: NavController, - @Optional() private content: Content, @Optional() private svComponent: CoreSplitViewComponent) { + @Optional() private content: Content, @Optional() private svComponent: CoreSplitViewComponent, + private textUtils: CoreTextUtilsProvider) { // This directive can be added dynamically. In that case, the first param is the anchor HTMLElement. this.element = element.nativeElement || element; } @@ -62,12 +64,13 @@ export class CoreLinkDirective implements OnInit { this.element.addEventListener('click', (event) => { // If the event prevented default action, do nothing. if (!event.defaultPrevented) { - const href = this.element.getAttribute('href'); + let href = this.element.getAttribute('href'); if (href) { event.preventDefault(); event.stopPropagation(); if (this.utils.isTrueOrOne(this.capture)) { + href = this.textUtils.decodeURI(href); this.contentLinksHelper.handleLink(href, undefined, navCtrl, true, true).then((treated) => { if (!treated) { this.navigate(href);