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 {