parent
7fa7639c5f
commit
90f8257432
|
@ -0,0 +1,25 @@
|
|||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
|
||||
[*]
|
||||
|
||||
# change these settings to your own preference
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# we recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[{package,bower}.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
|
@ -0,0 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
|
||||
# Build generated files
|
||||
dist
|
||||
lib
|
||||
solution
|
||||
temp
|
||||
*.sppkg
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
# Visual Studio files
|
||||
.ntvs_analysis.dat
|
||||
.vs
|
||||
bin
|
||||
obj
|
||||
|
||||
# Resx Generated Code
|
||||
*.resx.ts
|
||||
|
||||
# Styles Generated Code
|
||||
*.scss.ts
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@microsoft/generator-sharepoint": {
|
||||
"version": "1.10.0",
|
||||
"libraryName": "react-global-news",
|
||||
"libraryId": "ee849c9a-efd9-4257-99d0-ad0b1c0fb095",
|
||||
"environment": "spo",
|
||||
"packageManager": "npm",
|
||||
"framework": "react",
|
||||
"isCreatingSolution": true,
|
||||
"isDomainIsolated": false,
|
||||
"componentType": "webpart"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
page_type: sample
|
||||
products:
|
||||
- office-sp
|
||||
languages:
|
||||
- javascript
|
||||
- typescript
|
||||
extensions:
|
||||
contentType: samples
|
||||
technologies:
|
||||
- SharePoint Framework
|
||||
- Microsoft Graph
|
||||
platforms:
|
||||
- React
|
||||
createdDate: 4/14/2020 12:00:00 AM
|
||||
---
|
||||
|
||||
# React Global News
|
||||
|
||||
## Summary
|
||||
|
||||
This web part show a world news from various sources, it uses the API available on https://newsapi.org this collect information from news and blog sites arround the world.
|
||||
|
||||
Please go to https://newsapi.org to get more information.
|
||||
|
||||
|
||||
|
||||
![picture of the web part in action](assets/globalNews.gif)
|
||||
|
||||
|
||||
![picture of the web part in action](assets/GlobalNews.png)
|
||||
|
||||
|
||||
## Used SharePoint Framework Version
|
||||
|
||||
![1.10.0](https://img.shields.io/badge/version-1.10.0-green.svg)
|
||||
|
||||
## Applies to
|
||||
|
||||
* [SharePoint Framework](https:/dev.office.com/sharepoint)
|
||||
* [Office 365 tenant](https://dev.office.com/sharepoint/docs/spfx/set-up-your-development-environment)
|
||||
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
> none
|
||||
|
||||
## Solution
|
||||
|
||||
Solution|Author(s)
|
||||
--------|---------
|
||||
react-global-news | João Mendes
|
||||
|
||||
## Version history
|
||||
|
||||
Version|Date|Comments
|
||||
-------|----|--------
|
||||
1.0|April 14, 2020|Initial release
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.**
|
||||
|
||||
---
|
||||
|
||||
## Minimal Path to Awesome
|
||||
|
||||
* Clone this repository
|
||||
* in the command line run:
|
||||
* `npm install`
|
||||
* `gulp serve`
|
||||
|
||||
> Include any additional steps as needed.
|
||||
|
||||
## Features
|
||||
|
||||
Description of the web part with possible additional details than in short summary.
|
||||
This Web Part illustrates the following concepts on top of the SharePoint Framework:
|
||||
|
||||
* Office-ui-fabric-react components
|
||||
* Office-ui-fabric themes
|
||||
|
||||
<img src="https://telemetry.sharepointpnp.com/sp-dev-fx-webparts/samples/react-global-news" />
|
Binary file not shown.
After Width: | Height: | Size: 2.2 MiB |
Binary file not shown.
After Width: | Height: | Size: 14 MiB |
Binary file not shown.
After Width: | Height: | Size: 575 KiB |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/config.2.0.schema.json",
|
||||
"version": "2.0",
|
||||
"bundles": {
|
||||
"news-web-part": {
|
||||
"components": [
|
||||
{
|
||||
"entrypoint": "./lib/webparts/news/NewsWebPart.js",
|
||||
"manifest": "./src/webparts/news/NewsWebPart.manifest.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"externals": {},
|
||||
"localizedResources": {
|
||||
"NewsWebPartStrings": "lib/webparts/news/loc/{locale}.js",
|
||||
"PropertyControlStrings": "node_modules/@pnp/spfx-property-controls/lib/loc/{locale}.js",
|
||||
"ControlStrings": "node_modules/@pnp/spfx-controls-react/lib/loc/{locale}.js"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/copy-assets.schema.json",
|
||||
"deployCdnPath": "temp/deploy"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
|
||||
"workingDir": "./temp/deploy/",
|
||||
"account": "<!-- STORAGE ACCOUNT NAME -->",
|
||||
"container": "react-global-news",
|
||||
"accessKey": "<!-- ACCESS KEY -->"
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/package-solution.schema.json",
|
||||
"solution": {
|
||||
"name": "react-global-news-client-side-solution",
|
||||
"id": "ee849c9a-efd9-4257-99d0-ad0b1c0fb095",
|
||||
"version": "1.0.0.0",
|
||||
"includeClientSideAssets": true,
|
||||
"skipFeatureDeployment": true,
|
||||
"isDomainIsolated": false
|
||||
},
|
||||
"paths": {
|
||||
"zippedPackage": "solution/react-global-news.sppkg"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
|
||||
"port": 4321,
|
||||
"https": true,
|
||||
"initialPage": "https://localhost:5432/workbench",
|
||||
"api": {
|
||||
"port": 5432,
|
||||
"entryPath": "node_modules/@microsoft/sp-webpart-workbench/lib/api/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/write-manifests.schema.json",
|
||||
"cdnBasePath": "<!-- PATH TO CDN -->"
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
'use strict';
|
||||
|
||||
// check if gulp dist was called
|
||||
if (process.argv.indexOf('dist') !== -1) {
|
||||
// add ship options to command call
|
||||
process.argv.push('--ship');
|
||||
}
|
||||
|
||||
const path = require('path');
|
||||
const gulp = require('gulp');
|
||||
const build = require('@microsoft/sp-build-web');
|
||||
const gulpSequence = require('gulp-sequence');
|
||||
|
||||
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);
|
||||
|
||||
// Create clean distrubution package
|
||||
gulp.task('dist', gulpSequence('clean', 'bundle', 'package-solution'));
|
||||
// Create clean development package
|
||||
gulp.task('dev', gulpSequence('clean', 'bundle', 'package-solution'));
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Webpack Bundle Anlayzer
|
||||
* Reference and gulp task
|
||||
*/
|
||||
const bundleAnalyzer = require('webpack-bundle-analyzer');
|
||||
|
||||
build.configureWebpack.mergeConfig({
|
||||
|
||||
additionalConfiguration: (generatedConfiguration) => {
|
||||
const lastDirName = path.basename(__dirname);
|
||||
const dropPath = path.join(__dirname, 'temp', 'stats');
|
||||
generatedConfiguration.plugins.push(new bundleAnalyzer.BundleAnalyzerPlugin({
|
||||
openAnalyzer: false,
|
||||
analyzerMode: 'static',
|
||||
reportFilename: path.join(dropPath, `${lastDirName}.stats.html`),
|
||||
generateStatsFile: true,
|
||||
statsFilename: path.join(dropPath, `${lastDirName}.stats.json`),
|
||||
logLevel: 'error'
|
||||
}));
|
||||
|
||||
return generatedConfiguration;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Custom Framework Specific gulp tasks
|
||||
*/
|
||||
|
||||
|
||||
build.initialize(gulp);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "react-global-news",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "lib/index.js",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp bundle",
|
||||
"clean": "gulp clean",
|
||||
"test": "gulp test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.9.10",
|
||||
"@material-ui/lab": "^4.0.0-alpha.49",
|
||||
"@microsoft/sp-core-library": "1.10.0",
|
||||
"@microsoft/sp-lodash-subset": "1.10.0",
|
||||
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
|
||||
"@microsoft/sp-property-pane": "1.10.0",
|
||||
"@microsoft/sp-webpart-base": "1.10.0",
|
||||
"@pnp/spfx-controls-react": "^1.17.0",
|
||||
"@pnp/spfx-property-controls": "^1.17.0",
|
||||
"@types/es6-promise": "0.0.33",
|
||||
"@types/react": "16.8.8",
|
||||
"@types/react-dom": "16.8.3",
|
||||
"@types/webpack-env": "1.13.1",
|
||||
"gulp-sequence": "^1.0.0",
|
||||
"jquery": "^3.5.0",
|
||||
"lodash": "^4.17.15",
|
||||
"material-ui": "^0.20.2",
|
||||
"moment": "^2.24.0",
|
||||
"office-ui-fabric-react": "6.189.2",
|
||||
"react": "16.8.5",
|
||||
"react-dom": "16.8.5",
|
||||
"spfx-uifabric-themes": "^0.8.0",
|
||||
"webpack-bundle-analyzer": "^3.7.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "16.8.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/sp-build-web": "1.10.0",
|
||||
"@microsoft/sp-tslint-rules": "1.10.0",
|
||||
"@microsoft/sp-module-interfaces": "1.10.0",
|
||||
"@microsoft/sp-webpart-workbench": "1.10.0",
|
||||
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
|
||||
"gulp": "~3.9.1",
|
||||
"@types/chai": "3.4.34",
|
||||
"@types/mocha": "2.2.38",
|
||||
"ajv": "~5.2.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"themePrimary": "#6264a7",
|
||||
"themeLighterAlt": "#f7f7fb",
|
||||
"themeLighter": "#e1e1f1",
|
||||
"themeLight": "#c8c9e4",
|
||||
"themeTertiary": "#989ac9",
|
||||
"themeSecondary": "#7173b0",
|
||||
"themeDarkAlt": "#585a95",
|
||||
"themeDark": "#4a4c7e",
|
||||
"themeDarker": "#37385d",
|
||||
"neutralLighterAlt": "#0b0b0b",
|
||||
"neutralLighter": "#151515",
|
||||
"neutralLight": "#252525",
|
||||
"neutralQuaternaryAlt": "#2f2f2f",
|
||||
"neutralQuaternary": "#373737",
|
||||
"neutralTertiaryAlt": "#595959",
|
||||
"neutralTertiary": "#c8c8c8",
|
||||
"neutralSecondary": "#d0d0d0",
|
||||
"neutralPrimaryAlt": "#dadada",
|
||||
"neutralPrimary": "#ffffff",
|
||||
"neutralDark": "#f4f4f4",
|
||||
"black": "#f8f8f8",
|
||||
"white": "#000000"
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"themePrimary": "#6264a7",
|
||||
"themeLighterAlt": "#f7f7fb",
|
||||
"themeLighter": "#e1e1f1",
|
||||
"themeLight": "#c8c9e4",
|
||||
"themeTertiary": "#989ac9",
|
||||
"themeSecondary": "#7173b0",
|
||||
"themeDarkAlt": "#585a95",
|
||||
"themeDark": "#4a4c7e",
|
||||
"themeDarker": "#37385d",
|
||||
"neutralLighterAlt": "#2d2c2c",
|
||||
"neutralLighter": "#2c2b2b",
|
||||
"neutralLight": "#2a2929",
|
||||
"neutralQuaternaryAlt": "#272626",
|
||||
"neutralQuaternary": "#252525",
|
||||
"neutralTertiaryAlt": "#242323",
|
||||
"neutralTertiary": "#c8c8c8",
|
||||
"neutralSecondary": "#d0d0d0",
|
||||
"neutralPrimaryAlt": "#dadada",
|
||||
"neutralPrimary": "#ffffff",
|
||||
"neutralDark": "#f4f4f4",
|
||||
"black": "#f8f8f8",
|
||||
"white": "#2d2c2c"
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"themePrimary": "#6264a7",
|
||||
"themeLighterAlt": "#f7f7fb",
|
||||
"themeLighter": "#e1e1f1",
|
||||
"themeLight": "#c8c9e4",
|
||||
"themeTertiary": "#989ac9",
|
||||
"themeSecondary": "#7173b0",
|
||||
"themeDarkAlt": "#585a95",
|
||||
"themeDark": "#4a4c7e",
|
||||
"themeDarker": "#37385d",
|
||||
"neutralLighterAlt": "#ecebe9",
|
||||
"neutralLighter": "#e8e7e6",
|
||||
"neutralLight": "#dedddc",
|
||||
"neutralQuaternaryAlt": "#cfcecd",
|
||||
"neutralQuaternary": "#c6c5c4",
|
||||
"neutralTertiaryAlt": "#bebdbc",
|
||||
"neutralTertiary": "#b5b4b2",
|
||||
"neutralSecondary": "#9d9c9a",
|
||||
"neutralPrimaryAlt": "#868482",
|
||||
"neutralPrimary": "#252423",
|
||||
"neutralDark": "#565453",
|
||||
"black": "#3e3d3b",
|
||||
"white": "#f3f2f1"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
// A file is required to be in the root of the /src directory by the TypeScript compiler
|
|
@ -0,0 +1,13 @@
|
|||
export interface IArticle {
|
||||
source: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
author: string;
|
||||
title: string;
|
||||
description: string;
|
||||
url: string;
|
||||
urlToImage: string;
|
||||
publishedAt: string;
|
||||
content: string;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import { IArticle } from "./IArticle";
|
||||
|
||||
export interface INewsResults {
|
||||
status: string;
|
||||
totalResults: number;
|
||||
articles: IArticle[];
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export interface INewsResultsError {
|
||||
status: string;
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
export interface ISource {
|
||||
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
category: string;
|
||||
language: string;
|
||||
country: string;
|
||||
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { ISource } from "./ISource";
|
||||
|
||||
export interface ISourcesResults {
|
||||
status: string;
|
||||
sources: ISource[];
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
import {
|
||||
HttpClient,
|
||||
HttpClientResponse,
|
||||
IHttpClientOptions
|
||||
} from "@microsoft/sp-http";
|
||||
import { INewsResults } from "./INewsResults";
|
||||
import { ISourcesResults } from "./ISourcesResults";
|
||||
|
||||
export default class dataservices {
|
||||
private static _httpClient: HttpClient;
|
||||
|
||||
/*
|
||||
initialize the static class
|
||||
*/
|
||||
public static async init(context: WebPartContext) {
|
||||
//obtain the httpClient from the webpart context
|
||||
this._httpClient = context.httpClient;
|
||||
}
|
||||
|
||||
|
||||
// Get Sources
|
||||
public static async getSources(apiKey:string):Promise<ISourcesResults>{
|
||||
|
||||
let requestHeaders = new Headers();
|
||||
let sourceUrl = `https://newsapi.org/v2/sources`;
|
||||
let _corsUrl = `https://cors-anywhere.herokuapp.com/${sourceUrl}`;
|
||||
|
||||
if (!sourceUrl || !apiKey) return;
|
||||
try {
|
||||
let _url = new URL(sourceUrl);
|
||||
let _count = 0;
|
||||
_url.searchParams.forEach((v,k,p) =>{
|
||||
_count ++;
|
||||
});
|
||||
if (_count !== 0 ) { // test if has parameters
|
||||
_corsUrl = _corsUrl + `&apiKey=${apiKey}`; // has parameters addpikey to last one
|
||||
} else {
|
||||
_corsUrl = _corsUrl.replace('?','') + `?apiKey=${apiKey}`; // add parameter apikey
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
return; // not valid Url
|
||||
}
|
||||
|
||||
requestHeaders.append("Accept", "application/json");
|
||||
requestHeaders.append("content-type", "application/json");
|
||||
|
||||
try {
|
||||
//set up get options
|
||||
const requestGetOptions: IHttpClientOptions = {
|
||||
method: "GET",
|
||||
headers: requestHeaders,
|
||||
mode: "cors"
|
||||
};
|
||||
|
||||
let query: HttpClientResponse = await this._httpClient.fetch(
|
||||
_corsUrl,
|
||||
HttpClient.configurations.v1,
|
||||
requestGetOptions
|
||||
);
|
||||
|
||||
return await query.json();
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw new Error('Is not possible to read news at this moment, please try later.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Get News
|
||||
public static async getNews(
|
||||
newsUrl: string,
|
||||
apiKey: string,
|
||||
page?:number,
|
||||
|
||||
): Promise<INewsResults> {
|
||||
let requestHeaders = new Headers();
|
||||
const defaultPageSize = 12;
|
||||
let _page:number = page ? page : 1;
|
||||
let _corsUrl = `https://cors-anywhere.herokuapp.com/${newsUrl}&page=${_page}`;
|
||||
if (!newsUrl || !apiKey) return;
|
||||
try {
|
||||
let _url = new URL(newsUrl);
|
||||
let _count = 0;
|
||||
_url.searchParams.forEach((v,k,p) =>{
|
||||
_count ++;
|
||||
});
|
||||
if (_count !== 0 ) { // test if has parameters
|
||||
_corsUrl = _corsUrl + `&apiKey=${apiKey}`; // has parameters addpikey to last one
|
||||
} else {
|
||||
_corsUrl = _corsUrl.replace('?','') + `?apiKey=${apiKey}`; // add parameter apikey
|
||||
}
|
||||
// teste if has pagesize parameter must be equal to default parameter value of web part
|
||||
if ( !_url.searchParams.has('pagesize')){
|
||||
_corsUrl = _corsUrl + `&pageSize=${defaultPageSize}`; //
|
||||
}
|
||||
} catch (error) {
|
||||
return; // not valid Url
|
||||
}
|
||||
|
||||
requestHeaders.append("Accept", "application/json");
|
||||
requestHeaders.append("content-type", "application/json");
|
||||
|
||||
try {
|
||||
//set up get options
|
||||
const requestGetOptions: IHttpClientOptions = {
|
||||
method: "GET",
|
||||
headers: requestHeaders,
|
||||
mode: "cors"
|
||||
};
|
||||
|
||||
let query: HttpClientResponse = await this._httpClient.fetch(
|
||||
_corsUrl,
|
||||
HttpClient.configurations.v1,
|
||||
requestGetOptions
|
||||
);
|
||||
|
||||
return await query.json();
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw new Error('Is not possible to read news at this moment, please try later.');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { WebPartContext } from "@microsoft/sp-webpart-base";
|
||||
import { DisplayMode } from "@microsoft/sp-core-library";
|
||||
import { IReadonlyTheme } from '@microsoft/sp-component-base';
|
||||
export interface INewsProps {
|
||||
title: string;
|
||||
newsUrl: string;
|
||||
apiKey: string;
|
||||
context: WebPartContext;
|
||||
updateProperty: (value: string) => void;
|
||||
displayMode: DisplayMode;
|
||||
viewOption:string;
|
||||
pageSize: number;
|
||||
themeVariant: IReadonlyTheme | undefined;
|
||||
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { IArticle } from "../../services/IArticle";
|
||||
|
||||
export interface INewsState{
|
||||
hasError:boolean;
|
||||
articles: IArticle[];
|
||||
errorMesage:string;
|
||||
isLoading: boolean;
|
||||
currentPage:number;
|
||||
totalPages:number;
|
||||
}
|
|
@ -0,0 +1,370 @@
|
|||
@import "~office-ui-fabric-react/dist/sass/References.scss";
|
||||
@import "./node_modules/spfx-uifabric-themes/office.theme";
|
||||
@import "~office-ui-fabric-react/dist/sass/semanticSlots";
|
||||
@media screen and (min-width: 320px) and (max-width: 1024px) {
|
||||
.card {
|
||||
flex-direction: column;
|
||||
}
|
||||
.image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.newsTitle {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* mixin for multiline */
|
||||
@mixin multiLineEllipsis($lineHeight: 1.5em, $lineCount: 1, $bgColor: white) {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
line-height: $lineHeight;
|
||||
max-height: $lineHeight * $lineCount;
|
||||
text-align: start;
|
||||
|
||||
/* @include ms-fontWeight-semibold; */
|
||||
color: $ms-color-neutralSecondaryAlt;
|
||||
@include ms-fontSize-m;
|
||||
&:before {
|
||||
content: "...";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-top: 0.2em;
|
||||
/* background: $bgColor;*/
|
||||
}
|
||||
}
|
||||
|
||||
.News {
|
||||
/*:global { */
|
||||
padding: 10px;
|
||||
|
||||
.cardsTiles {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, auto));
|
||||
column-gap: 1rem;
|
||||
row-gap: 1.5rem;
|
||||
grid-auto-rows: auto;
|
||||
}
|
||||
|
||||
.cardsItem {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
/* box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); */
|
||||
display: Flex;
|
||||
flex-direction: row;
|
||||
height: 180px;
|
||||
min-width: 280px;
|
||||
align-items: flex-start;
|
||||
margin-right: 15px;
|
||||
margin-left: 15px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 7px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: $ms-color-neutralTertiaryAlt;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: $ms-color-neutralTertiary;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.separator {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: #edebe9;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.image {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.newsTitle {
|
||||
padding-top: 10px;
|
||||
overflow: hidden;
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
@include ms-fontWeight-semibold;
|
||||
text-align: start;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.secondaryText {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
padding-top: 10px;
|
||||
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 15px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
[data-theme="default"] {
|
||||
.card {
|
||||
/* box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); */
|
||||
display: Flex;
|
||||
flex-direction: row;
|
||||
height: 180px;
|
||||
min-width: 280px;
|
||||
|
||||
align-items: flex-start;
|
||||
margin: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: $ms-color-neutralTertiaryAlt;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: $ms-color-neutralTertiary;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
.nonewsIcon {
|
||||
color: $ms-color-white;
|
||||
}
|
||||
.card {
|
||||
/* box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); */
|
||||
display: Flex;
|
||||
flex-direction: row;
|
||||
height: 180px;
|
||||
min-width: 280px;
|
||||
|
||||
align-items: flex-start;
|
||||
margin: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: $ms-color-neutralSecondaryAlt;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: $ms-color-neutralTertiary;
|
||||
cursor: pointer;
|
||||
}
|
||||
.secondaryText {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
padding-top: 10px;
|
||||
color: $ms-color-neutralTertiaryAlt;
|
||||
margin-left: 16px;
|
||||
margin-right: 16px;
|
||||
height: 55px;
|
||||
}
|
||||
.author {
|
||||
margin-top: 15px;
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system,
|
||||
BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
}
|
||||
[data-theme="contrast"] {
|
||||
.nonewsIcon {
|
||||
color: $ms-color-white;
|
||||
}
|
||||
.card {
|
||||
/* box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); */
|
||||
display: Flex;
|
||||
flex-direction: row;
|
||||
height: 180px;
|
||||
min-width: 280px;
|
||||
align-items: flex-start;
|
||||
margin: 5px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: $ms-color-neutralTertiaryAlt;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
border-color: $ms-color-neutralTertiary;
|
||||
cursor: pointer;
|
||||
}
|
||||
.author {
|
||||
margin-top: 15px;
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system,
|
||||
BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
.author {
|
||||
margin-top: 15px;
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system,
|
||||
BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
.date {
|
||||
@include ms-font-s-plus;
|
||||
max-width: 200px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.nonewsIcon {
|
||||
font-size: 96px;
|
||||
/* color: $ms-color-neutralPrimary;*/
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
}
|
||||
.nonewsMessage {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-top: 0px;
|
||||
/* color: $ms-color-neutralPrimary;*/
|
||||
max-width: 500px;
|
||||
@include ms-fontWeight-semibold;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-left: 15px;
|
||||
}
|
||||
/*}*/
|
||||
:global {
|
||||
// default theme
|
||||
$default-background: #f3f2f1;
|
||||
$default-color: #252423;
|
||||
$default-button-background: #6264a7;
|
||||
$default-Button-color: #f3f2f1;
|
||||
|
||||
// dark theme
|
||||
$dark-background: #2d2c2c;
|
||||
$dark-color: #ffffff;
|
||||
$dark-button-background: #6264a7;
|
||||
$dark-button-color: #2d2c2c;
|
||||
|
||||
// contrast theme
|
||||
$contrast-background: #000000;
|
||||
$contrast-color: #ffffff;
|
||||
$contrast-button-background: #b5c01c;
|
||||
$contrast-Button-color: #000000;
|
||||
|
||||
.MuiPaginationItem-root {
|
||||
/* color: rgba(0, 0, 0, 0.87); */
|
||||
color: $ms-color-neutralPrimary !important;
|
||||
}
|
||||
|
||||
.MuiButtonBase-root {
|
||||
/* color: inherit; */
|
||||
color: $ms-color-neutralPrimary !important;
|
||||
}
|
||||
|
||||
.MuiPaginationItem-textPrimary.Mui-selected {
|
||||
color: $ms-color-white !important;
|
||||
background-color: $ms-themePrimary !important;
|
||||
}
|
||||
|
||||
.MuiPaginationItem-page:hover {
|
||||
background-color: $ms-themePrimary !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
.MuiPaginationItem-root {
|
||||
/* color: rgba(0, 0, 0, 0.87); */
|
||||
color: $dark-color !important;
|
||||
}
|
||||
|
||||
.MuiButtonBase-root {
|
||||
/* color: inherit; */
|
||||
color: $dark-color !important;
|
||||
}
|
||||
|
||||
.MuiPaginationItem-textPrimary.Mui-selected {
|
||||
color: $dark-color !important;
|
||||
background-color: $dark-button-background !important;
|
||||
}
|
||||
|
||||
.MuiPaginationItem-page:hover {
|
||||
background-color: $dark-button-background !important;
|
||||
}
|
||||
|
||||
.ms-Dropdown-optionText {
|
||||
color: $ms-color-white !important;
|
||||
}
|
||||
|
||||
[class^="propertyPaneFooter_"] {
|
||||
background-color: $dark-background;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="default"] {
|
||||
.MuiPaginationItem-root {
|
||||
/* color: rgba(0, 0, 0, 0.87); */
|
||||
color: $default-color !important;
|
||||
}
|
||||
|
||||
.MuiButtonBase-root {
|
||||
/* color: inherit; */
|
||||
color: $default-color !important;
|
||||
}
|
||||
|
||||
.MuiPaginationItem-textPrimary.Mui-selected {
|
||||
color: $ms-color-white !important;
|
||||
background-color: $default-button-background !important;
|
||||
}
|
||||
|
||||
.MuiPaginationItem-page:hover {
|
||||
background-color: $default-button-background !important;
|
||||
}
|
||||
|
||||
[class^="propertyPaneFooter_"] {
|
||||
background-color: $default-background;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="contrast"] {
|
||||
.MuiPaginationItem-root {
|
||||
/* color: rgba(0, 0, 0, 0.87); */
|
||||
color: $contrast-color !important;
|
||||
}
|
||||
|
||||
.MuiButtonBase-root {
|
||||
/* color: inherit; */
|
||||
color: $contrast-color !important;
|
||||
}
|
||||
|
||||
.MuiPaginationItem-textPrimary.Mui-selected {
|
||||
color: $contrast-color !important;
|
||||
background-color: $contrast-button-background !important;
|
||||
}
|
||||
|
||||
.MuiPaginationItem-page:hover {
|
||||
background-color: $contrast-button-background !important;
|
||||
}
|
||||
|
||||
.ms-Dropdown-optionText {
|
||||
color: $ms-color-white !important;
|
||||
}
|
||||
|
||||
[class^="propertyPaneFooter_"] {
|
||||
background-color: $contrast-background;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
import * as React from "react";
|
||||
import styles from "./News.module.scss";
|
||||
import { INewsProps } from "./INewsProps";
|
||||
import { INewsState } from "./INewsState";
|
||||
import { escape } from "@microsoft/sp-lodash-subset";
|
||||
import dataservices from "../../services/dataservices";
|
||||
import { NewsItem } from "./NewsItem/NewsItem";
|
||||
import { NoNews } from "./NoNews";
|
||||
import { WebPartTitle } from "@pnp/spfx-controls-react/lib/WebPartTitle";
|
||||
import { NewsTile } from "./NewsTile/NewsTile";
|
||||
import * as strings from "NewsWebPartStrings";
|
||||
import { Placeholder } from "@pnp/spfx-controls-react/lib/Placeholder";
|
||||
import {
|
||||
Spinner,
|
||||
SpinnerSize,
|
||||
MessageBar,
|
||||
MessageBarType,
|
||||
mergeStyles,
|
||||
getTheme
|
||||
} from "office-ui-fabric-react";
|
||||
import { INewsResults } from "../../services/INewsResults";
|
||||
import { IArticle } from "../../services/IArticle";
|
||||
import Pagination from "@material-ui/lab/Pagination";
|
||||
import { Customizer } from "@uifabric/utilities/lib/";
|
||||
|
||||
const theme = getTheme();
|
||||
|
||||
export default class News extends React.Component<INewsProps, INewsState> {
|
||||
private _totalResuts: number = 0;
|
||||
constructor(props: INewsProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isLoading: false,
|
||||
hasError: false,
|
||||
errorMesage: null,
|
||||
articles: [],
|
||||
currentPage: 1,
|
||||
totalPages: 0
|
||||
};
|
||||
}
|
||||
|
||||
private _onConfigure = () => {
|
||||
this.props.context.propertyPane.open();
|
||||
}
|
||||
// Component Did Mount
|
||||
public async componentDidMount(): Promise<void> {
|
||||
//await dataservices.init(this.props.context);
|
||||
this._getNews(
|
||||
this.props.newsUrl,
|
||||
this.props.apiKey,
|
||||
this.state.currentPage,
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
// Component Did Update
|
||||
public async componentDidUpdate(
|
||||
prevProps: INewsProps,
|
||||
prevState: INewsState
|
||||
): Promise<void> {
|
||||
if (
|
||||
this.props.newsUrl !== prevProps.newsUrl ||
|
||||
this.props.apiKey !== prevProps.apiKey
|
||||
) {
|
||||
this._getNews(
|
||||
this.props.newsUrl,
|
||||
this.props.apiKey,
|
||||
1 // force current page to 1 because new propeerties defined
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Get News from newsApi.org
|
||||
private _getNews = async (newsUrl: string, apiKey: string, page?: number) => {
|
||||
try {
|
||||
const { pageSize } = this.props;
|
||||
this.setState({ isLoading: true, hasError: false, errorMesage: "" });
|
||||
const results: any = await dataservices.getNews(newsUrl, apiKey, page);
|
||||
|
||||
if (results && results.status == "error") {
|
||||
throw new Error(results.message);
|
||||
}
|
||||
|
||||
// calculate number of pages
|
||||
let _reminder: number = (results as INewsResults).totalResults % pageSize; // get Reminder
|
||||
_reminder = _reminder ? 1 : 0;
|
||||
const _totalPages: number =
|
||||
parseInt((results.totalResults / pageSize).toString()) + _reminder;
|
||||
|
||||
this.setState({
|
||||
articles: results ? results.articles : [],
|
||||
isLoading: false,
|
||||
hasError: false,
|
||||
errorMesage: "",
|
||||
totalPages: _totalPages,
|
||||
currentPage: page
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
this.setState({
|
||||
isLoading: false,
|
||||
hasError: true,
|
||||
errorMesage: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Render WebPart
|
||||
public render(): React.ReactElement<INewsProps> {
|
||||
const { isLoading, errorMesage, articles, hasError } = this.state;
|
||||
let _renderNews: JSX.Element[] = [];
|
||||
|
||||
if (articles && articles.length > 0) {
|
||||
for (const article of articles) {
|
||||
if (!this.props.viewOption || this.props.viewOption == "list") {
|
||||
_renderNews.push(<NewsItem article={article} key={article.title} />);
|
||||
} else {
|
||||
_renderNews.push(<NewsTile article={article} key={article.title} />);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Customizer settings={{ theme: this.props.themeVariant }}>
|
||||
<div className={styles.News}>
|
||||
{!this.props.apiKey || !this.props.newsUrl ? (
|
||||
<Placeholder
|
||||
iconName="Edit"
|
||||
iconText={strings.ConfigureWebPartMessage}
|
||||
description={strings.configureWebPartTextMessage}
|
||||
buttonLabel={strings.ConfigureWebPartButtonLabel}
|
||||
onConfigure={this._onConfigure}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<WebPartTitle
|
||||
displayMode={this.props.displayMode}
|
||||
title={this.props.title}
|
||||
className={styles.title}
|
||||
updateProperty={this.props.updateProperty}
|
||||
/>
|
||||
{isLoading ? (
|
||||
<Spinner size={SpinnerSize.medium} label="Loading..." />
|
||||
) : hasError ? (
|
||||
<>
|
||||
<MessageBar messageBarType={MessageBarType.error}>
|
||||
{errorMesage}
|
||||
</MessageBar>
|
||||
</>
|
||||
) : _renderNews.length > 0 ? (
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
this.props.viewOption == "tiles"
|
||||
? styles.cardsTiles
|
||||
: styles.cardsItem
|
||||
}
|
||||
>
|
||||
{_renderNews}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
marginTop: 10,
|
||||
marginBottom: 20,
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
{this.state.totalPages > 1 && (
|
||||
<>
|
||||
<Pagination
|
||||
color="primary"
|
||||
count={this.state.totalPages}
|
||||
page={this.state.currentPage}
|
||||
onChange={(event, page) => {
|
||||
this._getNews(
|
||||
this.props.newsUrl,
|
||||
this.props.apiKey,
|
||||
page
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<NoNews />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Customizer>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
import { IArticle } from "../../../services/IArticle";
|
||||
import {
|
||||
Label,
|
||||
Text,
|
||||
DocumentCardActivity
|
||||
} from "office-ui-fabric-react";
|
||||
import * as moment from "moment";
|
||||
import styles from "../News.module.scss";
|
||||
import * as React from "react";
|
||||
import * as strings from "NewsWebPartStrings";
|
||||
|
||||
const _defaultImage:string = require("./../../../../assets/news.jpg");
|
||||
|
||||
export const NewsItem = (props: { article: IArticle }) => {
|
||||
let { article } = props;
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={styles.card}
|
||||
onClick={ev => {
|
||||
ev.preventDefault();
|
||||
window.open(article.url, "_blank");
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width:180,
|
||||
height: '100%'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className={styles.image}
|
||||
src={
|
||||
article.urlToImage
|
||||
? article.urlToImage
|
||||
: _defaultImage
|
||||
}
|
||||
width="180"
|
||||
height="180"
|
||||
onError={(ev)=> {
|
||||
ev.currentTarget.setAttribute('src',_defaultImage); // set Default image if can not load article image
|
||||
}}
|
||||
></img>
|
||||
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
display:'Flex',
|
||||
height: '100%',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'sflex-start'
|
||||
}}
|
||||
>
|
||||
|
||||
<Label className={styles.newsTitle} >
|
||||
{article.title.trim()}
|
||||
</Label>
|
||||
<div>
|
||||
|
||||
<Text
|
||||
className={styles.secondaryText}
|
||||
variant='medium'
|
||||
block
|
||||
|
||||
title={
|
||||
article.description
|
||||
? article.description.trim()
|
||||
: strings.CanNotShowArticleTextMessage
|
||||
}
|
||||
>
|
||||
{article.description
|
||||
? article.description.trim()
|
||||
: strings.CanNotShowArticleTextMessage}
|
||||
</Text>
|
||||
<DocumentCardActivity
|
||||
className={styles.author}
|
||||
activity={moment(article.publishedAt).format("LL")}
|
||||
people={[
|
||||
{
|
||||
name: article.source.name
|
||||
? article.source.name
|
||||
: article.author
|
||||
? article.author
|
||||
: strings.AuthorNotAvailableMessage,
|
||||
profileImageSrc: ""
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,178 @@
|
|||
@import "~office-ui-fabric-react/dist/sass/References.scss";
|
||||
@media only screen and (min-device-width: 320px) and (max-device-width: 768px) {
|
||||
.card {
|
||||
flex-direction: column;
|
||||
}
|
||||
.image {
|
||||
width: 100%;
|
||||
}
|
||||
.newsTitle {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 320px) and (max-width: 1024px){
|
||||
|
||||
}
|
||||
|
||||
.image {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.newsTitle {
|
||||
font-size: 20px;
|
||||
padding-top: 0px;
|
||||
/* color: $ms-color-themePrimary; */
|
||||
margin-bottom: 20px;
|
||||
@include ms-fontWeight-semibold;
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system,
|
||||
BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
.row {
|
||||
@include ms-Grid-row;
|
||||
@include ms-fontColor-white;
|
||||
background-color: $ms-color-themeDark;
|
||||
padding: 20px;
|
||||
}
|
||||
.column {
|
||||
@include ms-Grid-col;
|
||||
@include ms-lg10;
|
||||
@include ms-xl8;
|
||||
@include ms-xlPush2;
|
||||
@include ms-lgPush1;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include ms-font-xl;
|
||||
@include ms-fontColor-white;
|
||||
}
|
||||
|
||||
.description {
|
||||
/* color: $ms-color-neutralSecondaryAlt; */
|
||||
@include ms-fontSize-m;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
line-height: 22px;
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system,
|
||||
BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.author {
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system,
|
||||
BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
.date {
|
||||
@include ms-font-s-plus;
|
||||
max-width: 200px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-right: 15px;
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system,
|
||||
BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
.descriptionTile {
|
||||
/* color: $ms-color-neutralSecondaryAlt;*/
|
||||
@include ms-fontSize-m;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
line-height: 22px;
|
||||
max-height: 135px;
|
||||
min-height: 135px;
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system,
|
||||
BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
.separator {
|
||||
border-top: 1px solid lighten(#333, 70%);
|
||||
margin: 0 auto;
|
||||
padding-bottom: 5px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.cardTile {
|
||||
max-width: 100%
|
||||
}
|
||||
|
||||
[data-theme="default"] {
|
||||
.cardTile {
|
||||
border-color: $ms-color-neutralTertiaryAlt;
|
||||
}
|
||||
.separator {
|
||||
border-top: 1px solid $ms-color-neutralTertiaryAlt;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 5px;
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
[data-theme="dark"] {
|
||||
.cardTile {
|
||||
border-color: $ms-color-neutralSecondaryAlt;
|
||||
}
|
||||
.separator {
|
||||
border-top: 1px solid $ms-color-neutralSecondaryAlt;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 5px;
|
||||
width: 90%;
|
||||
}
|
||||
.cardTile:hover {
|
||||
border-color: $ms-color-neutralTertiary;
|
||||
}
|
||||
.descriptionTile {
|
||||
/* color: $ms-color-neutralSecondaryAlt;*/
|
||||
@include ms-fontSize-m;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
line-height: 22px;
|
||||
max-height: 135px;
|
||||
min-height: 135px;
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: $ms-color-neutralTertiaryAlt;
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system,
|
||||
BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
|
||||
:global {
|
||||
.ms-Persona-initials {
|
||||
color: $ms-color-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
[data-theme="contrast"] {
|
||||
.cardTile {
|
||||
border-color: $ms-color-neutralTertiaryAlt;
|
||||
}
|
||||
.separator {
|
||||
border-top: 1px solid $ms-color-neutralTertiaryAlt;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 5px;
|
||||
width: 90%;
|
||||
}
|
||||
.descriptionTile {
|
||||
/* color: $ms-color-neutralSecondaryAlt;*/
|
||||
@include ms-fontSize-m;
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
line-height: 22px;
|
||||
max-height: 135px;
|
||||
min-height: 135px;
|
||||
margin-bottom: 15px;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: $ms-color-neutralTertiaryAlt;
|
||||
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system,
|
||||
BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
|
||||
}
|
||||
:global {
|
||||
.ms-Persona-initials {
|
||||
color: $ms-color-white;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
import { IArticle } from "../../../services/IArticle";
|
||||
import {
|
||||
Text,
|
||||
DocumentCard,
|
||||
DocumentCardTitle,
|
||||
DocumentCardDetails,
|
||||
Persona,
|
||||
DocumentCardActivity,
|
||||
} from "office-ui-fabric-react";
|
||||
import * as moment from "moment";
|
||||
import styles from "./NewsTile.module.scss";
|
||||
import * as React from "react";
|
||||
import * as strings from "NewsWebPartStrings";
|
||||
import { escape } from "@microsoft/sp-lodash-subset";
|
||||
|
||||
const _defaultImage: string = require("../../../../assets/news.jpg");
|
||||
|
||||
export const NewsTile = (props: { article: IArticle }) => {
|
||||
let { article } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocumentCard
|
||||
className={styles.cardTile}
|
||||
onClick={ev => {
|
||||
ev.preventDefault();
|
||||
window.open(article.url, "_blank");
|
||||
}}
|
||||
>
|
||||
<div style={{ minWidth: "100%", maxHeight: 160 }}>
|
||||
<img
|
||||
className={styles.image}
|
||||
src={article.urlToImage ? article.urlToImage : _defaultImage}
|
||||
width={"100%"}
|
||||
height={160}
|
||||
onError={ev => {
|
||||
ev.currentTarget.setAttribute("src", _defaultImage); // set Default image if can not load article image
|
||||
}}
|
||||
></img>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{ display: "flex", flexDirection: "column", marginTop: 25 }}
|
||||
>
|
||||
<DocumentCardTitle
|
||||
title={article.title}
|
||||
shouldTruncate={true}
|
||||
className={styles.newsTitle}
|
||||
></DocumentCardTitle>
|
||||
<DocumentCardDetails styles={{ root: { paddingBottom: 25 } }}>
|
||||
<Text block variant="mediumPlus" className={styles.descriptionTile}>
|
||||
{article.description
|
||||
? article.description
|
||||
: strings.CanNotShowArticleTextMessage}
|
||||
</Text>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
></div>
|
||||
</DocumentCardDetails>
|
||||
<div className={styles.separator}></div>
|
||||
<DocumentCardActivity
|
||||
className={styles.author}
|
||||
activity={moment(article.publishedAt).format("LL")}
|
||||
|
||||
people={[
|
||||
{
|
||||
name: article.source.name
|
||||
? article.source.name
|
||||
: article.author
|
||||
? article.author
|
||||
: strings.AuthorNotAvailableMessage,
|
||||
profileImageSrc: "",
|
||||
}
|
||||
] }
|
||||
/>
|
||||
</div>
|
||||
</DocumentCard>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
import {
|
||||
Icon,
|
||||
Stack,
|
||||
DocumentCardTitle,
|
||||
} from "office-ui-fabric-react";
|
||||
import styles from "./News.module.scss";
|
||||
import * as React from "react";
|
||||
|
||||
export const NoNews = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="card">
|
||||
<Stack
|
||||
style={{ justifyContent: "center", alignItems: "center" }}
|
||||
verticalAlign="center"
|
||||
tokens={{ childrenGap: 5 }}
|
||||
>
|
||||
<div>
|
||||
<Icon iconName="News" className={styles.nonewsIcon} />
|
||||
</div>
|
||||
<DocumentCardTitle
|
||||
title="No News find at this moment"
|
||||
className={styles.nonewsMessage}
|
||||
></DocumentCardTitle>
|
||||
</Stack>
|
||||
</div>
|
||||
<div className="separator"></div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
import { IArticle } from "../../../services/IArticle";
|
||||
import {
|
||||
Icon,
|
||||
Label,
|
||||
Text,
|
||||
Stack,
|
||||
Image,
|
||||
ImageFit,
|
||||
DocumentCard,
|
||||
DocumentCardTitle,
|
||||
DocumentCardDetails
|
||||
} from "office-ui-fabric-react";
|
||||
import moment from "moment";
|
||||
import styles from "../News.module.scss";
|
||||
import React from "react";
|
||||
|
||||
export const NoNews = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="card"
|
||||
>
|
||||
<Stack style={{justifyContent: 'center', alignItems:'center'}} verticalAlign="center" tokens={{ childrenGap: 5 }}>
|
||||
<div>
|
||||
<Icon iconName="News" className={styles.nonewsIcon}/>
|
||||
</div>
|
||||
<DocumentCardTitle
|
||||
title="No News find at this moment"
|
||||
className={styles.nonewsMessage}
|
||||
></DocumentCardTitle>
|
||||
</Stack>
|
||||
</div>
|
||||
<div className="separator"></div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
|
||||
"id": "137868a8-8e7e-4e06-85f3-a8913f5ff71c",
|
||||
"alias": "NewsWebPart",
|
||||
"componentType": "WebPart",
|
||||
"supportsFullBleed": true, // suport to full width sections
|
||||
// The "*" signifies that the version should be taken from the package.json
|
||||
"version": "*",
|
||||
"manifestVersion": 2,
|
||||
"supportsThemeVariants": true,
|
||||
|
||||
// If true, the component can only be installed on sites where Custom Script is allowed.
|
||||
// Components that allow authors to embed arbitrary script code should set this to true.
|
||||
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
|
||||
"requiresCustomScript": false,
|
||||
"supportedHosts": ["SharePointWebPart","TeamsTab","TeamsPersonalApp","SharePointFullPage"],
|
||||
|
||||
"preconfiguredEntries": [{
|
||||
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
|
||||
"group": { "default": "Other" },
|
||||
"title": { "default": "World News" },
|
||||
"description": { "default": "World News WebPart" },
|
||||
"officeFabricIconFontName": "NewsSearch",
|
||||
"properties": {
|
||||
"title": "News",
|
||||
"newsUrl": "https://newsapi.org/v2/everything?q=*",
|
||||
"apiKey": "3f28c6d88b804e75a66840dbb5aa84b1",
|
||||
"endpoint":"",
|
||||
"queryTitleOnly" : "",
|
||||
"domains": "",
|
||||
"excludeDomains": "",
|
||||
"language":"",
|
||||
"country": "ALL",
|
||||
"category": "general",
|
||||
"pagesize": 12,
|
||||
"viewOption": "tiles" // tiles or list
|
||||
}
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,541 @@
|
|||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
import { Version } from "@microsoft/sp-core-library";
|
||||
import {
|
||||
IPropertyPaneConfiguration,
|
||||
PropertyPaneTextField,
|
||||
PropertyPaneChoiceGroup,
|
||||
PropertyPaneToggle,
|
||||
PropertyPaneDropdown,
|
||||
IPropertyPaneDropdownOption,
|
||||
PropertyPaneSlider,
|
||||
PropertyPaneHorizontalRule,
|
||||
PropertyPaneLabel
|
||||
} from "@microsoft/sp-property-pane";
|
||||
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
|
||||
import * as lodash from "lodash";
|
||||
import * as strings from "NewsWebPartStrings";
|
||||
import News from "../components/News";
|
||||
import { INewsProps } from "../components/INewsProps";
|
||||
import dataservices from "../../services/dataservices";
|
||||
import { DisplayMode } from "@microsoft/sp-core-library";
|
||||
import * as $ from "jquery";
|
||||
import { loadTheme, IDropdownOption } from "office-ui-fabric-react";
|
||||
import { PropertyFieldMultiSelect } from "@pnp/spfx-property-controls/lib/PropertyFieldMultiSelect";
|
||||
import { ISourcesResults } from "../../services/ISourcesResults";
|
||||
import { ISource } from "../../services/ISource";
|
||||
import { ThemeProvider, ThemeChangedEventArgs, IReadonlyTheme } from '@microsoft/sp-component-base';
|
||||
|
||||
const languages: IPropertyPaneDropdownOption[] = [
|
||||
{ key: "all", text: "All Languages" },
|
||||
{ key: "ar", text: "Arabic" },
|
||||
{ key: "de", text: "German" },
|
||||
{ key: "en", text: "English" },
|
||||
{ key: "es", text: "Castilian" },
|
||||
{ key: "fr", text: "French" },
|
||||
{ key: "he", text: "Hebrew" },
|
||||
{ key: "it", text: "Italian" },
|
||||
{ key: "nl", text: "Dutch" },
|
||||
{ key: "no", text: "Norwegian" },
|
||||
{ key: "pt", text: "Portuguese" },
|
||||
{ key: "ru", text: "Russian" },
|
||||
{ key: "se", text: "Northern Sami" },
|
||||
{ key: "zh", text: "Chinese" }
|
||||
];
|
||||
|
||||
const countries: IPropertyPaneDropdownOption[] = [
|
||||
{ key: "ALL", text: "All Countries" },
|
||||
{ key: "AE", text: "United Arab Emirates" },
|
||||
{ key: "AR", text: "Argentina" },
|
||||
{ key: "AT", text: "Austria" },
|
||||
{ key: "AU", text: "Australia" },
|
||||
{ key: "BE", text: "Belgium" },
|
||||
{ key: "BG", text: "Bulgaria" },
|
||||
{ key: "BR", text: "Brazil" },
|
||||
{ key: "CA", text: "Canada" },
|
||||
{ key: "CH", text: "Switzerland" },
|
||||
{ key: "CN", text: "China" },
|
||||
{ key: "CO", text: "Colombia" },
|
||||
{ key: "CU", text: "Cuba" },
|
||||
{ key: "CZ", text: "Czech Republic" },
|
||||
{ key: "DE", text: "Germany" },
|
||||
{ key: "EG", text: "Egypt" },
|
||||
{ key: "FR", text: "France" },
|
||||
{ key: "GB", text: "United Kingdom" },
|
||||
{ key: "GR", text: "Greece" },
|
||||
{ key: "HK", text: "Hong Kong" },
|
||||
{ key: "HU", text: "Hungary" },
|
||||
{ key: "ID", text: "Indonesia" },
|
||||
{ key: "IE", text: "Ireland" },
|
||||
{ key: "IL", text: "Israel" },
|
||||
{ key: "IN", text: "India" },
|
||||
{ key: "IT", text: "Italy" },
|
||||
{ key: "JP", text: "Japan" },
|
||||
{ key: "KR", text: "Korea, Republic of" },
|
||||
{ key: "LT", text: "Lithuania" },
|
||||
{ key: "LV", text: "Latvia" },
|
||||
{ key: "MA", text: "Morocco" },
|
||||
{ key: "MX", text: "Mexico" },
|
||||
{ key: "MY", text: "Malaysia" },
|
||||
{ key: "NG", text: "Nigeria" },
|
||||
{ key: "NL", text: "Netherlands" },
|
||||
{ key: "NO", text: "Norway" },
|
||||
{ key: "NZ", text: "New Zealand" },
|
||||
{ key: "PH", text: "Philippines" },
|
||||
{ key: "PL", text: "Poland" },
|
||||
{ key: "PT", text: "Portugal" },
|
||||
{ key: "RO", text: "Romania" },
|
||||
{ key: "RS", text: "Serbia" },
|
||||
{ key: "RU", text: "Russian Federation" },
|
||||
{ key: "SA", text: "Saudi Arabia" },
|
||||
{ key: "SE", text: "Sweden" },
|
||||
{ key: "SG", text: "Singapore" },
|
||||
{ key: "SI", text: "Slovenia" },
|
||||
{ key: "SK", text: "Slovakia" },
|
||||
{ key: "TH", text: "Thailand" },
|
||||
{ key: "TR", text: "Turkey" },
|
||||
{ key: "TW", text: "Taiwan, Province of China" },
|
||||
{ key: "UA", text: "Ukraine" },
|
||||
{ key: "US", text: "United States" },
|
||||
{ key: "VE", text: "Venezuela, Bolivarian Republic of" },
|
||||
{ key: "ZA", text: "South Africa" }
|
||||
];
|
||||
|
||||
const categories: IPropertyPaneDropdownOption[] = [
|
||||
{ key: "business", text: "business" },
|
||||
{ key: "entertainment", text: "entertainment" },
|
||||
{ key: "general", text: "general" },
|
||||
{ key: "health", text: "health" },
|
||||
{ key: "science", text: "science" },
|
||||
{ key: "sports", text: "sports" },
|
||||
{ key: "technology", text: "technology" }
|
||||
];
|
||||
|
||||
const teamsDefaultTheme = require("../../common/TeamsDefaultTheme.json");
|
||||
const teamsDarkTheme = require("../../common/TeamsDarkTheme.json");
|
||||
const teamsContrastTheme = require("../../common/TeamsContrastTheme.json");
|
||||
|
||||
export interface INewsWebPartProps {
|
||||
title: string;
|
||||
newsUrl: string;
|
||||
apiKey: string;
|
||||
endpoint: number;
|
||||
queryTitleOnly: boolean;
|
||||
domains: string;
|
||||
excludeDomains: string;
|
||||
language: string;
|
||||
country: string;
|
||||
category: string;
|
||||
query: string;
|
||||
pagesize: number;
|
||||
viewOption: string;
|
||||
sources: string[];
|
||||
}
|
||||
|
||||
export default class NewsWebPart extends BaseClientSideWebPart<
|
||||
INewsWebPartProps
|
||||
> {
|
||||
private _sourcesOptions: IDropdownOption[] = [];
|
||||
private _themeProvider: ThemeProvider;
|
||||
private _themeVariant: IReadonlyTheme | undefined;
|
||||
|
||||
protected async onInit<T>(): Promise<T> {
|
||||
await dataservices.init(this.context);
|
||||
|
||||
this._themeProvider = this.context.serviceScope.consume(ThemeProvider.serviceKey);
|
||||
// If it exists, get the theme variant
|
||||
this._themeVariant = this._themeProvider.tryGetTheme();
|
||||
// Register a handler to be notified if the theme variant changes
|
||||
this._themeProvider.themeChangedEvent.add(this, this._handleThemeChangedEvent);
|
||||
// test if is teams context
|
||||
if (this.context.sdks.microsoftTeams) {
|
||||
// in teams ?
|
||||
const context = this.context.sdks.microsoftTeams!.context;
|
||||
this._applyTheme(context.theme || "default");
|
||||
this.context.sdks.microsoftTeams.teamsJs.registerOnThemeChangeHandler(
|
||||
this._applyTheme
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
/**
|
||||
* Update the current theme variant reference and re-render.
|
||||
*
|
||||
* @param args The new theme
|
||||
*/
|
||||
private _handleThemeChangedEvent(args: ThemeChangedEventArgs): void {
|
||||
this._themeVariant = args.theme;
|
||||
this.render();
|
||||
}
|
||||
|
||||
// Apply Teams Context
|
||||
private _applyTheme = (theme: string): void => {
|
||||
this.context.domElement.setAttribute("data-theme", theme);
|
||||
document.body.setAttribute("data-theme", theme);
|
||||
|
||||
if (theme == "dark") {
|
||||
loadTheme({
|
||||
palette: teamsDarkTheme
|
||||
});
|
||||
}
|
||||
|
||||
if (theme == "default") {
|
||||
loadTheme({
|
||||
palette: teamsDefaultTheme
|
||||
});
|
||||
}
|
||||
|
||||
if (theme == "contrast") {
|
||||
loadTheme({
|
||||
palette: teamsContrastTheme
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _validateNewsUrl = (value: string): string => {
|
||||
try {
|
||||
let _url = new URL(value);
|
||||
|
||||
return "";
|
||||
} catch (error) {
|
||||
return "news Url is not valid, please specify valid url";
|
||||
}
|
||||
}
|
||||
|
||||
public updateProperty = (value: string) => {
|
||||
this.properties.title = value;
|
||||
}
|
||||
|
||||
private _getSources = async (apiKey: string) => {
|
||||
let _resultSources: ISourcesResults = await dataservices.getSources(apiKey);
|
||||
|
||||
try {
|
||||
if (_resultSources && _resultSources.sources.length > 0) {
|
||||
for (const source of _resultSources.sources) {
|
||||
this._sourcesOptions.push({ key: source.id, text: source.name });
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error loading Sources", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Render WebPart
|
||||
public render(): void {
|
||||
const element: React.ReactElement<INewsProps> = React.createElement(
|
||||
News,
|
||||
{
|
||||
newsUrl: this.properties.newsUrl,
|
||||
apiKey: this.properties.apiKey,
|
||||
context: this.context,
|
||||
title: this.properties.title,
|
||||
updateProperty: this.updateProperty,
|
||||
displayMode: this.displayMode,
|
||||
viewOption: this.properties.viewOption,
|
||||
pageSize: this.properties.pagesize,
|
||||
themeVariant: this._themeVariant,
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
ReactDom.render(element, this.domElement);
|
||||
}
|
||||
|
||||
protected onDispose(): void {
|
||||
ReactDom.unmountComponentAtNode(this.domElement);
|
||||
}
|
||||
|
||||
protected get disableReactivePropertyChanges(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected get dataVersion(): Version {
|
||||
return Version.parse("1.0");
|
||||
}
|
||||
|
||||
protected async onPropertyPaneConfigurationStart() {
|
||||
await this._getSources(this.properties.apiKey);
|
||||
this.context.propertyPane.refresh();
|
||||
}
|
||||
|
||||
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
|
||||
let _showPropertyQueryTitle: any = "";
|
||||
let _showPropertyDomains: any = "";
|
||||
let _showPropertyexcludeDomains: any = "";
|
||||
let _showPropertyLanguage: any = "";
|
||||
let _showPropertyCountry: any = "";
|
||||
let _showPropertyCategory: any = "";
|
||||
let _viewOption: string = undefined;
|
||||
|
||||
if (!this.properties.viewOption || this.properties.viewOption === "list") {
|
||||
_viewOption = "list";
|
||||
} else {
|
||||
_viewOption = "tiles";
|
||||
}
|
||||
|
||||
if (!this.properties.newsUrl) {
|
||||
this.properties.newsUrl =
|
||||
"https://newsapi.org/v2/top-headlines?category=general&sortBy=publishedAt";
|
||||
}
|
||||
if (!this.properties.endpoint) {
|
||||
this.properties.endpoint = 1;
|
||||
}
|
||||
|
||||
if (!this.properties.category) {
|
||||
this.properties.category = "general";
|
||||
}
|
||||
|
||||
// teste if All News or TOP Headding news
|
||||
switch (this.properties.endpoint) {
|
||||
case 2:
|
||||
this.properties.country = "";
|
||||
this.properties.category = "";
|
||||
_showPropertyCountry = "";
|
||||
_showPropertyCategory = "";
|
||||
|
||||
_showPropertyQueryTitle = PropertyPaneToggle("queryTitleOnly", {
|
||||
label: "Search on Article Title Only",
|
||||
onText: "On",
|
||||
offText: "Off",
|
||||
checked: this.properties.queryTitleOnly
|
||||
});
|
||||
_showPropertyDomains = PropertyPaneTextField("domains", {
|
||||
label: "Selected Domains",
|
||||
value: this.properties.domains,
|
||||
description:
|
||||
"comma-seperated (eg bbc.co.uk, techcrunch.com, engadget.com)"
|
||||
});
|
||||
_showPropertyexcludeDomains = PropertyPaneTextField("excludeDomains", {
|
||||
label: "Exclude Domains",
|
||||
value: this.properties.excludeDomains,
|
||||
description:
|
||||
"comma-seperated (eg bbc.co.uk, techcrunch.com, engadget.com)"
|
||||
});
|
||||
|
||||
// Create a news URL API
|
||||
this.properties.newsUrl = `https://newsapi.org/v2/everything?sortBy=publishedAt`;
|
||||
// Test Properties
|
||||
// Query on Article Title
|
||||
|
||||
if (this.properties.queryTitleOnly) {
|
||||
if (
|
||||
this.properties.query &&
|
||||
this.properties.query.trim().length > 0
|
||||
) {
|
||||
let _query: string = encodeURIComponent(this.properties.query);
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&qInTitle=${_query}`;
|
||||
} else {
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&qInTitle=*`;
|
||||
}
|
||||
}
|
||||
// Domains
|
||||
if (this.properties.domains) {
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&domains=${this.properties.domains}`;
|
||||
}
|
||||
// Excluded Domains
|
||||
if (this.properties.excludeDomains) {
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&excludeDomains=${this.properties.excludeDomains}`;
|
||||
}
|
||||
// Language
|
||||
if (this.properties.language && this.properties.language !== "all") {
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&language=${this.properties.language}`;
|
||||
}
|
||||
// Query Title Only
|
||||
if (!this.properties.queryTitleOnly) {
|
||||
if (
|
||||
this.properties.query &&
|
||||
this.properties.query.trim().length > 0
|
||||
) {
|
||||
let _query: string = encodeURIComponent(this.properties.query);
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&q=${_query}`;
|
||||
} else {
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&q=*`;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.properties.pagesize) {
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&pageSize=${this.properties.pagesize}`;
|
||||
}
|
||||
|
||||
if (this.properties.sources && this.properties.sources.length > 0) {
|
||||
if (this.properties.sources.length < 20) {
|
||||
this.properties.newsUrl = `${
|
||||
this.properties.newsUrl
|
||||
}&sources=${this.properties.sources.join()}`;
|
||||
} else {
|
||||
this.properties.newsUrl = `${
|
||||
this.properties.newsUrl
|
||||
}&sources=${this.properties.sources.slice(0, 19).join()}`;
|
||||
}
|
||||
}
|
||||
|
||||
_showPropertyLanguage = PropertyPaneDropdown("language", {
|
||||
label: "Show Articles in this language",
|
||||
options: lodash.sortBy(languages, ["key"]),
|
||||
selectedKey: this.properties.language || "all"
|
||||
});
|
||||
|
||||
break;
|
||||
// Top Heading
|
||||
case 1:
|
||||
// Reset Properties Vars
|
||||
this.properties.queryTitleOnly = false;
|
||||
this.properties.domains = "";
|
||||
this.properties.excludeDomains = "";
|
||||
this.properties.language = "";
|
||||
|
||||
_showPropertyQueryTitle = "";
|
||||
_showPropertyDomains = "";
|
||||
_showPropertyexcludeDomains = "";
|
||||
_showPropertyLanguage = "";
|
||||
|
||||
this.properties.newsUrl = `https://newsapi.org/v2/top-headlines?sortBy=publishedAt`;
|
||||
|
||||
if (this.properties.query && this.properties.query.trim().length > 0) {
|
||||
let _query: string = encodeURIComponent(this.properties.query);
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&q=${_query}`;
|
||||
}
|
||||
|
||||
if (this.properties.pagesize) {
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&pageSize=${this.properties.pagesize}`;
|
||||
}
|
||||
|
||||
// Has sources ? add parameter to newsURl disable country and Category
|
||||
if (this.properties.sources && this.properties.sources.length > 0) {
|
||||
if (this.properties.sources.length < 20) {
|
||||
// only the first 20 sources selectd limited by API
|
||||
this.properties.newsUrl = `${
|
||||
this.properties.newsUrl
|
||||
}&sources=${this.properties.sources.join()}`;
|
||||
} else {
|
||||
this.properties.newsUrl = `${
|
||||
this.properties.newsUrl
|
||||
}&sources=${this.properties.sources.slice(0, 19).join()}`;
|
||||
}
|
||||
} else {
|
||||
// Show Category and Country if sources is not specified
|
||||
if (this.properties.category) {
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&category=${this.properties.category}`;
|
||||
}
|
||||
|
||||
if (this.properties.country && this.properties.country !== "ALL") {
|
||||
this.properties.newsUrl = `${this.properties.newsUrl}&country=${this.properties.country}`;
|
||||
}
|
||||
|
||||
_showPropertyCountry = PropertyPaneDropdown("country", {
|
||||
label: "Country",
|
||||
options: lodash.sortBy(countries, ["text"]),
|
||||
selectedKey: this.properties.country || "ALL"
|
||||
});
|
||||
|
||||
_showPropertyCategory = PropertyPaneDropdown("category", {
|
||||
label: "Category",
|
||||
options: lodash.sortBy(categories, ["key"]),
|
||||
selectedKey: this.properties.category || "general"
|
||||
});
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
pages: [
|
||||
{
|
||||
header: {
|
||||
description: strings.PropertyPaneDescription
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupName: strings.BasicGroupName,
|
||||
groupFields: [
|
||||
PropertyPaneTextField("title", {
|
||||
label: strings.Title,
|
||||
value: this.properties.title
|
||||
}),
|
||||
PropertyPaneTextField("query", {
|
||||
label: "Search Keyword or Phrase",
|
||||
value: this.properties.query
|
||||
}),
|
||||
PropertyFieldMultiSelect("sources", {
|
||||
key: "sources",
|
||||
label: "Sources",
|
||||
disabled: false,
|
||||
options: this._sourcesOptions,
|
||||
selectedKeys: this.properties.sources
|
||||
}),
|
||||
PropertyPaneChoiceGroup("endpoint", {
|
||||
label: "Show Articles from:",
|
||||
|
||||
options: [
|
||||
{ text: "Top Headlines", key: 1 },
|
||||
{ text: "All News", key: 2 }
|
||||
]
|
||||
}),
|
||||
_showPropertyCountry,
|
||||
_showPropertyCategory,
|
||||
_showPropertyQueryTitle,
|
||||
_showPropertyDomains,
|
||||
_showPropertyexcludeDomains,
|
||||
_showPropertyLanguage
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
header: {
|
||||
description: strings.ViewSettings
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
groupFields: [
|
||||
PropertyPaneChoiceGroup("viewOption", {
|
||||
label: strings.ViewOption,
|
||||
options: [
|
||||
{
|
||||
text: "List View",
|
||||
key: "list",
|
||||
checked: _viewOption === "list" ? true : false,
|
||||
iconProps: { officeFabricIconFontName: "list" }
|
||||
},
|
||||
{
|
||||
text: "Tiles View",
|
||||
key: "tiles",
|
||||
checked: _viewOption === "ltiles" ? true : false,
|
||||
iconProps: { officeFabricIconFontName: "Tiles" }
|
||||
}
|
||||
]
|
||||
}),
|
||||
PropertyPaneLabel("", { text: "" }),
|
||||
PropertyPaneSlider("pagesize", {
|
||||
label: strings.PageSizeLabel,
|
||||
max: 100,
|
||||
min: 3,
|
||||
step: 1,
|
||||
showValue: true,
|
||||
value: this.properties.pagesize
|
||||
}),
|
||||
PropertyPaneLabel("", { text: strings.APILabelText }),
|
||||
PropertyPaneTextField("apiKey", {
|
||||
label: strings.ApiKey,
|
||||
value: this.properties.apiKey,
|
||||
validateOnFocusOut: true,
|
||||
onGetErrorMessage: value => {
|
||||
if (!value) {
|
||||
return "ApiKey is Required";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
define([], function() {
|
||||
return {
|
||||
ConfigureWebPartButtonLabel: "Configure",
|
||||
configureWebPartTextMessage: "Please configure the web part.",
|
||||
ConfigureWebPartMessage: "Configure News web part",
|
||||
AuthorNotAvailableMessage: "Author not Available",
|
||||
CanNotShowArticleTextMessage: "Can not show article text, please click to see the full article.",
|
||||
APILabelText: "APIKEY use on API Calls, Please go to https://newsapi.org see more details",
|
||||
PageSizeLabel: "Number of Articles per page",
|
||||
ViewOption: "View Option",
|
||||
ViewSettings: "View Settings",
|
||||
Title: "Title",
|
||||
"PropertyPaneDescription": "This Web Part Get News from newsapi.org API, please go to https://newsapi.org for more information.",
|
||||
"BasicGroupName": "News",
|
||||
"NewsUrlFieldLabel": "NewsApi Url",
|
||||
"ApiKey": "APIKEY"
|
||||
}
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
declare interface INewsWebPartStrings {
|
||||
ConfigureWebPartButtonLabel: string;
|
||||
configureWebPartTextMessage: string;
|
||||
ConfigureWebPartMessage: string;
|
||||
AuthorNotAvailableMessage: string;
|
||||
CanNotShowArticleTextMessage: string;
|
||||
APILabelText: string;
|
||||
PageSizeLabel: string;
|
||||
ViewOption: string;
|
||||
ViewSettings: string;
|
||||
Title: string;
|
||||
PropertyPaneDescription: string;
|
||||
BasicGroupName: string;
|
||||
NewsUrlFieldLabel: string;
|
||||
ApiKey: string;
|
||||
}
|
||||
|
||||
declare module 'NewsWebPartStrings' {
|
||||
const strings: INewsWebPartStrings;
|
||||
export = strings;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react",
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "lib",
|
||||
"inlineSources": false,
|
||||
"strictNullChecks": false,
|
||||
"noUnusedLocals": false,
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@microsoft"
|
||||
],
|
||||
"types": [
|
||||
"es6-promise",
|
||||
"webpack-env"
|
||||
],
|
||||
"lib": [
|
||||
"es5",
|
||||
"dom",
|
||||
"es2015.collection"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"lib"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"extends": "@microsoft/sp-tslint-rules/base-tslint.json",
|
||||
"rules": {
|
||||
"class-name": false,
|
||||
"export-name": false,
|
||||
"forin": false,
|
||||
"label-position": false,
|
||||
"member-access": true,
|
||||
"no-arg": false,
|
||||
"no-console": false,
|
||||
"no-construct": false,
|
||||
"no-duplicate-variable": true,
|
||||
"no-eval": false,
|
||||
"no-function-expression": true,
|
||||
"no-internal-module": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-unnecessary-semicolons": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-with-statement": true,
|
||||
"semicolon": true,
|
||||
"trailing-comma": false,
|
||||
"typedef": false,
|
||||
"typedef-whitespace": false,
|
||||
"use-named-parameter": true,
|
||||
"variable-name": false,
|
||||
"whitespace": false
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -121,7 +121,7 @@ export function ManageApps(appProps: IManageAppsProps) {
|
|||
},
|
||||
{
|
||||
title: "Url",
|
||||
field: "Url",
|
||||
field: "url",
|
||||
editComponent: props => (
|
||||
<TextField
|
||||
underlined
|
||||
|
@ -279,6 +279,7 @@ export function ManageApps(appProps: IManageAppsProps) {
|
|||
showTitle:false,
|
||||
searchFieldAlignment:'left',
|
||||
pageSize: 7,
|
||||
pageSizeOptions: [],
|
||||
search: true,
|
||||
minBodyHeight: "100%"
|
||||
}}
|
||||
|
|
Loading…
Reference in New Issue