Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

model display hint, centralise special format definitions #491

Merged
merged 7 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion common-lib/src/main/scala/models/Stub.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ case class ExternalData(
rightsSyndicationAggregate: Option[Boolean] = None,
rightsSubscriptionDatabases: Option[Boolean] = None,
rightsDeveloperCommunity: Option[Boolean] = None,
byline: Option[String] = None) {
byline: Option[String] = None,
displayHint: Option[String] = None) {
}

object ExternalData {
Expand Down
2 changes: 1 addition & 1 deletion public/components/stub-modal/stub-modal.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ <h4 class="modal-title">{{ modalTitle }}</h4>
<input type="text" ng-model="formData.importUrl" ng-change="importUrlChanged()" id="import_url" name="import_url" class="form-control" required wf-focus focus-me="{{ mode === 'import' }}">
</div>
<div class="form-horizontal clearfix">
<div class="format-dropdown" ng-if="(showFormatDropdown && (stubFormat === 'Standard Article' || stubFormat === 'Key Takeaways' || stubFormat === 'Q&A Explainer' || stubFormat === 'Timeline' || stubFormat === 'Mini profiles' || stubFormat === 'Multi-byline'))">
<div class="format-dropdown" ng-if="(showFormatDropdown && stubFormatIsCorrectlyPopulated())">
<label for="stub_format">Format</label>
<div>
<select id="stub_format" name="articleFormat" ng-model="stub.articleFormat" ng-options="f.value as f.name for f in articleFormats"></select>
Expand Down
51 changes: 11 additions & 40 deletions public/components/stub-modal/stub-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import 'lib/prodoffice-service';
import 'lib/telemetry-service';
import { punters } from 'components/punters/punters';
import { generateErrorMessages, doesContentTypeRequireCommissionedLength, useNativeFormFeedback } from '../../lib/stub-form-validation.ts';
import { setDisplayHintForFormat } from 'lib/model/special-formats.ts';
import { getArticleFormatLabel, isFormatLabel } from 'lib/model/format-helpers.ts';

const wfStubModal = angular.module('wfStubModal', [
'ui.bootstrap', 'articleFormatService', 'legalStatesService', 'pictureDeskStatesService', 'wfComposerService', 'wfContentService', 'wfDateTimePicker', 'wfProdOfficeService', 'wfFiltersService', 'wfCapiAtomService', 'wfTelemetryService'])
Expand All @@ -31,20 +33,8 @@ function StubModalInstanceCtrl($rootScope, $scope, $modalInstance, $window, conf
(wfContentService.getAtomTypes())[stub.contentType] ?
"Atom" : (types[stub.contentType] || "News item");

$scope.stubFormat = ''
if (stub.contentType === 'article') {
$scope.stubFormat = "Standard Article"
} else if (stub.contentType === 'keyTakeaways') {
$scope.stubFormat = "Key Takeaways"
} else if (stub.contentType === 'qAndA') {
$scope.stubFormat = "Q&A Explainer"
} else if (stub.contentType === 'timeline') {
$scope.stubFormat = "Timeline"
} else if (stub.contentType === 'miniProfiles') {
$scope.stubFormat = "Mini profiles"
} else if (stub.contentType === 'multiByline') {
$scope.stubFormat = "Multi-byline"
}
$scope.stubFormat = getArticleFormatLabel(stub.contentType);

$scope.$watch('stub.articleFormat', (newValue) => {
$scope.stubFormat = newValue;
})
Expand All @@ -58,6 +48,10 @@ function StubModalInstanceCtrl($rootScope, $scope, $modalInstance, $window, conf
})[mode];
});

$scope.stubFormatIsCorrectlyPopulated = function() {
return isFormatLabel($scope.stubFormat)
}

$scope.loadingTemplates = true;

