Skip to content

Commit

Permalink
[TGA-38] Author Profiles (#7)
Browse files Browse the repository at this point in the history
* client[custom_field]: ProfileID

* client[custom_field]: ProfileText

* client[custom_field]: Vocabs

* client: Register custom fields

* server: Author profile signal hooks

* server: Author profile tests

* client: Add typescript files

* server: Update CVs

* Use 2.4 hotfix branches

* Return `null` in `VocabularyField` if vocab name not supplied in config

* Remove labels from preview components

* add missing disabled field to vocab editor field

* Change `field_id` to `fieldId`

* update package-lock.json
  • Loading branch information
MarkLark86 authored Dec 11, 2022
1 parent 4bff6e7 commit 3c54dbb
Show file tree
Hide file tree
Showing 24 changed files with 2,066 additions and 920 deletions.
14 changes: 14 additions & 0 deletions client/extensions/tga-author-profile-fields/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"private": true,
"main": "dist/src/extension.js",
"scripts": {
"compile": "tsc -p src",
"compile-e2e": "echo 'No end-to-end tests defiend'",
"watch": "tsc -watch -p src"
},
"devDependencies": {
"superdesk-code-style": "1.5.0",
"typescript": "3.9.7",
"superdesk-ui-framework": "2.4.17"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from 'react';

import {IUser, IEditorComponentProps} from 'superdesk-api';
import {superdesk} from '../../superdesk';

type IProps = IEditorComponentProps<IUser['_id'] | undefined, {}>

export class ProfileIDField extends React.PureComponent<IProps> {
private _mounted: boolean = false;

constructor(props: IProps) {
super(props);

this.changeProfileId = this.changeProfileId.bind(this);
}

componentDidMount() {
this._mounted = true;
if (this.props.value == null) {
superdesk.session.getCurrentUser().then((user) => {
if (this._mounted) {
this.changeProfileId(user);
}
});
}
}

componentWillUnmount() {
this._mounted = false;
}

changeProfileId(user: IUser) {
if (this._mounted) {
this.props.setValue(user._id);
window.dispatchEvent(new CustomEvent('content--custom-profile-id--changed', {detail: user}));
}
}

render() {
const {SelectUser} = superdesk.components;

return (
<SelectUser
onSelect={this.changeProfileId}
selectedUserId={this.props.value}
disabled={this.props.readOnly}
autoFocus={false}
horizontalSpacing={false}
/>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as React from 'react';

import {IConfigComponentProps} from 'superdesk-api';
import {superdesk} from '../../superdesk';
import {Switch} from 'superdesk-ui-framework/react';
import {IProfileTextFieldConfig} from './interfaces';

type IProps = IConfigComponentProps<IProfileTextFieldConfig>;


export class ProfileTextFieldConfig extends React.PureComponent<IProps> {
render() {
const config = this.props.config ?? {use_editor_3: false};
const {gettext} = superdesk.localization;

return (
<Switch
label={{text: gettext('Use Editor 3')}}
value={config?.use_editor_3 == true}
onChange={(use_editor_3) => {
this.props.onChange({
...config,
use_editor_3: use_editor_3,
});
}}
/>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import * as React from 'react';

import {IEditorComponentProps, IUser} from 'superdesk-api';
import {superdesk} from '../../superdesk';
import {IProfileTextFieldConfig} from './interfaces';
import {Input} from 'superdesk-ui-framework/react'

type IProps = IEditorComponentProps<string, IProfileTextFieldConfig>;

export class ProfileTextField extends React.PureComponent<IProps> {
constructor(props: IProps) {
super(props);

this.onProfileIDChanged = this.onProfileIDChanged.bind(this);
}

componentDidMount() {
window.addEventListener('content--custom-profile-id--changed', this.onProfileIDChanged);
}

componentWillUnmount() {
window.removeEventListener('content--custom-profile-id--changed', this.onProfileIDChanged)
}

onProfileIDChanged(event: Event) {
const user = (event as CustomEvent<IUser>).detail;

switch (this.props.fieldId) {
case 'profile_first_name':
this.props.setValue(user.first_name || '');
break;
case 'profile_last_name':
this.props.setValue(user.last_name || '');
break;
case 'profile_email':
this.props.setValue(user.email || '');
break;
case 'profile_biography':
this.props.setValue(user.biography || '');
break;
}
}

render() {
const {Editor3Html} = superdesk.components;

return this.props.config?.use_editor_3 ? (
<Editor3Html
key={this.props.item.extra?.profile_id}
value={this.props.value}
onChange={this.props.setValue}
readOnly={this.props.readOnly}
/>
) : (
<Input
key={this.props.item.extra?.profile_id}
value={this.props.value}
disabled={this.props.readOnly}
onChange={this.props.setValue}
/>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {IEditorComponentProps} from 'superdesk-api';

export interface IProfileTextFieldConfig {
use_editor_3: boolean;
}

export type IProfileTextFieldProps = IEditorComponentProps<string | undefined, IProfileTextFieldConfig>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';

import {IPreviewComponentProps} from 'superdesk-api';

export class ProfileTextPreview extends React.PureComponent<IPreviewComponentProps> {
render() {
return (
<div className="form__row form__row--small-padding">
<div dangerouslySetInnerHTML={{__html: this.props.value}} />
</div>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as React from 'react';

import {IConfigComponentProps, IVocabulary} from 'superdesk-api';
import {superdesk} from '../../superdesk';
import {Select, Option, Switch} from 'superdesk-ui-framework/react';
import {IVocabularyFieldConfig} from './interfaces';

function getVocabularies(): Promise<Array<IVocabulary>> {
const {dataApi} = superdesk;

return dataApi.query<IVocabulary>(
'vocabularies',
1,
{field: 'display_name', direction: 'ascending'},
{
$and: [
{field_type: {$exists: false, $eq: null}},
{custom_field_type: {$exists: false, $eq: null}},
],
}
).then((vocabularies) => {
return vocabularies._items;
})
}

type IProps = IConfigComponentProps<IVocabularyFieldConfig>;
interface IState {
cvs: Array<IVocabulary>
}


export class VocabularyFieldConfig extends React.Component<IProps, IState> {
constructor(props: IProps) {
super(props);

this.state = {cvs: []};
}

componentDidMount() {
getVocabularies().then((cvs) => {
this.setState({cvs: cvs});
if (!this.props.config?.vocabulary_name) {
this.props.onChange({
vocabulary_name: cvs[0]._id,
allow_freetext: this.props.config?.allow_freetext ?? false,
});
}
});
}

render() {
const config = this.props.config ?? {
vocabulary_name: '',
allow_freetext: false,
};
const {Spacer} = superdesk.components;
const {gettext} = superdesk.localization;

return (
<Spacer type="vertical" spacing="medium">
<Select
value={config?.vocabulary_name}
label={gettext('Vocabulary')}
onChange={(cv_id) => {
this.props.onChange({
...config,
vocabulary_name: cv_id,
});
}}
>
{this.state.cvs.map((cv) => (
<Option key={cv._id} value={cv._id}>{cv.display_name}</Option>
))}
</Select>
<Switch
label={{text: gettext('Allow Freetext')}}
value={config?.allow_freetext == true}
onChange={(allow_freetext) => {
this.props.onChange({
...config,
allow_freetext: allow_freetext,
});
}}
/>
</Spacer>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as React from 'react';

import {ILiveResourcesProps, IRestApiResponse, IVocabulary} from 'superdesk-api';
import {superdesk} from '../../superdesk';
import {IVocabularyFieldProps} from './interfaces';

import {Select, Option} from "superdesk-ui-framework/react";


export class VocabularyField extends React.PureComponent<IVocabularyFieldProps> {
render() {
if (this.props.config?.vocabulary_name == null) {
return null;
}

const {WithLiveResources} = superdesk.components;
const resources: ILiveResourcesProps['resources'] = [
{resource: 'vocabularies', ids: [this.props.config.vocabulary_name]}
];

return (
<WithLiveResources resources={resources}>
{(resourcesResponse) => {
const vocab = resourcesResponse[0] as IRestApiResponse<IVocabulary>;
const items = vocab._items[0].items;

return (
<Select
onChange={(qcode) => {
this.props.setValue(items.find((item) => item.qcode === qcode));
}}
value={this.props.value?.qcode}
inlineLabel={true}
disabled={this.props.readOnly}
>
<Option />
{items.map((item) => (
<Option key={item.qcode} value={item.qcode}>{item.name}</Option>
))}
</Select>
);
}}
</WithLiveResources>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {IEditorComponentProps, IVocabularyItem} from 'superdesk-api';

export interface IVocabularyFieldConfig {
vocabulary_name: string;
allow_freetext: boolean;
}

export type IVocabularyFieldProps = IEditorComponentProps<IVocabularyItem | null | undefined, IVocabularyFieldConfig>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from 'react';

import {IPreviewComponentProps, IVocabularyItem,} from 'superdesk-api';

export class VocabularyPreview extends React.PureComponent<IPreviewComponentProps> {
render() {
const item: IVocabularyItem = this.props.value;

return (
<div className="form__row form__row--small-padding">
<p className="sd-text__normal">
{item.name}
</p>
</div>
);
}
}
Loading

0 comments on commit 3c54dbb

Please sign in to comment.