From 496d17a4e547b66a4beda44f5a82e5009ffd039a Mon Sep 17 00:00:00 2001 From: Yuhuai Liu Date: Thu, 30 Nov 2023 11:56:14 -0500 Subject: [PATCH 1/2] Feature/preprint details page (#2067) * [ENG-4450] Add new share-search models (#1835) - Ticket: [ENG-4450] - Feature flag: n/a - Add new models needed for SHARE-powered search page - Add new models - `metadata-record-search` - `metadata-property-search` - `metadata-value-search` - `metadata-record` - `search-match` - New `ShareAdapter` and `ShareSerializer` to be used by these new models - New mirage endpoint for metadata-record-search (other endpoints coming later) * Add basic search page layout (#1850) * [ENG-4465] Left panel facets manager (#1858) - Ticket: [ENG-4465] [ENG-4466] - Feature flag: n/a - Add logic to search page controller to handle active filters and list of filterable properties - Add a component to handle fetching values in a filterable properties in the search page - Add a `filter-facet` component - takes care of fetching filterable property values - `See more` modal * [ENG-4469] Add object filter and sort dropdown to search (#1864) - Ticket: [ENG-4469] - Feature flag: n/a - Add object type filter and sort dropdown to search page - Add tabs to filter by object type (All, Projects, Registrations, Preprints, Files, Users) - Add dropdown to sort results by Relevance, Date modified/created ascending and descending - Change model names to reflect more library-analogy based names - Change how metadata properties are fetched from SHARE models * [No ticket] Update SHARE endpoints (#1879) - Ticket: [No ticket] - Feature flag: n/a - Update SHAREAdapter to point to correct locations - Update SHAREAdapter parent class to point use config variable for share-url - Update SHAREAdapter parent class to point use api/v3 endpoints - Update search-related adapters to point to singularized endpoint names (e.g. api/v3/index-card-search**es** -> api/v3/index-card-search - Update mirage endpoints to reflect these changes * [ENG-4568] Componentize search page (#1886) - Ticket: [ENG-4568] - Feature flag: n/a - Componentize search page for reuse in branded pages - Move logic and templating from search page route to `search-page` component - No logic for branding and default query-params yet in this PR * [ENG-4574] Preprint discover rewrite (#1896) * add brand relationship to preprint provider model (#1887) * Remove unused services from search controller * Use search-page component on preprint discover page * Modifiy branded-navbar for preprints * Error handling and theme resetting * Branded preprint discover part 1 * Branded preprint discover part 2 * Test prerpint discover page * Group CR feedback re: search-page component arguments * Fix test --------- Co-authored-by: Yuhuai Liu * [ENG-4573] Registry discover (#1900) * preliminary * moar * some more * delete unused components * remove top-level aggregate registries discover route * remove top-level registries discover route cont. * remove unused action and variable on registries application route * remove aggregate registries discover page tests * fix tests * remove discover-test.ts * CR followup * [ENG-4574] Preprint discover fixes (#1905) - Ticket: [ENG-4574] - Feature flag: n/a - Add appropriate page title to discover page - Add appropriate analytics scope to discover page - Make provider description now show html entities - Use `{{html-safe}}` when showing provider description - Add `providerTitle` in preprint-provider model - Most branded providers should show their name with their preprint word (e.g. AfricaRxiv Preprints, MarXiv Papers), except Thesis Commons - If it's OSF, we just show "OSF Preprints" - Add page-title and analytics scope using the new `providerTitle` * [ENG-4535] Search help feature (#1907) - Ticket: [ENG-4535] - Feature flag: n/a - Add search help feature - Basically a re-implementation of https://github.com/CenterForOpenScience/ember-osf-web/pull/1891 and https://github.com/CenterForOpenScience/ember-osf-web/pull/1877 - Notable difference is moving the Popovers to the end of the file to avoid merge conflicts - Added EmberPopovers to the search-page component - Added getters to search-page component to fetch EmberPopover targets dynamically - Translations - Tests * [No Ticket] Change queryparam passed to SHARE when filtering by resourceType (#1915) * change queryparam passed to SHARE when filtering by resourceType * add types * add some more types * update tests * [No ticket] Preprint branding rework (#1913) - Ticket: [] - Feature flag: n/a - Only rely on `brand` relationship for setting preprint colors - Use `brand.primaryColor` for branded navbar background color - Add styling if the brand's primaryColor does not provide sufficient contrast with white text - Add special-case for BioHackrXiv to change navbar color to white (their primary color would be white, but that creates problems for ` + + +

{{t 'preprints.detail.author-assertions.conflict_of_interest.title'}}

+
+ +
+ {{#if this.hasCoiStatement}} + {{~this.preprint.conflictOfInterestStatement~}} + {{else}} + {{~t 'preprints.detail.author-assertions.conflict_of_interest.no'~}} + {{/if}} +
+
+
+ + {{/if}} + + {{#if this.hasDataLinks}} +
+
+ {{~t 'preprints.detail.author-assertions.public_data.title'~}} +
+ + + +

{{t 'preprints.detail.author-assertions.public_data.title'}}

+
+ +
+ {{#if (eq this.preprint.hasDataLinks 'available')}} + + {{else if (eq this.preprint.hasDataLinks 'no')}} + {{#if this.preprint.whyNoData}} + {{this.preprint.whyNoData}} + {{else}} + {{~t 'preprints.detail.author-assertions.public_data.no'~}} + {{/if}} + {{else}} + {{~t 'preprints.detail.author-assertions.public_data.not_applicable' documentType=this.documentType~}} + {{/if}} +
+
+
+
+ {{/if}} + + {{#if this.hasPreregLinks}} +
+
+ {{~t 'preprints.detail.author-assertions.prereg.title'~}} +
+ + + +

{{t 'preprints.detail.author-assertions.prereg.title'}}

+
+ +
+ {{#if (eq this.preprint.hasPreregLinks 'available')}} + + {{else if (eq this.preprint.hasPreregLinks 'no')}} + {{#if this.preprint.whyNoPrereg}} + {{this.preprint.whyNoPrereg}} + {{else}} + {{~t 'preprints.detail.author-assertions.prereg.no'~}} + {{/if}} + {{else}} + {{~t 'preprints.detail.author-assertions.prereg.not_applicable' documentType=this.documentType~}} + {{/if}} +
+
+
+
+ {{/if}} + + + +{{/if}} \ No newline at end of file diff --git a/app/preprints/-components/preprint-discipline/component.ts b/app/preprints/-components/preprint-discipline/component.ts new file mode 100644 index 00000000000..50866809b2d --- /dev/null +++ b/app/preprints/-components/preprint-discipline/component.ts @@ -0,0 +1,15 @@ +import Component from '@glimmer/component'; +import SubjectModel from 'ember-osf-web/models/subject'; + +interface InputArgs { + subjects: SubjectModel[]; +} + +export default class PreprintDiscipline extends Component { + subjects = this.args.subjects; + + get disciplineReduced(): SubjectModel[] { + // Preprint disciplines are displayed in collapsed form on content page + return this.subjects.reduce((acc: SubjectModel[], val: SubjectModel) => acc.concat(val), []).uniqBy('id'); + } +} diff --git a/app/preprints/-components/preprint-discipline/styles.scss b/app/preprints/-components/preprint-discipline/styles.scss new file mode 100644 index 00000000000..4f580fce232 --- /dev/null +++ b/app/preprints/-components/preprint-discipline/styles.scss @@ -0,0 +1,8 @@ +.subject-preview { + display: inline-block; + background-color: $bg-light; + border-radius: 3px; + border: 1px solid $color-light; + padding: 1px 7px; + margin-bottom: 4px; +} diff --git a/app/preprints/-components/preprint-discipline/template.hbs b/app/preprints/-components/preprint-discipline/template.hbs new file mode 100644 index 00000000000..7de2e4725c9 --- /dev/null +++ b/app/preprints/-components/preprint-discipline/template.hbs @@ -0,0 +1,6 @@ +
+

{{t 'preprints.detail.disciplines'}}

+ {{#each this.disciplineReduced as |subject|}} + {{subject.text}} + {{/each}} +
\ No newline at end of file diff --git a/app/preprints/-components/preprint-doi/component.ts b/app/preprints/-components/preprint-doi/component.ts new file mode 100644 index 00000000000..6ad155176a8 --- /dev/null +++ b/app/preprints/-components/preprint-doi/component.ts @@ -0,0 +1,14 @@ +import Component from '@glimmer/component'; +import PreprintModel from 'ember-osf-web/models/preprint'; +import PreprintProviderModel from 'ember-osf-web/models/preprint-provider'; + +interface InputArgs { + preprint: PreprintModel; + provider: PreprintProviderModel; +} + +export default class PreprintAbstract extends Component { + provider = this.args.provider; + + documentType = this.provider.documentType.singular; +} diff --git a/app/preprints/-components/preprint-doi/template.hbs b/app/preprints/-components/preprint-doi/template.hbs new file mode 100644 index 00000000000..55942ff37e3 --- /dev/null +++ b/app/preprints/-components/preprint-doi/template.hbs @@ -0,0 +1,23 @@ +
+

{{t 'preprints.detail.preprint_doi' documentType=this.documentType}}

+ {{#if @preprint.preprintDoiUrl}} + {{#if @preprint.preprintDoiCreated}} + + {{@preprint.preprintDoiUrl}} + + {{else}} +

{{@preprint.preprintDoiUrl}}

+

{{t 'preprints.detail.preprint_pending_doi_minted'}}

+ {{/if}} + {{else}} + {{#if (not @preprint.public)}} + {{t 'preprints.detail.preprint_pending_doi' documentType=this.documentType}} + {{else if (and this.provider.reviewsWorkflow (not this.preprint.isPublished))}} + {{t 'preprints.detail.preprint_pending_doi_moderation'}} + {{/if}} + {{/if}} +
\ No newline at end of file diff --git a/app/preprints/-components/preprint-license/component.ts b/app/preprints/-components/preprint-license/component.ts new file mode 100644 index 00000000000..b29ae5e3e06 --- /dev/null +++ b/app/preprints/-components/preprint-license/component.ts @@ -0,0 +1,18 @@ +import { action } from '@ember/object'; +import Component from '@glimmer/component'; +import { tracked } from '@glimmer/tracking'; +import PreprintModel from 'ember-osf-web/models/preprint'; + +interface InputArgs { + preprint: PreprintModel; +} + +export default class PreprintLicense extends Component { + preprint = this.args.preprint; + @tracked showLicenseText = false; + + @action + toggleLicenseText(): void { + this.showLicenseText = !this.showLicenseText; + } +} diff --git a/app/preprints/-components/preprint-license/styles.scss b/app/preprints/-components/preprint-license/styles.scss new file mode 100644 index 00000000000..ddf500d88e3 --- /dev/null +++ b/app/preprints/-components/preprint-license/styles.scss @@ -0,0 +1,23 @@ +.license-text { + pre { + white-space: pre-wrap; + font-size: 75%; + width: 100%; + text-align: justify; + max-height: 300px; + word-break: normal; + overflow: auto; + display: block; + padding: 9.5px; + margin: 0 0 10px; + line-height: 1.42857; + word-wrap: break-word; + background-color: $bg-light; + border: 1px solid $color-shadow-gray-light; + border-radius: 4px; + } + + span { + cursor: pointer; + } +} diff --git a/app/preprints/-components/preprint-license/template.hbs b/app/preprints/-components/preprint-license/template.hbs new file mode 100644 index 00000000000..4830862ec35 --- /dev/null +++ b/app/preprints/-components/preprint-license/template.hbs @@ -0,0 +1,14 @@ +{{#if this.preprint.license.name}} +
+

{{t 'preprints.detail.license'}}

+ {{this.preprint.license.name}} + + + + {{#if this.showLicenseText}} +
{{this.preprint.licenseText}}
+ {{/if}} +
+{{/if}} \ No newline at end of file diff --git a/app/preprints/-components/preprint-status-banner/component.ts b/app/preprints/-components/preprint-status-banner/component.ts new file mode 100644 index 00000000000..6a187f9216c --- /dev/null +++ b/app/preprints/-components/preprint-status-banner/component.ts @@ -0,0 +1,216 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import Theme from 'ember-osf-web/services/theme'; +import Intl from 'ember-intl/services/intl'; +import PreprintModel from 'ember-osf-web/models/preprint'; +import { task } from 'ember-concurrency'; +import { waitFor } from '@ember/test-waiters'; +import { alias } from '@ember/object/computed'; +import PreprintRequestActionModel from 'ember-osf-web/models/preprint-request-action'; +import { taskFor } from 'ember-concurrency-ts'; +import PreprintProviderModel from 'ember-osf-web/models/preprint-provider'; +import { tracked } from '@glimmer/tracking'; +import { ReviewsState } from 'ember-osf-web/models/provider'; +import ReviewActionModel from 'ember-osf-web/models/review-action'; +import Media from 'ember-responsive'; + +const UNKNOWN = 'unknown'; +const PENDING = 'pending'; +const ACCEPTED = 'accepted'; +const REJECTED = 'rejected'; +const PENDING_WITHDRAWAL = 'pendingWithdrawal'; +const WITHDRAWAL_REJECTED = 'withdrawalRejected'; +const WITHDRAWN = 'withdrawn'; + +const PRE_MODERATION = 'pre-moderation'; +const POST_MODERATION = 'post-moderation'; + +const STATUS = Object({}); +STATUS[PENDING]= 'preprints.detail.status_banner.pending'; +STATUS[ACCEPTED]= 'preprints.detail.status_banner.accepted'; +STATUS[REJECTED]= 'preprints.detail.status_banner.rejected'; +STATUS[PENDING_WITHDRAWAL]= 'preprints.detail.status_banner.pending_withdrawal'; +STATUS[WITHDRAWAL_REJECTED]= 'preprints.detail.status_banner.withdrawal_rejected'; + +const MESSAGE = Object({}); +MESSAGE[PRE_MODERATION] = 'preprints.detail.status_banner.message.pending_pre'; +MESSAGE[POST_MODERATION] = 'preprints.detail.status_banner.message.pending_post'; +MESSAGE[ACCEPTED] = 'preprints.detail.status_banner.message.accepted'; +MESSAGE[REJECTED] = 'preprints.detail.status_banner.message.rejected'; +MESSAGE[PENDING_WITHDRAWAL] = 'preprints.detail.status_banner.message.pending_withdrawal'; +MESSAGE[WITHDRAWAL_REJECTED] = 'preprints.detail.status_banner.message.withdrawal_rejected'; +MESSAGE[WITHDRAWN] = 'preprints.detail.status_banner.message.withdrawn'; +MESSAGE[UNKNOWN] = 'preprints.detail.status_banner.message.withdrawn'; + +const WORKFLOW = Object({}); +WORKFLOW[PRE_MODERATION] = 'preprints.detail.status_banner.pre_moderation'; +WORKFLOW[POST_MODERATION] = 'preprints.detail.status_banner.post_moderation'; +WORKFLOW[UNKNOWN] = 'preprints.detail.status_banner.post_moderation'; + +const CLASS_NAMES = Object({}); +CLASS_NAMES[PRE_MODERATION] = 'preprint-status-pending-pre'; +CLASS_NAMES[POST_MODERATION] = 'preprint-status-pending-post'; +CLASS_NAMES[ACCEPTED] = 'preprint-status-accepted'; +CLASS_NAMES[REJECTED] = 'preprint-status-rejected'; +CLASS_NAMES[PENDING_WITHDRAWAL] = 'preprint-status-rejected'; +CLASS_NAMES[WITHDRAWAL_REJECTED] = 'preprint-status-rejected'; +CLASS_NAMES[WITHDRAWN] = 'preprint-status-withdrawn'; +CLASS_NAMES[UNKNOWN] = 'preprint-status-withdrawn'; + +const ICONS = Object({}); +ICONS[PENDING] = 'hourglass'; +ICONS[ACCEPTED] = 'check-circle'; +ICONS[REJECTED] = 'times-circle'; +ICONS[PENDING_WITHDRAWAL] = 'hourglass'; +ICONS[WITHDRAWAL_REJECTED] = 'times-circle'; +ICONS[WITHDRAWN] = 'exclamation-triangle'; +ICONS[UNKNOWN] = 'exclamation-triangle'; + +interface InputArgs { + submission: PreprintModel; +} + +export default class PreprintStatusBanner extends Component{ + @service intl!: Intl; + @service theme!: Theme; + @service media!: Media; + + submission = this.args.submission; + isWithdrawn = this.args.submission.isWithdrawn; + + provider: PreprintProviderModel | undefined; + + @tracked displayComment = false; + isPendingWithdrawal = false; + isWithdrawalRejected = false; + + // translations + labelModeratorFeedback = 'preprints.detail.status_banner.feedback.moderator_feedback'; + moderator = 'preprints.detail.status_banner.feedback.moderator'; + baseMessage = 'preprints.detail.status_banner.message.base'; + + latestAction: PreprintRequestActionModel | ReviewActionModel | undefined; + + @alias('latestAction.comment') reviewerComment: string | undefined; + @alias('latestAction.creator.fullName') reviewerName: string | undefined; + + constructor(owner: unknown, args: InputArgs) { + super(owner, args); + + taskFor(this.loadPreprintState).perform(); + } + + public get getClassName(): string { + if (this.isPendingWithdrawal) { + return CLASS_NAMES[PENDING_WITHDRAWAL]; + } else if (this.isWithdrawn) { + return CLASS_NAMES[WITHDRAWN]; + } else if (this.isWithdrawalRejected) { + return CLASS_NAMES[WITHDRAWAL_REJECTED]; + } else { + return this.submission.reviewsState === PENDING ? + CLASS_NAMES[this.provider?.reviewsWorkflow || UNKNOWN] : + CLASS_NAMES[this.submission.reviewsState]; + } + } + + public get bannerContent(): string { + if (this.isPendingWithdrawal) { + return this.intl.t(this.statusExplanation, { documentType: this.provider?.documentType.singular }); + } else if (this.isWithdrawn) { + return this.intl.t(MESSAGE[WITHDRAWN], { documentType: this.provider?.documentType.singular }); + } else if (this.isWithdrawalRejected) { + return this.intl.t(MESSAGE[WITHDRAWAL_REJECTED], { documentType: this.provider?.documentType.singular }); + } else { + const tName = this.theme.isProvider ? + this.theme.provider?.name : + this.intl.t('preprints.detail.status_banner.brand_name'); + const tWorkflow = this.intl.t(this.workflow); + const tStatusExplanation = this.intl.t(this.statusExplanation); + const base = (this.intl.t(this.baseMessage, { + name: tName, + reviewsWorkflow: + tWorkflow, + documentType: this.provider?.documentType.singular, + })); + return `${base} ${tStatusExplanation}`; + } + } + + private get statusExplanation(): string { + if (this.isPendingWithdrawal) { + return MESSAGE[PENDING_WITHDRAWAL]; + } else if (this.isWithdrawalRejected) { + return MESSAGE[WITHDRAWAL_REJECTED]; + } else { + return this.submission.reviewsState === PENDING ? + MESSAGE[this.provider?.reviewsWorkflow || UNKNOWN ] : + MESSAGE[this.submission.reviewsState]; + } + } + + public get status(): string { + let currentState = this.submission.reviewsState; + if (this.isPendingWithdrawal) { + currentState = ReviewsState.PENDING_WITHDRAWAL; + } else if (this.isWithdrawalRejected) { + currentState = ReviewsState.WITHDRAWAL_REJECTED; + } + return STATUS[currentState]; + } + + public get icon(): string { + let currentState = this.submission.reviewsState; + if (this.isPendingWithdrawal) { + currentState = ReviewsState.PENDING_WITHDRAWAL; + } else if (this.isWithdrawalRejected) { + currentState = ReviewsState.WITHDRAWAL_REJECTED; + } else if (this.isWithdrawn) { + currentState = ReviewsState.WITHDRAWN; + } + return ICONS[currentState]; + } + + private get workflow(): string { + return WORKFLOW[this.provider?.reviewsWorkflow || UNKNOWN]; + } + + @task + @waitFor + async loadPreprintState() { + this.provider = await this.submission.provider; + + if (this.isWithdrawn) { + return; + } + const submissionActions = await this.submission.reviewActions; + const latestSubmissionAction = submissionActions.firstObject; + const withdrawalRequests = await this.submission.requests; + const withdrawalRequest = withdrawalRequests.firstObject; + if (withdrawalRequest) { + const requestActions = await withdrawalRequest.queryHasMany('actions', { + sort: '-modified', + }); + + const latestRequestAction = requestActions.firstObject; + if (latestRequestAction && latestRequestAction.actionTrigger === 'reject') { + this.isWithdrawalRejected = true; + this.latestAction = latestRequestAction; + return; + } else { + this.isPendingWithdrawal = true; + return; + } + } + + if (this.provider.reviewsCommentsPrivate) { + return; + } + + this.latestAction = latestSubmissionAction; + } + + get isMobile() { + return this.media.isMobile; + } +} diff --git a/app/preprints/-components/preprint-status-banner/styles.scss b/app/preprints/-components/preprint-status-banner/styles.scss new file mode 100644 index 00000000000..0a372191015 --- /dev/null +++ b/app/preprints/-components/preprint-status-banner/styles.scss @@ -0,0 +1,140 @@ +// stylelint-disable max-nesting-depth, selector-max-compound-selectors + +$color-alert-bg-warning: #fcf8e3; +$color-alert-border-warning: #faebcc; +$color-alert-text-warning: #8a6d3b; + +$color-alert-bg-success: #dff0d8; +$color-alert-border-success: #d6e9c6; +$color-alert-text-success: #3c763d; + +$color-alert-bg-danger: #f2dede; +$color-alert-border-danger: #ebccd1; +$color-alert-text-danger: #a94442; + +$color-alert-bg-info: #d9edf7; +$color-alert-border-info: #bce8f1; +$color-alert-text-info: #31708f; + +$color-bg-color-grey: #333; +$color-bg-color-light: #eee; +$color-border-light: #ddd; + +.preprint-status-pending-pre { + background-color: $color-alert-bg-warning; + border: 1px solid $color-alert-border-warning; + color: $color-alert-text-warning; + + .status-icon { + -webkit-text-stroke: 1px $color-alert-text-warning; + font-size: 14px; + } +} + +.preprint-status-pending-post { + background-color: $color-alert-bg-info; + border: 1px solid $color-alert-border-info; + color: $color-alert-text-info; + + .status-icon { + -webkit-text-stroke: 1px $color-alert-text-info; + font-size: 14px; + } +} + +.preprint-banner-status-container { + height: 75px; + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + font-size: 16px; + + .preprint-banner-status { + height: 75px; + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + font-size: 16px; + + &.preprint-status-accepted { + background-color: $color-alert-bg-success; + border: 1px solid $color-alert-border-success; + color: $color-alert-text-success; + + .status-icon { + -webkit-text-stroke: 0; + font-size: 16px; + } + } + + &.preprint-status-rejected { + background-color: $color-alert-bg-danger; + border: 1px solid $color-alert-border-danger; + color: $color-alert-text-danger; + + .status-icon { + -webkit-text-stroke: 0; + font-size: 16px; + } + } + + &.preprint-status-withdrawn { + background-color: $color-alert-bg-warning; + border: 1px solid $color-alert-border-warning; + color: $color-alert-text-warning; + + .status-icon { + -webkit-text-stroke: 1px $color-alert-text-warning; + font-size: 14px; + } + } + + .display-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 10px; + + .reviewer-feedback { + margin-left: 15px; + + } + } + } + + &.mobile { + height: fit-content; + + .preprint-banner-status { + height: fit-content; + } + } +} + +.status-banner-dialog { + .status { + font-size: 1.4em; + + strong { + display: inline-block; // required for the text-transform + } + + strong::first-letter { + text-transform: uppercase; + } + } + + .moderator-comment { + font-size: 1.8em; + padding: 30px 15px; + + p { + padding-bottom: 5px; + } + } +} diff --git a/app/preprints/-components/preprint-status-banner/template.hbs b/app/preprints/-components/preprint-status-banner/template.hbs new file mode 100644 index 00000000000..3eb8817690a --- /dev/null +++ b/app/preprints/-components/preprint-status-banner/template.hbs @@ -0,0 +1,58 @@ +
+ {{#if (or this.submission.provider?.isPending this.loadPreprintState.isRunning) }} + {{ t 'preprints.detail.status_banner.loading' }} + {{else}} +
+
+ {{#if this.isWithdrawn}} +
+
+ {{else}} +
+
+ {{#if (and this.reviewerComment (not this.submission.provider.reviewsCommentsPrivate))}} +
+ + + +

{{t this.labelModeratorFeedback }}

+
+ +
+ {{t this.status}} +
+
+

{{this.reviewerComment}}

+ {{#unless this.submission.provider.reviewsCommentsAnonymous}} +
{{this.reviewerName}}
+ {{/unless}} + {{if this.theme.isProvider this.theme.provider.name (t 'preprints.detail.status_banner.brand_name')}} {{t this.moderator}} +
+
+
+
+ {{/if}} + {{/if}} +
+
+ {{/if}} +
\ No newline at end of file diff --git a/app/preprints/-components/preprint-tag/styles.scss b/app/preprints/-components/preprint-tag/styles.scss new file mode 100644 index 00000000000..c49c0eaaaa8 --- /dev/null +++ b/app/preprints/-components/preprint-tag/styles.scss @@ -0,0 +1,8 @@ +.badge { + display: inline-block; + background-color: $bg-light; + border-radius: 3px; + border: 1px solid $color-light; + padding: 1px 7px; + margin-bottom: 4px; +} diff --git a/app/preprints/-components/preprint-tag/template.hbs b/app/preprints/-components/preprint-tag/template.hbs new file mode 100644 index 00000000000..f394e021d98 --- /dev/null +++ b/app/preprints/-components/preprint-tag/template.hbs @@ -0,0 +1,10 @@ +
+

{{t 'preprints.detail.tags'}}

+ {{#if @preprint.tags.length}} + {{#each @preprint.tags as |tag|}} + {{tag}} + {{/each}} + {{else}} + {{t 'preprints.detail.none'}} + {{/if}} +
\ No newline at end of file diff --git a/app/preprints/-components/preprint-tombstone/styles.scss b/app/preprints/-components/preprint-tombstone/styles.scss new file mode 100644 index 00000000000..b0e955fa4d4 --- /dev/null +++ b/app/preprints/-components/preprint-tombstone/styles.scss @@ -0,0 +1,16 @@ +.withdrawn-container { + padding: 0 15px; + width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + + h4 { + padding-bottom: 15px; + padding-top: 15px; + margin-top: 10px; + margin-bottom: 10px; + font-weight: bold; + } +} diff --git a/app/preprints/-components/preprint-tombstone/template.hbs b/app/preprints/-components/preprint-tombstone/template.hbs new file mode 100644 index 00000000000..d29d1c4eba1 --- /dev/null +++ b/app/preprints/-components/preprint-tombstone/template.hbs @@ -0,0 +1,15 @@ +
+ {{#if @preprint.withdrawalJustification}} +
+

{{t 'preprints.detail.reason_for_withdrawal'}}

+

+ {{@preprint.withdrawalJustification}} +

+
+ {{/if}} + + + + + +
\ No newline at end of file diff --git a/app/preprints/-components/taxonomy-top-list/component.ts b/app/preprints/-components/taxonomy-top-list/component.ts new file mode 100644 index 00000000000..da20db0b30f --- /dev/null +++ b/app/preprints/-components/taxonomy-top-list/component.ts @@ -0,0 +1,92 @@ +import Component from '@glimmer/component'; +import SubjectModel from 'ember-osf-web/models/subject'; +import Media from 'ember-responsive'; +import { inject as service } from '@ember/service'; + +interface InputArgs { + list: SubjectModel[]; + provider: string; +} + +interface PairModel { + queryParam: object; + text: string; +} + +export default class TaxonomyTopList extends Component { + @service media!: Media; + + provider = this.args.provider; + + get isMobile(): boolean { + return this.media.isMobile; + } + + get sortedList() { + if (!this.args.list) { + return; + } + const sortedList = this.args.list.sortBy('text'); + const pairedList = [] as PairModel[][]; + + if (this.isMobile) { + for (let i = 0; i < sortedList.get('length'); i += 1) { + const pair: PairModel[] = []; + const subject= sortedList.objectAt(i) as SubjectModel; + pair.pushObject({ + queryParam: { + activeFilters: [{ + propertyVisibleLabel:'Subject', + propertyPathKey:'subject', + label:subject.text, + value: subject?.links?.iri, + }], + }, + text: subject?.text, + } as PairModel); + pairedList.pushObject(pair); + } + } else { + for (let i = 0; i < sortedList.get('length'); i += 2) { + const pair: PairModel[] = []; + // path in pair needs to be a list because that's what the + // subject param in the discover controller is expecting + const subjectOdd = sortedList.objectAt(i) as SubjectModel; + pair.pushObject({ + queryParam: { + activeFilters: [{ + propertyVisibleLabel:'Subject', + propertyPathKey:'subject', + label:subjectOdd.text, + value: subjectOdd?.links?.iri, + }], + }, + text: subjectOdd?.text, + } as PairModel); + + if (sortedList.objectAt(i + 1)) { + const subjectEven = sortedList.objectAt(i + 1) as SubjectModel; + pair.pushObject({ + queryParam: { + activeFilters: [{ + propertyVisibleLabel:'Subject', + propertyPathKey:'subject', + label:subjectEven.text, + value: subjectEven?.links?.iri, + }], + }, + text: subjectEven?.text, + }); + } + pairedList.pushObject(pair); + } + + } + + if (pairedList.length > 0 && typeof this.args.provider !== 'string') { + throw new Error('A provider string must be provided with a valid list'); + } + + return pairedList; + } +} diff --git a/app/preprints/-components/taxonomy-top-list/styles.scss b/app/preprints/-components/taxonomy-top-list/styles.scss new file mode 100644 index 00000000000..4991ca1c5bf --- /dev/null +++ b/app/preprints/-components/taxonomy-top-list/styles.scss @@ -0,0 +1,57 @@ +// stylelint-disable max-nesting-depth, selector-max-compound-selectors + +.subject-container { + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 10px; + + .subject-item { + width: calc(50% - 20px); + + .btn { + display: inline-block; + margin-bottom: 0; + font-weight: 400; + text-align: center; + white-space: nowrap; + touch-action: manipulation; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + border-radius: 2px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857; + user-select: none; + vertical-align: middle; + color: $color-text-white; + } + + .subject-button { + width: 100%; + font-size: 17px; + overflow: hidden; + text-overflow: ellipsis; + background-color: var(--primary-color); + color: var(--secondary-color); + border: 1px solid var(--secondary-color); + + &:hover { + color: var(--primary-color); + background-color: var(--secondary-color); + border: 1px solid var(--primary-color); + } + } + } + + &.mobile { + flex-direction: column; + + .subject-item { + width: 100%; + } + } +} diff --git a/app/preprints/-components/taxonomy-top-list/template.hbs b/app/preprints/-components/taxonomy-top-list/template.hbs new file mode 100644 index 00000000000..897d2dc9630 --- /dev/null +++ b/app/preprints/-components/taxonomy-top-list/template.hbs @@ -0,0 +1,18 @@ +{{#each this.sortedList as |pair|}} +
+ {{#each pair as |subject|}} +
+ + {{subject.text}} + +
+ {{/each}} +
+{{/each}} diff --git a/app/preprints/detail/controller.ts b/app/preprints/detail/controller.ts new file mode 100644 index 00000000000..620245f4f34 --- /dev/null +++ b/app/preprints/detail/controller.ts @@ -0,0 +1,134 @@ +import Controller from '@ember/controller'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; +import config from 'ember-osf-web/config/environment'; +import Theme from 'ember-osf-web/services/theme'; +import CurrentUserService from 'ember-osf-web/services/current-user'; +import Features from 'ember-feature-flags'; +import ContributorModel from 'ember-osf-web/models/contributor'; +import Intl from 'ember-intl/services/intl'; +import { Permission } from 'ember-osf-web/models/osf-model'; +import { ReviewsState, PreprintProviderReviewsWorkFlow } from 'ember-osf-web/models/provider'; +import { tracked } from '@glimmer/tracking'; +import Media from 'ember-responsive'; + + +/** + * Takes an object with query parameter name as the key and value, + * or [value, maxLength] as the values. + * + * @method queryStringify + * @param queryParams {!object} + * @param queryParams.key {!array|!string} + * @param queryParams.key[0] {!string} + * @param queryParams.key[1] {int} + * @return {string} + */ + +const DATE_LABEL = { + created: 'preprints.detail.date_label.created_on', + submitted: 'preprints.detail.date_label.submitted_on', +}; + +/** + * @module ember-preprints + * @submodule controllers + */ + +/** + * @class Content Controller + */ +export default class PrePrintsDetailController extends Controller { + @service theme!: Theme; + @service currentUser!: CurrentUserService; + @service features!: Features; + @service intl!: Intl; + @service media!: Media; + + @tracked fullScreenMFR = false; + + metricsStartDate = config.OSF.metricsStartDate; + + get hyperlink(): string { + return window.location.href; + } + + get fileDownloadUrl(): string { + const version = this.model.primaryFile.version; + const path = `${this.model.preprint.id}/download/?`; + return `${config.OSF.url}${path}${version ? `version=${version}` : ''}`.replace(/[&?]$/, ''); + } + + get facebookAppId(): string { + return this.model.provider.facebookAppId ? this.model.provider.facebookAppId : config.FB_APP_ID; + } + + get dateLabel(): string { + return this.model.provider.reviewsWorkflow === PreprintProviderReviewsWorkFlow.PRE_MODERATION ? + DATE_LABEL.submitted : + DATE_LABEL.created; + } + + get editButtonLabel(): string { + const editPreprint = 'preprints.detail.project_button.edit_preprint'; + const editResubmitPreprint = 'preprints.detail.project_button.edit_resubmit_preprint'; + const translation = this.model.provider.reviewsWorkflow === PreprintProviderReviewsWorkFlow.PRE_MODERATION + && this.model.preprint.reviewsState === ReviewsState.REJECTED && this.isAdmin() + ? editResubmitPreprint : editPreprint; + return this.intl.t(translation, { + documentType: this.model.provider.documentType.singular, + }); + } + + private isAdmin(): boolean { + // True if the current user has admin permissions for the node that contains the preprint + return (this.model.preprint.currentUserPermissions).includes(Permission.Admin); + } + + get userIsContrib(): boolean { + if (this.isAdmin()) { + return true; + } else if (this.model.contributors.length) { + const authorIds = [] as string[]; + this.model.contributors.forEach((author: ContributorModel) => { + authorIds.push(author.id); + }); + return this.currentUser.currentUserId ? authorIds.includes(this.currentUser.currentUserId) : false; + } + return false; + } + + get showStatusBanner(): boolean { + return ( + this.model.provider.reviewsWorkflow + && this.model.preprint.public + && this.userIsContrib + && this.model.preprint.reviewsState !== ReviewsState.INITIAL + && !this.model.preprint.isPreprintOrphan + ); + } + + get authors(): ContributorModel[] { + return this.model.contributors; + } + + emailHref(): string { + const titleEncoded = encodeURIComponent(this.model.title); + const hrefEncoded = encodeURIComponent(window.location.href); + return `mailto:?subject=${titleEncoded}&body=${hrefEncoded}`; + } + + @action + expandMFR() { + this.fullScreenMFR = !this.fullScreenMFR; + } + + @action + trackNonContributors(category: string, label: string, url: string): void { + this.send('click', category, label, url); + } + + get isMobile() { + return this.media.isMobile; + } +} diff --git a/app/preprints/detail/route.ts b/app/preprints/detail/route.ts new file mode 100644 index 00000000000..f46b9e145b3 --- /dev/null +++ b/app/preprints/detail/route.ts @@ -0,0 +1,155 @@ +import Store from '@ember-data/store'; +import Route from '@ember/routing/route'; +import RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; +import { waitFor } from '@ember/test-waiters'; +import { taskFor } from 'ember-concurrency-ts'; +import { all, restartableTask } from 'ember-concurrency'; +import moment from 'moment-timezone'; + +import config from 'ember-osf-web/config/environment'; +import CurrentUser from 'ember-osf-web/services/current-user'; +import Identifier from 'ember-osf-web/models/identifier'; +import LicenseModel from 'ember-osf-web/models/license'; +import { SparseModel } from 'ember-osf-web/utils/sparse-fieldsets'; +import MetaTags, { HeadTagDef } from 'ember-osf-web/services/meta-tags'; +import Ready from 'ember-osf-web/services/ready'; +import Theme from 'ember-osf-web/services/theme'; +import captureException from 'ember-osf-web/utils/capture-exception'; +import pathJoin from 'ember-osf-web/utils/path-join'; + +/** + * @module ember-preprints + * @submodule routes + */ + +/** + * @class Content Route Handler + */ + + +/** + * Loads all disciplines and preprint providers to the index page + * @class Index Route Handler + */ +export default class PreprintsDetail extends Route { + @service store!: Store; + @service theme!: Theme; + @service router!: RouterService; + @service currentUser!: CurrentUser; + @service metaTags!: MetaTags; + @service ready!: Ready; + + headTags?: HeadTagDef[]; + + async model(params: { guid : string }) { + try { + const guid = params.guid; + + const preprint = await this.store.findRecord('preprint', guid, { + adapterOptions: { + query: { + 'metrics[views]': 'total', + 'metrics[downloads]': 'total', + }, + }, + }); + + const provider = await preprint?.get('provider'); + + const primaryFile = await preprint?.get('primaryFile'); + + this.theme.set('providerType', 'preprint'); + this.theme.set('id', provider.id); + + const contributors = await preprint?.queryHasMany('contributors'); + + const license = await preprint?.get('license'); + + const node = await preprint?.get('node'); + + const subjects = await preprint?.queryHasMany('subjects'); + + return { + preprint, + brand: provider.brand.content, + contributors, + provider, + primaryFile, + license, + subjects, + node, + }; + + } catch (error) { + captureException(error); + this.router.transitionTo('not-found', 'preprints'); + return null; + } + } + + @restartableTask({ cancelOn: 'deactivate' }) + @waitFor + async setHeadTags(model: any) { + const blocker = this.ready.getBlocker(); + const {preprint} = await model; + + if (preprint) { + const [ + contributors = [], + license = null, + identifiers = [], + provider = null, + ] = await all([ + preprint.sparseLoadAll( + 'bibliographicContributors', + { contributor: ['users', 'index'], user: ['fullName'] }, + ), + preprint.license, + preprint.identifiers, + preprint.provider, + ]); + + const doi = (identifiers as Identifier[]).find(identifier => identifier.category === 'doi'); + const image = 'engines-dist/registries/assets/img/osf-sharing.png'; + + const metaTagsData = { + title: preprint.title, + description: preprint.description, + publishedDate: moment(preprint.dateRegistered).format('YYYY-MM-DD'), + modifiedDate: moment(preprint.dateModified).format('YYYY-MM-DD'), + identifier: preprint.id, + url: pathJoin(config.OSF.url, preprint.id), + doi: doi && doi.value, + image, + keywords: preprint.tags, + siteName: 'OSF', + license: license && (license as LicenseModel).name, + author: (contributors as SparseModel[]).map( + contrib => (contrib.users as { fullName: string }).fullName, + ), + }; + + const allTags: HeadTagDef[] = this.metaTags.getHeadTags(metaTagsData); + + if (provider && provider.assets && provider.assets.favicon) { + allTags.push({ + type: 'link', + attrs: { + rel: 'icon', + href: provider.assets.favicon, + }, + }); + } + this.set('headTags', allTags); + this.metaTags.updateHeadTags(); + } + blocker.done(); + } + + afterModel(model: any) { + if (!this.currentUser.viewOnlyToken) { + taskFor(this.setHeadTags).perform(model); + } + } +} diff --git a/app/preprints/detail/styles.scss b/app/preprints/detail/styles.scss new file mode 100644 index 00000000000..cf86e1a40dc --- /dev/null +++ b/app/preprints/detail/styles.scss @@ -0,0 +1,260 @@ +// stylelint-disable max-nesting-depth, selector-max-compound-selectors + +.preprints-details-page-container { + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .btn { + display: inline-block; + margin-bottom: 0; + font-weight: 400; + text-align: center; + white-space: nowrap; + touch-action: manipulation; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + border-radius: 2px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857; + user-select: none; + vertical-align: middle; + color: $color-text-white; + } + + .btn-primary { + color: $color-text-white; + background-color: $color-bg-blue-dark; + border-color: #2e6da4; + } + + .header-container { + padding: 30px 0; + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: $color-text-white; + background: url('assets/images/preprints/preprints-detail-header-overlay.png') top center $color-bg-color-grey; + + .preprint-author-container, + .preprint-title-container { + width: calc(100% - 100px); + padding-top: 20px; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + + .view-authors { + color: $color-text-white; + padding-bottom: 15px; + + a { + color: #6dd1de; + } + + .detail-header-label { + font-size: 11px; + text-transform: uppercase; + padding-bottom: 2px; + } + } + } + } + + .data-container { + padding: 30px; + width: 100%; + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; + + .withdrawn-container { + padding: 0 15px; + width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + } + + .data-container-left, + .data-container-right { + padding: 0 15px; + width: 50%; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + } + + .data-container-left { + height: 1150px; + + &.expanded { + width: 100%; + } + + &.collapsed { + width: 50%; + } + + .file-description-container { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; + width: 100%; + padding-top: 15px; + + .file-description { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: flex-start; + width: 75%; + } + + .toggle-button { + width: 25%; + min-width: 100px; + display: flex; + justify-content: flex-end; + align-items: flex-start; + } + } + } + + .withdrawn-container, + .data-container-right { + height: 100%; + + h4 { + padding-bottom: 15px; + padding-top: 15px; + margin-top: 10px; + margin-bottom: 10px; + font-weight: bold; + } + + .plaudit-container { + display: flex; + flex-direction: row; + justify-content: flex-start; + align-items: center; + width: 100%; + min-height: 90px; + + .plaudit { + width: calc(100% - 145px); + padding-right: 30px; + } + + .sharing-icons { + font-size: 1.5rem; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: flex-start; + width: 145px; + } + } + + .download-container { + background-color: $bg-light; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; + padding: 10px; + } + } + } + + &.mobile { + .header-container { + padding: 0; + flex-direction: column; + + .preprint-author-container, + .preprint-title-container { + width: 100%; + padding: 0 10px; + } + } + + .data-container { + padding: 10px; + flex-direction: column; + justify-content: flex-start; + align-items: center; + + .data-container-left, + .data-container-right { + width: 100%; + padding: 0; + } + + .data-container-left { + height: 481px; + + .file-description-container { + flex-direction: column; + justify-content: flex-start; + margin-bottom: 20px; + + & > div { + margin-top: 10px; + } + + .file-description { + width: 100%; + flex-direction: column; + } + + .toggle-button { + width: 100%; + align-items: flex-end; + } + } + } + + .data-container-right { + .download-container { + flex-direction: column; + align-items: flex-start; + + & > div { + margin-top: 10px; + } + } + + .plaudit-container { + flex-direction: column; + align-items: flex-start; + + .plaudit { + width: 100%; + padding-right: 0; + height: 90px; + } + + .sharing-icons { + padding: 10px; + justify-content: space-between; + align-items: flex-start; + width: 145px; + } + } + } + } + } +} diff --git a/app/preprints/detail/template.hbs b/app/preprints/detail/template.hbs new file mode 100644 index 00000000000..8ae3c8dbce9 --- /dev/null +++ b/app/preprints/detail/template.hbs @@ -0,0 +1,202 @@ +{{page-title this.model.preprint.title replace=false}} + + +
+
+
+

{{this.model.preprint.title}}

+ {{#unless this.model.preprint.isWithdrawn}} +
+ {{#if (and this.userIsContrib (not this.isPendingWithdrawal))}} + + {{this.editButtonLabel}} + + + {{/if}} +
+
+ {{/unless}} +
+
+
+
+ {{t 'preprints.detail.header.authors_label'}} +
+ +
+
+ +
+ {{#if (or this.showStatusBanner this.isWithdrawn)}} + + {{/if}} +
+ {{#if this.model.preprint.isWithdrawn}} + + {{else}} +
+ {{#if this.model.preprint.isPreprintOrphan}} + + {{t 'preprints.detail.orphan_preprint'}} + + {{else}} + {{#if this.model.preprint.public}} + +
+
+
{{t this.dateLabel}}: {{moment-format this.model.dateCreated 'MMMM DD, YYYY'}}
+ {{#unless this.isMobile}} +
|
+ {{/unless}} + {{#if this.isWithdrawn}} +
{{t 'preprints.detail.header.withdrawn_on'}}: {{moment-format this.model.preprint.dateWithdrawn 'MMMM DD, YYYY'}}
+ {{else}} +
{{t 'preprints.detail.header.last_edited'}}: {{moment-format this.model.preprint.dateModified 'MMMM DD, YYYY'}}
+ {{/if}} +
+ {{#unless this.isMobile}} +
+ +
+ {{/unless}} +
+ {{else}} + + {{t 'preprints.detail.private_preprint_warning' documentType=this.model.provider.documentType.singular supportEmail='support@osf.io'}} + + {{/if}} + {{/if}} +
+ {{#unless this.fullScreenMFR }} +
+
+
+ + {{t 'preprints.detail.share.download' documentType=this.model.provider.documentType.singular}} + +
+
+ {{t 'preprints.detail.share.views'}}: {{this.model.preprint.apiMeta.metrics.views}} | {{t 'preprints.detail.share.downloads'}}: {{this.model.preprint.apiMeta.metrics.downloads}} + + {{t 'preprints.detail.share.metrics_disclaimer'}} {{moment-format this.metricsStartDate 'YYYY-MM-DD'}} + +
+
+
+
+ +
+ +
+ {{links.email}} +
+
+ {{links.twitter}} +
+
+ {{links.facebook}} +
+
+ {{links.linkedIn}} +
+
+
+ + {{#if this.model.node}} +
+

{{t 'preprints.detail.supplemental_materials'}}

+ + {{this.model.node.links.html}} + + +
+ {{/if}} + + {{#if this.model.preprint.articleDoiUrl}} +
+

{{t 'preprints.detail.article_doi'}}

+ + {{this.model.preprint.articleDoiUrl}} + +
+ {{/if}} + + + + {{#if this.model.preprint.originalPublicationDate}} +
+

{{t 'preprints.detail.original_publication_date'}}

+

+ {{moment-format this.model.preprint.originalPublicationDate 'YYYY-MM-DD'}} +

+
+ {{/if}} +
+

{{t 'preprints.detail.citations'}}

+ +
+
+ {{/unless}} + {{/if}} +
+
diff --git a/app/preprints/discover/route.ts b/app/preprints/discover/route.ts index ff32962c088..23dd26fb99c 100644 --- a/app/preprints/discover/route.ts +++ b/app/preprints/discover/route.ts @@ -3,7 +3,6 @@ import Route from '@ember/routing/route'; import RouterService from '@ember/routing/router-service'; import { inject as service } from '@ember/service'; import config from 'ember-osf-web/config/environment'; -import PreprintProviderModel from 'ember-osf-web/models/preprint-provider'; import MetaTags, { HeadTagDef } from 'ember-osf-web/services/meta-tags'; import Theme from 'ember-osf-web/services/theme'; @@ -39,22 +38,4 @@ export default class PreprintDiscoverRoute extends Route { return null; } } - - // TODO: Move this to app/preprints/index/route.ts when landing page PR is merged - afterModel(model: PreprintProviderModel) { - if (model && model.assets && model.assets.favicon) { - const headTags = [{ - type: 'link', - attrs: { - rel: 'icon', - href: model.assets.favicon, - }, - }]; - this.set('headTags', headTags); - } - } - - deactivate() { - this.theme.reset(); - } } diff --git a/app/preprints/index/controller.ts b/app/preprints/index/controller.ts new file mode 100644 index 00000000000..b7dc6e2c6e8 --- /dev/null +++ b/app/preprints/index/controller.ts @@ -0,0 +1,38 @@ +import Store from '@ember-data/store'; +import Controller from '@ember/controller'; +import { action } from '@ember/object'; +import RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; +import Theme from 'ember-osf-web/services/theme'; +import Media from 'ember-responsive'; +import Intl from 'ember-intl/services/intl'; + +export default class Preprints extends Controller { + @service store!: Store; + @service theme!: Theme; + @service router!: RouterService; + @service media!: Media; + @service intl!: Intl; + + get isMobile(): boolean { + return this.media.isMobile; + } + + get isOsf(): boolean { + return this.theme?.provider?.id === 'osf'; + } + + @action + onSearch(query: string) { + const route = 'preprints.discover'; + + this.router.transitionTo(route, this.theme.id, { queryParams: { q: query } }); + } + + get supportEmail(): string { + const { isProvider, provider } = this.theme; + + // eslint-disable-next-line max-len + return `mailto:${isProvider && provider && provider.emailSupport ? provider.emailSupport : this.intl.t('contact.email')}`; + } +} diff --git a/app/preprints/index/route.ts b/app/preprints/index/route.ts new file mode 100644 index 00000000000..e3382f7ff7e --- /dev/null +++ b/app/preprints/index/route.ts @@ -0,0 +1,81 @@ +import Store from '@ember-data/store'; +import Route from '@ember/routing/route'; +import RouterService from '@ember/routing/router-service'; +import { inject as service } from '@ember/service'; +import Theme from 'ember-osf-web/services/theme'; +import captureException from 'ember-osf-web/utils/capture-exception'; +import MetaTags, { HeadTagDef } from 'ember-osf-web/services/meta-tags'; + +/** + * Loads all disciplines and preprint providers to the index page + * @class Index Route Handler + */ +export default class Preprints extends Route { + @service store!: Store; + @service theme!: Theme; + @service router!: RouterService; + @service metaTags!: MetaTags; + headTags?: HeadTagDef[]; + + + async model(params: { provider_id : string }) { + const provider_id = params.provider_id ? params.provider_id : 'osf'; + let provider; + try { + provider = await this.store.findRecord('preprint-provider', provider_id, { + include: 'brand', + }); + } catch (error) { + if (params.provider_id) { + this.router.transitionTo('resolve-guid', params.provider_id); + return null; + } else { + this.router.transitionTo('not-found', 'preprints'); + return null; + } + } + + this.theme.set('providerType', 'preprint'); + this.theme.set('id', provider_id); + + try { + const taxonomies = await this.theme.provider?.queryHasMany('highlightedSubjects', { + page: { + size: 20, + }, + }); + + let brandedProviders = []; + + if (this.theme.id === 'osf') { + const allProviders = await this.store.findAll('preprint-provider', { reload: true }); + brandedProviders = allProviders.filter(item => item.id !== 'osf'); + } + + return { + provider, + taxonomies, + brandedProviders, + brand: provider.brand.content, + }; + } catch (error) { + captureException(error); + this.router.transitionTo('not-found', 'preprints'); + return null; + } + } + + afterModel(model: any) { + const {provider} = model; + if (provider && provider.assets && provider.assets.favicon) { + const headTags = [{ + type: 'link', + attrs: { + rel: 'icon', + href: provider.assets.favicon, + }, + }]; + this.set('headTags', headTags); + } + } +} diff --git a/app/preprints/index/styles.scss b/app/preprints/index/styles.scss new file mode 100644 index 00000000000..e90e5bd0d33 --- /dev/null +++ b/app/preprints/index/styles.scss @@ -0,0 +1,213 @@ +// stylelint-disable max-nesting-depth, selector-max-compound-selectors + +.preprints-page-container { + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .preprint-header { + border-bottom: 1px solid var(--primary-color); + width: 100%; + background-color: var(--secondary-color); + color: var(--primary-color) !important; + + a:not(.btn) { + color: var(--primary-color); + text-decoration: underline; + } + + .description { + color: var(--primary-color); + } + + .submit-container { + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: var(--primary-color); + + .or-container { + font-size: 21px; + margin-bottom: 20px; + font-weight: 300; + line-height: 1.4; + color: $color-text-white; + + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + } + + .example-container { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + margin-top: 25px; + } + } + } + + /* Subject Panel */ + .preprint-subjects-container { + background-color: $color-bg-gray-lighter; + border-bottom: 1px solid #6d8a98; + padding: 15px 0; + width: 100%; + flex-direction: column; + display: flex; + justify-content: center; + align-items: center; + + .preprint-subjects-inner-container { + background-color: $color-bg-gray-lighter; + padding: 0 30px; + width: calc(100% - 60px); + flex-direction: column; + display: flex; + justify-content: flex-start; + align-items: flex-start; + + .subject-list-container { + padding: 25px; + width: 100%; + flex-direction: column; + display: flex; + justify-content: flex-start; + align-items: flex-start; + } + } + } + + /* Preprint providers and become provider */ + .preprint-tool-container { + background: var(--hero-background-img-url); + color: $color-text-white; + text-shadow: 0 0 5px #506069; + background-size: cover; + padding: 15px 0; + min-width: 100%; + min-height: 100%; + width: 100%; + flex-direction: column; + display: flex; + justify-content: center; + align-items: center; + + .preprint-tool-inner-container { + padding-right: 30px; + padding-left: 30px; + width: calc(100% - 60px); + flex-direction: column; + display: flex; + justify-content: flex-start; + align-items: flex-start; + + .subtitle { + margin-bottom: 25px; + } + + .preprint-contact-container, + .preprint-logos-container { + padding: 25px; + width: 100%; + flex-direction: row; + flex-wrap: wrap; + display: flex; + justify-content: center; + align-items: flex-start; + + .provider-logo { + width: 150px; + height: 75px; + background-size: contain; + background-repeat: no-repeat; + background-position: center; + margin: 15px; + } + } + + .preprint-contact-container { + flex-direction: column; + align-items: center; + + .lead { + font-size: 21px; + line-height: 1.4; + font-weight: 300; + padding-bottom: 10px; + text-align: center; + + .links { + color: $color-text-white; + text-decoration: underline; + } + } + + .contact { + margin-top: 20px; + } + } + } + } + + .osf-preprint-advisory-container { + background-image: none; + } + + &.mobile { + .preprint-subjects-inner-container { + padding: 0; + background-color: $color-bg-gray-lighter; + padding: 0, 10px; + width: calc(100% - 20px); + + .subject-list-container { + padding: 25px 0; + width: 100%; + flex-direction: column; + } + } + + .preprint-tool-inner-container { + padding: 15px 10px; + width: calc(100% - 20px); + } + } + + .btn { + display: inline-block; + margin-bottom: 0; + font-weight: 400; + text-align: center; + white-space: nowrap; + touch-action: manipulation; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + border-radius: 2px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857; + user-select: none; + vertical-align: middle; + color: $color-text-white; + } + + .btn-success { + background-color: #357935; + border-color: #2d672d; + } + + .btn-lg { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33333; + } +} diff --git a/app/preprints/index/template.hbs b/app/preprints/index/template.hbs new file mode 100644 index 00000000000..f89a2e79b2c --- /dev/null +++ b/app/preprints/index/template.hbs @@ -0,0 +1,167 @@ +
+ {{!HEADER}} +
+ + {{#branded-header.lead}} +
+ {{html-safe this.theme.provider.description}} +
+
+ + {{t 'preprints.header.powered_by'}} + +
+ {{/branded-header.lead}} + {{#branded-header.row}} + {{#if this.theme.provider.allowSubmissions}} +
+
{{t 'preprints.header.or'}}
+ + {{t 'preprints.header.submit_label' documentType=this.theme.provider.content.documentType}} + +
+ + {{t 'preprints.header.example'}} + +
+
+ {{/if}} + {{/branded-header.row}} +
+
+ {{!END HEADER}} + + {{!SUBJECTS}} +
+
+

{{#if this.theme.provider.additionalProviders}} + {{t 'preprints.subjects.heading.provider'}} + {{else}} + {{if this.theme.provider.hasHighlightedSubjects (t 'preprints.subjects.heading.hasHighlightedSubjects') (t 'preprints.subjects.heading.noHighlightedSubjects')}} + {{/if}} +

+ {{#if this.theme.provider.hasHighlightedSubjects}} + + {{t 'preprints.subjects.links.seeAllSubjects'}} + + {{/if}} +
+ {{#if this.theme.provider.additionalProviders}} + {{additional-provider-list additionalProviders=this.theme.provider.additionalProviders}} + {{else}} + + {{/if}} +
+
+
+ + {{#unless this.theme.isProvider}} + {{!SERVICES}} +
+
+

{{t 'preprints.services.top.heading' documentType=this.theme.provider.documentType.singularCapitalized}}

+

+ {{t 'preprints.services.top.paragraph' documentType=this.theme.provider.documentType.singular}} +

+
+ {{#each this.model.brandedProviders as |provider| }} + {{#if (not-eq provider.id 'livedata') }} + + {{/if}} + {{/each}} +
+
+
+ {{t 'preprints.services.bottom.p1' documentType=this.theme.provider.documentType.singular}} +
+
+ {{t 'preprints.services.bottom.div.line1'}} + + + {{t 'preprints.services.bottom.div.linkText1'}} + + + {{t 'preprints.services.bottom.div.line2'}} + + + {{t 'preprints.services.bottom.div.linkText2'}} + + + {{t 'preprints.services.bottom.div.line3'}} +
+
+ + {{t 'preprints.services.bottom.contact'}} + +
+
+
+
+ {{/unless}} + + + {{!ADVISORY GROUP}} + {{#if this.theme.provider.advisoryBoard.length}} +
+
+ {{html-safe this.theme.provider.advisoryBoard}} +
+
+ {{/if}} +
+{{!END INDEX}} diff --git a/app/preprints/route.ts b/app/preprints/route.ts index d72fd19a9e2..445605f7bd9 100644 --- a/app/preprints/route.ts +++ b/app/preprints/route.ts @@ -5,4 +5,8 @@ import Theme from 'ember-osf-web/services/theme'; export default class Preprints extends Route { @service theme!: Theme; + + deactivate() { + this.theme.reset(); + } } diff --git a/app/preprints/template.hbs b/app/preprints/template.hbs index 21847e39d8d..f83103440ff 100644 --- a/app/preprints/template.hbs +++ b/app/preprints/template.hbs @@ -1,9 +1,16 @@ {{page-title this.theme.provider.providerTitle replace=true}} -
- - {{outlet}} -
+{{#if this.theme.isProvider}} +
+ + +
+{{/if}} +{{outlet}} + +
+ +
\ No newline at end of file diff --git a/app/resolve-guid/route.ts b/app/resolve-guid/route.ts index 111dbaef86f..8d6571eb323 100644 --- a/app/resolve-guid/route.ts +++ b/app/resolve-guid/route.ts @@ -36,7 +36,7 @@ export default class ResolveGuid extends Route { return { file: 'guid-file', node: 'guid-node', - preprint: 'guid-preprint', + preprint: 'preprints.detail', registration: this.features.isEnabled(routes['registries.overview']) ? 'registries.overview' : 'guid-registration', @@ -75,7 +75,14 @@ export default class ResolveGuid extends Route { throw new Error(`Unknown GUID referentType: ${guid.referentType}`); } - expanded = this.generateURL(this.routeMap[guid.referentType], params.guid); + if (guid.referentType === 'preprint') { + const preprint = await this.store.findRecord('preprint', params.guid); + const providerId = preprint.belongsTo('provider').id(); + expanded = this.generateURL(this.routeMap['preprint'], providerId, params.guid); + } else { + expanded = this.generateURL(this.routeMap[guid.referentType], params.guid); + } + } let url = expanded; diff --git a/app/router.ts b/app/router.ts index e69ef624be8..2cd6ea7babd 100644 --- a/app/router.ts +++ b/app/router.ts @@ -25,10 +25,15 @@ Router.map(function() { this.route('discover', { path: '/:institution_id' }); this.route('dashboard', { path: '/:institution_id/dashboard' }); }); + this.route('preprints', function() { - this.route('discover'); + this.route('index', { path: '/:provider_id' }); + this.route('index', { path: '/' }); this.route('discover', { path: '/:provider_id/discover' }); + this.route('detail', { path: '/:provider_id/:guid' }); }); + + this.route('register'); this.route('settings', function() { this.route('profile', function() { @@ -74,7 +79,6 @@ Router.map(function() { }); }); - this.route('guid-preprint', { path: '--preprint/:guid' }); this.route('guid-registration', { path: '--registration/:guid' }, function() { this.mount('analytics-page', { as: 'analytics' }); diff --git a/app/search/controller.ts b/app/search/controller.ts index fbfa7ec1f15..24625559b86 100644 --- a/app/search/controller.ts +++ b/app/search/controller.ts @@ -6,7 +6,7 @@ import { } from 'osf-components/components/search-page/component'; export default class SearchController extends Controller { - @tracked q?: string = ''; + @tracked cardSearchText?: string = ''; @tracked sort?: string = '-relevance'; @tracked resourceType?: ResourceTypeFilterValue | null = null; @tracked activeFilters?: Filter[] = []; diff --git a/app/serializers/preprint-request-action.ts b/app/serializers/preprint-request-action.ts new file mode 100644 index 00000000000..2b9d3a4fd52 --- /dev/null +++ b/app/serializers/preprint-request-action.ts @@ -0,0 +1,14 @@ +import OsfSerializer from './osf-serializer'; + +export default class PreprintRequestActionSerializer extends OsfSerializer { + attrs: any = { + ...this.attrs, // from OsfSerializer + actionTrigger: 'trigger', + }; +} + +declare module 'ember-data/types/registries/serializer' { + export default interface SerializerRegistry { + 'preprint-request-action': PreprintRequestActionSerializer; + } // eslint-disable-line semi +} diff --git a/app/serializers/preprint-request.ts b/app/serializers/preprint-request.ts new file mode 100644 index 00000000000..18319f8c50a --- /dev/null +++ b/app/serializers/preprint-request.ts @@ -0,0 +1,10 @@ +import OsfSerializer from './osf-serializer'; + +export default class PreprintRequestSerializer extends OsfSerializer { +} + +declare module 'ember-data/types/registries/serializer' { + export default interface SerializerRegistry { + 'preprint-request': PreprintRequestSerializer; + } // eslint-disable-line semi +} diff --git a/app/styles/_preprint.scss b/app/styles/_preprint.scss new file mode 100644 index 00000000000..bb1767500fc --- /dev/null +++ b/app/styles/_preprint.scss @@ -0,0 +1,67 @@ +// stylelint-disable max-nesting-depth, selector-max-compound-selectors + +.preprint-advisory-container { + background: var(--hero-background-img-url); + background-color: $color-bg-gray-lighter; + border-bottom: 1px solid #6d8a98; + padding: 15px 0; + width: 100%; + flex-direction: column; + display: flex; + justify-content: center; + align-items: center; + + .preprint-advisory-inner-container { + padding: 0 30px; + width: calc(100% - 60px); + flex-direction: column; + display: flex; + justify-content: flex-start; + align-items: flex-start; + + .preprint-advisory-header { + width: 100%; + } + + .preprint-advisory-list { + width: 100%; + flex-direction: row; + display: flex; + justify-content: flex-start; + align-items: flex-start; + margin-top: 25px; + + .preprint-advisory-list-column { + width: 50%; + + ul { + list-style: none; + + li { + padding-top: 5px; + font-size: 16px; + } + } + } + } + } + + &.mobile { + .preprint-advisory-inner-container { + padding: 0 10px; + width: calc(100% - 20px); + + .preprint-advisory-list { + flex-direction: column; + + .preprint-advisory-list-column { + width: 100%; + + ul { + padding: 0; + } + } + } + } + } +} diff --git a/app/styles/headers.scss b/app/styles/headers.scss index ce654bede9b..5663ca4d799 100644 --- a/app/styles/headers.scss +++ b/app/styles/headers.scss @@ -19,6 +19,9 @@ // Branding @import 'branding'; +// Preprints +@import 'preprint'; + .theme-dropdown .dropdown-menu { position: static; display: block; diff --git a/config/environment.js b/config/environment.js index e802090a825..c2b5c62e970 100644 --- a/config/environment.js +++ b/config/environment.js @@ -28,6 +28,7 @@ const { GOOGLE_TAG_MANAGER_ID, KEEN_CONFIG: keenConfig, LINT_ON_BUILD: lintOnBuild = false, + WATER_BUTLER_ENABLED = true, MIRAGE_ENABLED = false, MIRAGE_SCENARIOS = [ 'loggedIn', @@ -48,6 +49,8 @@ const { OSF_FILE_URL: waterbutlerUrl = 'http://localhost:7777/', OSF_HELP_URL: helpUrl = 'http://localhost:4200/help', OSF_AUTHENTICATOR: osfAuthenticator = 'osf-cookie', + PLAUDIT_WIDGET_URL: plauditWidgetUrl = 'https://osf-review.plaudit.pub/embed/endorsements.js', + METRICS_START_DATE: metricsStartDate = '2019-01-01', POLICY_URL_PREFIX = 'https://github.com/CenterForOpenScience/centerforopenscience.org/blob/master/', POPULAR_LINKS_NODE: popularNode = '57tnq', // POPULAR_LINKS_REGISTRATIONS = '', @@ -69,6 +72,8 @@ module.exports = function(environment) { const ENV = { modulePrefix: 'ember-osf-web', + WATER_BUTLER_ENABLED, + plauditWidgetUrl, environment, lintOnBuild, testsEnabled: false, // Disable tests by default. @@ -144,6 +149,7 @@ module.exports = function(environment) { Accept: `application/vnd.api+json; version=${apiVersion}`, }, learnMoreUrl: 'https://cos.io/our-products/osf/', + donateUrl: 'https://cos.io/donate', renderUrl, waterbutlerUrl, helpUrl, @@ -151,6 +157,7 @@ module.exports = function(environment) { shareApiUrl, shareSearchUrl, devMode, + metricsStartDate, cookieDomain, authenticator: `authenticator:${osfAuthenticator}`, cookies: { diff --git a/lib/app-components/addon/components/branded-navbar/component.ts b/lib/app-components/addon/components/branded-navbar/component.ts index e323a64f9ae..ce62cde5e4d 100644 --- a/lib/app-components/addon/components/branded-navbar/component.ts +++ b/lib/app-components/addon/components/branded-navbar/component.ts @@ -23,7 +23,7 @@ import template from './template'; type ObjectType = 'collection' | 'preprint' | 'registration'; -const osfURL = config.OSF.url; +const { url: osfURL, donateUrl } = config.OSF; @layout(template, styles) @tagName('') @@ -43,6 +43,7 @@ export default class BrandedNavbar extends Component { campaign = `${this.theme.id}-collections`; myProjectsUrl = serviceLinks.myProjects; + donateUrl = donateUrl; get reviewUrl() { return `${osfURL}reviews`; diff --git a/lib/app-components/addon/components/branded-navbar/styles.scss b/lib/app-components/addon/components/branded-navbar/styles.scss index e14d214d3db..b301cf97dd2 100644 --- a/lib/app-components/addon/components/branded-navbar/styles.scss +++ b/lib/app-components/addon/components/branded-navbar/styles.scss @@ -113,31 +113,30 @@ .preprint-branded-navbar.preprint-branded-navbar { background-color: var(--primary-color); background-image: none; + color: $color-text-white; .secondary-navigation { border-color: transparent; } - &.light-text { - a:not(:global(.btn-top-signup)), - :global(.secondary-nav-dropdown) { - color: $color-text-white !important; - } - } - + // Dark text only applied to provider name, hamburger menu (only shows up in mobile), and navbar links in desktop view &.dark-text { - a:not(:global(.btn-top-signup)), - :global(.secondary-nav-dropdown) { + a:global(.navbar-brand), + .navbar-toggle { color: $color-text-black !important; } + + /* stylelint-disable */ + .desktop { + a:not(:global(.btn-top-signup)), + :global(.secondary-nav-dropdown) { + color: $color-text-black !important; + } + } + /* stylelint-enable */ } } -.white-background-branded-navbar.white-background-branded-navbar.white-background-branded-navbar { - background-color: #fff; - - a:not(:global(.btn-top-signup)), - :global(.secondary-nav-dropdown) { - color: $color-text-black !important; - } +.white-background-branded-navbar { + background-color: #fff !important; } diff --git a/lib/app-components/addon/components/branded-navbar/template.hbs b/lib/app-components/addon/components/branded-navbar/template.hbs index e6b1a93f2a5..f754bd92761 100644 --- a/lib/app-components/addon/components/branded-navbar/template.hbs +++ b/lib/app-components/addon/components/branded-navbar/template.hbs @@ -2,8 +2,8 @@