wfComposerService.loadTemplates().then(templates => {
Expand Down Expand Up @@ -344,7 +338,8 @@ function StubModalInstanceCtrl($rootScope, $scope, $modalInstance, $window, conf
};

$scope.ok = function (addToComposer, addToAtomEditor) {
const stub = $scope.stub;
const stub = setDisplayHintForFormat ($scope.stub);

function createItemPromise() {
if ($scope.contentName === 'Atom') {
stub.contentType = $scope.stub.contentType.toLowerCase();
Expand Down Expand Up @@ -496,32 +491,8 @@ wfStubModal.run([
function setUpPreferredStub (contentType) {

function createStubData (contentType, sectionName) {
let chosenArticleFormat = ""
switch (contentType) {
case "article":
chosenArticleFormat = "Standard Article"
break;
case "keyTakeaways":
chosenArticleFormat = "Key Takeaways"
break;
case "qAndA":
chosenArticleFormat = "Q&A Explainer"
break;
case "timeline":
chosenArticleFormat = "Timeline"
break;
case "miniProfiles":
chosenArticleFormat = "Mini profiles"
break;
case "multiByline":
chosenArticleFormat = "Multi-byline"
break;
default:
break;
}

return {
articleFormat: chosenArticleFormat,
articleFormat: getArticleFormatLabel(contentType),
contentType: contentType === "atom" ? defaultAtomType : contentType,
// Only send through a section if one is found in the prefs
section: sectionName === null ? sectionName : sections.filter((section) => section.name === sectionName)[0],
Expand Down
35 changes: 11 additions & 24 deletions public/lib/article-format-service.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
define(['angular'], function (angular) {
'use strict';
import angular from 'angular';
import { provideArticleFormatsForDropDown } from './model/format-helpers.ts';

var articleFormatService = angular.module('articleFormatService', []);

articleFormatService.factory('articleFormatService',['wfPreferencesService', function (wfPreferencesService) {
function getArticleFormats() {
const featureSwitches = wfPreferencesService.preferences.featureSwitches;

const articleFormats = [
{name: 'Standard Article', value: 'Standard Article'},
{name: 'Key Takeaways', value: 'Key Takeaways'},
{name: 'Q&A Explainer', value: 'Q&A Explainer'},
{name: 'Timeline', value: 'Timeline'},
{name: 'Mini profiles', value: 'Mini profiles'},
]
if (featureSwitches && featureSwitches.multiByline){
articleFormats.push({name: 'Multi-byline', value: 'Multi-byline'})
}
return articleFormats
};
angular.module('articleFormatService', [])
.factory('articleFormatService', ['wfPreferencesService', function (wfPreferencesService) {
function getArticleFormats() {
const featureSwitches = wfPreferencesService.preferences?.featureSwitches;
return provideArticleFormatsForDropDown(featureSwitches)
};
return {
getArticleFormats: getArticleFormats
};
}]);
return articleFormatService;
});
getArticleFormats: getArticleFormats
};
}]);
37 changes: 4 additions & 33 deletions public/lib/composer-service.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import angular from 'angular';
import './telemetry-service';
import { getSpecialFormatFromLabel, contentTypeToComposerContentType } from './model/special-formats.ts';

angular.module('wfComposerService', ['wfTelemetryService'])
.service('wfComposerService', ['$http', '$q', 'config', '$log', 'wfHttpSessionService', 'wfTelemetryService', wfComposerService]);
Expand Down Expand Up @@ -92,42 +93,12 @@ function wfComposerService($http, $q, config, $log, wfHttpSessionService, wfTele

this.getComposerContent = getComposerContent;
this.parseComposerData = parseComposerData;

const getDisplayHint = (articleFormat) => {
switch (articleFormat){
case "Key Takeaways":
return "keyTakeaways"
case "Q&A Explainer":
return "qAndA"
case "Timeline":
return "timeline"
case "Mini profiles":
return "miniProfiles"
case "Multi-byline":
return "multiByline"
default:
return undefined
}
}

const getType = (type) => {
switch (type){
case 'keyTakeaways':
case 'qAndA':
case "timeline":
case "miniProfiles":
case "multiByline":
return "article"
default:
return type
}
}

this.create = function createInComposer(type, commissioningDesks, commissionedLength, prodOffice, template, articleFormat, priority, missingCommissionedLengthReason) {
var selectedDisplayHint = getDisplayHint(articleFormat);
var selectedDisplayHint = getSpecialFormatFromLabel(articleFormat)?.value;

var params = {
'type': getType(type),
'type': contentTypeToComposerContentType(type),
'tracking': commissioningDesks,
'productionOffice': prodOffice,
'displayHint': selectedDisplayHint,
Expand Down Expand Up @@ -158,7 +129,7 @@ function wfComposerService($http, $q, config, $log, wfHttpSessionService, wfTele
}

const tags = {
contentType: getType(type),
contentType: contentTypeToComposerContentType(type),
productionOffice: prodOffice,
priority: getPriorityName(priority),
}
Expand Down
35 changes: 6 additions & 29 deletions public/lib/content-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import './atom-workshop-service'
import './http-session-service';
import './user';
import './visibility-service';
import { provideFormats } from './model/format-helpers.ts'

angular.module('wfContentService', ['wfHttpSessionService', 'wfVisibilityService', 'wfDateService', 'wfFiltersService', 'wfUser', 'wfComposerService', 'wfMediaAtomMakerService', 'wfAtomWorkshopService', 'wfPreferencesService'])
.factory('wfContentService', ['$rootScope', '$log', 'wfHttpSessionService', 'wfDateParser', 'wfFormatDateTimeFilter', 'wfFiltersService', 'wfComposerService', 'wfMediaAtomMakerService', 'wfAtomWorkshopService', 'wfPreferencesService', 'config',
Expand All @@ -15,37 +16,12 @@ angular.module('wfContentService', ['wfHttpSessionService', 'wfVisibilityService

class ContentService {

provideFormats(featureSwitches){
const articleFormats = {
"article": "Article",
"keyTakeaways": "Key Takeaways",
"qAndA": "Q&A Explainer",
"timeline": "Timeline",
"miniProfiles": "Mini profiles"
}

if (featureSwitches && featureSwitches.multiByline){
articleFormats.multiByline = "Multi-byline"
}

const nonArticleFormats = {
"liveblog": "Live blog",
"gallery": "Gallery",
"interactive": "Interactive",
"picture": "Picture",
"audio": "Audio",
"atom": "Video/Atom"
}
// Assembling the object this way preserves the existing order in the UI
return Promise.resolve({...articleFormats, ...nonArticleFormats});
}

getTypes() {
return wfPreferencesService.getPreference('featureSwitches')
.then((featureSwitches) => {
return this.provideFormats(featureSwitches)
})
.catch((err) => {return this.provideFormats()})
.then((featureSwitches) => {
return provideFormats(featureSwitches)
})
.catch((err) => { return provideFormats() })
}

/* what types of stub should be treated as atoms? */
Expand Down Expand Up @@ -284,6 +260,7 @@ angular.module('wfContentService', ['wfHttpSessionService', 'wfVisibilityService
'hasPrintInfo': modelParams['hasPrintInfo'] || null,
'hasMainMedia': modelParams['hasMainMedia'] || null,
'rights': modelParams['rights'] || null,
'display-hint': modelParams["display-hint"],
};

return params;
Expand Down
3 changes: 2 additions & 1 deletion public/lib/filters-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,8 @@ angular.module('wfFiltersService', ['wfDateService', 'wfTrustedHtml'])
'editorId' : params['editorId'],
'hasPrintInfo' : params['hasPrintInfo'],
'hasMainMedia' : params['hasMainMedia'],
'hasAnyRights' : params['rights']
'hasAnyRights' : params['rights'],
'display-hint' : params['display-hint'],
};

$rootScope.currentlySelectedStatusFilters = self.transformStatusList(self.filters['status']);
Expand Down
84 changes: 84 additions & 0 deletions public/lib/model/format-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { specialFormats } from './special-formats'
import { ContentType } from './stub'

const STANDARD_ARTICLE_FORMAT_LABEL = "Standard Article";
const STANDARD_ARTICLE_FORMAT_SHORT_LABEL = "Article";

const nonArticleFormats = {
"liveblog": "Live blog",
"gallery": "Gallery",
"interactive": "Interactive",
"picture": "Picture",
"audio": "Audio",
"atom": "Video/Atom"
}

/**
* Returns an object mapping ContentType to user facings labels, excluding any
* special formats that that behind a feature switch in the 'off' state.
*/
const provideFormats = (featureSwitches?: Record<string, boolean>): Partial<Record<ContentType, string>> => {
const articleFormats: Record<string, string> = {
"article": STANDARD_ARTICLE_FORMAT_SHORT_LABEL,
}

specialFormats.forEach(format => {
if (format.behindFeatureSwitch) {
if (featureSwitches?.[format.behindFeatureSwitch]) {
articleFormats[format.value] = format.label
}
} else {
articleFormats[format.value] = format.label
}
})


// Assembling the object this way preserves the existing order in the UI
return { ...articleFormats, ...nonArticleFormats };
}

/**
* Returns a list of objects describing the available article formats
* that can be used to as a model for a select input in an angular template
*/
const provideArticleFormatsForDropDown = (featureSwitches?: Record<string, boolean>): { name: string; value: string }[] => {

const list = [STANDARD_ARTICLE_FORMAT_LABEL]

specialFormats.forEach(format => {
if (format.behindFeatureSwitch) {
if (featureSwitches && featureSwitches[format.behindFeatureSwitch]) {
list.push(format.label)
}
} else {
list.push(format.label)
}
})

return list.map(label => ({ name: label, value: label }))
}

/**
* returns "Standard Article" for normal articles, the label for special article formats
* or empty string for non-article content types
*/
const getArticleFormatLabel = (contentType: ContentType): string => {
const maybeMatchingFormat = specialFormats.find(format => format.value === contentType)
if (maybeMatchingFormat) {
return maybeMatchingFormat.label
}
if (contentType === 'article') {
return STANDARD_ARTICLE_FORMAT_LABEL
}
return ''
}

/**
* true if the value is the label of a special format or the "Standard Article" label
*/
const isFormatLabel = (value: string): boolean => {
return value === STANDARD_ARTICLE_FORMAT_LABEL || specialFormats.some(format => format.label === value)
}


export { provideFormats, provideArticleFormatsForDropDown, getArticleFormatLabel, isFormatLabel }
37 changes: 37 additions & 0 deletions public/lib/model/special-formats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ComposerContentType, ContentType, SpecialArticleFormat, Stub } from "./stub";

const specialFormats: SpecialArticleFormat[] = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a great idea, centralising the list of special formats in this constant.

{ label: 'Key Takeaways', value: 'keyTakeaways' },
{ label: 'Q&A Explainer', value: 'qAndA' },
{ label: 'Timeline', value: 'timeline' },
{ label: 'Mini profiles', value: 'miniProfiles' },
{ label: 'Multi-byline', value: 'multiByline', behindFeatureSwitch: 'multiByline' },
]

const setDisplayHintForFormat = (stub: Stub): Stub => {
const maybeMatchingFormat = specialFormats.find(format => format.value === stub.contentType)
if (maybeMatchingFormat) {
stub.displayHint = maybeMatchingFormat.value
}
return stub
}

const getSpecialFormatFromLabel = (label: string): SpecialArticleFormat | undefined =>
specialFormats.find(format => format.label === label)

const contentTypeToComposerContentType = (type: ContentType): ComposerContentType => {
switch (type) {
case "article":
case "liveblog":
case "gallery":
case "interactive":
case "picture":
case "video":
case "audio":
return type;
default:
return 'article'
}
}

export { specialFormats, setDisplayHintForFormat, getSpecialFormatFromLabel, contentTypeToComposerContentType }
Loading
Loading