mirror of
https://github.com/discourse/discourse.git
synced 2026-08-07 13:19:19 +08:00
Previously, the RSS Polling plugin's admin page predated Discourse's admin interface guidelines: it was a single inline-editable table with no breadcrumbs or page header, it truncated long feed URLs, it required horizontal scrolling on mobile, and it gave admins no way to verify a feed before saving it. This change rebuilds the page on the standard plugin "show route" structure (breadcrumbs, page header, and Settings + Feeds tabs) with a `d-table` feed list and dedicated FormKit new/edit routes, so the UI now matches the rest of the admin interface and works on mobile. It also: - Adds a **Test feed** dry-run that fetches a feed and previews which items would be imported or skipped, with plain-language reasons, so admins can validate a feed (and fix setup errors) before saving it. - Adds the `rss_polling_feed_request_timeout` and `rss_polling_verbose_logging` site settings. - Extracts the fetch/parse and import/skip logic into `FeedFetcher` and `FeedAnalyzer`, shared by the poll job and the new preview so the dry-run and the real import always agree. - Normalizes feed dates and categories so Atom feeds (which use `<updated>` and `term`-style `<category>` elements) are handled like RSS, and returns a clean error instead of a 500 when a feed cannot be read. --------- Co-authored-by: Martin Brennan <martin@discourse.org>
400 lines
6.7 KiB
SCSS
Vendored
400 lines
6.7 KiB
SCSS
Vendored
@use "lib/viewport";
|
|
|
|
@mixin feed-url {
|
|
font-family: var(--d-font-family--monospace);
|
|
font-weight: normal;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
.rss-polling-feed-editor {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-4);
|
|
}
|
|
|
|
.rss-polling-feed-form {
|
|
.form-kit__fieldset {
|
|
width: 100%;
|
|
}
|
|
|
|
&__discourse-fields {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-4);
|
|
|
|
> .form-kit__field {
|
|
flex: 1 1 14em;
|
|
}
|
|
}
|
|
|
|
&__buttons {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-2);
|
|
align-items: center;
|
|
}
|
|
|
|
&__enabled-control {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
&__enabled {
|
|
font-weight: 600;
|
|
}
|
|
|
|
&__tag-requirement {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
margin-top: var(--space-1);
|
|
font-size: var(--font-down-1);
|
|
color: var(--primary-medium);
|
|
}
|
|
}
|
|
|
|
.rss-polling-feed-test {
|
|
margin-top: var(--space-4);
|
|
|
|
&__error {
|
|
margin-top: var(--space-4);
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
&__header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
&__title {
|
|
margin: 0;
|
|
font-size: var(--font-0);
|
|
font-weight: 600;
|
|
}
|
|
|
|
&__count {
|
|
color: var(--primary-medium);
|
|
font-size: var(--font-down-1);
|
|
}
|
|
|
|
&__dismiss {
|
|
margin-inline-start: auto;
|
|
}
|
|
|
|
&__panel {
|
|
max-height: 22em;
|
|
overflow-y: auto;
|
|
margin-top: var(--space-2);
|
|
border: 1px solid var(--primary-low);
|
|
border-radius: var(--d-border-radius);
|
|
}
|
|
|
|
&__list {
|
|
margin: 0;
|
|
padding: 0;
|
|
list-style: none;
|
|
}
|
|
|
|
&__item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: var(--space-2);
|
|
padding: var(--space-2);
|
|
border-inline-start: 3px solid transparent;
|
|
|
|
& + & {
|
|
border-top: 1px solid var(--primary-low);
|
|
}
|
|
|
|
&.--import {
|
|
border-inline-start-color: var(--success);
|
|
}
|
|
|
|
&.--update {
|
|
border-inline-start-color: var(--tertiary);
|
|
}
|
|
|
|
&.--skip {
|
|
border-inline-start-color: var(--primary-low-mid);
|
|
|
|
.rss-polling-feed-test__item-title {
|
|
color: var(--primary-high);
|
|
}
|
|
}
|
|
}
|
|
|
|
&__body {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-half);
|
|
min-width: 0;
|
|
}
|
|
|
|
&__item-title {
|
|
overflow-wrap: anywhere;
|
|
|
|
&:hover {
|
|
text-decoration: underline;
|
|
}
|
|
}
|
|
|
|
&__item-date,
|
|
&__item-reason {
|
|
font-size: var(--font-down-1);
|
|
color: var(--primary-medium);
|
|
}
|
|
|
|
&__item-reason a {
|
|
color: var(--tertiary);
|
|
|
|
&:hover {
|
|
text-decoration: underline;
|
|
}
|
|
}
|
|
|
|
&__more,
|
|
&__empty {
|
|
color: var(--primary-medium);
|
|
font-size: var(--font-down-1);
|
|
}
|
|
}
|
|
|
|
.rss-polling-feed {
|
|
.d-table__overview-name.rss-polling-feed__url {
|
|
@include feed-url;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.d-table__cell.--overview {
|
|
width: 100%;
|
|
}
|
|
|
|
&__meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
margin-top: var(--space-1);
|
|
font-size: var(--font-down-1);
|
|
line-height: var(--line-height-medium);
|
|
color: var(--primary-medium);
|
|
|
|
> * {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
> * + *::before {
|
|
content: "·";
|
|
margin-inline: var(--space-2);
|
|
color: var(--primary-low-mid);
|
|
}
|
|
|
|
a,
|
|
.badge-category__name,
|
|
.discourse-tag {
|
|
font-size: inherit;
|
|
}
|
|
|
|
.avatar {
|
|
width: 1.5em;
|
|
height: 1.5em;
|
|
}
|
|
}
|
|
|
|
&__author {
|
|
gap: var(--space-2);
|
|
|
|
a {
|
|
color: var(--primary-high);
|
|
|
|
&:hover {
|
|
color: var(--primary);
|
|
}
|
|
}
|
|
}
|
|
|
|
&__tags {
|
|
flex-wrap: wrap;
|
|
gap: var(--space-1);
|
|
}
|
|
|
|
&__filter {
|
|
gap: var(--space-1);
|
|
|
|
.d-icon {
|
|
color: var(--primary-medium);
|
|
}
|
|
}
|
|
|
|
// Set on the .d-toggle-switch root, which defines these vars and passes them
|
|
// to the slider; the __toggle class lands on the inner button, a sibling that
|
|
// can't reach the slider via inheritance.
|
|
.d-toggle-switch {
|
|
--toggle-switch-width: 36px;
|
|
--toggle-switch-height: 20px;
|
|
}
|
|
|
|
&.is-disabled {
|
|
.d-table__overview-link,
|
|
.rss-polling-feed__meta {
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
.d-table__cell-actions {
|
|
align-items: center;
|
|
}
|
|
|
|
@include viewport.until(md) {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
|
|
.d-table__cell {
|
|
font-size: var(--font-down-1);
|
|
border-top: 0;
|
|
}
|
|
|
|
.d-table__cell.--overview {
|
|
flex: 0 0 100%;
|
|
padding-inline: 0;
|
|
border-bottom: 1px solid var(--content-border-color);
|
|
|
|
.d-table__overview-link {
|
|
padding-inline: var(--space-2);
|
|
}
|
|
|
|
.d-table__overview-name {
|
|
font-size: var(--font-0);
|
|
}
|
|
}
|
|
|
|
&__meta {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: var(--space-1);
|
|
margin-top: var(--space-2);
|
|
padding: var(--space-2) var(--space-2) 0;
|
|
border-top: 1px solid var(--content-border-color);
|
|
|
|
.d-table__mobile-label {
|
|
min-width: 8.5em;
|
|
font-weight: 700;
|
|
color: var(--primary);
|
|
}
|
|
|
|
> * {
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
> * + *::before {
|
|
content: none;
|
|
}
|
|
}
|
|
|
|
&__filter .d-icon {
|
|
display: none;
|
|
}
|
|
|
|
&__status {
|
|
flex: 1 1 auto;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
|
|
.d-table__mobile-label {
|
|
color: var(--primary-medium);
|
|
}
|
|
}
|
|
|
|
.d-table__cell.--controls {
|
|
flex: 0 0 auto;
|
|
position: static;
|
|
}
|
|
|
|
.d-table__cell-actions {
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
}
|
|
|
|
.rss-polling-feed-history {
|
|
&__table {
|
|
margin: 0;
|
|
}
|
|
|
|
.d-button-label {
|
|
display: none;
|
|
}
|
|
|
|
&__attempt {
|
|
&.--danger .rss-polling-feed-history__counts {
|
|
color: var(--danger);
|
|
}
|
|
|
|
.d-table__cell.--overview {
|
|
width: 100%;
|
|
}
|
|
|
|
@include viewport.from(md) {
|
|
border-top: 1px solid var(--primary-low);
|
|
|
|
&:first-child {
|
|
border-top: 0;
|
|
}
|
|
|
|
> .d-table__cell.--detail {
|
|
text-align: end;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
}
|
|
|
|
&__summary {
|
|
display: inline-flex;
|
|
gap: var(--space-2);
|
|
align-items: center;
|
|
text-align: start;
|
|
padding-inline-start: 0;
|
|
padding-inline-end: calc(var(--space-8) * 6);
|
|
|
|
svg.d-icon {
|
|
margin-inline-end: 0;
|
|
}
|
|
}
|
|
|
|
&__date {
|
|
font-weight: 600;
|
|
}
|
|
|
|
&__counts {
|
|
color: var(--primary-medium);
|
|
font-size: var(--font-down-1);
|
|
}
|
|
|
|
&__detail > .d-table__cell {
|
|
padding-top: 0;
|
|
|
|
@include viewport.from(md) {
|
|
padding-inline: var(--space-2);
|
|
}
|
|
}
|
|
|
|
&__more {
|
|
margin-top: var(--space-2);
|
|
}
|
|
|
|
&__empty {
|
|
color: var(--primary-medium);
|
|
font-size: var(--font-down-1);
|
|
}
|
|
|
|
.rss-polling-feed-test__list {
|
|
margin-top: var(--space-1);
|
|
}
|
|
}
|