mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
NewsPanel: Add support for showing social image before content (#33949)
* NewsPanel: Add support for showing social image before content * Fix link and spacing * Add wide layout
This commit is contained in:
parent
95a356a840
commit
f346bafdc9
@ -9,11 +9,11 @@ import { feedToDataFrame } from './utils';
|
|||||||
import { loadRSSFeed } from './rss';
|
import { loadRSSFeed } from './rss';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { PanelProps, DataFrameView, dateTimeFormat, GrafanaTheme, textUtil } from '@grafana/data';
|
import { PanelProps, DataFrameView, dateTimeFormat, GrafanaTheme2, textUtil } from '@grafana/data';
|
||||||
import { NewsItem } from './types';
|
import { NewsItem } from './types';
|
||||||
import { PanelOptions } from './models.gen';
|
import { PanelOptions } from './models.gen';
|
||||||
import { DEFAULT_FEED_URL, PROXY_PREFIX } from './constants';
|
import { DEFAULT_FEED_URL, PROXY_PREFIX } from './constants';
|
||||||
import { css } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
|
|
||||||
interface Props extends PanelProps<PanelOptions> {}
|
interface Props extends PanelProps<PanelOptions> {}
|
||||||
|
|
||||||
@ -63,8 +63,11 @@ export class NewsPanel extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { width } = this.props;
|
||||||
|
const { showImage } = this.props.options;
|
||||||
const { isError, news } = this.state;
|
const { isError, news } = this.state;
|
||||||
const styles = getStyles(config.theme);
|
const styles = getStyles(config.theme2);
|
||||||
|
const useWideLayout = width > 600;
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return <div>Error Loading News</div>;
|
return <div>Error Loading News</div>;
|
||||||
@ -77,17 +80,29 @@ export class NewsPanel extends PureComponent<Props, State> {
|
|||||||
<CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
|
<CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
|
||||||
{news.map((item, index) => {
|
{news.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<div key={index} className={styles.item}>
|
<div key={index} className={cx(styles.item, useWideLayout && styles.itemWide)}>
|
||||||
<a
|
{showImage && item.ogImage && (
|
||||||
className={styles.link}
|
<a
|
||||||
href={textUtil.sanitizeUrl(item.link)}
|
href={textUtil.sanitizeUrl(item.link)}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
className={cx(styles.socialImage, useWideLayout && styles.socialImageWide)}
|
||||||
<div className={styles.title}>{item.title}</div>
|
>
|
||||||
|
<img src={item.ogImage} />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
<div className={styles.body}>
|
||||||
<div className={styles.date}>{dateTimeFormat(item.date, { format: 'MMM DD' })} </div>
|
<div className={styles.date}>{dateTimeFormat(item.date, { format: 'MMM DD' })} </div>
|
||||||
</a>
|
<a
|
||||||
<div className={styles.content} dangerouslySetInnerHTML={{ __html: textUtil.sanitize(item.content) }} />
|
className={styles.link}
|
||||||
|
href={textUtil.sanitizeUrl(item.link)}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className={styles.title}>{item.title}</div>
|
||||||
|
</a>
|
||||||
|
<div className={styles.content} dangerouslySetInnerHTML={{ __html: textUtil.sanitize(item.content) }} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@ -96,29 +111,53 @@ export class NewsPanel extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
const getStyles = stylesFactory((theme: GrafanaTheme2) => ({
|
||||||
container: css`
|
container: css`
|
||||||
height: 100%;
|
height: 100%;
|
||||||
`,
|
`,
|
||||||
item: css`
|
item: css`
|
||||||
padding: ${theme.spacing.sm};
|
display: flex;
|
||||||
|
padding: ${theme.spacing(1)};
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
margin-right: ${theme.spacing.sm};
|
margin-right: ${theme.spacing(1)};
|
||||||
border-bottom: 2px solid ${theme.colors.border1};
|
border-bottom: 2px solid ${theme.colors.border.weak};
|
||||||
|
background: ${theme.colors.background.primary};
|
||||||
|
flex-direction: column;
|
||||||
|
`,
|
||||||
|
itemWide: css`
|
||||||
|
flex-direction: row;
|
||||||
|
`,
|
||||||
|
body: css``,
|
||||||
|
socialImage: css`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: ${theme.spacing(1)};
|
||||||
|
> img {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: ${theme.shape.borderRadius(2)} ${theme.shape.borderRadius(2)} 0 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
socialImageWide: css`
|
||||||
|
margin-right: ${theme.spacing(2)};
|
||||||
|
margin-bottom: 0;
|
||||||
|
> img {
|
||||||
|
width: 250px;
|
||||||
|
border-radius: ${theme.shape.borderRadius()};
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
link: css`
|
link: css`
|
||||||
color: ${theme.colors.linkExternal};
|
color: ${theme.colors.text.link};
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: ${theme.colors.linkExternal};
|
color: ${theme.colors.text.link};
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
title: css`
|
title: css`
|
||||||
max-width: calc(100% - 70px);
|
max-width: calc(100% - 70px);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-bottom: ${theme.spacing.sm};
|
margin-bottom: ${theme.spacing(0.5)};
|
||||||
`,
|
`,
|
||||||
content: css`
|
content: css`
|
||||||
p {
|
p {
|
||||||
@ -127,15 +166,9 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => ({
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
date: css`
|
date: css`
|
||||||
position: absolute;
|
margin-bottom: ${theme.spacing(0.5)};
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
background: ${theme.colors.panelBg};
|
|
||||||
width: 55px;
|
|
||||||
text-align: right;
|
|
||||||
padding: ${theme.spacing.xs};
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border-radius: 0 0 0 3px;
|
border-radius: 0 0 0 3px;
|
||||||
color: ${theme.colors.textWeak};
|
color: ${theme.colors.text.secondary};
|
||||||
`,
|
`,
|
||||||
}));
|
}));
|
||||||
|
@ -8,6 +8,7 @@ Family: {
|
|||||||
// empty/missing will default to grafana blog
|
// empty/missing will default to grafana blog
|
||||||
feedUrl?: string
|
feedUrl?: string
|
||||||
useProxy?: bool
|
useProxy?: bool
|
||||||
|
showImage?: bool | *true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -8,6 +8,9 @@ export const modelVersion = Object.freeze([1, 0]);
|
|||||||
export interface PanelOptions {
|
export interface PanelOptions {
|
||||||
feedUrl?: string;
|
feedUrl?: string;
|
||||||
useProxy?: boolean;
|
useProxy?: boolean;
|
||||||
|
showImage?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultPanelOptions: PanelOptions = {};
|
export const defaultPanelOptions: PanelOptions = {
|
||||||
|
showImage: true,
|
||||||
|
};
|
||||||
|
@ -15,6 +15,15 @@ export const plugin = new PanelPlugin<PanelOptions>(NewsPanel).setPanelOptions((
|
|||||||
},
|
},
|
||||||
defaultValue: defaultPanelOptions.feedUrl,
|
defaultValue: defaultPanelOptions.feedUrl,
|
||||||
})
|
})
|
||||||
|
.addBooleanSwitch({
|
||||||
|
path: 'showImage',
|
||||||
|
name: 'Show image',
|
||||||
|
description: 'Controls if the news item social (og:image) image is shown above text content',
|
||||||
|
showIf: (currentConfig: PanelOptions) => {
|
||||||
|
return isString(currentConfig.feedUrl) && !currentConfig.feedUrl.startsWith(PROXY_PREFIX);
|
||||||
|
},
|
||||||
|
defaultValue: defaultPanelOptions.showImage,
|
||||||
|
})
|
||||||
.addBooleanSwitch({
|
.addBooleanSwitch({
|
||||||
path: 'useProxy',
|
path: 'useProxy',
|
||||||
name: 'Use Proxy',
|
name: 'Use Proxy',
|
||||||
|
@ -25,6 +25,11 @@ export async function loadRSSFeed(url: string): Promise<RssFeed> {
|
|||||||
pubDate: getProperty(node, 'pubDate'),
|
pubDate: getProperty(node, 'pubDate'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const imageNode = node.querySelector("meta[property='og:image']");
|
||||||
|
if (imageNode) {
|
||||||
|
item.ogImage = imageNode.getAttribute('content');
|
||||||
|
}
|
||||||
|
|
||||||
feed.items.push(item);
|
feed.items.push(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ export interface NewsItem {
|
|||||||
title: string;
|
title: string;
|
||||||
link: string;
|
link: string;
|
||||||
content: string;
|
content: string;
|
||||||
|
ogImage?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -20,4 +21,5 @@ export interface RssItem {
|
|||||||
pubDate?: string;
|
pubDate?: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
contentSnippet?: string;
|
contentSnippet?: string;
|
||||||
|
ogImage?: string | null;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ export function feedToDataFrame(feed: RssFeed): DataFrame {
|
|||||||
const title = new ArrayVector<string>([]);
|
const title = new ArrayVector<string>([]);
|
||||||
const link = new ArrayVector<string>([]);
|
const link = new ArrayVector<string>([]);
|
||||||
const content = new ArrayVector<string>([]);
|
const content = new ArrayVector<string>([]);
|
||||||
|
const ogImage = new ArrayVector<string | undefined | null>([]);
|
||||||
|
|
||||||
for (const item of feed.items) {
|
for (const item of feed.items) {
|
||||||
const val = dateTime(item.pubDate);
|
const val = dateTime(item.pubDate);
|
||||||
@ -14,6 +15,7 @@ export function feedToDataFrame(feed: RssFeed): DataFrame {
|
|||||||
date.buffer.push(val.valueOf());
|
date.buffer.push(val.valueOf());
|
||||||
title.buffer.push(item.title);
|
title.buffer.push(item.title);
|
||||||
link.buffer.push(item.link);
|
link.buffer.push(item.link);
|
||||||
|
ogImage.buffer.push(item.ogImage);
|
||||||
|
|
||||||
if (item.content) {
|
if (item.content) {
|
||||||
const body = item.content.replace(/<\/?[^>]+(>|$)/g, '');
|
const body = item.content.replace(/<\/?[^>]+(>|$)/g, '');
|
||||||
@ -30,6 +32,7 @@ export function feedToDataFrame(feed: RssFeed): DataFrame {
|
|||||||
{ name: 'title', type: FieldType.string, config: {}, values: title },
|
{ name: 'title', type: FieldType.string, config: {}, values: title },
|
||||||
{ name: 'link', type: FieldType.string, config: {}, values: link },
|
{ name: 'link', type: FieldType.string, config: {}, values: link },
|
||||||
{ name: 'content', type: FieldType.string, config: {}, values: content },
|
{ name: 'content', type: FieldType.string, config: {}, values: content },
|
||||||
|
{ name: 'ogImage', type: FieldType.string, config: {}, values: ogImage },
|
||||||
],
|
],
|
||||||
length: date.length,
|
length: date.length,
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user