Skip to content

Commit

Permalink
Merge pull request #355 from superdesk/content-lists-superdesk
Browse files Browse the repository at this point in the history
Superdesk articles support in content lists
  • Loading branch information
IvanJelicSF authored Oct 24, 2024
2 parents 24096d5 + ebc581a commit 5a1dfff
Show file tree
Hide file tree
Showing 14 changed files with 357 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dist/
*.egg-info
*.pyc
src/
!client/publisher-extension/src
yarn.lock
build

Expand Down
30 changes: 25 additions & 5 deletions client/components/ContentLists/Manual/ArticleItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ const ArticleItem = ({
thumbnail = helpers.getRenditionUrl(item.feature_media.renditions);
}

if (item.associations && item.associations.featuremedia) {
const renditions = item.associations.featuremedia.renditions;

const renditionsArray = Object.keys(renditions).map(key => ({
...renditions[key],
name: key
}));

thumbnail = helpers.getRenditionUrl(renditionsArray);
}

return (
<div
className={classNames("sd-list-item", {
Expand Down Expand Up @@ -93,11 +104,20 @@ const ArticleItem = ({
) : null}
</span>

<Label
text={item.route && item.route.name}
type="success"
style="hollow"
/>
{item.route?.name && (
<Label
text={item.route.name}
type="success"
style="hollow"
/>
)}
{item.status && item.status !== 'published' && (
<Label
text={item.status === 'new' ? "Non published" : item.status}
type="warning"
style="hollow"
/>
)}
{item.sticky && <Label text="pinned" type="alert" style="hollow" />}
</div>
</div>
Expand Down
151 changes: 138 additions & 13 deletions client/components/ContentLists/Manual/Manual.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import SearchBar from "../../UI/SearchBar";
import ArticleItem from "./ArticleItem";
import Loading from "../../UI/Loading/Loading";
import LanguageSelect from "../../UI/LanguageSelect";
import SourceSelect from "../../UI/SourceSelect";

// a little function to help us with reordering the result
const reorder = (list, startIndex, endIndex) => {
Expand Down Expand Up @@ -65,6 +66,7 @@ class Manual extends React.Component {
? { language: this.props.site.default_language }
: {},
changesRecord: [],
source: { id: 'publisher', name: 'All published articles' }
};
}

Expand All @@ -90,6 +92,7 @@ class Manual extends React.Component {
? { language: this.props.site.default_language }
: {},
changesRecord: [],
source: ""
},
this._loadData
);
Expand Down Expand Up @@ -121,7 +124,9 @@ class Manual extends React.Component {

if (listEl.scrollHeight - el.scrollTop - el.clientHeight < 100) {
if (list === "articles") {
this._queryArticles();
this.state.source && this.state.source.id === 'superdesk' ?
this._querySuperdeskArticles() :
this._queryArticles();
} else {
this._queryListArticles();
}
Expand Down Expand Up @@ -245,6 +250,97 @@ class Manual extends React.Component {
});
};

_querySuperdeskArticles = (filter, reset = false) => {
// Get Superdesk API instance
const superedeskApi = window['extensionsApiInstances']['publisher-extension'];

let articles = this.state.articles;
if (articles.loading || (articles.page === articles.totalPages && !reset))
return;

if (reset) {
articles = {
items: [],
page: 0,
totalPages: 1,
loading: false,
};
}

articles.loading = true;
this.setState({ articles }, () => {
const query = {
filter: {
$and: [
{ 'state': { $in: [filter] } },
{ 'type': { $eq: 'text' } }
]
},
page: this.state.articles.page + 1,
max_results: 20,
sort: [{ 'versioncreated': 'desc' }],
};

superedeskApi.httpRequestJsonLocal({
...superedeskApi.helpers.prepareSuperdeskQuery('/search', query),
}).then((response) => {
const articleItemsMapped = response._items.map((
{ _id, authors, body_html, headline, versioncreated, state, associations }) => ({
id: _id,
authors,
body: body_html,
title: headline,
published_at: versioncreated,
status: this.state.source && this.state.source.label,
// status: state.replace('_', ' '),
associations
})
);

const articles = {
page: this.state.articles.page + 1,
totalPages: Math.round(response._meta.total / 20),
items: [...this.state.articles.items, ...articleItemsMapped],
loading: false,
};

if (this._isMounted) this.setState({ articles });
});

});
}

publishItemFromSuperdesk = (item_id) => {
const superedeskApi = window['extensionsApiInstances']['publisher-extension'];

return new Promise((resolve, reject) => {
superedeskApi.httpRequestJsonLocal({
method: 'POST',
path: '/export',
payload: {
item_ids: [item_id],
validate: false,
inline: true,
format_type: "NINJSFormatter"
},
}).then((response) => {
const ninjs = this.props.publisher.publishSuperdeskArticle(response.export[item_id]).then(() => {
this.props.publisher.getArticleByCode(item_id).then((res) => {
resolve(res);
});
});
});
});
}

handleSourceChange = (source) => {
if (source && (source.id === 'scheduled' || source.id === 'in_progress')) {
this._querySuperdeskArticles(source.id, true);
} else {
this._queryArticles(true);
}
}

handleListSearch = (query) => {
this.setState(
{
Expand Down Expand Up @@ -359,19 +455,21 @@ class Manual extends React.Component {

getIndexInList = (list, draggableId) => {
let ids = draggableId.split('_');
let id = parseInt(ids[ids.length - 1]);
let id = this.state.source && (this.state.source.id === 'scheduled' || this.state.source.id === 'in_progress') ?
ids[ids.length - 1] : parseInt(ids[ids.length - 1]);

return list.findIndex(item => ids.length > 2 ? item.content.id === id : item.id === id);
}
}

onDragEnd = (result) => {
const { source, destination, draggableId } = result;

// dropped outside the list
if (!destination) {
return;
}

let list = { ...this.state.list };
if (source.droppableId === destination.droppableId) {
let items = reorder(
this.getList(source.droppableId),
Expand All @@ -381,8 +479,6 @@ class Manual extends React.Component {

items = this.fixPinnedItemsPosition(items);

let list = { ...this.state.list };

list.items = items;
this.recordChange("move", this.getIndexInList(list.items, draggableId), [...list.items]);
this.setState({ list });
Expand All @@ -394,7 +490,6 @@ class Manual extends React.Component {
destination
);

let list = { ...this.state.list };
let articles = { ...this.state.articles };

list.items = this.fixPinnedItemsPosition(result.contentList);
Expand All @@ -406,6 +501,25 @@ class Manual extends React.Component {
articles,
});
}

if (this.state.source && (this.state.source.id === 'scheduled' || this.state.source.id === 'in_progress')) {
list.loading = true;

const item_id = draggableId.replace('draggable_', '');

this.publishItemFromSuperdesk(item_id).then((res) => {
let changesRecord = [...this.state.changesRecord];
changesRecord = changesRecord.map((change) => {
if (change.content_id === item_id) {
change.content_id = res.id;
}
return change;
});

list.loading = false;
this.setState({ list });
});
}
};

fixPinnedItemsPosition = (items) => {
Expand Down Expand Up @@ -484,11 +598,11 @@ class Manual extends React.Component {
filteredContentListItems = filteredContentListItems.filter((item) =>
item.content
? item.content.title
.toLowerCase()
.includes(this.state.listSearchQuery.toLowerCase())
.toLowerCase()
.includes(this.state.listSearchQuery.toLowerCase())
: item.title
.toLowerCase()
.includes(this.state.listSearchQuery.toLowerCase())
.toLowerCase()
.includes(this.state.listSearchQuery.toLowerCase())
);
}

Expand Down Expand Up @@ -559,7 +673,7 @@ class Manual extends React.Component {
ref={provided.innerRef}
style={
!this.state.list.items.length &&
!this.state.list.loading
!this.state.list.loading
? { height: "calc(100% - 50px)" }
: {}
}
Expand Down Expand Up @@ -653,7 +767,18 @@ class Manual extends React.Component {
}
onChange={(value) => this.handleArticlesSearch(value)}
/>
<h3 className="subnav__page-title">All published articles</h3>
<SourceSelect
sources={[
{ id: 'scheduled', name: 'Scheduled Articles', label: 'Non published' },
{ id: 'in_progress', name: 'Articles in progress', label: 'Non published' },
]}
selectedSource={this.state.source}
setSource={(source) => {
this.setState({ source: source }, () => {
this.handleSourceChange(source);
});
}}
/>
{this.props.isLanguagesEnabled && (
<LanguageSelect
languages={this.props.languages}
Expand Down
57 changes: 57 additions & 0 deletions client/components/UI/SourceSelect.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from "react";
import PropTypes from "prop-types";
import classNames from "classnames";

import DropdownScrollable from "./DropdownScrollable";

const SourceSelect = props => {
let selectedSource = {id: 'publisher', name: 'All published articles'};

if (props.selectedSource) selectedSource = props.selectedSource;

return (
<React.Fragment>
<div className="subnav__spacer subnav__spacer--no-margin" />
<div
className={classNames(
"subnav__content-bar sd-flex-no-shrink sd-margin-x--0"
)}
>
<DropdownScrollable
button={
<button className="dropdown__toggle navbtn navbtn--text-only dropdown-toggle">
{selectedSource.name}
<span className="dropdown__caret" />
</button>
}
classes="dropdown--align-right"
>
<li>
<button onClick={() => props.setSource(null)}>
All published articles
</button>
</li>
<li className="dropdown__menu-divider" />
{props.sources.map(item => (
<li key={"sourceSelect-" + item.id}>
<button onClick={() => props.setSource(item)}>
{item.name}
</button>
</li>
))}
</DropdownScrollable>
</div>
</React.Fragment>
);
};

SourceSelect.propTypes = {
sources: PropTypes.array.isRequired,
selectedSource: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
}),
setSource: PropTypes.func.isRequired
};

export default SourceSelect;
1 change: 1 addition & 0 deletions client/publisher-extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
5 changes: 5 additions & 0 deletions client/publisher-extension/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"extension": ["ts", ".tsx"],
"spec": "src/**/*.spec.*",
"require": "ts-node/register"
}
19 changes: 19 additions & 0 deletions client/publisher-extension/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"private": true,
"name": "superdesk-publisher-extension",
"main": "dist/src/extension.js",
"scripts": {
"compile": "tsc -p src",
"compile-e2e": "echo 'No end-to-end tests defined'",
"watch": "tsc -watch -p src",
"test": "mocha",
"debug-tests": "mocha --inspect-brk"
},
"devDependencies": {
"@types/mocha": "9.1.1",
"mocha": "8.4.0",
"superdesk-code-style": "1.3.0",
"ts-node": "10.9.1",
"typescript": "~4.9.5"
}
}
9 changes: 9 additions & 0 deletions client/publisher-extension/src/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {IExtension} from 'superdesk-api';

const extension: IExtension = {
activate: () => {
return Promise.resolve({});
},
};

export default extension;
Loading

0 comments on commit 5a1dfff

Please sign in to comment.