mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-27 12:53:13 +03:00
Add lexical feedback (#16772)
no refs - add lexical feedback modal in the editor, labs, and publish workflows - modal is a basic textarea form --------- Co-authored-by: Djordje Vlaisavljevic <dzvlais@gmail.com>
This commit is contained in:
parent
62f7600aa4
commit
72ed8f56f6
@ -96,7 +96,7 @@
|
||||
</button>
|
||||
</p>
|
||||
{{else}}
|
||||
<p class="gh-publish-confirmation">
|
||||
<p class="gh-publish-confirmation gh-publish-confirmation-with-feedback">
|
||||
<button
|
||||
type="button"
|
||||
class="gh-back-to-editor"
|
||||
@ -105,8 +105,17 @@
|
||||
>
|
||||
<span>Back to editor</span>
|
||||
</button>
|
||||
{{#if (feature 'lexicalEditor')}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-publish-confirmation-feedback"
|
||||
{{on "click" this.openFeedbackLexical}}
|
||||
data-test-button="lexical-feedback"
|
||||
>
|
||||
<span>Beta feedback?</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{/let}}
|
@ -0,0 +1,16 @@
|
||||
import Component from '@glimmer/component';
|
||||
import FeedbackLexicalModal from '../../../modal-feedback-lexical';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
export default class PublishFlowComplete extends Component {
|
||||
@service modals;
|
||||
|
||||
@tracked showFeedbackLexicalModal = false;
|
||||
|
||||
@action
|
||||
async openFeedbackLexical() {
|
||||
await this.modals.open(FeedbackLexicalModal, {post: this.args.publishOptions.post});
|
||||
}
|
||||
}
|
38
ghost/admin/app/components/modal-feedback-lexical.hbs
Normal file
38
ghost/admin/app/components/modal-feedback-lexical.hbs
Normal file
@ -0,0 +1,38 @@
|
||||
<div class="modal-content" data-test-modal="lexical-feedback">
|
||||
<header class="modal-header">
|
||||
<h1>Editor beta feedback</h1>
|
||||
</header>
|
||||
<a class="close" href="" role="button" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span
|
||||
class="hidden">Close</span></a>
|
||||
|
||||
<div class="modal-body gh-modal-feedback-lexical">
|
||||
{{!-- <p>Have any issues? Feedback? Let us know!</p> --}}
|
||||
<form>
|
||||
<GhFormGroup>
|
||||
<label for="feedback-lexical">Have any issues? Feedback? Let us know below!</label>
|
||||
<GhTextarea
|
||||
@id="feedback-lexical"
|
||||
@name="feedback-lexical"
|
||||
@value={{this.feedbackMessage}}
|
||||
@placeholder="I've noticed that..."
|
||||
@shouldFocus={{true}}
|
||||
data-test-lexical-feedback-textarea
|
||||
/>
|
||||
{{!--
|
||||
<GhErrorMessage @errors={{this.member.errors}} @property="note" /> --}}
|
||||
{{!-- <p> Maximum: <b>500</b> characters. You’ve used
|
||||
{{gh-count-down-characters this.feedbackMessage 500}}</p> --}}
|
||||
</GhFormGroup>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="gh-btn" type="button" {{action "closeModal" }}><span>Cancel</span></button>
|
||||
<GhTaskButton
|
||||
@buttonText="Send feedback"
|
||||
@task={{this.submitFeedback}}
|
||||
@class="gh-btn gh-btn-black gh-btn-icon"
|
||||
data-test-button="submit-lexical-feedback"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
69
ghost/admin/app/components/modal-feedback-lexical.js
Normal file
69
ghost/admin/app/components/modal-feedback-lexical.js
Normal file
@ -0,0 +1,69 @@
|
||||
import Component from '@glimmer/component';
|
||||
import {action} from '@ember/object';
|
||||
import {inject} from 'ghost-admin/decorators/inject';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default class FeedbackLexicalModalComponent extends Component {
|
||||
@service ajax;
|
||||
@service ghostPaths;
|
||||
@service session;
|
||||
@service notifications;
|
||||
|
||||
@inject config;
|
||||
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.feedbackMessage = this.args.feedbackMessage;
|
||||
}
|
||||
|
||||
@action
|
||||
closeModal() {
|
||||
this.args.close();
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
*submitFeedback() {
|
||||
let url = `https://submit-form.com/us6uBWv8`;
|
||||
|
||||
let postData;
|
||||
if (this.args.data?.post) {
|
||||
postData = {
|
||||
PostId: this.args.data.post?.id,
|
||||
PostTitle: this.args.data.post?.title
|
||||
};
|
||||
}
|
||||
|
||||
let ghostData = {
|
||||
Site: this.config.blogUrl,
|
||||
StaffMember: this.session.user.name,
|
||||
StaffMemberEmail: this.session.user.email,
|
||||
StaffAccessLevel: this.session.user.role?.description,
|
||||
UserAgent: navigator.userAgent,
|
||||
Version: this.config.version,
|
||||
Feedback: this.feedbackMessage
|
||||
};
|
||||
|
||||
let data = {
|
||||
...ghostData,
|
||||
...postData
|
||||
};
|
||||
|
||||
let response = yield this.ajax.post(url, {data});
|
||||
|
||||
if (response.status < 200 || response.status >= 300) {
|
||||
throw new Error('api failed ' + response.status + ' ' + response.statusText);
|
||||
}
|
||||
|
||||
this.args.close();
|
||||
|
||||
this.notifications.showNotification('Feedback sent',
|
||||
{
|
||||
icon: 'send-email',
|
||||
description: 'Thank you!'
|
||||
}
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ import ConfirmEditorLeaveModal from '../components/modals/editor/confirm-leave';
|
||||
import Controller, {inject as controller} from '@ember/controller';
|
||||
import DeletePostModal from '../components/modals/delete-post';
|
||||
import DeleteSnippetModal from '../components/editor/modals/delete-snippet';
|
||||
import FeedbackLexicalModal from '../components/modal-feedback-lexical';
|
||||
import PostModel from 'ghost-admin/models/post';
|
||||
import PublishLimitModal from '../components/modals/limits/publish-limit';
|
||||
import ReAuthenticateModal from '../components/editor/modals/re-authenticate';
|
||||
@ -23,6 +24,7 @@ import {isArray as isEmberArray} from '@ember/array';
|
||||
import {isHostLimitError, isServerUnreachableError, isVersionMismatchError} from 'ghost-admin/services/ajax';
|
||||
import {isInvalidError} from 'ember-ajax/errors';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const DEFAULT_TITLE = '(Untitled)';
|
||||
|
||||
@ -113,6 +115,8 @@ export default class LexicalEditorController extends Controller {
|
||||
|
||||
@inject config;
|
||||
|
||||
@tracked showFeedbackLexicalModal = false;
|
||||
|
||||
/* public properties -----------------------------------------------------*/
|
||||
|
||||
shouldFocusTitle = false;
|
||||
@ -408,6 +412,11 @@ export default class LexicalEditorController extends Controller {
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
async openFeedbackLexical() {
|
||||
await this.modals.open(FeedbackLexicalModal);
|
||||
}
|
||||
|
||||
/* Public tasks ----------------------------------------------------------*/
|
||||
|
||||
// separate task for autosave so that it doesn't override a manual save
|
||||
|
@ -3,6 +3,7 @@ import {inject as service} from '@ember/service';
|
||||
/* eslint-disable ghost/ember/alias-model-in-controller */
|
||||
import Controller from '@ember/controller';
|
||||
import DeleteAllModal from '../../components/settings/labs/delete-all-content-modal';
|
||||
import FeedbackLexicalModal from '../../components/modal-feedback-lexical';
|
||||
import ImportContentModal from '../../components/modal-import-content';
|
||||
import RSVP from 'rsvp';
|
||||
import config from 'ghost-admin/config/environment';
|
||||
@ -17,6 +18,7 @@ import {isBlank} from '@ember/utils';
|
||||
import {isArray as isEmberArray} from '@ember/array';
|
||||
import {run} from '@ember/runloop';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
const {Promise} = RSVP;
|
||||
|
||||
@ -50,6 +52,8 @@ export default class LabsController extends Controller {
|
||||
|
||||
@inject config;
|
||||
|
||||
@tracked showFeedbackLexicalModal = false;
|
||||
|
||||
importErrors = null;
|
||||
importSuccessful = false;
|
||||
showEarlyAccessModal = false;
|
||||
@ -160,6 +164,11 @@ export default class LabsController extends Controller {
|
||||
this.toggleProperty('showEarlyAccessModal');
|
||||
}
|
||||
|
||||
@action
|
||||
async openFeedbackLexical() {
|
||||
await this.modals.open(FeedbackLexicalModal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a file selection dialog - Triggered by "Upload x" buttons,
|
||||
* searches for the hidden file input within the .gh-setting element
|
||||
|
@ -732,6 +732,18 @@
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.gh-publish-confirmation-with-feedback {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.gh-publish-confirmation-feedback {
|
||||
color: var(--green);
|
||||
font-size: 1.6rem;
|
||||
font-weight: 400;
|
||||
letter-spacing: .4px;
|
||||
}
|
||||
|
||||
.gh-revert-to-draft {
|
||||
color: var(--green-d1);
|
||||
font-weight: 500;
|
||||
|
@ -387,6 +387,10 @@
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.gh-editor-feedback {
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.gh-editor-status {
|
||||
color: var(--midgrey);
|
||||
font-size: 1.3rem;
|
||||
|
@ -94,6 +94,16 @@
|
||||
/>
|
||||
|
||||
<div class="gh-editor-wordcount-container">
|
||||
{{#if (feature 'lexicalEditor')}}
|
||||
<button
|
||||
type="button"
|
||||
class="gh-editor-feedback"
|
||||
{{on "click" this.openFeedbackLexical}}
|
||||
data-test-button="lexical-editor-feedback"
|
||||
>
|
||||
<span>Feedback?</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
<div class="gh-editor-wordcount">
|
||||
{{gh-pluralize this.wordCount.wordCount "word"}}
|
||||
</div>
|
||||
|
@ -206,7 +206,11 @@
|
||||
<div>
|
||||
<h4 class="gh-expandable-title">Lexical editor</h4>
|
||||
<p class="gh-expandable-description">
|
||||
Makes lexical editor the default when creating new posts/pages.
|
||||
{{#if (feature 'lexicalEditor')}}
|
||||
<span>Makes lexical editor the default when creating new posts/pages. Any issues? Feedback?</span><button class="green ml1" role="button" {{on "click" this.openFeedbackLexical}} data-test-button="lexical-feedback">Let us know</button>
|
||||
{{else}}
|
||||
<span>Makes lexical editor the default when creating new posts/pages.</span>
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="for-switch">
|
||||
|
@ -1,7 +1,9 @@
|
||||
import loginAsRole from '../../helpers/login-as-role';
|
||||
import {BLANK_DOC} from 'koenig-editor/components/koenig-editor';
|
||||
import {currentURL} from '@ember/test-helpers';
|
||||
import {enableLabsFlag} from '../../helpers/labs-flag';
|
||||
import {expect} from 'chai';
|
||||
import {find} from '@ember/test-helpers';
|
||||
import {setupApplicationTest} from 'ember-mocha';
|
||||
import {setupMirage} from 'ember-cli-mirage/test-support';
|
||||
import {visit} from '../../helpers/visit';
|
||||
@ -18,6 +20,8 @@ describe('Acceptance: Lexical editor', function () {
|
||||
config.attrs.editor = {url: 'https://cdn.pkg/editor.js'};
|
||||
config.save();
|
||||
|
||||
enableLabsFlag(this.server, 'lexicalEditor');
|
||||
|
||||
// stub loaded external module to avoid loading of external dep
|
||||
window['@tryghost/koenig-lexical'] = {
|
||||
KoenigComposer: () => null,
|
||||
@ -47,6 +51,14 @@ describe('Acceptance: Lexical editor', function () {
|
||||
expect(currentURL(), 'currentURL').to.equal('/lexical-editor/post/');
|
||||
});
|
||||
|
||||
it('shows feedback link in lexical editor', async function () {
|
||||
await loginAsRole('Administrator', this.server);
|
||||
await visit('/lexical-editor/post/');
|
||||
expect(currentURL(), 'currentURL').to.equal('/lexical-editor/post/');
|
||||
|
||||
expect(find('.gh-editor-feedback'), 'feedback button').to.exist;
|
||||
});
|
||||
|
||||
it('redirects mobiledoc editor to lexical editor when post.lexical is present', async function () {
|
||||
const post = this.server.create('post', {
|
||||
lexical: JSON.stringify({})
|
||||
|
@ -1,13 +1,12 @@
|
||||
import {authenticateSession, invalidateSession} from 'ember-simple-auth/test-support';
|
||||
import {beforeEach, describe, it} from 'mocha';
|
||||
import {click, currentURL, fillIn, find, findAll} from '@ember/test-helpers';
|
||||
import {enableLabsFlag} from '../../helpers/labs-flag';
|
||||
import {expect} from 'chai';
|
||||
import {fileUpload} from '../../helpers/file-upload';
|
||||
import {setupApplicationTest} from 'ember-mocha';
|
||||
import {setupMirage} from 'ember-cli-mirage/test-support';
|
||||
import {visit} from '../../helpers/visit';
|
||||
// import wait from 'ember-test-helpers/wait';
|
||||
// import {timeout} from 'ember-concurrency';
|
||||
|
||||
describe('Acceptance: Settings - Labs', function () {
|
||||
let hooks = setupApplicationTest();
|
||||
@ -314,6 +313,47 @@ describe('Acceptance: Settings - Labs', function () {
|
||||
let iframe = document.querySelector('#iframeDownload');
|
||||
expect(iframe.getAttribute('src')).to.have.string('/settings/routes/yaml/');
|
||||
});
|
||||
|
||||
it('displays lexical feedback button when the labs setting is enabled', async function () {
|
||||
enableLabsFlag(this.server, 'lexicalEditor');
|
||||
|
||||
await visit('/settings/labs');
|
||||
|
||||
expect(find('[data-test-button="lexical-feedback"]')).to.exist;
|
||||
});
|
||||
|
||||
it('does not display lexical feedback button when the labs setting is disabled', async function () {
|
||||
await visit('/settings/labs');
|
||||
|
||||
expect(find('[data-test-button="lexical-feedback"]')).to.not.exist;
|
||||
});
|
||||
|
||||
it('allows the user to launch the feedback modal', async function () {
|
||||
enableLabsFlag(this.server, 'lexicalEditor');
|
||||
|
||||
await visit('/settings/labs');
|
||||
await click('[data-test-button="lexical-feedback"]');
|
||||
|
||||
expect(find('[data-test-modal="lexical-feedback"]')).to.exist;
|
||||
});
|
||||
|
||||
it('allows the user to send lexical feedback', async function () {
|
||||
enableLabsFlag(this.server, 'lexicalEditor');
|
||||
|
||||
// mock successful request
|
||||
this.server.post('https://submit-form.com/us6uBWv8', {}, 200);
|
||||
|
||||
await visit('/settings/labs');
|
||||
await click('[data-test-button="lexical-feedback"]');
|
||||
|
||||
expect(find('[data-test-modal="lexical-feedback"]')).to.exist;
|
||||
await fillIn('[data-test-lexical-feedback-textarea]', 'This is test feedback');
|
||||
await click('[data-test-button="submit-lexical-feedback"]');
|
||||
|
||||
// successful request will close the modal and show a notification toast
|
||||
expect(find('[data-test-modal="lexical-feedback"]')).to.not.exist;
|
||||
expect(find('[data-test-text="notification-content"]')).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
describe('When logged in as Owner', function () {
|
||||
|
Loading…
Reference in New Issue
Block a user