UX: admins embedding page follows admin ux guideline
Conversion of `/admin/customize/embedding` page to follow admin UX guidelines.
This commit is contained in:
parent
b3fa335c7d
commit
f1a5e4d968
|
@ -0,0 +1,142 @@
|
||||||
|
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 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 isUpdate() {
|
||||||
|
return this.args.host;
|
||||||
|
}
|
||||||
|
|
||||||
|
get header() {
|
||||||
|
return this.isUpdate
|
||||||
|
? "admin.embedding.host_form.edit_header"
|
||||||
|
: "admin.embedding.host_form.add_header";
|
||||||
|
}
|
||||||
|
|
||||||
|
get formData() {
|
||||||
|
if (this.isUpdate) {
|
||||||
|
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: this.args.host.user,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.isUpdate) {
|
||||||
|
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>
|
||||||
|
}
|
|
@ -1,87 +1,45 @@
|
||||||
{{#if this.editing}}
|
<td class="d-admin-row__detail">
|
||||||
<td class="editing-input">
|
{{this.host.host}}
|
||||||
<div class="label">{{i18n "admin.embedding.host"}}</div>
|
</td>
|
||||||
<Input
|
<td class="d-admin-row__detail">
|
||||||
@value={{this.buffered.host}}
|
{{this.host.allowed_paths}}
|
||||||
placeholder="example.com"
|
</td>
|
||||||
@enter={{this.save}}
|
<td class="d-admin-row__detail">
|
||||||
class="host-name"
|
{{category-badge this.category allowUncategorized=true}}
|
||||||
autofocus={{true}}
|
</td>
|
||||||
/>
|
<td class="d-admin-row__detail">
|
||||||
</td>
|
{{this.tags}}
|
||||||
<td class="editing-input">
|
</td>
|
||||||
<div class="label">{{i18n "admin.embedding.allowed_paths"}}</div>
|
<td class="d-admin-row__detail">
|
||||||
<Input
|
{{this.user}}
|
||||||
@value={{this.buffered.allowed_paths}}
|
</td>
|
||||||
placeholder="/blog/.*"
|
|
||||||
@enter={{this.save}}
|
<td class="d-admin-row__controls">
|
||||||
class="path-allowlist"
|
<div class="d-admin-row__controls-options">
|
||||||
/>
|
|
||||||
</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
|
<DButton
|
||||||
@icon="check"
|
class="btn-small admin-embeddable-host-item__edit"
|
||||||
@action={{this.save}}
|
@route="adminEmbedding.edit"
|
||||||
@disabled={{this.cantSave}}
|
@routeModels={{this.host}}
|
||||||
class="btn-primary"
|
@label="admin.embedding.edit"
|
||||||
/>
|
/>
|
||||||
<DButton
|
|
||||||
@icon="xmark"
|
<DMenu
|
||||||
@action={{this.cancel}}
|
@identifier="embedding-host-menu"
|
||||||
@disabled={{this.host.isSaving}}
|
@title={{i18n "admin.embedding.more_options"}}
|
||||||
class="btn-danger"
|
@icon="ellipsis-vertical"
|
||||||
/>
|
>
|
||||||
</td>
|
<:content>
|
||||||
{{else}}
|
<DropdownMenu as |dropdown|>
|
||||||
<td>
|
<dropdown.item>
|
||||||
<div class="label">{{i18n "admin.embedding.host"}}</div>
|
<DButton
|
||||||
{{this.host.host}}
|
@action={{this.delete}}
|
||||||
</td>
|
@icon="trash-can"
|
||||||
<td>
|
class="btn-transparent admin-embeddable-host-item__delete"
|
||||||
<div class="label">
|
@label="admin.embedding.delete"
|
||||||
{{i18n "admin.embedding.allowed_paths"}}
|
/>
|
||||||
</div>
|
</dropdown.item>
|
||||||
{{this.host.allowed_paths}}
|
</DropdownMenu>
|
||||||
</td>
|
</:content>
|
||||||
<td>
|
</DMenu>
|
||||||
<div class="label">{{i18n "admin.embedding.category"}}</div>
|
</div>
|
||||||
{{category-badge this.category allowUncategorized=true}}
|
</td>
|
||||||
</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,28 +1,18 @@
|
||||||
import Component from "@ember/component";
|
import Component from "@ember/component";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { or } from "@ember/object/computed";
|
|
||||||
import { service } from "@ember/service";
|
import { service } from "@ember/service";
|
||||||
import { isEmpty } from "@ember/utils";
|
import { classNames, tagName } from "@ember-decorators/component";
|
||||||
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 Category from "discourse/models/category";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
@tagName("tr")
|
@tagName("tr")
|
||||||
export default class EmbeddableHost extends Component.extend(
|
@classNames("d-admin-row__content")
|
||||||
bufferedProperty("host")
|
export default class EmbeddableHost extends Component {
|
||||||
) {
|
|
||||||
@service dialog;
|
@service dialog;
|
||||||
editToggled = false;
|
|
||||||
categoryId = null;
|
|
||||||
category = null;
|
category = null;
|
||||||
tags = null;
|
tags = null;
|
||||||
user = null;
|
user = null;
|
||||||
|
|
||||||
@or("host.isNew", "editToggled") editing;
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(...arguments);
|
super.init(...arguments);
|
||||||
|
|
||||||
|
@ -31,51 +21,10 @@ export default class EmbeddableHost extends Component.extend(
|
||||||
const category = Category.findById(categoryId);
|
const category = Category.findById(categoryId);
|
||||||
|
|
||||||
this.set("category", category);
|
this.set("category", category);
|
||||||
this.set("tags", host.tags || []);
|
this.set("tags", (host.tags || []).join(", "));
|
||||||
this.set("user", host.user);
|
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
|
@action
|
||||||
delete() {
|
delete() {
|
||||||
return this.dialog.confirm({
|
return this.dialog.confirm({
|
||||||
|
@ -87,15 +36,4 @@ export default class EmbeddableHost extends Component.extend(
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@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,35 @@
|
||||||
|
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 AdminEmbeddingCrawlerSettingsController 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(data);
|
||||||
|
this.toasts.success({
|
||||||
|
duration: 1500,
|
||||||
|
data: { message: i18n("admin.embedding.crawler_settings_saved") },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
popupAjaxError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
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() {
|
||||||
|
const hosts = this.get("embedding.embeddable_hosts");
|
||||||
|
return hosts.length > 0 && !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,42 @@
|
||||||
|
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 AdminEmbeddingSettingsController 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({
|
||||||
|
...data,
|
||||||
|
embed_by_username: data.embed_by_username[0],
|
||||||
|
});
|
||||||
|
this.toasts.success({
|
||||||
|
duration: 1500,
|
||||||
|
data: { message: i18n("admin.embedding.embedding_settings_saved") },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
popupAjaxError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,61 +1,13 @@
|
||||||
import Controller from "@ember/controller";
|
import Controller from "@ember/controller";
|
||||||
import { action } from "@ember/object";
|
import { service } from "@ember/service";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
|
||||||
|
|
||||||
export default class AdminEmbeddingController extends Controller {
|
export default class AdminEmbeddingController extends Controller {
|
||||||
saved = false;
|
@service router;
|
||||||
embedding = null;
|
get showHeader() {
|
||||||
|
return [
|
||||||
// show settings if we have at least one created host
|
"adminEmbedding.index",
|
||||||
@discourseComputed("embedding.embeddable_hosts.@each.isCreated")
|
"adminEmbedding.settings",
|
||||||
showSecondary() {
|
"adminEmbedding.crawler_settings",
|
||||||
const hosts = this.get("embedding.embeddable_hosts");
|
].includes(this.router.currentRouteName);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
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.findBy("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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -96,10 +96,20 @@ export default function () {
|
||||||
this.route("edit", { path: "/:permalink_id" });
|
this.route("edit", { path: "/:permalink_id" });
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.route("adminEmbedding", {
|
this.route(
|
||||||
path: "/embedding",
|
"adminEmbedding",
|
||||||
resetNamespace: true,
|
{
|
||||||
});
|
path: "/embedding",
|
||||||
|
resetNamespace: true,
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
this.route("index", { path: "/" });
|
||||||
|
this.route("settings");
|
||||||
|
this.route("crawler_settings");
|
||||||
|
this.route("new");
|
||||||
|
this.route("edit", { path: "/:id" });
|
||||||
|
}
|
||||||
|
);
|
||||||
this.route(
|
this.route(
|
||||||
"adminCustomizeEmailTemplates",
|
"adminCustomizeEmailTemplates",
|
||||||
{ path: "/email_templates", resetNamespace: true },
|
{ path: "/email_templates", resetNamespace: true },
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<AdminPageSubheader
|
||||||
|
@titleLabelTranslated={{i18n "admin.embedding.crawling_settings"}}
|
||||||
|
@descriptionLabelTranslated={{i18n "admin.embedding.crawling_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,42 @@
|
||||||
|
{{#if this.embedding.embeddable_hosts}}
|
||||||
|
<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"
|
||||||
|
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}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{{#if this.showEmbeddingCode}}
|
||||||
|
<div class="admin-embedding-index__code">
|
||||||
|
{{html-safe (i18n "admin.embedding.sample")}}
|
||||||
|
<HighlightedCode @code={{this.embeddingCode}} @lang="html" />
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
|
@ -0,0 +1 @@
|
||||||
|
<AdminEmbeddingHostForm />
|
|
@ -0,0 +1,53 @@
|
||||||
|
<AdminPageSubheader @titleLabelTranslated={{i18n "admin.embedding.settings"}} />
|
||||||
|
|
||||||
|
<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-settings-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>
|
|
@ -1,111 +1,45 @@
|
||||||
<div class="embeddable-hosts">
|
<div class="admin-embedding admin-config-page">
|
||||||
{{#if this.embedding.embeddable_hosts}}
|
{{#if this.showHeader}}
|
||||||
<table class="embedding grid">
|
<AdminPageHeader
|
||||||
<thead>
|
@titleLabel="admin.embedding.title"
|
||||||
<th style="width: 18%">{{i18n "admin.embedding.host"}}</th>
|
@descriptionLabel="admin.embedding.description"
|
||||||
<th style="width: 18%">{{i18n "admin.embedding.allowed_paths"}}</th>
|
@learnMoreUrl="https://meta.discourse.org/t/embed-discourse-comments-on-another-website-via-javascript/31963"
|
||||||
<th style="width: 18%">{{i18n "admin.embedding.category"}}</th>
|
>
|
||||||
<th style="width: 18%">{{i18n "admin.embedding.tags"}}</th>
|
<:breadcrumbs>
|
||||||
{{#if this.embedding.embed_by_username}}
|
<DBreadcrumbsItem
|
||||||
<th style="width: 18%">{{i18n
|
@path="/admin/customize/embedding"
|
||||||
"admin.embedding.post_author"
|
@label={{i18n "admin.embedding.title"}}
|
||||||
author=this.embedding.embed_by_username
|
/>
|
||||||
}}</th>
|
</:breadcrumbs>
|
||||||
{{else}}
|
<:actions as |actions|>
|
||||||
<th style="width: 18%">{{i18n "admin.embedding.post_author"}}</th>
|
<actions.Primary
|
||||||
{{/if}}
|
@route="adminEmbedding.new"
|
||||||
<th style="width: 10%"> </th>
|
@title="admin.embedding.add_host"
|
||||||
</thead>
|
@label="admin.embedding.add_host"
|
||||||
<tbody>
|
class="admin-embedding__header-add-host"
|
||||||
{{#each this.embedding.embeddable_hosts as |host|}}
|
/>
|
||||||
<EmbeddableHost @host={{host}} @deleteHost={{action "deleteHost"}} />
|
</:actions>
|
||||||
{{/each}}
|
<:tabs>
|
||||||
</tbody>
|
<NavItem
|
||||||
</table>
|
@route="adminEmbedding.index"
|
||||||
{{else}}
|
@label="admin.embedding.nav.hosts"
|
||||||
<p>{{i18n "admin.embedding.get_started"}}</p>
|
class="admin-embedding-tabs__hosts"
|
||||||
|
/>
|
||||||
|
<NavItem
|
||||||
|
@route="adminEmbedding.settings"
|
||||||
|
@label="admin.embedding.nav.embedding_settings"
|
||||||
|
class="admin-embedding-tabs__embedding-settings"
|
||||||
|
/>
|
||||||
|
<NavItem
|
||||||
|
@route="adminEmbedding.crawler_settings"
|
||||||
|
@label="admin.embedding.nav.crawler_settings"
|
||||||
|
class="admin-embedding-tabs__crawler-settings"
|
||||||
|
/>
|
||||||
|
</:tabs>
|
||||||
|
</AdminPageHeader>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<DButton
|
<div class="admin-container admin-config-page__main-area">
|
||||||
@label="admin.embedding.add_host"
|
{{outlet}}
|
||||||
@action={{this.addHost}}
|
</div>
|
||||||
@icon="plus"
|
|
||||||
class="btn-primary add-host"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<PluginOutlet
|
|
||||||
@name="after-embeddable-hosts-table"
|
|
||||||
@outletArgs={{hash embedding=this.embedding}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if this.showSecondary}}
|
|
||||||
<div class="embedding-secondary">
|
|
||||||
{{html-safe (i18n "admin.embedding.sample")}}
|
|
||||||
<HighlightedCode @code={{this.embeddingCode}} @lang="html" />
|
|
||||||
</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}}
|
|
|
@ -68,7 +68,7 @@
|
||||||
class="btn-small admin-permalink-item__edit"
|
class="btn-small admin-permalink-item__edit"
|
||||||
@route="adminPermalinks.edit"
|
@route="adminPermalinks.edit"
|
||||||
@routeModels={{pl}}
|
@routeModels={{pl}}
|
||||||
@label="admin.config_areas.flags.edit"
|
@label="admin.config_areas.permalinks.edit"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DMenu
|
<DMenu
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
@action={{fn this.destroyRecord pl}}
|
@action={{fn this.destroyRecord pl}}
|
||||||
@icon="trash-can"
|
@icon="trash-can"
|
||||||
class="btn-transparent admin-permalink-item__delete"
|
class="btn-transparent admin-permalink-item__delete"
|
||||||
@label="admin.config_areas.flags.delete"
|
@label="admin.config_areas.permalinks.delete"
|
||||||
/>
|
/>
|
||||||
</dropdown.item>
|
</dropdown.item>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
@route="adminPermalinks.new"
|
@route="adminPermalinks.new"
|
||||||
@title="admin.permalink.add"
|
@title="admin.permalink.add"
|
||||||
@label="admin.permalink.add"
|
@label="admin.permalink.add"
|
||||||
@icon="plus"
|
|
||||||
class="admin-permalinks__header-add-permalink"
|
class="admin-permalinks__header-add-permalink"
|
||||||
/>
|
/>
|
||||||
</:actions>
|
</:actions>
|
||||||
|
|
|
@ -897,26 +897,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.embedding-secondary {
|
.admin-embeddable-host-item__delete {
|
||||||
h3 {
|
color: var(--danger);
|
||||||
margin: 1em 0;
|
svg.d-icon {
|
||||||
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
margin-bottom: 2em;
|
|
||||||
.embed-setting {
|
|
||||||
input[type="text"] {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
margin: 0.75em 0;
|
|
||||||
}
|
|
||||||
p.description {
|
|
||||||
color: var(--primary-medium);
|
|
||||||
margin-bottom: 1em;
|
|
||||||
max-width: 700px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.embedding td input {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-fields {
|
.user-fields {
|
||||||
|
|
|
@ -26,7 +26,7 @@ class Admin::EmbeddableHostsController < Admin::AdminController
|
||||||
host.host = params[:embeddable_host][:host]
|
host.host = params[:embeddable_host][:host]
|
||||||
host.allowed_paths = params[:embeddable_host][:allowed_paths]
|
host.allowed_paths = params[:embeddable_host][:allowed_paths]
|
||||||
host.category_id = params[:embeddable_host][:category_id]
|
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]
|
username = params[:embeddable_host][:user]
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,6 @@ class Admin::EmbeddingController < Admin::AdminController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
if params[:embedding][:embed_by_username].blank?
|
|
||||||
return render_json_error(I18n.t("site_settings.embed_username_required"))
|
|
||||||
end
|
|
||||||
|
|
||||||
Embedding.settings.each { |s| @embedding.public_send("#{s}=", params[:embedding][s]) }
|
Embedding.settings.each { |s| @embedding.public_send("#{s}=", params[:embedding][s]) }
|
||||||
|
|
||||||
if @embedding.save
|
if @embedding.save
|
||||||
|
@ -22,6 +18,18 @@ class Admin::EmbeddingController < Admin::AdminController
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
end
|
||||||
|
|
||||||
|
def settings
|
||||||
|
end
|
||||||
|
|
||||||
|
def crawler_settings
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def fetch_embedding
|
def fetch_embedding
|
||||||
|
|
|
@ -5736,6 +5736,12 @@ en:
|
||||||
title: "More options"
|
title: "More options"
|
||||||
move_up: "Move up"
|
move_up: "Move up"
|
||||||
move_down: "Move down"
|
move_down: "Move down"
|
||||||
|
permalinks:
|
||||||
|
edit: "Edit"
|
||||||
|
delete: "Delete"
|
||||||
|
embeddable_host:
|
||||||
|
edit: "Edit"
|
||||||
|
delete: "Delete"
|
||||||
look_and_feel:
|
look_and_feel:
|
||||||
title: "Look and feel"
|
title: "Look and feel"
|
||||||
description: "Customize and brand your Discourse site, giving it a distinctive style."
|
description: "Customize and brand your Discourse site, giving it a distinctive style."
|
||||||
|
@ -7296,6 +7302,7 @@ en:
|
||||||
|
|
||||||
embedding:
|
embedding:
|
||||||
get_started: "If you'd like to embed Discourse on another website, begin by adding its host."
|
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?"
|
confirm_delete: "Are you sure you want to delete that host?"
|
||||||
sample: |
|
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>
|
<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>
|
||||||
|
@ -7304,6 +7311,7 @@ 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>
|
<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"
|
title: "Embedding"
|
||||||
|
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"
|
host: "Allowed Hosts"
|
||||||
allowed_paths: "Path Allowlist"
|
allowed_paths: "Path Allowlist"
|
||||||
edit: "edit"
|
edit: "edit"
|
||||||
|
@ -7324,6 +7332,18 @@ en:
|
||||||
blocked_embed_selectors: "CSS selector for elements that are removed from embeds"
|
blocked_embed_selectors: "CSS selector for elements that are removed from embeds"
|
||||||
allowed_embed_classnames: "Allowed CSS class names"
|
allowed_embed_classnames: "Allowed CSS class names"
|
||||||
save: "Save Embedding Settings"
|
save: "Save Embedding Settings"
|
||||||
|
embedding_settings_saved: "Embedding settings saved."
|
||||||
|
crawler_settings_saved: "Crawler settings saved."
|
||||||
|
back: "Back to Embedding"
|
||||||
|
more_options: "More options"
|
||||||
|
host_form:
|
||||||
|
add_header: "Add host"
|
||||||
|
edit_header: "Edit host"
|
||||||
|
save: "Save"
|
||||||
|
nav:
|
||||||
|
hosts: "Hosts"
|
||||||
|
embedding_settings: "Embedding Settings"
|
||||||
|
crawler_settings: "Crawler Settings"
|
||||||
|
|
||||||
permalink:
|
permalink:
|
||||||
title: "Permalinks"
|
title: "Permalinks"
|
||||||
|
|
|
@ -223,6 +223,11 @@ Discourse::Application.routes.draw do
|
||||||
get "customize/permalinks" => "permalinks#index", :constraints => AdminConstraint.new
|
get "customize/permalinks" => "permalinks#index", :constraints => AdminConstraint.new
|
||||||
get "customize/embedding" => "embedding#show", :constraints => AdminConstraint.new
|
get "customize/embedding" => "embedding#show", :constraints => AdminConstraint.new
|
||||||
put "customize/embedding" => "embedding#update", :constraints => AdminConstraint.new
|
put "customize/embedding" => "embedding#update", :constraints => AdminConstraint.new
|
||||||
|
get "customize/embedding/settings" => "embedding#settings",
|
||||||
|
:constraints => AdminConstraint.new
|
||||||
|
get "customize/embedding/crawler_settings" => "embedding#crawler_settings",
|
||||||
|
:constraints => AdminConstraint.new
|
||||||
|
get "customize/embedding/:id" => "embedding#edit", :constraints => AdminConstraint.new
|
||||||
|
|
||||||
resources :themes,
|
resources :themes,
|
||||||
only: %i[index create show update destroy],
|
only: %i[index create show update destroy],
|
||||||
|
|
|
@ -3,59 +3,74 @@
|
||||||
RSpec.describe "Admin EmbeddableHost Management", type: :system do
|
RSpec.describe "Admin EmbeddableHost Management", type: :system do
|
||||||
fab!(:admin)
|
fab!(:admin)
|
||||||
fab!(:author) { Fabricate(:admin) }
|
fab!(:author) { Fabricate(:admin) }
|
||||||
|
fab!(:author_2) { Fabricate(:admin) }
|
||||||
fab!(:category)
|
fab!(:category)
|
||||||
fab!(:category2) { Fabricate(:category) }
|
fab!(:category_2) { Fabricate(:category) }
|
||||||
fab!(:tag)
|
fab!(:tag)
|
||||||
fab!(:tag2) { Fabricate(:tag) }
|
fab!(:tag_2) { Fabricate(:tag) }
|
||||||
|
|
||||||
before { sign_in(admin) }
|
before { sign_in(admin) }
|
||||||
|
|
||||||
it "allows admin to add and edit embeddable hosts" do
|
let(:admin_embedding_page) { PageObjects::Pages::AdminEmbedding.new }
|
||||||
visit "/admin/customize/embedding"
|
let(:admin_embedding_host_form_page) { PageObjects::Pages::AdminEmbeddingHostForm.new }
|
||||||
|
let(:admin_embedding_settings_page) { PageObjects::Pages::AdminEmbeddingSettings.new }
|
||||||
|
|
||||||
find("button.btn-icon-text", text: "Add Host").click
|
it "allows admin to add, edit and delete embeddable hosts" do
|
||||||
within find("tr.ember-view") do
|
admin_embedding_page.visit
|
||||||
find('input[placeholder="example.com"]').set("awesome-discourse-site.local")
|
|
||||||
find('input[placeholder="/blog/.*"]').set("/blog/.*")
|
|
||||||
|
|
||||||
category_chooser = PageObjects::Components::SelectKit.new(".category-chooser")
|
expect(page).not_to have_css(".admin-embedding-index__code")
|
||||||
category_chooser.expand
|
|
||||||
category_chooser.select_row_by_name(category.name)
|
|
||||||
|
|
||||||
tag_chooser = PageObjects::Components::SelectKit.new(".tag-chooser")
|
admin_embedding_page.click_add_host
|
||||||
tag_chooser.expand
|
|
||||||
tag_chooser.select_row_by_name(tag.name)
|
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("awesome-discourse-site.local")
|
||||||
expect(page).to have_content("/blog/.*")
|
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("#{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
|
admin_embedding_host_form_page.fill_in_allowed_hosts("updated-example.com")
|
||||||
find('input[placeholder="example.com"]').set("updated-example.com")
|
admin_embedding_host_form_page.fill_in_path_allow_list("/updated-blog/.*")
|
||||||
find('input[placeholder="/blog/.*"]').set("/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-example.com")
|
||||||
expect(page).to have_content("/updated-blog/.*")
|
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.open_embedding_host_menu
|
||||||
|
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 embedding settings" do
|
||||||
|
Fabricate(:embeddable_host)
|
||||||
|
|
||||||
|
admin_embedding_page.visit
|
||||||
|
expect(page).not_to have_content("#{author.username}")
|
||||||
|
|
||||||
|
admin_embedding_page.click_embedding_settings_tab
|
||||||
|
|
||||||
|
admin_embedding_settings_page.fill_in_embed_by_username(author)
|
||||||
|
admin_embedding_settings_page.click_save
|
||||||
|
|
||||||
|
admin_embedding_page.click_embedding_hosts_tab
|
||||||
|
expect(page).to have_content("#{author.username}")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module PageObjects
|
||||||
|
module Pages
|
||||||
|
class AdminEmbedding < PageObjects::Pages::Base
|
||||||
|
def visit
|
||||||
|
page.visit("/admin/customize/embedding")
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def click_embedding_settings_tab
|
||||||
|
find(".admin-embedding-tabs__embedding-settings").click
|
||||||
|
end
|
||||||
|
|
||||||
|
def click_embedding_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 open_embedding_host_menu
|
||||||
|
find(".embedding-host-menu-trigger").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
|
|
@ -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 AdminEmbeddingSettings < PageObjects::Pages::Base
|
||||||
|
def fill_in_embed_by_username(author)
|
||||||
|
dropdown =
|
||||||
|
PageObjects::Components::SelectKit.new(
|
||||||
|
".admin-embedding-settings-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