* Article query spec for scheduled articles * Added scheduled article badge on the user dashboard * Added published_at field to editor options * Accept and validate published_at from editor * Refactor published_at validation * Allow 1-minute difference in published_at * Notice on an unpublished article page * Added specs for 'Click to edit' link on scheduled article preview page * ContextNotification model * Articles::Publish worker * Added specs for articles publish worker * Schedule publish articles worker * Added tests to check for scheduled posts in feeds * Don't allow managing scheduled articles * Don't send notifications for scheduled articles * Set published_at in Articles::Updater when publishing * Published_at value in post options * Pass timezone and set published_at accordingly * Limit setting published_at to the future * Readonly published_at for articles that were already published * Chagning published_at format in editor v1 (start) * Changed published_at format in frontmatter, specs * Added specs for updating published_at from frontmatter * Fixed accepting past published_at for articles published_from_feed * Enabled published_at validation: don't allow updating published_at for already published articles * Validate published_at on create * Added a spec for updating published_at for exported articles * Fixed specs related to creating articles with past published_at * Fixed specs related to past published_at for articles * Added a hack so that admins would be able to update published_at * Switch button text schedule/publish when changin publishedAt * Fixed saving published_at with timezone * Added a feature flag for scheduling articles * Default text in markdown editor depends on feature flag * Enable article editor cache again * Fixed the default value in the markdown editor * Fix sitemaps spec * Removed tooltip * Fixed articles update specs * Added missing locales * Fixed article create specs * Fixed spec * Removed commented code * Returned enabling extensions in the schema * Returned accidentally deleted constraint * Make articles query spec more stable Co-authored-by: Jeremy Friesen <jeremy.n.friesen@gmail.com> * Removed commented code * Removed unused code * A clearer policy Co-authored-by: Jeremy Friesen <jeremy.n.friesen@gmail.com> * Use StringInquirer for article current state * Added a note and todo to articles factory past trait * Remove duplicated PropType Co-authored-by: Suzanne Aitchison <suzanne@forem.com> * Refactor query in the Articles::PublishWorker Co-authored-by: Mac Siri <krairit.siri@gmail.com> * Refactor articleForm.jsx Co-authored-by: Mac Siri <krairit.siri@gmail.com> * Removed specs that are no longer relevant * Removed useless onKeyUp on a hidden input * Refactored articleForm * Hide scheduling from post options when published_at is readonly * Run sends notifications worker every 5 minutes instead of every minute Co-authored-by: Jeremy Friesen <jeremy.n.friesen@gmail.com> Co-authored-by: Suzanne Aitchison <suzanne@forem.com> Co-authored-by: Mac Siri <krairit.siri@gmail.com>
210 lines
5.9 KiB
JavaScript
210 lines
5.9 KiB
JavaScript
import { h } from 'preact';
|
|
import PropTypes from 'prop-types';
|
|
import { Dropdown, ButtonNew as Button } from '@crayons';
|
|
import CogIcon from '@images/cog.svg';
|
|
|
|
/**
|
|
* Component comprising a trigger button and dropdown with additional post options.
|
|
*
|
|
* @param {Object} props
|
|
* @param {Object} props.passedData The current post options data
|
|
* @param {Function} props.onSaveDraft Callback for when the post draft is saved
|
|
* @param {Function} props.onConfigChange Callback for when the config options have changed
|
|
*/
|
|
|
|
function toISOStringLocal(datetime) {
|
|
const month = (datetime.getMonth() + 1).toString().padStart(2, '0');
|
|
const day = datetime.getDate().toString().padStart(2, '0');
|
|
return [
|
|
datetime.getFullYear(),
|
|
'-',
|
|
month,
|
|
'-',
|
|
day,
|
|
'T',
|
|
datetime.toLocaleTimeString(),
|
|
].join('');
|
|
}
|
|
|
|
export const Options = ({
|
|
passedData: {
|
|
published = false,
|
|
publishedAt = '',
|
|
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
allSeries = [],
|
|
canonicalUrl = '',
|
|
series = '',
|
|
},
|
|
schedulingEnabled,
|
|
onSaveDraft,
|
|
onConfigChange,
|
|
previewLoading,
|
|
}) => {
|
|
let publishedField = '';
|
|
let existingSeries = '';
|
|
let publishedAtField = '';
|
|
const publishedDate = new Date(publishedAt);
|
|
const currentDate = new Date();
|
|
|
|
const localPublishedAt = toISOStringLocal(publishedDate);
|
|
const minPublishedAt = toISOStringLocal(currentDate);
|
|
|
|
const readonlyPublishedAt = published && publishedDate < currentDate;
|
|
|
|
if (allSeries.length > 0) {
|
|
const seriesNames = allSeries.map((name, index) => {
|
|
return (
|
|
<option key={`series-${index}`} value={name}>
|
|
{name}
|
|
</option>
|
|
);
|
|
});
|
|
existingSeries = (
|
|
<div className="crayons-field__description">
|
|
Existing series:
|
|
{` `}
|
|
<select
|
|
value=""
|
|
name="series"
|
|
className="crayons-select"
|
|
onInput={onConfigChange}
|
|
required
|
|
aria-label="Select one of the existing series"
|
|
>
|
|
<option value="" disabled>
|
|
Select...
|
|
</option>
|
|
{seriesNames}
|
|
</select>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (published) {
|
|
publishedField = (
|
|
<div data-testid="options__danger-zone" className="crayons-field mb-6">
|
|
<div className="crayons-field__label color-accent-danger">
|
|
Danger Zone
|
|
</div>
|
|
<Button variant="primary" destructive onClick={onSaveDraft}>
|
|
Unpublish post
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
if (schedulingEnabled && !readonlyPublishedAt) {
|
|
publishedAtField = (
|
|
<div className="crayons-field mb-6">
|
|
<label htmlFor="publishedAt" className="crayons-field__label">
|
|
Schedule Publication
|
|
</label>
|
|
<input
|
|
type="datetime-local"
|
|
min={minPublishedAt}
|
|
value={localPublishedAt} // "2022-04-28T15:00:00"
|
|
className="crayons-textfield"
|
|
name="publishedAt"
|
|
onChange={onConfigChange}
|
|
id="publishedAt"
|
|
placeholder="..."
|
|
/>
|
|
<input
|
|
type="hidden"
|
|
value={timezone} // "Asia/Magadan"
|
|
className="crayons-textfield"
|
|
name="timezone"
|
|
id="timezone"
|
|
placeholder="..."
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="s:relative">
|
|
<Button
|
|
id="post-options-btn"
|
|
icon={CogIcon}
|
|
title="Post options"
|
|
aria-label="Post options"
|
|
disabled={previewLoading}
|
|
/>
|
|
|
|
<Dropdown
|
|
triggerButtonId="post-options-btn"
|
|
dropdownContentId="post-options-dropdown"
|
|
dropdownContentCloseButtonId="post-options-done-btn"
|
|
className="reverse left-2 s:left-0 right-2 s:left-auto p-4"
|
|
>
|
|
<h3 className="mb-6">Post options</h3>
|
|
<div className="crayons-field mb-6">
|
|
<label htmlFor="canonicalUrl" className="crayons-field__label">
|
|
Canonical URL
|
|
</label>
|
|
<p className="crayons-field__description">
|
|
Change meta tag
|
|
{` `}
|
|
<code>canonical_url</code>
|
|
{` `}
|
|
if this post was first published elsewhere (like your own blog).
|
|
</p>
|
|
<input
|
|
type="text"
|
|
value={canonicalUrl}
|
|
className="crayons-textfield"
|
|
placeholder="https://yoursite.com/post-title"
|
|
name="canonicalUrl"
|
|
onKeyUp={onConfigChange}
|
|
id="canonicalUrl"
|
|
/>
|
|
</div>
|
|
{publishedAtField}
|
|
<div className="crayons-field mb-6">
|
|
<label htmlFor="series" className="crayons-field__label">
|
|
Series
|
|
</label>
|
|
<p className="crayons-field__description">
|
|
Will this post be part of a series? Give the series a unique name.
|
|
(Series visible once it has multiple posts)
|
|
</p>
|
|
<input
|
|
type="text"
|
|
value={series}
|
|
className="crayons-textfield"
|
|
name="series"
|
|
onKeyUp={onConfigChange}
|
|
id="series"
|
|
placeholder="..."
|
|
/>
|
|
{existingSeries}
|
|
</div>
|
|
{publishedField}
|
|
<Button
|
|
id="post-options-done-btn"
|
|
className="w-100"
|
|
data-content="exit"
|
|
variant="secondary"
|
|
>
|
|
Done
|
|
</Button>
|
|
</Dropdown>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
Options.propTypes = {
|
|
passedData: PropTypes.shape({
|
|
published: PropTypes.bool.isRequired,
|
|
publishedAt: PropTypes.string.isRequired,
|
|
timezone: PropTypes.string.isRequired,
|
|
allSeries: PropTypes.array.isRequired,
|
|
canonicalUrl: PropTypes.string.isRequired,
|
|
series: PropTypes.string.isRequired,
|
|
}).isRequired,
|
|
schedulingEnabled: PropTypes.bool.isRequired,
|
|
onSaveDraft: PropTypes.func.isRequired,
|
|
onConfigChange: PropTypes.func.isRequired,
|
|
previewLoading: PropTypes.bool.isRequired,
|
|
};
|
|
|
|
Options.displayName = 'Options';
|