Hi Flarum devs,
I'm helping someone troubleshoot a conflict between their Guest Posting extension and askvortsov/rich-text
.
Problem:
RTE's extend(TextEditor.prototype, 'controlItems', ...)
accesses app.session.user.preferences()
, causing TypeError: Cannot read properties of null (reading 'preferences')
for guests. See applyEditor.js
:
extend(TextEditor.prototype, 'controlItems', function (items) {
items.add('rte', <Button className={classList({ active: app.session.user.preferences().useRichTextEditor })} />); // Error Here
});
Attempt:
The Guest Posting extension tries high-priority override
:
override(TextEditor.prototype, 'controlItems', function (original) {
if (!app.session.user) return new ItemList();
return original();
}, -100);
Issue:
The TypeError
still occurs, even with the override. extend
seems to still execute/evaluate for guests despite the override.
Question:
How can the Guest Posting extension reliably prevent RTE's preferences()
access only for guests? It seems the extend
is being run. Goal: Guests=Markdown, Logged-in=Preference. Any ideas?
Thanks!
Full Code;
import app from 'flarum/forum/app';
import { extend, override } from 'flarum/common/extend';
import IndexPage from 'flarum/forum/components/IndexPage';
import DiscussionPage from 'flarum/forum/components/DiscussionPage';
import Component from 'flarum/common/Component';
import Button from 'flarum/common/components/Button';
import listItems from 'flarum/common/helpers/listItems';
import SignUpModal from 'flarum/forum/components/SignUpModal';
import Badge from 'flarum/common/components/Badge';
import CommentPost from 'flarum/forum/components/CommentPost';
import DiscussionComposer from 'flarum/forum/components/DiscussionComposer';
import ReplyComposer from 'flarum/forum/components/ReplyComposer';
import DiscussionControls from 'flarum/forum/utils/DiscussionControls';
import ReplyPlaceholder from 'flarum/forum/components/ReplyPlaceholder';
import avatar from 'flarum/common/helpers/avatar';
import username from 'flarum/common/helpers/username';
import ComposerPostPreview from 'flarum/forum/components/ComposerPostPreview';
import TextEditor from 'flarum/common/components/TextEditor';
import ItemList from 'flarum/common/utils/ItemList';
class GuestPromptAlert extends Component {
view() {
let message = '';
const hasPostCount = app.forum.attribute('guestPostCount');
const hasVoteCount = app.forum.attribute('guestVoteCount');
if (hasPostCount && hasVoteCount) {
message = app.translator.trans('guest-posting.forum.alert.create-account-posts-and-votes', { postCount: app.forum.attribute('guestPostCount'), voteCount: app.forum.attribute('guestVoteCount') });
} else if (hasVoteCount) {
message = app.translator.trans('guest-posting.forum.alert.create-account-votes-only', { count: app.forum.attribute('guestVoteCount') });
} else {
message = app.translator.trans('guest-posting.forum.alert.create-account', { count: app.forum.attribute('guestPostCount') });
}
return m('.Alert.Alert--info',
m('.container', [
m('span.Alert-body', message),
m('ul.Alert-controls', listItems([
Button.component({ className: 'Button Button--link', onclick: () => { app.modal.show(SignUpModal); } }, app.translator.trans('guest-posting.forum.alert.signup')),
])),
])
);
}
}
function addGuestPrompt(original, ...args) {
const originalOutput = original.apply(this, args);
if (!app.forum.attribute('guestPostCount') && !app.forum.attribute('guestVoteCount')) return originalOutput;
const alert = GuestPromptAlert.component();
if (Array.isArray(originalOutput)) {
originalOutput.unshift(alert);
return originalOutput;
}
return m('div', [alert, originalOutput]);
}
export function filterPostMentions(tag) {
return false; // Disables mentions
}
app.initializers.add('guest-posting', () => {
override(IndexPage.prototype, 'hero', addGuestPrompt);
override(DiscussionPage.prototype, 'view', addGuestPrompt);
override(DiscussionControls, 'replyAction', function (original, start, isFullScreen) {
if (app.session.user || !this.canReply()) return original(start, isFullScreen);
return new Promise((resolve) => {
if (!app.composer.composingReplyTo(this) || isFullScreen) {
app.composer.load(ReplyComposer, { user: app.session.user, discussion: this, });
app.composer.show();
}
if (start && app.viewingDiscussion(this) && !app.composer.isFullScreen()) {
app.current.get('stream').goToNumber('reply');
}
resolve(app.composer);
});
});
override(IndexPage.prototype, 'newDiscussionAction', function (original) {
if (app.session.user || !app.forum.attribute('canStartDiscussion')) return original();
return new Promise((resolve) => {
app.composer.load(DiscussionComposer, { user: app.session.user, });
app.composer.show();
resolve(app.composer);
});
});
extend(CommentPost.prototype, 'headerItems', function (items) {
const guestUsername = this.attrs.post.attribute('guest_username');
if (guestUsername) {
if (items.has('user')) items.remove('user');
items.add('guest-user', m('.PostUser', [
m('h3', [
m('span.Avatar.PostUser-avatar', guestUsername.charAt(0).toUpperCase()),
m('span.username', guestUsername),
]),
m('ul.PostUser-badges.badges', m('li', Badge.component({ type: 'guest', icon: 'fas fa-user-secret', label: app.translator.trans('guest-posting.forum.badge.guest'), }))),
]), 100);
}
});
override(ReplyPlaceholder.prototype, 'view', function (original, ...args) {
if (app.composer.composingReplyTo(this.attrs.discussion)) {
return m('article', { className: 'Post CommentPost editing' },
m('header', { className: 'Post-header' },
m('div', { className: 'PostUser' },
m('h3', null,
avatar(app.session.user, { className: 'PostUser-avatar' }),
username(app.session.user)
),
app.session.user ? m('ul', { className: 'PostUser-badges badges' }, listItems(app.session.user.badges().toArray())) : null
)
),
m(ComposerPostPreview, { className: 'Post-body', composer: app.composer, surround: this.anchorPreview.bind(this) })
);
}
return original.apply(this, args);
});
extend(SignUpModal.prototype, 'oninit', function () {
this.importGuestContent = !!app.forum.attribute('guestPostCount') || !!app.forum.attribute('guestVoteCount');
});
extend(SignUpModal.prototype, 'fields', function (items) {
if (app.forum.attribute('guestPostCount') || app.forum.attribute('guestVoteCount')) {
items.add('guest-posting', m('.Form-group',
m('div',
m('label.checkbox', [
m('input', { type: 'checkbox', checked: this.importGuestContent, onchange: () => { this.importGuestContent = !this.importGuestContent; }, disabled: this.loading }),
app.forum.attribute('guestVoteCount')
? app.translator.trans('guest-posting.forum.modal.import-votes', { postCount: app.forum.attribute('guestPostCount') || '0', voteCount: app.forum.attribute('guestVoteCount') })
: app.translator.trans('guest-posting.forum.modal.import', { count: app.forum.attribute('guestPostCount') })
])
)
), 5);
}
});
extend(SignUpModal.prototype, 'submitData', function (data) {
if (this.importGuestContent) {
data.importGuestContent = this.importGuestContent;
}
});
const richTextExtensionId = 'askvortsov-rich-text';
if (flarum.extensions[richTextExtensionId]) {
console.log(`Guest Posting: RTE active. Applying RTE overrides.`);
override(TextEditor.prototype, 'controlItems', function (original, items) {
if (!app.session.user) return new ItemList();
return original.apply(this, arguments);
});
override(TextEditor.prototype, 'toolbarItems', function (original, items) {
if (!app.session.user) return new ItemList();
return original.apply(this, arguments);
});
override(TextEditor.prototype, 'buildEditorParams', function(original) {
if (!app.session.user) return {};
return original.apply(this, arguments);
});
override(TextEditor.prototype, 'buildEditor', function (original, dom) {
if (!app.session.user) {
try {
return original.apply(this, arguments);
} catch (e) {
console.error("Guest Posting: buildEditor failed for guest. Falling back.", e);
return null;
}
}
return original.apply(this, arguments);
});
}
});