mirror of
https://github.com/discourse/discourse.git
synced 2025-01-16 03:42:41 +08:00
UX: admins embedding page follows admin ux guideline (#30122)
Conversion of /admin/customize/embedding page to follow admin UX guidelines.
This commit is contained in:
parent
02113fc22a
commit
407fa69778
|
@ -6,7 +6,7 @@ import icon from "discourse-common/helpers/d-icon";
|
|||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class AdminConfigAreaCard extends Component {
|
||||
@tracked collapsed = false;
|
||||
@tracked collapsed = this.args.collapsed;
|
||||
|
||||
get computedHeading() {
|
||||
if (this.args.heading) {
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { inject as controller } from "@ember/controller";
|
||||
import { hash } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import BackButton from "discourse/components/back-button";
|
||||
import Form from "discourse/components/form";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import AdminConfigAreaCard from "admin/components/admin-config-area-card";
|
||||
import CategoryChooser from "select-kit/components/category-chooser";
|
||||
import TagChooser from "select-kit/components/tag-chooser";
|
||||
import UserChooser from "select-kit/components/user-chooser";
|
||||
|
||||
export default class AdminEmbeddingHostForm extends Component {
|
||||
@service router;
|
||||
@service site;
|
||||
@service store;
|
||||
@controller adminEmbedding;
|
||||
|
||||
get isEditing() {
|
||||
return this.args.host;
|
||||
}
|
||||
|
||||
get header() {
|
||||
return this.isEditing
|
||||
? "admin.embedding.host_form.edit_header"
|
||||
: "admin.embedding.host_form.add_header";
|
||||
}
|
||||
|
||||
get formData() {
|
||||
if (!this.isEditing) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
host: this.args.host.host,
|
||||
allowed_paths: this.args.host.allowed_paths,
|
||||
category: this.args.host.category_id,
|
||||
tags: this.args.host.tags,
|
||||
user: isEmpty(this.args.host.user) ? null : [this.args.host.user],
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
async save(data) {
|
||||
const host = this.args.host || this.store.createRecord("embeddable-host");
|
||||
|
||||
try {
|
||||
await host.save({
|
||||
...data,
|
||||
user: data.user?.at(0),
|
||||
category_id: data.category,
|
||||
});
|
||||
if (!this.isEditing) {
|
||||
this.adminEmbedding.embedding.embeddable_hosts.push(host);
|
||||
}
|
||||
this.router.transitionTo("adminEmbedding");
|
||||
} catch (error) {
|
||||
popupAjaxError(error);
|
||||
}
|
||||
}
|
||||
|
||||
<template>
|
||||
<BackButton @route="adminEmbedding" @label="admin.embedding.back" />
|
||||
<div class="admin-config-area">
|
||||
<div class="admin-config-area__primary-content admin-embedding-host-form">
|
||||
<AdminConfigAreaCard @heading={{this.header}}>
|
||||
<:content>
|
||||
<Form @onSubmit={{this.save}} @data={{this.formData}} as |form|>
|
||||
<form.Field
|
||||
@name="host"
|
||||
@title={{i18n "admin.embedding.host"}}
|
||||
@validation="required"
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
<field.Input placeholder="example.com" />
|
||||
</form.Field>
|
||||
<form.Field
|
||||
@name="allowed_paths"
|
||||
@title={{i18n "admin.embedding.allowed_paths"}}
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
<field.Input placeholder="/blog/.*" />
|
||||
</form.Field>
|
||||
<form.Field
|
||||
@name="category"
|
||||
@title={{i18n "admin.embedding.category"}}
|
||||
as |field|
|
||||
>
|
||||
<field.Custom>
|
||||
<CategoryChooser
|
||||
@value={{field.value}}
|
||||
@onChange={{field.set}}
|
||||
class="admin-embedding-host-form__category"
|
||||
/>
|
||||
</field.Custom>
|
||||
</form.Field>
|
||||
<form.Field
|
||||
@name="tags"
|
||||
@title={{i18n "admin.embedding.tags"}}
|
||||
as |field|
|
||||
>
|
||||
<field.Custom>
|
||||
<TagChooser
|
||||
@tags={{field.value}}
|
||||
@everyTag={{true}}
|
||||
@excludeSynonyms={{true}}
|
||||
@unlimitedTagCount={{true}}
|
||||
@onChange={{field.set}}
|
||||
@options={{hash
|
||||
filterPlaceholder="category.tags_placeholder"
|
||||
}}
|
||||
class="admin-embedding-host-form__tags"
|
||||
/>
|
||||
</field.Custom>
|
||||
</form.Field>
|
||||
<form.Field
|
||||
@name="user"
|
||||
@title={{i18n "admin.embedding.post_author"}}
|
||||
as |field|
|
||||
>
|
||||
<field.Custom>
|
||||
<UserChooser
|
||||
@value={{field.value}}
|
||||
@onChange={{field.set}}
|
||||
@options={{hash maximum=1 excludeCurrentUser=false}}
|
||||
class="admin-embedding-host-form__post_author"
|
||||
/>
|
||||
</field.Custom>
|
||||
</form.Field>
|
||||
|
||||
<form.Submit @label="admin.embedding.host_form.save" />
|
||||
</Form>
|
||||
</:content>
|
||||
</AdminConfigAreaCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import categoryBadge from "discourse/helpers/category-badge";
|
||||
import Category from "discourse/models/category";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class EmbeddableHost extends Component {
|
||||
@service dialog;
|
||||
@tracked category = null;
|
||||
@tracked tags = null;
|
||||
@tracked user = null;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.host = this.args.host;
|
||||
const categoryId =
|
||||
this.host.category_id || this.site.uncategorized_category_id;
|
||||
const category = Category.findById(categoryId);
|
||||
|
||||
this.category = category;
|
||||
this.tags = (this.host.tags || []).join(", ");
|
||||
this.user = this.host.user;
|
||||
}
|
||||
|
||||
@action
|
||||
delete() {
|
||||
return this.dialog.confirm({
|
||||
message: i18n("admin.embedding.confirm_delete"),
|
||||
didConfirm: () => {
|
||||
return this.host.destroyRecord().then(() => {
|
||||
this.args.deleteHost(this.host);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
<template>
|
||||
<tr class="d-admin-row__content">
|
||||
<td class="d-admin-row__detail">
|
||||
{{this.host.host}}
|
||||
</td>
|
||||
<td class="d-admin-row__detail">
|
||||
{{this.host.allowed_paths}}
|
||||
</td>
|
||||
<td class="d-admin-row__detail">
|
||||
{{categoryBadge this.category allowUncategorized=true}}
|
||||
</td>
|
||||
<td class="d-admin-row__detail">
|
||||
{{this.tags}}
|
||||
</td>
|
||||
<td class="d-admin-row__detail">
|
||||
{{this.user}}
|
||||
</td>
|
||||
|
||||
<td class="d-admin-row__controls">
|
||||
<div class="d-admin-row__controls-options">
|
||||
<DButton
|
||||
class="btn-small admin-embeddable-host-item__edit"
|
||||
@route="adminEmbedding.edit"
|
||||
@routeModels={{this.host}}
|
||||
@label="admin.embedding.edit"
|
||||
/>
|
||||
<DButton
|
||||
@action={{this.delete}}
|
||||
@label="admin.embedding.delete"
|
||||
class="btn-default btn-small admin-embeddable-host-item__delete"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
{{#if this.editing}}
|
||||
<td class="editing-input">
|
||||
<div class="label">{{i18n "admin.embedding.host"}}</div>
|
||||
<Input
|
||||
@value={{this.buffered.host}}
|
||||
placeholder="example.com"
|
||||
@enter={{this.save}}
|
||||
class="host-name"
|
||||
autofocus={{true}}
|
||||
/>
|
||||
</td>
|
||||
<td class="editing-input">
|
||||
<div class="label">{{i18n "admin.embedding.allowed_paths"}}</div>
|
||||
<Input
|
||||
@value={{this.buffered.allowed_paths}}
|
||||
placeholder="/blog/.*"
|
||||
@enter={{this.save}}
|
||||
class="path-allowlist"
|
||||
/>
|
||||
</td>
|
||||
<td class="editing-input">
|
||||
<div class="label">{{i18n "admin.embedding.category"}}</div>
|
||||
<CategoryChooser
|
||||
@value={{this.category.id}}
|
||||
@onChangeCategory={{fn (mut this.category)}}
|
||||
class="small"
|
||||
/>
|
||||
</td>
|
||||
<td class="editing-input">
|
||||
<div class="label">{{i18n "admin.embedding.tags"}}</div>
|
||||
<TagChooser
|
||||
@tags={{this.tags}}
|
||||
@everyTag={{true}}
|
||||
@excludeSynonyms={{true}}
|
||||
@unlimitedTagCount={{true}}
|
||||
@onChange={{fn (mut this.tags)}}
|
||||
@options={{hash filterPlaceholder="category.tags_placeholder"}}
|
||||
/>
|
||||
</td>
|
||||
<td class="editing-input">
|
||||
<div class="label">{{i18n "admin.embedding.user"}}</div>
|
||||
<UserChooser
|
||||
@value={{this.user}}
|
||||
@onChange={{action "onUserChange"}}
|
||||
@options={{hash maximum=1 excludeCurrentUser=false}}
|
||||
/>
|
||||
</td>
|
||||
<td class="editing-controls">
|
||||
<DButton
|
||||
@icon="check"
|
||||
@action={{this.save}}
|
||||
@disabled={{this.cantSave}}
|
||||
class="btn-primary"
|
||||
/>
|
||||
<DButton
|
||||
@icon="xmark"
|
||||
@action={{this.cancel}}
|
||||
@disabled={{this.host.isSaving}}
|
||||
class="btn-danger"
|
||||
/>
|
||||
</td>
|
||||
{{else}}
|
||||
<td>
|
||||
<div class="label">{{i18n "admin.embedding.host"}}</div>
|
||||
{{this.host.host}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="label">
|
||||
{{i18n "admin.embedding.allowed_paths"}}
|
||||
</div>
|
||||
{{this.host.allowed_paths}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="label">{{i18n "admin.embedding.category"}}</div>
|
||||
{{category-badge this.category allowUncategorized=true}}
|
||||
</td>
|
||||
<td>
|
||||
{{this.tags}}
|
||||
</td>
|
||||
<td>
|
||||
{{this.user}}
|
||||
</td>
|
||||
<td class="controls">
|
||||
<DButton @icon="pencil" @action={{this.edit}} />
|
||||
<DButton @icon="trash-can" @action={{this.delete}} class="btn-danger" />
|
||||
</td>
|
||||
{{/if}}
|
|
@ -1,101 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import { or } from "@ember/object/computed";
|
||||
import { service } from "@ember/service";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { tagName } from "@ember-decorators/component";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { bufferedProperty } from "discourse/mixins/buffered-content";
|
||||
import Category from "discourse/models/category";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
@tagName("tr")
|
||||
export default class EmbeddableHost extends Component.extend(
|
||||
bufferedProperty("host")
|
||||
) {
|
||||
@service dialog;
|
||||
editToggled = false;
|
||||
categoryId = null;
|
||||
category = null;
|
||||
tags = null;
|
||||
user = null;
|
||||
|
||||
@or("host.isNew", "editToggled") editing;
|
||||
|
||||
init() {
|
||||
super.init(...arguments);
|
||||
|
||||
const host = this.host;
|
||||
const categoryId = host.category_id || this.site.uncategorized_category_id;
|
||||
const category = Category.findById(categoryId);
|
||||
|
||||
this.set("category", category);
|
||||
this.set("tags", host.tags || []);
|
||||
this.set("user", host.user);
|
||||
}
|
||||
|
||||
@discourseComputed("buffered.host", "host.isSaving")
|
||||
cantSave(host, isSaving) {
|
||||
return isSaving || isEmpty(host);
|
||||
}
|
||||
|
||||
@action
|
||||
edit() {
|
||||
this.set("editToggled", true);
|
||||
}
|
||||
|
||||
@action
|
||||
onUserChange(user) {
|
||||
this.set("user", user);
|
||||
}
|
||||
|
||||
@action
|
||||
save() {
|
||||
if (this.cantSave) {
|
||||
return;
|
||||
}
|
||||
|
||||
const props = this.buffered.getProperties(
|
||||
"host",
|
||||
"allowed_paths",
|
||||
"class_name"
|
||||
);
|
||||
props.category_id = this.category.id;
|
||||
props.tags = this.tags;
|
||||
props.user =
|
||||
Array.isArray(this.user) && this.user.length > 0 ? this.user[0] : null;
|
||||
|
||||
const host = this.host;
|
||||
|
||||
host
|
||||
.save(props)
|
||||
.then(() => {
|
||||
this.set("editToggled", false);
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
|
||||
@action
|
||||
delete() {
|
||||
return this.dialog.confirm({
|
||||
message: i18n("admin.embedding.confirm_delete"),
|
||||
didConfirm: () => {
|
||||
return this.host.destroyRecord().then(() => {
|
||||
this.deleteHost(this.host);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
cancel() {
|
||||
const host = this.host;
|
||||
if (host.get("isNew")) {
|
||||
this.deleteHost(host);
|
||||
} else {
|
||||
this.rollbackBuffer();
|
||||
this.set("editToggled", false);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
{{#if this.isCheckbox}}
|
||||
<label for={{this.inputId}}>
|
||||
<Input @checked={{this.checked}} id={{this.inputId}} @type="checkbox" />
|
||||
{{i18n this.translationKey}}
|
||||
</label>
|
||||
{{else}}
|
||||
<label for={{this.inputId}}>{{i18n this.translationKey}}</label>
|
||||
<Input
|
||||
@value={{this.value}}
|
||||
id={{this.inputId}}
|
||||
placeholder={{this.placeholder}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<div class="clearfix"></div>
|
|
@ -1,32 +0,0 @@
|
|||
import Component from "@ember/component";
|
||||
import { computed } from "@ember/object";
|
||||
import { dasherize } from "@ember/string";
|
||||
import { classNames } from "@ember-decorators/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
@classNames("embed-setting")
|
||||
export default class EmbeddingSetting extends Component {
|
||||
@discourseComputed("field")
|
||||
inputId(field) {
|
||||
return dasherize(field);
|
||||
}
|
||||
|
||||
@discourseComputed("field")
|
||||
translationKey(field) {
|
||||
return `admin.embedding.${field}`;
|
||||
}
|
||||
|
||||
@discourseComputed("type")
|
||||
isCheckbox(type) {
|
||||
return type === "checkbox";
|
||||
}
|
||||
|
||||
@computed("value")
|
||||
get checked() {
|
||||
return !!this.value;
|
||||
}
|
||||
|
||||
set checked(value) {
|
||||
this.set("value", value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class AdminEmbeddingCrawlersController extends Controller {
|
||||
@service toasts;
|
||||
@controller adminEmbedding;
|
||||
|
||||
get formData() {
|
||||
const embedding = this.adminEmbedding.embedding;
|
||||
|
||||
return {
|
||||
allowed_embed_selectors: embedding.allowed_embed_selectors,
|
||||
blocked_embed_selectors: embedding.blocked_embed_selectors,
|
||||
allowed_embed_classnames: embedding.allowed_embed_classnames,
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
async save(data) {
|
||||
const embedding = this.adminEmbedding.embedding;
|
||||
|
||||
try {
|
||||
await embedding.update({
|
||||
type: "crawlers",
|
||||
...data,
|
||||
});
|
||||
this.toasts.success({
|
||||
duration: 1500,
|
||||
data: { message: i18n("admin.embedding.crawler_settings_saved") },
|
||||
});
|
||||
} catch (error) {
|
||||
popupAjaxError(error);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import { service } from "@ember/service";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default class AdminEmbeddingIndexController extends Controller {
|
||||
@service router;
|
||||
@service site;
|
||||
@controller adminEmbedding;
|
||||
@alias("adminEmbedding.embedding") embedding;
|
||||
|
||||
get showEmbeddingCode() {
|
||||
return !this.site.isMobileDevice;
|
||||
}
|
||||
|
||||
@discourseComputed("embedding.base_url")
|
||||
embeddingCode(baseUrl) {
|
||||
const html = `<div id='discourse-comments'></div>
|
||||
<meta name='discourse-username' content='DISCOURSE_USERNAME'>
|
||||
|
||||
<script type="text/javascript">
|
||||
DiscourseEmbed = {
|
||||
discourseUrl: '${baseUrl}/',
|
||||
discourseEmbedUrl: 'EMBED_URL',
|
||||
// className: 'CLASS_NAME',
|
||||
};
|
||||
|
||||
(function() {
|
||||
var d = document.createElement('script'); d.type = 'text/javascript'; d.async = true;
|
||||
d.src = DiscourseEmbed.discourseUrl + 'javascripts/embed.js';
|
||||
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(d);
|
||||
})();
|
||||
</script>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
@action
|
||||
deleteHost(host) {
|
||||
this.get("embedding.embeddable_hosts").removeObject(host);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class AdminEmbeddingPostsAndTopicsController extends Controller {
|
||||
@service toasts;
|
||||
@controller adminEmbedding;
|
||||
|
||||
get formData() {
|
||||
const embedding = this.adminEmbedding.embedding;
|
||||
|
||||
return {
|
||||
embed_by_username: isEmpty(embedding.embed_by_username)
|
||||
? null
|
||||
: [embedding.embed_by_username],
|
||||
embed_post_limit: embedding.embed_post_limit,
|
||||
embed_title_scrubber: embedding.embed_title_scrubber,
|
||||
embed_truncate: embedding.embed_truncate,
|
||||
embed_unlisted: embedding.embed_unlisted,
|
||||
};
|
||||
}
|
||||
|
||||
@action
|
||||
async save(data) {
|
||||
const embedding = this.adminEmbedding.embedding;
|
||||
|
||||
try {
|
||||
await embedding.update({
|
||||
type: "posts_and_topics",
|
||||
...data,
|
||||
embed_by_username: data.embed_by_username[0],
|
||||
});
|
||||
this.toasts.success({
|
||||
duration: 1500,
|
||||
data: {
|
||||
message: i18n("admin.embedding.posts_and_topics_settings_saved"),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
popupAjaxError(error);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import AdminAreaSettingsBaseController from "admin/controllers/admin-area-settings-base";
|
||||
|
||||
export default class AdminEmbeddingSettingsController extends AdminAreaSettingsBaseController {}
|
|
@ -1,61 +1,3 @@
|
|||
import Controller from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
|
||||
export default class AdminEmbeddingController extends Controller {
|
||||
saved = false;
|
||||
embedding = null;
|
||||
|
||||
// show settings if we have at least one created host
|
||||
@discourseComputed("embedding.embeddable_hosts.@each.isCreated")
|
||||
showSecondary() {
|
||||
const hosts = this.get("embedding.embeddable_hosts");
|
||||
return hosts.length && hosts.findBy("isCreated");
|
||||
}
|
||||
|
||||
@discourseComputed("embedding.base_url")
|
||||
embeddingCode(baseUrl) {
|
||||
const html = `<div id='discourse-comments'></div>
|
||||
<meta name='discourse-username' content='DISCOURSE_USERNAME'>
|
||||
|
||||
<script type="text/javascript">
|
||||
DiscourseEmbed = {
|
||||
discourseUrl: '${baseUrl}/',
|
||||
discourseEmbedUrl: 'EMBED_URL',
|
||||
// className: 'CLASS_NAME',
|
||||
};
|
||||
|
||||
(function() {
|
||||
var d = document.createElement('script'); d.type = 'text/javascript'; d.async = true;
|
||||
d.src = DiscourseEmbed.discourseUrl + 'javascripts/embed.js';
|
||||
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(d);
|
||||
})();
|
||||
</script>`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
@action
|
||||
saveChanges() {
|
||||
const embedding = this.embedding;
|
||||
const updates = embedding.getProperties(embedding.get("fields"));
|
||||
|
||||
this.set("saved", false);
|
||||
this.embedding
|
||||
.update(updates)
|
||||
.then(() => this.set("saved", true))
|
||||
.catch(popupAjaxError);
|
||||
}
|
||||
|
||||
@action
|
||||
addHost() {
|
||||
const host = this.store.createRecord("embeddable-host");
|
||||
this.get("embedding.embeddable_hosts").pushObject(host);
|
||||
}
|
||||
|
||||
@action
|
||||
deleteHost(host) {
|
||||
this.get("embedding.embeddable_hosts").removeObject(host);
|
||||
}
|
||||
}
|
||||
export default class AdminEmbeddingController extends Controller {}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class AdminEmbeddingEditRoute extends DiscourseRoute {
|
||||
async model(params) {
|
||||
const embedding = await this.store.find("embedding");
|
||||
return embedding.embeddable_hosts.find(
|
||||
(host) => host.id === parseInt(params.id, 10)
|
||||
);
|
||||
}
|
||||
|
||||
titleToken() {
|
||||
return i18n("admin.embedding.host_form.edit_header");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class AdminEmbeddingNewRoute extends DiscourseRoute {
|
||||
titleToken() {
|
||||
return i18n("admin.embedding.host_form.add_header");
|
||||
}
|
||||
}
|
|
@ -65,10 +65,21 @@ export default function () {
|
|||
this.route("edit", { path: "/:id" });
|
||||
}
|
||||
);
|
||||
this.route("adminEmbedding", {
|
||||
path: "/embedding",
|
||||
resetNamespace: true,
|
||||
});
|
||||
this.route(
|
||||
"adminEmbedding",
|
||||
{
|
||||
path: "/embedding",
|
||||
resetNamespace: true,
|
||||
},
|
||||
function () {
|
||||
this.route("index", { path: "/" });
|
||||
this.route("settings");
|
||||
this.route("postsAndTopics", { path: "/posts_and_topics" });
|
||||
this.route("crawlers");
|
||||
this.route("new");
|
||||
this.route("edit", { path: "/:id" });
|
||||
}
|
||||
);
|
||||
this.route(
|
||||
"adminCustomizeEmailTemplates",
|
||||
{ path: "/email_templates", resetNamespace: true },
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<DPageSubheader
|
||||
@titleLabel={{i18n "admin.embedding.crawlers"}}
|
||||
@descriptionLabel={{i18n "admin.embedding.crawlers_description"}}
|
||||
/>
|
||||
|
||||
<Form @onSubmit={{this.save}} @data={{this.formData}} as |form|>
|
||||
<form.Field
|
||||
@name="allowed_embed_selectors"
|
||||
@title={{i18n "admin.embedding.allowed_embed_selectors"}}
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
<field.Input placeholder="article, #story, .post" />
|
||||
</form.Field>
|
||||
<form.Field
|
||||
@name="blocked_embed_selectors"
|
||||
@title={{i18n "admin.embedding.blocked_embed_selectors"}}
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
<field.Input placeholder=".ad-unit, header" />
|
||||
</form.Field>
|
||||
<form.Field
|
||||
@name="allowed_embed_classnames"
|
||||
@title={{i18n "admin.embedding.allowed_embed_classnames"}}
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
<field.Input placeholder="emoji, classname" />
|
||||
</form.Field>
|
||||
<form.Submit @label="admin.embedding.save" />
|
||||
</Form>
|
|
@ -0,0 +1 @@
|
|||
<AdminEmbeddingHostForm @host={{this.model}} />
|
|
@ -0,0 +1,49 @@
|
|||
{{#if this.embedding.embeddable_hosts}}
|
||||
{{#if this.showEmbeddingCode}}
|
||||
<AdminConfigAreaCard
|
||||
@heading="admin.embedding.configuration_snippet"
|
||||
@collapsable={{true}}
|
||||
@collapsed={{true}}
|
||||
class="admin-embedding-index__code"
|
||||
>
|
||||
<:content>
|
||||
{{html-safe (i18n "admin.embedding.sample")}}
|
||||
<HighlightedCode @code={{this.embeddingCode}} @lang="html" />
|
||||
</:content>
|
||||
</AdminConfigAreaCard>
|
||||
{{/if}}
|
||||
|
||||
<table class="d-admin-table">
|
||||
<thead>
|
||||
<th>{{i18n "admin.embedding.host"}}</th>
|
||||
<th>{{i18n "admin.embedding.allowed_paths"}}</th>
|
||||
<th>{{i18n "admin.embedding.category"}}</th>
|
||||
<th>{{i18n "admin.embedding.tags"}}</th>
|
||||
{{#if this.embedding.embed_by_username}}
|
||||
<th>{{i18n
|
||||
"admin.embedding.post_author_with_default"
|
||||
author=this.embedding.embed_by_username
|
||||
}}</th>
|
||||
{{else}}
|
||||
<th>{{i18n "admin.embedding.post_author"}}</th>
|
||||
{{/if}}
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each this.embedding.embeddable_hosts as |host|}}
|
||||
<EmbeddableHost @host={{host}} @deleteHost={{action "deleteHost"}} />
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<AdminConfigAreaEmptyList
|
||||
@ctaLabel="admin.embedding.add_host"
|
||||
@ctaRoute="adminEmbedding.new"
|
||||
@ctaClass="admin-embedding__add-host"
|
||||
@emptyLabel="admin.embedding.get_started"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<PluginOutlet
|
||||
@name="after-embeddable-hosts-table"
|
||||
@outletArgs={{hash embedding=this.embedding}}
|
||||
/>
|
|
@ -0,0 +1 @@
|
|||
<AdminEmbeddingHostForm />
|
|
@ -0,0 +1,53 @@
|
|||
<DPageSubheader @titleLabel={{i18n "admin.embedding.posts_and_topics"}} />
|
||||
|
||||
<Form @onSubmit={{this.save}} @data={{this.formData}} as |form|>
|
||||
<form.Field
|
||||
@name="embed_by_username"
|
||||
@title={{i18n "admin.embedding.embed_by_username"}}
|
||||
@validation="required"
|
||||
as |field|
|
||||
>
|
||||
<field.Custom>
|
||||
<UserChooser
|
||||
@value={{field.value}}
|
||||
@onChange={{field.set}}
|
||||
@options={{hash maximum=1 excludeCurrentUser=false}}
|
||||
class="admin-embedding-posts-and-topics-form__embed_by_username"
|
||||
/>
|
||||
</field.Custom>
|
||||
</form.Field>
|
||||
<form.Field
|
||||
@name="embed_post_limit"
|
||||
@title={{i18n "admin.embedding.embed_post_limit"}}
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
<field.Input />
|
||||
</form.Field>
|
||||
<form.Field
|
||||
@name="embed_title_scrubber"
|
||||
@title={{i18n "admin.embedding.embed_title_scrubber"}}
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
<field.Input placeholder="- site.com$" />
|
||||
</form.Field>
|
||||
<form.CheckboxGroup as |checkboxGroup|>
|
||||
<checkboxGroup.Field
|
||||
@name="embed_truncate"
|
||||
@title={{i18n "admin.embedding.embed_truncate"}}
|
||||
as |field|
|
||||
>
|
||||
<field.Checkbox />
|
||||
</checkboxGroup.Field>
|
||||
|
||||
<checkboxGroup.Field
|
||||
@name="embed_unlisted"
|
||||
@title={{i18n "admin.embedding.embed_unlisted"}}
|
||||
as |field|
|
||||
>
|
||||
<field.Checkbox />
|
||||
</checkboxGroup.Field>
|
||||
</form.CheckboxGroup>
|
||||
<form.Submit @label="admin.embedding.save" />
|
||||
</Form>
|
|
@ -0,0 +1,6 @@
|
|||
<AdminAreaSettings
|
||||
@area="embedding"
|
||||
@path="/admin/customize/embedding/settings"
|
||||
@filter={{this.filter}}
|
||||
@adminSettingsFilterChangedCallback={{this.adminSettingsFilterChangedCallback}}
|
||||
/>
|
|
@ -1,111 +1,49 @@
|
|||
<div class="embeddable-hosts">
|
||||
{{#if this.embedding.embeddable_hosts}}
|
||||
<table class="embedding grid">
|
||||
<thead>
|
||||
<th style="width: 18%">{{i18n "admin.embedding.host"}}</th>
|
||||
<th style="width: 18%">{{i18n "admin.embedding.allowed_paths"}}</th>
|
||||
<th style="width: 18%">{{i18n "admin.embedding.category"}}</th>
|
||||
<th style="width: 18%">{{i18n "admin.embedding.tags"}}</th>
|
||||
{{#if this.embedding.embed_by_username}}
|
||||
<th style="width: 18%">{{i18n
|
||||
"admin.embedding.post_author"
|
||||
author=this.embedding.embed_by_username
|
||||
}}</th>
|
||||
{{else}}
|
||||
<th style="width: 18%">{{i18n "admin.embedding.post_author"}}</th>
|
||||
{{/if}}
|
||||
<th style="width: 10%"> </th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each this.embedding.embeddable_hosts as |host|}}
|
||||
<EmbeddableHost @host={{host}} @deleteHost={{action "deleteHost"}} />
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<p>{{i18n "admin.embedding.get_started"}}</p>
|
||||
{{/if}}
|
||||
<div class="admin-embedding admin-config-page">
|
||||
<DPageHeader
|
||||
@titleLabel={{i18n "admin.embedding.title"}}
|
||||
@descriptionLabel={{i18n "admin.embedding.description"}}
|
||||
@learnMoreUrl="https://meta.discourse.org/t/embed-discourse-comments-on-another-website-via-javascript/31963"
|
||||
>
|
||||
<:breadcrumbs>
|
||||
<DBreadcrumbsItem @path="/admin" @label={{i18n "admin_title"}} />
|
||||
<DBreadcrumbsItem
|
||||
@path="/admin/customize/embedding"
|
||||
@label={{i18n "admin.embedding.title"}}
|
||||
/>
|
||||
</:breadcrumbs>
|
||||
<:actions as |actions|>
|
||||
<actions.Primary
|
||||
@route="adminEmbedding.new"
|
||||
@title="admin.embedding.add_host"
|
||||
@label="admin.embedding.add_host"
|
||||
class="admin-embedding__header-add-host"
|
||||
/>
|
||||
</:actions>
|
||||
<:tabs>
|
||||
<NavItem
|
||||
@route="adminEmbedding.settings"
|
||||
@label="admin.embedding.nav.settings"
|
||||
class="admin-embedding-tabs__settings"
|
||||
/>
|
||||
<NavItem
|
||||
@route="adminEmbedding.index"
|
||||
@label="admin.embedding.nav.hosts"
|
||||
class="admin-embedding-tabs__hosts"
|
||||
/>
|
||||
<NavItem
|
||||
@route="adminEmbedding.postsAndTopics"
|
||||
@label="admin.embedding.nav.posts_and_topics"
|
||||
class="admin-embedding-tabs__posts-and-topics"
|
||||
/>
|
||||
<NavItem
|
||||
@route="adminEmbedding.crawlers"
|
||||
@label="admin.embedding.nav.crawlers"
|
||||
class="admin-embedding-tabs__crawlers"
|
||||
/>
|
||||
</:tabs>
|
||||
</DPageHeader>
|
||||
|
||||
<DButton
|
||||
@label="admin.embedding.add_host"
|
||||
@action={{this.addHost}}
|
||||
@icon="plus"
|
||||
class="btn-primary add-host"
|
||||
/>
|
||||
|
||||
<PluginOutlet
|
||||
@name="after-embeddable-hosts-table"
|
||||
@outletArgs={{hash embedding=this.embedding}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{#if this.showSecondary}}
|
||||
<div class="embedding-secondary">
|
||||
{{html-safe (i18n "admin.embedding.sample")}}
|
||||
<HighlightedCode @code={{this.embeddingCode}} @lang="html" />
|
||||
<div class="admin-container admin-config-page__main-area">
|
||||
{{outlet}}
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="embedding-secondary">
|
||||
<h3>{{i18n "admin.embedding.settings"}}</h3>
|
||||
|
||||
<EmbeddingSetting
|
||||
@field="embed_by_username"
|
||||
@value={{this.embedding.embed_by_username}}
|
||||
/>
|
||||
<EmbeddingSetting
|
||||
@field="embed_post_limit"
|
||||
@value={{this.embedding.embed_post_limit}}
|
||||
/>
|
||||
<EmbeddingSetting
|
||||
@field="embed_title_scrubber"
|
||||
@value={{this.embedding.embed_title_scrubber}}
|
||||
@placeholder="- site.com$"
|
||||
/>
|
||||
<EmbeddingSetting
|
||||
@field="embed_truncate"
|
||||
@value={{this.embedding.embed_truncate}}
|
||||
@type="checkbox"
|
||||
/>
|
||||
<EmbeddingSetting
|
||||
@field="embed_unlisted"
|
||||
@value={{this.embedding.embed_unlisted}}
|
||||
@type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="embedding-secondary">
|
||||
<h3>{{i18n "admin.embedding.crawling_settings"}}</h3>
|
||||
<p class="description">{{i18n "admin.embedding.crawling_description"}}</p>
|
||||
|
||||
<EmbeddingSetting
|
||||
@field="allowed_embed_selectors"
|
||||
@value={{this.embedding.allowed_embed_selectors}}
|
||||
@placeholder="article, #story, .post"
|
||||
/>
|
||||
|
||||
<EmbeddingSetting
|
||||
@field="blocked_embed_selectors"
|
||||
@value={{this.embedding.blocked_embed_selectors}}
|
||||
@placeholder=".ad-unit, header"
|
||||
/>
|
||||
|
||||
<EmbeddingSetting
|
||||
@field="allowed_embed_classnames"
|
||||
@value={{this.embedding.allowed_embed_classnames}}
|
||||
@placeholder="emoji, classname"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="embedding-secondary">
|
||||
<DButton
|
||||
@label="admin.embedding.save"
|
||||
@action={{this.saveChanges}}
|
||||
@disabled={{this.embedding.isSaving}}
|
||||
class="btn-primary embed-save"
|
||||
/>
|
||||
|
||||
{{#if this.saved}}{{i18n "saved"}}{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -68,7 +68,7 @@
|
|||
class="btn-small admin-permalink-item__edit"
|
||||
@route="adminPermalinks.edit"
|
||||
@routeModels={{pl}}
|
||||
@label="admin.config_areas.flags.edit"
|
||||
@label="admin.config_areas.permalinks.edit"
|
||||
/>
|
||||
|
||||
<DMenu
|
||||
|
@ -84,7 +84,7 @@
|
|||
@action={{fn this.destroyRecord pl}}
|
||||
@icon="trash-can"
|
||||
class="btn-transparent admin-permalink-item__delete"
|
||||
@label="admin.config_areas.flags.delete"
|
||||
@label="admin.config_areas.permalinks.delete"
|
||||
/>
|
||||
</dropdown.item>
|
||||
</DropdownMenu>
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
@route="adminPermalinks.new"
|
||||
@title="admin.permalink.add"
|
||||
@label="admin.permalink.add"
|
||||
@icon="plus"
|
||||
class="admin-permalinks__header-add-permalink"
|
||||
/>
|
||||
</:actions>
|
||||
|
|
|
@ -36,6 +36,20 @@ module("Integration | Component | AdminConfigAreaCard", function (hooks) {
|
|||
assert.dom(".admin-config-area-card__content").exists();
|
||||
});
|
||||
|
||||
test("renders admin config area card with toggle button and collapsed by default", async function (assert) {
|
||||
await render(<template>
|
||||
<AdminConfigAreaCard
|
||||
@translatedHeading="test heading"
|
||||
@collapsable={{true}}
|
||||
@collapsed={{true}}
|
||||
><:content>test</:content></AdminConfigAreaCard>
|
||||
</template>);
|
||||
|
||||
assert.dom(".admin-config-area-card__title").exists();
|
||||
assert.dom(".admin-config-area-card__toggle-button").exists();
|
||||
assert.dom(".admin-config-area-card__content").doesNotExist();
|
||||
});
|
||||
|
||||
test("renders admin config area card with header action", async function (assert) {
|
||||
await render(<template>
|
||||
<AdminConfigAreaCard
|
||||
|
|
|
@ -897,26 +897,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
.embedding-secondary {
|
||||
h3 {
|
||||
margin: 1em 0;
|
||||
}
|
||||
margin-bottom: 2em;
|
||||
.embed-setting {
|
||||
input[type="text"] {
|
||||
width: 50%;
|
||||
.admin-embedding {
|
||||
.admin-embeddable-host-item__delete {
|
||||
&:hover {
|
||||
svg.d-icon {
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
}
|
||||
svg.d-icon {
|
||||
color: var(--primary-low-mid);
|
||||
}
|
||||
margin: 0.75em 0;
|
||||
}
|
||||
p.description {
|
||||
color: var(--primary-medium);
|
||||
margin-bottom: 1em;
|
||||
max-width: 700px;
|
||||
}
|
||||
}
|
||||
|
||||
.embedding td input {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.user-fields {
|
||||
|
|
|
@ -26,7 +26,7 @@ class Admin::EmbeddableHostsController < Admin::AdminController
|
|||
host.host = params[:embeddable_host][:host]
|
||||
host.allowed_paths = params[:embeddable_host][:allowed_paths]
|
||||
host.category_id = params[:embeddable_host][:category_id]
|
||||
host.category_id = SiteSetting.uncategorized_category_id if host.category_id.blank?
|
||||
host.category_id = SiteSetting.uncategorized_category_id if host.category.blank?
|
||||
|
||||
username = params[:embeddable_host][:user]
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@ class Admin::EmbeddingController < Admin::AdminController
|
|||
end
|
||||
|
||||
def update
|
||||
if params[:embedding][:embed_by_username].blank?
|
||||
return render_json_error(I18n.t("site_settings.embed_username_required"))
|
||||
end
|
||||
raise InvalidAccess if !(%w[posts_and_topics crawlers].include?(params[:embedding][:type]))
|
||||
|
||||
Embedding.settings.each { |s| @embedding.public_send("#{s}=", params[:embedding][s]) }
|
||||
Embedding
|
||||
.send("#{params[:embedding][:type]}_settings")
|
||||
.each { |s| @embedding.public_send("#{s}=", params[:embedding][s]) }
|
||||
|
||||
if @embedding.save
|
||||
fetch_embedding
|
||||
|
@ -22,6 +22,12 @@ class Admin::EmbeddingController < Admin::AdminController
|
|||
end
|
||||
end
|
||||
|
||||
def new
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def fetch_embedding
|
||||
|
|
|
@ -6,16 +6,15 @@ class Embedding < OpenStruct
|
|||
include HasErrors
|
||||
|
||||
def self.settings
|
||||
%i[
|
||||
embed_by_username
|
||||
embed_post_limit
|
||||
embed_title_scrubber
|
||||
embed_truncate
|
||||
embed_unlisted
|
||||
allowed_embed_selectors
|
||||
blocked_embed_selectors
|
||||
allowed_embed_classnames
|
||||
]
|
||||
posts_and_topics_settings | crawlers_settings
|
||||
end
|
||||
|
||||
def self.posts_and_topics_settings
|
||||
%i[embed_by_username embed_post_limit embed_title_scrubber embed_truncate embed_unlisted]
|
||||
end
|
||||
|
||||
def self.crawlers_settings
|
||||
%i[allowed_embed_selectors blocked_embed_selectors allowed_embed_classnames]
|
||||
end
|
||||
|
||||
def base_url
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SiteSetting < ActiveRecord::Base
|
||||
VALID_AREAS = %w[flags about emojis permalinks notifications]
|
||||
VALID_AREAS = %w[about embedding emojis flags notifications permalinks]
|
||||
|
||||
extend GlobalPath
|
||||
extend SiteSettingExtension
|
||||
|
|
|
@ -5750,6 +5750,12 @@ en:
|
|||
title: "More options"
|
||||
move_up: "Move up"
|
||||
move_down: "Move down"
|
||||
permalinks:
|
||||
edit: "Edit"
|
||||
delete: "Delete"
|
||||
embeddable_host:
|
||||
edit: "Edit"
|
||||
delete: "Delete"
|
||||
look_and_feel:
|
||||
title: "Look and feel"
|
||||
description: "Customize and brand your Discourse site, giving it a distinctive style."
|
||||
|
@ -7310,6 +7316,7 @@ en:
|
|||
|
||||
embedding:
|
||||
get_started: "If you'd like to embed Discourse on another website, begin by adding its host."
|
||||
delete: "Delete"
|
||||
confirm_delete: "Are you sure you want to delete that host?"
|
||||
sample: |
|
||||
<p>Paste the following HTML code into your site to create and embed Discourse topics. Replace <b>EMBED_URL</b> with the canonical URL of the page you are embedding it on.</p>
|
||||
|
@ -7318,16 +7325,18 @@ en:
|
|||
|
||||
<p>Replace <b>DISCOURSE_USERNAME</b> with the Discourse username of the author that should create the topic. Discourse will automatically lookup the user by the <code>content</code> attribute of the <code><meta></code> tags with <code>name</code> attribute set to <code>discourse-username</code> or <code>author</code>. The <code>discourseUserName</code> parameter has been deprecated and will be removed in Discourse 3.2.</p>
|
||||
title: "Embedding"
|
||||
host: "Allowed Hosts"
|
||||
allowed_paths: "Path Allowlist"
|
||||
edit: "edit"
|
||||
category: "Post to Category"
|
||||
tags: "Topic Tags"
|
||||
post_author: "Post Author - Defaults to %{author}"
|
||||
add_host: "Add Host"
|
||||
settings: "Embedding Settings"
|
||||
crawling_settings: "Crawler Settings"
|
||||
crawling_description: "When Discourse creates topics for your posts, if no RSS/ATOM feed is present it will attempt to parse your content out of your HTML. Sometimes it can be challenging to extract your content, so we provide the ability to specify CSS rules to make extraction easier."
|
||||
description: "Discourse has the ability to embed the comments from a topic in a remote site using a Javascript API that creates an IFRAME"
|
||||
host: "Allowed hosts"
|
||||
allowed_paths: "Path allowlist"
|
||||
edit: "Edit"
|
||||
category: "Post to category"
|
||||
tags: "Topic tags"
|
||||
post_author: "Post author"
|
||||
post_author_with_default: "Post author (defaults to %{author})"
|
||||
add_host: "Add host"
|
||||
posts_and_topics: "Posts and Topics configuration"
|
||||
crawlers: "Crawlers configuration"
|
||||
crawlers_description: "When Discourse creates topics for your posts, if no RSS/ATOM feed is present it will attempt to parse your content out of your HTML. Sometimes it can be challenging to extract your content, so we provide the ability to specify CSS rules to make extraction easier."
|
||||
|
||||
embed_by_username: "Username for topic creation"
|
||||
embed_post_limit: "Maximum number of posts to embed"
|
||||
|
@ -7337,7 +7346,20 @@ en:
|
|||
allowed_embed_selectors: "CSS selector for elements that are allowed in embeds"
|
||||
blocked_embed_selectors: "CSS selector for elements that are removed from embeds"
|
||||
allowed_embed_classnames: "Allowed CSS class names"
|
||||
save: "Save Embedding Settings"
|
||||
save: "Save"
|
||||
posts_and_topics_settings_saved: "Posts and Topics settings saved"
|
||||
crawler_settings_saved: "Crawler settings saved"
|
||||
back: "Back to Embedding"
|
||||
configuration_snippet: "Configuration snippet"
|
||||
host_form:
|
||||
add_header: "Add host"
|
||||
edit_header: "Edit host"
|
||||
save: "Save"
|
||||
nav:
|
||||
hosts: "Hosts"
|
||||
settings: "Settings"
|
||||
posts_and_topics: "Posts and Topics"
|
||||
crawlers: "Crawlers"
|
||||
|
||||
permalink:
|
||||
title: "Permalinks"
|
||||
|
|
|
@ -223,6 +223,7 @@ Discourse::Application.routes.draw do
|
|||
get "config/permalinks" => "permalinks#index", :constraints => AdminConstraint.new
|
||||
get "customize/embedding" => "embedding#show", :constraints => AdminConstraint.new
|
||||
put "customize/embedding" => "embedding#update", :constraints => AdminConstraint.new
|
||||
get "customize/embedding/:id" => "embedding#edit", :constraints => AdminConstraint.new
|
||||
|
||||
resources :themes,
|
||||
only: %i[index create show update destroy],
|
||||
|
|
|
@ -1212,14 +1212,30 @@ posting:
|
|||
choices:
|
||||
- code-fences
|
||||
- 4-spaces-indent
|
||||
embed_any_origin: false
|
||||
embed_topics_list: false
|
||||
embed_set_canonical_url: false
|
||||
embed_unlisted: false
|
||||
import_embed_unlisted: true
|
||||
embed_truncate: true
|
||||
embed_support_markdown: false
|
||||
allowed_embed_selectors: ""
|
||||
embed_any_origin:
|
||||
default: false
|
||||
area: "embedding"
|
||||
embed_topics_list:
|
||||
default: false
|
||||
area: "embedding"
|
||||
embed_set_canonical_url:
|
||||
default: false
|
||||
area: "embedding"
|
||||
embed_unlisted:
|
||||
default: false
|
||||
area: "embedding"
|
||||
import_embed_unlisted:
|
||||
default: true
|
||||
area: "embedding"
|
||||
embed_truncate:
|
||||
default: true
|
||||
area: "embedding"
|
||||
embed_support_markdown:
|
||||
default: false
|
||||
area: "embedding"
|
||||
allowed_embed_selectors:
|
||||
default: ""
|
||||
area: "embedding"
|
||||
allowed_href_schemes:
|
||||
client: true
|
||||
default: ""
|
||||
|
|
|
@ -43,10 +43,11 @@ RSpec.describe Admin::EmbeddingController do
|
|||
context "when logged in as an admin" do
|
||||
before { sign_in(admin) }
|
||||
|
||||
it "updates embedding" do
|
||||
it "updates posts and topics settings" do
|
||||
put "/admin/customize/embedding.json",
|
||||
params: {
|
||||
embedding: {
|
||||
type: "posts_and_topics",
|
||||
embed_by_username: "system",
|
||||
embed_post_limit: 200,
|
||||
},
|
||||
|
@ -56,6 +57,21 @@ RSpec.describe Admin::EmbeddingController do
|
|||
expect(response.parsed_body["embedding"]["embed_by_username"]).to eq("system")
|
||||
expect(response.parsed_body["embedding"]["embed_post_limit"]).to eq(200)
|
||||
end
|
||||
|
||||
it "updates crawlers settings" do
|
||||
put "/admin/customize/embedding.json",
|
||||
params: {
|
||||
embedding: {
|
||||
type: "crawlers",
|
||||
allowed_embed_selectors: "article",
|
||||
blocked_embed_selectors: "p",
|
||||
},
|
||||
}
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
expect(response.parsed_body["embedding"]["allowed_embed_selectors"]).to eq("article")
|
||||
expect(response.parsed_body["embedding"]["blocked_embed_selectors"]).to eq("p")
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples "embedding updates not allowed" do
|
||||
|
@ -63,6 +79,7 @@ RSpec.describe Admin::EmbeddingController do
|
|||
put "/admin/customize/embedding.json",
|
||||
params: {
|
||||
embedding: {
|
||||
type: "posts_and_topics",
|
||||
embed_by_username: "system",
|
||||
embed_post_limit: 200,
|
||||
},
|
||||
|
|
|
@ -3,59 +3,75 @@
|
|||
RSpec.describe "Admin EmbeddableHost Management", type: :system do
|
||||
fab!(:admin)
|
||||
fab!(:author) { Fabricate(:admin) }
|
||||
fab!(:author_2) { Fabricate(:admin) }
|
||||
fab!(:category)
|
||||
fab!(:category2) { Fabricate(:category) }
|
||||
fab!(:category_2) { Fabricate(:category) }
|
||||
fab!(:tag)
|
||||
fab!(:tag2) { Fabricate(:tag) }
|
||||
fab!(:tag_2) { Fabricate(:tag) }
|
||||
|
||||
before { sign_in(admin) }
|
||||
|
||||
it "allows admin to add and edit embeddable hosts" do
|
||||
visit "/admin/customize/embedding"
|
||||
let(:admin_embedding_page) { PageObjects::Pages::AdminEmbedding.new }
|
||||
let(:admin_embedding_host_form_page) { PageObjects::Pages::AdminEmbeddingHostForm.new }
|
||||
let(:admin_embedding_posts_and_topics_page) do
|
||||
PageObjects::Pages::AdminEmbeddingPostsAndTopics.new
|
||||
end
|
||||
|
||||
find("button.btn-icon-text", text: "Add Host").click
|
||||
within find("tr.ember-view") do
|
||||
find('input[placeholder="example.com"]').set("awesome-discourse-site.local")
|
||||
find('input[placeholder="/blog/.*"]').set("/blog/.*")
|
||||
it "allows admin to add, edit and delete embeddable hosts" do
|
||||
admin_embedding_page.visit
|
||||
|
||||
category_chooser = PageObjects::Components::SelectKit.new(".category-chooser")
|
||||
category_chooser.expand
|
||||
category_chooser.select_row_by_name(category.name)
|
||||
expect(page).not_to have_css(".admin-embedding-index__code")
|
||||
|
||||
tag_chooser = PageObjects::Components::SelectKit.new(".tag-chooser")
|
||||
tag_chooser.expand
|
||||
tag_chooser.select_row_by_name(tag.name)
|
||||
admin_embedding_page.click_add_host
|
||||
|
||||
admin_embedding_host_form_page.fill_in_allowed_hosts("awesome-discourse-site.local")
|
||||
admin_embedding_host_form_page.fill_in_path_allow_list("/blog/.*")
|
||||
admin_embedding_host_form_page.fill_in_category(category)
|
||||
admin_embedding_host_form_page.fill_in_tags(tag)
|
||||
admin_embedding_host_form_page.fill_in_post_author(author)
|
||||
admin_embedding_host_form_page.click_save
|
||||
|
||||
find(".user-chooser").click
|
||||
find(".select-kit-body .select-kit-filter input").fill_in with: author.username
|
||||
find(".select-kit-body", text: author.username).click
|
||||
end
|
||||
find("td.editing-controls .btn.btn-primary").click
|
||||
expect(page).to have_content("awesome-discourse-site.local")
|
||||
expect(page).to have_content("/blog/.*")
|
||||
expect(page).not_to have_content("#{tag.name},#{tag2.name}")
|
||||
expect(page).to have_content("#{tag.name}")
|
||||
expect(page).to have_content("#{category.name}")
|
||||
expect(page).to have_content("#{author.username}")
|
||||
|
||||
# Editing
|
||||
expect(page).to have_css(".admin-embedding-index__code")
|
||||
|
||||
find(".embeddable-hosts tr:first-child .controls svg.d-icon-pencil").find(:xpath, "..").click
|
||||
admin_embedding_page.click_edit_host
|
||||
|
||||
within find(".embeddable-hosts tr:first-child.ember-view") do
|
||||
find('input[placeholder="example.com"]').set("updated-example.com")
|
||||
find('input[placeholder="/blog/.*"]').set("/updated-blog/.*")
|
||||
admin_embedding_host_form_page.fill_in_allowed_hosts("updated-example.com")
|
||||
admin_embedding_host_form_page.fill_in_path_allow_list("/updated-blog/.*")
|
||||
admin_embedding_host_form_page.fill_in_category(category_2)
|
||||
admin_embedding_host_form_page.fill_in_tags(tag_2)
|
||||
admin_embedding_host_form_page.fill_in_post_author(author_2)
|
||||
admin_embedding_host_form_page.click_save
|
||||
|
||||
category_chooser = PageObjects::Components::SelectKit.new(".category-chooser")
|
||||
category_chooser.expand
|
||||
category_chooser.select_row_by_name(category2.name)
|
||||
|
||||
tag_chooser = PageObjects::Components::SelectKit.new(".tag-chooser")
|
||||
tag_chooser.expand
|
||||
tag_chooser.select_row_by_name(tag2.name)
|
||||
end
|
||||
|
||||
find("td.editing-controls .btn.btn-primary").click
|
||||
expect(page).to have_content("updated-example.com")
|
||||
expect(page).to have_content("/updated-blog/.*")
|
||||
expect(page).to have_content("#{tag.name},#{tag2.name}")
|
||||
expect(page).to have_content("#{tag.name}, #{tag_2.name}")
|
||||
expect(page).to have_content("#{category_2.name}")
|
||||
expect(page).to have_content("#{author_2.username}")
|
||||
|
||||
admin_embedding_page.click_delete
|
||||
admin_embedding_page.confirm_delete
|
||||
|
||||
expect(page).not_to have_css(".admin-embedding-index__code")
|
||||
end
|
||||
|
||||
it "allows admin to save posts and topics settings" do
|
||||
Fabricate(:embeddable_host)
|
||||
|
||||
admin_embedding_page.visit
|
||||
expect(page).not_to have_content("#{author.username}")
|
||||
|
||||
admin_embedding_page.click_posts_and_topics_tab
|
||||
|
||||
admin_embedding_posts_and_topics_page.fill_in_embed_by_username(author)
|
||||
admin_embedding_posts_and_topics_page.click_save
|
||||
|
||||
admin_embedding_page.click_hosts_tab
|
||||
expect(page).to have_content("#{author.username}")
|
||||
end
|
||||
end
|
||||
|
|
41
spec/system/page_objects/pages/admin_embedding.rb
Normal file
41
spec/system/page_objects/pages/admin_embedding.rb
Normal file
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PageObjects
|
||||
module Pages
|
||||
class AdminEmbedding < PageObjects::Pages::Base
|
||||
def visit
|
||||
page.visit("/admin/customize/embedding")
|
||||
self
|
||||
end
|
||||
|
||||
def click_posts_and_topics_tab
|
||||
find(".admin-embedding-tabs__posts-and-topics").click
|
||||
end
|
||||
|
||||
def click_hosts_tab
|
||||
find(".admin-embedding-tabs__hosts").click
|
||||
end
|
||||
|
||||
def click_add_host
|
||||
find(".admin-embedding__header-add-host").click
|
||||
self
|
||||
end
|
||||
|
||||
def click_edit_host
|
||||
find(".admin-embeddable-host-item__edit").click
|
||||
self
|
||||
end
|
||||
|
||||
def click_delete
|
||||
find(".admin-embeddable-host-item__delete").click
|
||||
self
|
||||
end
|
||||
|
||||
def confirm_delete
|
||||
find(".dialog-footer .btn-primary").click
|
||||
expect(page).to have_no_css(".dialog-body", wait: Capybara.default_max_wait_time * 3)
|
||||
self
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
53
spec/system/page_objects/pages/admin_embedding_host_form.rb
Normal file
53
spec/system/page_objects/pages/admin_embedding_host_form.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PageObjects
|
||||
module Pages
|
||||
class AdminEmbeddingHostForm < PageObjects::Pages::Base
|
||||
def fill_in_allowed_hosts(url)
|
||||
form.field("host").fill_in(url)
|
||||
self
|
||||
end
|
||||
|
||||
def fill_in_path_allow_list(path)
|
||||
form.field("allowed_paths").fill_in(path)
|
||||
self
|
||||
end
|
||||
|
||||
def fill_in_category(category)
|
||||
dropdown = PageObjects::Components::SelectKit.new(".admin-embedding-host-form__category")
|
||||
dropdown.expand
|
||||
dropdown.search(category.name)
|
||||
dropdown.select_row_by_value(category.id)
|
||||
dropdown.collapse
|
||||
self
|
||||
end
|
||||
|
||||
def fill_in_tags(tag)
|
||||
dropdown = PageObjects::Components::SelectKit.new(".admin-embedding-host-form__tags")
|
||||
dropdown.expand
|
||||
dropdown.search(tag.name)
|
||||
dropdown.select_row_by_value(tag.name)
|
||||
dropdown.collapse
|
||||
self
|
||||
end
|
||||
|
||||
def fill_in_post_author(author)
|
||||
dropdown = PageObjects::Components::SelectKit.new(".admin-embedding-host-form__post_author")
|
||||
dropdown.expand
|
||||
dropdown.search(author.username)
|
||||
dropdown.select_row_by_value(author.username)
|
||||
dropdown.collapse
|
||||
self
|
||||
end
|
||||
|
||||
def click_save
|
||||
form.submit
|
||||
expect(page).to have_css(".d-admin-table")
|
||||
end
|
||||
|
||||
def form
|
||||
@form ||= PageObjects::Components::FormKit.new(".admin-embedding-host-form .form-kit")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module PageObjects
|
||||
module Pages
|
||||
class AdminEmbeddingPostsAndTopics < PageObjects::Pages::Base
|
||||
def fill_in_embed_by_username(author)
|
||||
dropdown =
|
||||
PageObjects::Components::SelectKit.new(
|
||||
".admin-embedding-posts-and-topics-form__embed_by_username",
|
||||
)
|
||||
dropdown.expand
|
||||
dropdown.search(author.username)
|
||||
dropdown.select_row_by_value(author.username)
|
||||
dropdown.collapse
|
||||
self
|
||||
end
|
||||
|
||||
def click_save
|
||||
form = PageObjects::Components::FormKit.new(".admin-embedding .form-kit")
|
||||
form.submit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user