Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-22 17:30:29 +01:00
FEATURE: Add real Discourse composer
Dieser Commit ist enthalten in:
Ursprung
c812f60fd7
Commit
9ac57eeb98
12 geänderte Dateien mit 299 neuen und 698 gelöschten Zeilen
|
@ -3,6 +3,7 @@ window.Wizard = {};
|
||||||
Wizard.SiteSettings = {};
|
Wizard.SiteSettings = {};
|
||||||
Wizard.RAW_TEMPLATES = {};
|
Wizard.RAW_TEMPLATES = {};
|
||||||
Discourse.__widget_helpers = {};
|
Discourse.__widget_helpers = {};
|
||||||
|
Discourse.RAW_TEMPLATES = {};
|
||||||
Discourse.SiteSettings = Wizard.SiteSettings;
|
Discourse.SiteSettings = Wizard.SiteSettings;
|
||||||
Discourse.Model = Ember.Object.extend();
|
Discourse.Model = Ember.Object.extend();
|
||||||
Discourse.Site = Ember.Object.extend();
|
Discourse.Site = Ember.Object.extend();
|
|
@ -12,24 +12,25 @@
|
||||||
//= require discourse/lib/logout
|
//= require discourse/lib/logout
|
||||||
//= require discourse/lib/render-tag
|
//= require discourse/lib/render-tag
|
||||||
//= require discourse/lib/notification-levels
|
//= require discourse/lib/notification-levels
|
||||||
//= require discourse/lib/computed
|
//= require discourse/lib/computed
|
||||||
|
//= require discourse/lib/user-search
|
||||||
|
//= require discourse/lib/debounce
|
||||||
|
//= require discourse/lib/text
|
||||||
|
//= require discourse/lib/formatter
|
||||||
|
//= require discourse/lib/quote
|
||||||
|
//= require discourse/lib/link-mentions
|
||||||
|
//= require discourse/lib/link-category-hashtags
|
||||||
|
//= require discourse/lib/category-hashtags
|
||||||
|
//= require discourse/lib/link-tag-hashtag
|
||||||
|
//= require discourse/lib/tag-hashtags
|
||||||
|
//= require discourse/lib/raw-templates
|
||||||
|
//= require discourse/lib/uploads
|
||||||
|
//= require discourse/lib/category-tag-search
|
||||||
|
//= require discourse/lib/intercept-click
|
||||||
|
//= require discourse/lib/show-modal
|
||||||
|
//= require discourse/lib/key-value-store
|
||||||
|
|
||||||
//= require markdown-it-bundle
|
//= require discourse/mixins/singleton
|
||||||
//= require pretty-text/engines/discourse-markdown-it
|
|
||||||
//= require pretty-text/engines/discourse-markdown/helpers
|
|
||||||
//= require pretty-text/pretty-text
|
|
||||||
//= require ember-addons/fmt
|
|
||||||
//= require preload-store
|
|
||||||
|
|
||||||
//= require ./wizard/custom-wizard
|
|
||||||
//= require_tree ./wizard/components
|
|
||||||
//= require_tree ./wizard/controllers
|
|
||||||
//= require_tree ./wizard/helpers
|
|
||||||
//= require_tree ./wizard/initializers
|
|
||||||
//= require_tree ./wizard/lib
|
|
||||||
//= require_tree ./wizard/models
|
|
||||||
//= require_tree ./wizard/routes
|
|
||||||
//= require_tree ./wizard/templates
|
|
||||||
|
|
||||||
//= require discourse/models/permission-type
|
//= require discourse/models/permission-type
|
||||||
//= require discourse/models/archetype
|
//= require discourse/models/archetype
|
||||||
|
@ -41,14 +42,67 @@
|
||||||
//= require discourse/models/trust-level
|
//= require discourse/models/trust-level
|
||||||
//= require discourse/models/store
|
//= require discourse/models/store
|
||||||
//= require discourse/models/result-set
|
//= require discourse/models/result-set
|
||||||
|
//= require discourse/models/user
|
||||||
|
//= require discourse/models/user-stream
|
||||||
|
//= require discourse/models/user-action
|
||||||
|
//= require discourse/models/user-action-group
|
||||||
|
//= require discourse/models/user-posts-stream
|
||||||
|
//= require discourse/models/badge
|
||||||
|
//= require discourse/models/badge-grouping
|
||||||
|
//= require discourse/models/user-badge
|
||||||
|
//= require discourse/models/topic
|
||||||
|
//= require discourse/models/action-summary
|
||||||
|
//= require discourse/models/user-action-stat
|
||||||
|
//= require discourse/models/user-drafts-stream
|
||||||
|
//= require discourse/models/user-draft
|
||||||
|
//= require discourse/models/composer
|
||||||
|
//= require discourse/models/draft
|
||||||
|
//= require discourse/models/group
|
||||||
|
//= require discourse/models/group-history
|
||||||
|
|
||||||
//= require discourse/helpers/category-link
|
//= require discourse/helpers/category-link
|
||||||
//= require discourse/mixins/singleton
|
//= require discourse/helpers/user-avatar
|
||||||
|
//= require discourse/helpers/format-username
|
||||||
|
|
||||||
|
//= require discourse/services/app-events
|
||||||
|
//= require discourse/services/emoji-store
|
||||||
|
|
||||||
//= require discourse/components/user-selector
|
//= require discourse/components/user-selector
|
||||||
//= require discourse/helpers/user-avatar
|
|
||||||
//= require discourse/components/conditional-loading-spinner
|
//= require discourse/components/conditional-loading-spinner
|
||||||
//= require discourse/templates/components/conditional-loading-spinner
|
|
||||||
//= require discourse/components/d-button
|
//= require discourse/components/d-button
|
||||||
|
//= require discourse/components/composer-editor
|
||||||
|
//= require discourse/components/d-editor
|
||||||
|
//= require discourse/components/popup-input-tip
|
||||||
|
//= require discourse/components/emoji-picker
|
||||||
|
|
||||||
|
//= require discourse/templates/components/conditional-loading-spinner
|
||||||
//= require discourse/templates/components/d-button
|
//= require discourse/templates/components/d-button
|
||||||
|
//= require discourse/templates/components/d-editor
|
||||||
|
//= require discourse/templates/components/emoji-picker
|
||||||
|
//= require discourse/templates/category-tag-autocomplete
|
||||||
|
//= require discourse/templates/emoji-selector-autocomplete
|
||||||
|
|
||||||
|
//= require discourse/pre-initializers/sniff-capabilities
|
||||||
|
|
||||||
|
//= require ember-addons/decorator-alias
|
||||||
|
//= require ember-addons/macro-alias
|
||||||
|
//= require ember-addons/fmt
|
||||||
|
//= require polyfills
|
||||||
|
|
||||||
|
//= require markdown-it-bundle
|
||||||
|
//= require preload-store
|
||||||
//= require lodash.js
|
//= require lodash.js
|
||||||
//= require mousetrap.js
|
//= require mousetrap.js
|
||||||
|
//= require jquery.putcursoratend.js
|
||||||
|
//= require template_include.js
|
||||||
|
//= require caret_position.js
|
||||||
|
|
||||||
|
//= require ./wizard/custom-wizard
|
||||||
|
//= require_tree ./wizard/components
|
||||||
|
//= require_tree ./wizard/controllers
|
||||||
|
//= require_tree ./wizard/helpers
|
||||||
|
//= require_tree ./wizard/initializers
|
||||||
|
//= require_tree ./wizard/lib
|
||||||
|
//= require_tree ./wizard/models
|
||||||
|
//= require_tree ./wizard/routes
|
||||||
|
//= require_tree ./wizard/templates
|
||||||
|
|
|
@ -2,19 +2,29 @@
|
||||||
result = ''
|
result = ''
|
||||||
Discourse.unofficial_plugins.each do |plugin|
|
Discourse.unofficial_plugins.each do |plugin|
|
||||||
plugin_name = plugin.metadata.name
|
plugin_name = plugin.metadata.name
|
||||||
if require_plugin_assets = CustomWizard::Field.require_assets[plugin_name]
|
|
||||||
|
if plugin_name == 'discourse-custom-wizard' || CustomWizard::Field.require_assets[plugin_name]
|
||||||
|
|
||||||
|
files = []
|
||||||
|
|
||||||
plugin.each_globbed_asset do |f, is_dir|
|
plugin.each_globbed_asset do |f, is_dir|
|
||||||
if f.include? "raw.hbs"
|
files.push(f) if f.include? "raw.hbs"
|
||||||
name = File.basename(f, ".raw.hbs")
|
end
|
||||||
compiled = Barber::Precompiler.new().compile(File.read(f))
|
|
||||||
result << "
|
Dir.glob("#{Rails.root}/app/assets/javascripts/discourse/templates/*.raw.hbs").each do |f|
|
||||||
(function() {
|
files.push(f)
|
||||||
if ('Wizard' in window) {
|
end
|
||||||
Wizard.RAW_TEMPLATES['javascripts/#{name}'] = requirejs('discourse-common/lib/raw-handlebars').template(#{compiled});
|
|
||||||
}
|
files.each do |f|
|
||||||
})();
|
name = File.basename(f, ".raw.hbs")
|
||||||
"
|
compiled = Barber::Precompiler.new().compile(File.read(f))
|
||||||
end
|
result << "
|
||||||
|
(function() {
|
||||||
|
if ('Wizard' in window) {
|
||||||
|
Discourse.RAW_TEMPLATES['javascripts/#{name}'] = requirejs('discourse-common/lib/raw-handlebars').template(#{compiled});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
78
assets/javascripts/wizard/components/wizard-composer-editor.js.es6
Normale Datei
78
assets/javascripts/wizard/components/wizard-composer-editor.js.es6
Normale Datei
|
@ -0,0 +1,78 @@
|
||||||
|
import ComposerEditor from 'discourse/components/composer-editor';
|
||||||
|
import { default as computed, on } from 'ember-addons/ember-computed-decorators';
|
||||||
|
import { findRawTemplate } from "discourse/lib/raw-templates";
|
||||||
|
import { throttle } from "@ember/runloop";
|
||||||
|
import { scheduleOnce } from "@ember/runloop";
|
||||||
|
import { safariHacksDisabled } from "discourse/lib/utilities";
|
||||||
|
|
||||||
|
export default ComposerEditor.extend({
|
||||||
|
classNameBindings: ['fieldClass'],
|
||||||
|
allowUpload: false,
|
||||||
|
showLink: false,
|
||||||
|
topic: null,
|
||||||
|
showToolbar: true,
|
||||||
|
focusTarget: "reply",
|
||||||
|
canWhisper: false,
|
||||||
|
lastValidatedAt: 'lastValidatedAt',
|
||||||
|
uploadIcon: "upload",
|
||||||
|
popupMenuOptions: [],
|
||||||
|
draftStatus: 'null',
|
||||||
|
|
||||||
|
@on("didInsertElement")
|
||||||
|
_composerEditorInit() {
|
||||||
|
const $input = $(this.element.querySelector(".d-editor-input"));
|
||||||
|
const $preview = $(this.element.querySelector(".d-editor-preview-wrapper"));
|
||||||
|
|
||||||
|
if (this.siteSettings.enable_mentions) {
|
||||||
|
$input.autocomplete({
|
||||||
|
template: findRawTemplate("user-selector-autocomplete"),
|
||||||
|
dataSource: term => this.userSearchTerm.call(this, term),
|
||||||
|
key: "@",
|
||||||
|
transformComplete: v => v.username || v.name,
|
||||||
|
afterComplete() {
|
||||||
|
// ensures textarea scroll position is correct
|
||||||
|
scheduleOnce("afterRender", () => $input.blur().focus());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._enableAdvancedEditorPreviewSync()) {
|
||||||
|
this._initInputPreviewSync($input, $preview);
|
||||||
|
} else {
|
||||||
|
$input.on("scroll", () =>
|
||||||
|
throttle(this, this._syncEditorAndPreviewScroll, $input, $preview, 20)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._bindUploadTarget();
|
||||||
|
},
|
||||||
|
|
||||||
|
_bindUploadTarget() {
|
||||||
|
},
|
||||||
|
|
||||||
|
_unbindUploadTarget() {
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
extraButtons(toolbar) {
|
||||||
|
if (this.allowUpload && this.uploadIcon && !this.site.mobileView) {
|
||||||
|
toolbar.addButton({
|
||||||
|
id: "upload",
|
||||||
|
group: "insertions",
|
||||||
|
icon: this.uploadIcon,
|
||||||
|
title: "upload",
|
||||||
|
sendAction: this.showUploadModal
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toolbar.addButton({
|
||||||
|
id: "options",
|
||||||
|
group: "extras",
|
||||||
|
icon: "cog",
|
||||||
|
title: "composer.options",
|
||||||
|
sendAction: this.onExpandPopupMenuOptions.bind(this),
|
||||||
|
popupMenu: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
|
@ -1,659 +0,0 @@
|
||||||
/* eslint no-undef: 0 */
|
|
||||||
/*global Mousetrap:true */
|
|
||||||
import { default as computed, on, observes } from 'ember-addons/ember-computed-decorators';
|
|
||||||
import { cookAsync } from '../lib/text-lite';
|
|
||||||
import { getRegister } from 'discourse-common/lib/get-owner';
|
|
||||||
import { siteDir } from 'discourse/lib/text-direction';
|
|
||||||
import { determinePostReplaceSelection, clipboardData } from '../lib/utilities-lite';
|
|
||||||
import toMarkdown from 'discourse/lib/to-markdown';
|
|
||||||
|
|
||||||
// Our head can be a static string or a function that returns a string
|
|
||||||
// based on input (like for numbered lists).
|
|
||||||
function getHead(head, prev) {
|
|
||||||
if (typeof head === "string") {
|
|
||||||
return [head, head.length];
|
|
||||||
} else {
|
|
||||||
return getHead(head(prev));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getButtonLabel(labelKey, defaultLabel) {
|
|
||||||
// use the Font Awesome icon if the label matches the default
|
|
||||||
return I18n.t(labelKey) === defaultLabel ? null : labelKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
const OP = {
|
|
||||||
NONE: 0,
|
|
||||||
REMOVED: 1,
|
|
||||||
ADDED: 2
|
|
||||||
};
|
|
||||||
|
|
||||||
const FOUR_SPACES_INDENT = '4-spaces-indent';
|
|
||||||
|
|
||||||
const _createCallbacks = [];
|
|
||||||
|
|
||||||
const isInside = (text, regex) => {
|
|
||||||
const matches = text.match(regex);
|
|
||||||
return matches && (matches.length % 2);
|
|
||||||
};
|
|
||||||
|
|
||||||
class Toolbar {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.shortcuts = {};
|
|
||||||
|
|
||||||
this.groups = [
|
|
||||||
{group: 'fontStyles', buttons: []},
|
|
||||||
{group: 'insertions', buttons: []},
|
|
||||||
{group: 'extras', buttons: []}
|
|
||||||
];
|
|
||||||
|
|
||||||
this.addButton({
|
|
||||||
trimLeading: true,
|
|
||||||
id: 'bold',
|
|
||||||
group: 'fontStyles',
|
|
||||||
icon: 'bold',
|
|
||||||
label: getButtonLabel('wizard_composer.bold_label', 'B'),
|
|
||||||
shortcut: 'B',
|
|
||||||
perform: e => e.applySurround('**', '**', 'bold_text')
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addButton({
|
|
||||||
trimLeading: true,
|
|
||||||
id: 'italic',
|
|
||||||
group: 'fontStyles',
|
|
||||||
icon: 'italic',
|
|
||||||
label: getButtonLabel('wizard_composer.italic_label', 'I'),
|
|
||||||
shortcut: 'I',
|
|
||||||
perform: e => e.applySurround('_', '_', 'italic_text')
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addButton({
|
|
||||||
id: 'quote',
|
|
||||||
group: 'insertions',
|
|
||||||
icon: 'quote-right',
|
|
||||||
shortcut: 'Shift+9',
|
|
||||||
perform: e => e.applyList(
|
|
||||||
'> ',
|
|
||||||
'blockquote_text',
|
|
||||||
{ applyEmptyLines: true, multiline: true }
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addButton({id: 'code', group: 'insertions', shortcut: 'Shift+C', action: 'formatCode'});
|
|
||||||
|
|
||||||
this.addButton({
|
|
||||||
id: 'bullet',
|
|
||||||
group: 'extras',
|
|
||||||
icon: 'list-ul',
|
|
||||||
shortcut: 'Shift+8',
|
|
||||||
title: 'wizard_composer.ulist_title',
|
|
||||||
perform: e => e.applyList('* ', 'list_item')
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addButton({
|
|
||||||
id: 'list',
|
|
||||||
group: 'extras',
|
|
||||||
icon: 'list-ol',
|
|
||||||
shortcut: 'Shift+7',
|
|
||||||
title: 'wizard_composer.olist_title',
|
|
||||||
perform: e => e.applyList(i => !i ? "1. " : `${parseInt(i) + 1}. `, 'list_item')
|
|
||||||
});
|
|
||||||
|
|
||||||
if (Wizard.SiteSettings.support_mixed_text_direction) {
|
|
||||||
this.addButton({
|
|
||||||
id: 'toggle-direction',
|
|
||||||
group: 'extras',
|
|
||||||
icon: 'exchange',
|
|
||||||
shortcut: 'Shift+6',
|
|
||||||
title: 'wizard_composer.toggle_direction',
|
|
||||||
perform: e => e.toggleDirection(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.groups[this.groups.length-1].lastGroup = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
addButton(button) {
|
|
||||||
const g = this.groups.findBy('group', button.group);
|
|
||||||
if (!g) {
|
|
||||||
throw `Couldn't find toolbar group ${button.group}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const createdButton = {
|
|
||||||
id: button.id,
|
|
||||||
className: button.className || button.id,
|
|
||||||
label: button.label,
|
|
||||||
icon: button.label ? null : button.icon || button.id,
|
|
||||||
action: button.action || 'toolbarButton',
|
|
||||||
perform: button.perform || function() { },
|
|
||||||
trimLeading: button.trimLeading,
|
|
||||||
popupMenu: button.popupMenu || false
|
|
||||||
};
|
|
||||||
|
|
||||||
if (button.sendAction) {
|
|
||||||
createdButton.sendAction = button.sendAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
const title = I18n.t(button.title || `wizard_composer.${button.id}_title`);
|
|
||||||
if (button.shortcut) {
|
|
||||||
const mac = /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
||||||
const mod = mac ? 'Meta' : 'Ctrl';
|
|
||||||
var shortcutTitle = `${mod}+${button.shortcut}`;
|
|
||||||
|
|
||||||
// Mac users are used to glyphs for shortcut keys
|
|
||||||
if (mac) {
|
|
||||||
shortcutTitle = shortcutTitle
|
|
||||||
.replace('Shift', "\u21E7")
|
|
||||||
.replace('Meta', "\u2318")
|
|
||||||
.replace('Alt', "\u2325")
|
|
||||||
.replace(/\+/g, '');
|
|
||||||
} else {
|
|
||||||
shortcutTitle = shortcutTitle
|
|
||||||
.replace('Shift', I18n.t('shortcut_modifier_key.shift'))
|
|
||||||
.replace('Ctrl', I18n.t('shortcut_modifier_key.ctrl'))
|
|
||||||
.replace('Alt', I18n.t('shortcut_modifier_key.alt'));
|
|
||||||
}
|
|
||||||
|
|
||||||
createdButton.title = `${title} (${shortcutTitle})`;
|
|
||||||
|
|
||||||
this.shortcuts[`${mod}+${button.shortcut}`.toLowerCase()] = createdButton;
|
|
||||||
} else {
|
|
||||||
createdButton.title = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (button.unshift) {
|
|
||||||
g.buttons.unshift(createdButton);
|
|
||||||
} else {
|
|
||||||
g.buttons.push(createdButton);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
|
||||||
classNames: ['d-editor'],
|
|
||||||
ready: false,
|
|
||||||
lastSel: null,
|
|
||||||
_mouseTrap: null,
|
|
||||||
showPreview: false,
|
|
||||||
|
|
||||||
_readyNow() {
|
|
||||||
this.set('ready', true);
|
|
||||||
|
|
||||||
if (this.get('autofocus')) {
|
|
||||||
this.$('textarea').focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this._super();
|
|
||||||
this.register = getRegister(this);
|
|
||||||
},
|
|
||||||
|
|
||||||
didInsertElement() {
|
|
||||||
this._super();
|
|
||||||
|
|
||||||
Ember.run.scheduleOnce('afterRender', this, this._readyNow);
|
|
||||||
|
|
||||||
const mouseTrap = Mousetrap(this.$('.d-editor-input')[0]);
|
|
||||||
const shortcuts = this.get('toolbar.shortcuts');
|
|
||||||
|
|
||||||
// for some reason I am having trouble bubbling this so hack it in
|
|
||||||
mouseTrap.bind(['ctrl+alt+f'], (event) =>{
|
|
||||||
this.appEvents.trigger('header:keyboard-trigger', {type: 'search', event});
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(shortcuts).forEach(sc => {
|
|
||||||
const button = shortcuts[sc];
|
|
||||||
mouseTrap.bind(sc, () => {
|
|
||||||
this.send(button.action, button);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// disable clicking on links in the preview
|
|
||||||
this.$('.d-editor-preview').on('click.preview', e => {
|
|
||||||
if ($(e.target).is("a")) {
|
|
||||||
e.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.get('composerEvents')) {
|
|
||||||
this.appEvents.on('composer:insert-block', text => this._addBlock(this._getSelected(), text));
|
|
||||||
this.appEvents.on('composer:insert-text', (text, options) => this._addText(this._getSelected(), text, options));
|
|
||||||
this.appEvents.on('composer:replace-text', (oldVal, newVal) => this._replaceText(oldVal, newVal));
|
|
||||||
}
|
|
||||||
this._mouseTrap = mouseTrap;
|
|
||||||
},
|
|
||||||
|
|
||||||
@on('willDestroyElement')
|
|
||||||
_shutDown() {
|
|
||||||
if (this.get('composerEvents')) {
|
|
||||||
this.appEvents.off('composer:insert-block');
|
|
||||||
this.appEvents.off('composer:insert-text');
|
|
||||||
this.appEvents.off('composer:replace-text');
|
|
||||||
}
|
|
||||||
|
|
||||||
const mouseTrap = this._mouseTrap;
|
|
||||||
Object.keys(this.get('toolbar.shortcuts')).forEach(sc => mouseTrap.unbind(sc));
|
|
||||||
mouseTrap.unbind('ctrl+/','command+/');
|
|
||||||
this.$('.d-editor-preview').off('click.preview');
|
|
||||||
},
|
|
||||||
|
|
||||||
@computed
|
|
||||||
toolbar() {
|
|
||||||
const toolbar = new Toolbar(this.site);
|
|
||||||
_createCallbacks.forEach(cb => cb(toolbar));
|
|
||||||
this.sendAction('extraButtons', toolbar);
|
|
||||||
return toolbar;
|
|
||||||
},
|
|
||||||
|
|
||||||
_updatePreview() {
|
|
||||||
if (this._state !== "inDOM") { return; }
|
|
||||||
|
|
||||||
const value = this.get('value');
|
|
||||||
const markdownOptions = this.get('markdownOptions') || {};
|
|
||||||
|
|
||||||
cookAsync(value, markdownOptions).then(cooked => {
|
|
||||||
if (this.get('isDestroyed')) { return; }
|
|
||||||
this.set('preview', cooked);
|
|
||||||
Ember.run.scheduleOnce('afterRender', () => {
|
|
||||||
if (this._state !== "inDOM") { return; }
|
|
||||||
const $preview = this.$('.d-editor-preview');
|
|
||||||
if ($preview.length === 0) return;
|
|
||||||
this.sendAction('previewUpdated', $preview);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
@observes('ready', 'value')
|
|
||||||
_watchForChanges() {
|
|
||||||
if (!this.get('ready')) { return; }
|
|
||||||
|
|
||||||
// Debouncing in test mode is complicated
|
|
||||||
if (Ember.testing) {
|
|
||||||
this._updatePreview();
|
|
||||||
} else {
|
|
||||||
Ember.run.debounce(this, this._updatePreview, 30);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_getSelected(trimLeading, opts) {
|
|
||||||
if (!this.get('ready')) { return; }
|
|
||||||
|
|
||||||
const textarea = this.$('textarea.d-editor-input')[0];
|
|
||||||
const value = textarea.value;
|
|
||||||
let start = textarea.selectionStart;
|
|
||||||
let end = textarea.selectionEnd;
|
|
||||||
|
|
||||||
// trim trailing spaces cause **test ** would be invalid
|
|
||||||
while (end > start && /\s/.test(value.charAt(end-1))) {
|
|
||||||
end--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trimLeading) {
|
|
||||||
// trim leading spaces cause ** test** would be invalid
|
|
||||||
while(end > start && /\s/.test(value.charAt(start))) {
|
|
||||||
start++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const selVal = value.substring(start, end);
|
|
||||||
const pre = value.slice(0, start);
|
|
||||||
const post = value.slice(end);
|
|
||||||
|
|
||||||
if (opts && opts.lineVal) {
|
|
||||||
const lineVal = value.split("\n")[value.substr(0, textarea.selectionStart).split("\n").length - 1];
|
|
||||||
return { start, end, value: selVal, pre, post, lineVal };
|
|
||||||
} else {
|
|
||||||
return { start, end, value: selVal, pre, post };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_selectText(from, length) {
|
|
||||||
Ember.run.scheduleOnce('afterRender', () => {
|
|
||||||
const $textarea = this.$('textarea.d-editor-input');
|
|
||||||
const textarea = $textarea[0];
|
|
||||||
const oldScrollPos = $textarea.scrollTop();
|
|
||||||
if (!!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform)) {
|
|
||||||
$textarea.focus();
|
|
||||||
}
|
|
||||||
textarea.selectionStart = from;
|
|
||||||
textarea.selectionEnd = textarea.selectionStart + length;
|
|
||||||
$textarea.scrollTop(oldScrollPos);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// perform the same operation over many lines of text
|
|
||||||
_getMultilineContents(lines, head, hval, hlen, tail, tlen, opts) {
|
|
||||||
let operation = OP.NONE;
|
|
||||||
|
|
||||||
const applyEmptyLines = opts && opts.applyEmptyLines;
|
|
||||||
|
|
||||||
return lines.map(l => {
|
|
||||||
if (!applyEmptyLines && l.length === 0) {
|
|
||||||
return l;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation !== OP.ADDED &&
|
|
||||||
(l.slice(0, hlen) === hval && tlen === 0 ||
|
|
||||||
(tail.length && l.slice(-tlen) === tail))) {
|
|
||||||
|
|
||||||
operation = OP.REMOVED;
|
|
||||||
if (tlen === 0) {
|
|
||||||
const result = l.slice(hlen);
|
|
||||||
[hval, hlen] = getHead(head, hval);
|
|
||||||
return result;
|
|
||||||
} else if (l.slice(-tlen) === tail) {
|
|
||||||
const result = l.slice(hlen, -tlen);
|
|
||||||
[hval, hlen] = getHead(head, hval);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
} else if (operation === OP.NONE) {
|
|
||||||
operation = OP.ADDED;
|
|
||||||
} else if (operation === OP.REMOVED) {
|
|
||||||
return l;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = `${hval}${l}${tail}`;
|
|
||||||
[hval, hlen] = getHead(head, hval);
|
|
||||||
return result;
|
|
||||||
}).join("\n");
|
|
||||||
},
|
|
||||||
|
|
||||||
_applySurround(sel, head, tail, exampleKey, opts) {
|
|
||||||
const pre = sel.pre;
|
|
||||||
const post = sel.post;
|
|
||||||
|
|
||||||
const tlen = tail.length;
|
|
||||||
if (sel.start === sel.end) {
|
|
||||||
if (tlen === 0) { return; }
|
|
||||||
|
|
||||||
const [hval, hlen] = getHead(head);
|
|
||||||
const example = I18n.t(`wizard_composer.${exampleKey}`);
|
|
||||||
this.set('value', `${pre}${hval}${example}${tail}${post}`);
|
|
||||||
this._selectText(pre.length + hlen, example.length);
|
|
||||||
} else if (opts && !opts.multiline) {
|
|
||||||
const [hval, hlen] = getHead(head);
|
|
||||||
|
|
||||||
if (pre.slice(-hlen) === hval && post.slice(0, tail.length) === tail) {
|
|
||||||
this.set('value', `${pre.slice(0, -hlen)}${sel.value}${post.slice(tail.length)}`);
|
|
||||||
this._selectText(sel.start - hlen, sel.value.length);
|
|
||||||
} else {
|
|
||||||
this.set('value', `${pre}${hval}${sel.value}${tail}${post}`);
|
|
||||||
this._selectText(sel.start + hlen, sel.value.length);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const lines = sel.value.split("\n");
|
|
||||||
|
|
||||||
let [hval, hlen] = getHead(head);
|
|
||||||
if (lines.length === 1 && pre.slice(-tlen) === tail && post.slice(0, hlen) === hval) {
|
|
||||||
this.set('value', `${pre.slice(0, -hlen)}${sel.value}${post.slice(tlen)}`);
|
|
||||||
this._selectText(sel.start - hlen, sel.value.length);
|
|
||||||
} else {
|
|
||||||
const contents = this._getMultilineContents(
|
|
||||||
lines,
|
|
||||||
head,
|
|
||||||
hval,
|
|
||||||
hlen,
|
|
||||||
tail,
|
|
||||||
tlen,
|
|
||||||
opts
|
|
||||||
);
|
|
||||||
|
|
||||||
this.set('value', `${pre}${contents}${post}`);
|
|
||||||
if (lines.length === 1 && tlen > 0) {
|
|
||||||
this._selectText(sel.start + hlen, sel.value.length);
|
|
||||||
} else {
|
|
||||||
this._selectText(sel.start, contents.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_applyList(sel, head, exampleKey, opts) {
|
|
||||||
if (sel.value.indexOf("\n") !== -1) {
|
|
||||||
this._applySurround(sel, head, '', exampleKey, opts);
|
|
||||||
} else {
|
|
||||||
|
|
||||||
const [hval, hlen] = getHead(head);
|
|
||||||
if (sel.start === sel.end) {
|
|
||||||
sel.value = I18n.t(`wizard_composer.${exampleKey}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const trimmedPre = sel.pre.trim();
|
|
||||||
const number = (sel.value.indexOf(hval) === 0) ? sel.value.slice(hlen) : `${hval}${sel.value}`;
|
|
||||||
const preLines = trimmedPre.length ? `${trimmedPre}\n\n` : "";
|
|
||||||
|
|
||||||
const trimmedPost = sel.post.trim();
|
|
||||||
const post = trimmedPost.length ? `\n\n${trimmedPost}` : trimmedPost;
|
|
||||||
|
|
||||||
this.set('value', `${preLines}${number}${post}`);
|
|
||||||
this._selectText(preLines.length, number.length);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_replaceText(oldVal, newVal) {
|
|
||||||
const val = this.get('value');
|
|
||||||
const needleStart = val.indexOf(oldVal);
|
|
||||||
|
|
||||||
if (needleStart === -1) {
|
|
||||||
// Nothing to replace.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const textarea = this.$('textarea.d-editor-input')[0];
|
|
||||||
|
|
||||||
// Determine post-replace selection.
|
|
||||||
const newSelection = determinePostReplaceSelection({
|
|
||||||
selection: { start: textarea.selectionStart, end: textarea.selectionEnd },
|
|
||||||
needle: { start: needleStart, end: needleStart + oldVal.length },
|
|
||||||
replacement: { start: needleStart, end: needleStart + newVal.length }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Replace value (side effect: cursor at the end).
|
|
||||||
this.set('value', val.replace(oldVal, newVal));
|
|
||||||
|
|
||||||
// Restore cursor.
|
|
||||||
this._selectText(newSelection.start, newSelection.end - newSelection.start);
|
|
||||||
},
|
|
||||||
|
|
||||||
_addBlock(sel, text) {
|
|
||||||
text = (text || '').trim();
|
|
||||||
if (text.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pre = sel.pre;
|
|
||||||
let post = sel.value + sel.post;
|
|
||||||
|
|
||||||
if (pre.length > 0) {
|
|
||||||
pre = pre.replace(/\n*$/, "\n\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (post.length > 0) {
|
|
||||||
post = post.replace(/^\n*/, "\n\n");
|
|
||||||
} else {
|
|
||||||
post = "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = pre + text + post;
|
|
||||||
const $textarea = this.$('textarea.d-editor-input');
|
|
||||||
|
|
||||||
this.set('value', value);
|
|
||||||
|
|
||||||
$textarea.val(value);
|
|
||||||
$textarea.prop("selectionStart", (pre+text).length + 2);
|
|
||||||
$textarea.prop("selectionEnd", (pre+text).length + 2);
|
|
||||||
|
|
||||||
Ember.run.scheduleOnce("afterRender", () => $textarea.focus());
|
|
||||||
},
|
|
||||||
|
|
||||||
_addText(sel, text, options) {
|
|
||||||
const $textarea = this.$('textarea.d-editor-input');
|
|
||||||
|
|
||||||
if (options && options.ensureSpace) {
|
|
||||||
if ((sel.pre + '').length > 0) {
|
|
||||||
if (!sel.pre.match(/\s$/)) {
|
|
||||||
text = ' ' + text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((sel.post + '').length > 0) {
|
|
||||||
if (!sel.post.match(/^\s/)) {
|
|
||||||
text = text + ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const insert = `${sel.pre}${text}`;
|
|
||||||
const value = `${insert}${sel.post}`;
|
|
||||||
this.set('value', value);
|
|
||||||
$textarea.val(value);
|
|
||||||
$textarea.prop("selectionStart", insert.length);
|
|
||||||
$textarea.prop("selectionEnd", insert.length);
|
|
||||||
Ember.run.scheduleOnce("afterRender", () => $textarea.focus());
|
|
||||||
},
|
|
||||||
|
|
||||||
_extractTable(text) {
|
|
||||||
if (text.endsWith("\n")) {
|
|
||||||
text = text.substring(0, text.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let rows = text.split("\n");
|
|
||||||
|
|
||||||
if (rows.length > 1) {
|
|
||||||
const columns = rows.map(r => r.split("\t").length);
|
|
||||||
const isTable = columns.reduce((a, b) => a && columns[0] === b && b > 1) &&
|
|
||||||
!(columns[0] === 2 && rows[0].split("\t")[0].match(/^•$|^\d+.$/)); // to skip tab delimited lists
|
|
||||||
|
|
||||||
if (isTable) {
|
|
||||||
const splitterRow = [...Array(columns[0])].map(() => "---").join("\t");
|
|
||||||
rows.splice(1, 0, splitterRow);
|
|
||||||
|
|
||||||
return "|" + rows.map(r => r.split("\t").join("|")).join("|\n|") + "|\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
_toggleDirection() {
|
|
||||||
const $textArea = $(".d-editor-input");
|
|
||||||
let currentDir = $textArea.attr('dir') ? $textArea.attr('dir') : siteDir(),
|
|
||||||
newDir = currentDir === 'ltr' ? 'rtl' : 'ltr';
|
|
||||||
|
|
||||||
$textArea.attr('dir', newDir).focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
paste(e) {
|
|
||||||
if (!$(".d-editor-input").is(":focus")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isComposer = $("#reply-control .d-editor-input").is(":focus");
|
|
||||||
let { clipboard, canPasteHtml } = clipboardData(e, isComposer);
|
|
||||||
|
|
||||||
let plainText = clipboard.getData("text/plain");
|
|
||||||
let html = clipboard.getData("text/html");
|
|
||||||
let handled = false;
|
|
||||||
|
|
||||||
if (plainText) {
|
|
||||||
plainText = plainText.trim().replace(/\r/g,"");
|
|
||||||
const table = this._extractTable(plainText);
|
|
||||||
if (table) {
|
|
||||||
this.appEvents.trigger('composer:insert-text', table);
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { pre, lineVal } = this._getSelected(null, {lineVal: true});
|
|
||||||
const isInlinePasting = pre.match(/[^\n]$/);
|
|
||||||
|
|
||||||
if (canPasteHtml && plainText) {
|
|
||||||
if (isInlinePasting) {
|
|
||||||
canPasteHtml = !(lineVal.match(/^```/) || isInside(pre, /`/g) || lineVal.match(/^ /));
|
|
||||||
} else {
|
|
||||||
canPasteHtml = !isInside(pre, /(^|\n)```/g);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (canPasteHtml && !handled) {
|
|
||||||
let markdown = toMarkdown(html);
|
|
||||||
|
|
||||||
if (!plainText || plainText.length < markdown.length) {
|
|
||||||
if(isInlinePasting) {
|
|
||||||
markdown = markdown.replace(/^#+/, "").trim();
|
|
||||||
markdown = pre.match(/\S$/) ? ` ${markdown}` : markdown;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.appEvents.trigger('composer:insert-text', markdown);
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handled) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
keyPress(e) {
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
const selected = this._getSelected();
|
|
||||||
this._addText(selected, '\n');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
toolbarButton(button) {
|
|
||||||
const selected = this._getSelected(button.trimLeading);
|
|
||||||
const toolbarEvent = {
|
|
||||||
selected,
|
|
||||||
selectText: (from, length) => this._selectText(from, length),
|
|
||||||
applySurround: (head, tail, exampleKey, opts) => this._applySurround(selected, head, tail, exampleKey, opts),
|
|
||||||
applyList: (head, exampleKey, opts) => this._applyList(selected, head, exampleKey, opts),
|
|
||||||
addText: text => this._addText(selected, text),
|
|
||||||
replaceText: text => this._addText({pre: '', post: ''}, text),
|
|
||||||
getText: () => this.get('value'),
|
|
||||||
toggleDirection: () => this._toggleDirection(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (button.sendAction) {
|
|
||||||
return this.sendAction(button.sendAction, toolbarEvent);
|
|
||||||
} else {
|
|
||||||
button.perform(toolbarEvent);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
formatCode() {
|
|
||||||
const sel = this._getSelected('', { lineVal: true });
|
|
||||||
const selValue = sel.value;
|
|
||||||
const hasNewLine = selValue.indexOf("\n") !== -1;
|
|
||||||
const isBlankLine = sel.lineVal.trim().length === 0;
|
|
||||||
const isFourSpacesIndent = this.siteSettings.code_formatting_style === FOUR_SPACES_INDENT;
|
|
||||||
|
|
||||||
if (!hasNewLine) {
|
|
||||||
if (selValue.length === 0 && isBlankLine) {
|
|
||||||
if (isFourSpacesIndent) {
|
|
||||||
const example = I18n.t(`wizard_composer.code_text`);
|
|
||||||
this.set('value', `${sel.pre} ${example}${sel.post}`);
|
|
||||||
return this._selectText(sel.pre.length + 4, example.length);
|
|
||||||
} else {
|
|
||||||
return this._applySurround(sel, "```\n", "\n```", 'paste_code_text');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this._applySurround(sel, '`', '`', 'code_title');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isFourSpacesIndent) {
|
|
||||||
return this._applySurround(sel, ' ', '', 'code_text');
|
|
||||||
} else {
|
|
||||||
const preNewline = (sel.pre[-1] !== "\n" && sel.pre !== "") ? "\n" : "";
|
|
||||||
const postNewline = sel.post[0] !== "\n" ? "\n" : "";
|
|
||||||
return this._addText(sel, `${preNewline}\`\`\`\n${sel.value}\n\`\`\`${postNewline}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,6 +1,22 @@
|
||||||
import { default as computed } from 'ember-addons/ember-computed-decorators';
|
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
|
showPreview: false,
|
||||||
|
elementId: "reply-control",
|
||||||
|
classNameBindings: ["showPreview:show-preview:hide-preview"],
|
||||||
|
|
||||||
|
didInsertElement() {
|
||||||
|
this.set('composer', Ember.Object.create({
|
||||||
|
loading: false,
|
||||||
|
reply: this.get('field.value')
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
@observes('composer.reply')
|
||||||
|
setField() {
|
||||||
|
this.set('field.value', this.get('composer.reply'));
|
||||||
|
},
|
||||||
|
|
||||||
@computed('showPreview')
|
@computed('showPreview')
|
||||||
togglePreviewLabel(showPreview) {
|
togglePreviewLabel(showPreview) {
|
||||||
return showPreview ? 'wizard_composer.hide_preview' : 'wizard_composer.show_preview';
|
return showPreview ? 'wizard_composer.hide_preview' : 'wizard_composer.show_preview';
|
||||||
|
@ -9,6 +25,21 @@ export default Ember.Component.extend({
|
||||||
actions: {
|
actions: {
|
||||||
togglePreview() {
|
togglePreview() {
|
||||||
this.toggleProperty('showPreview');
|
this.toggleProperty('showPreview');
|
||||||
|
},
|
||||||
|
|
||||||
|
groupsMentioned() {
|
||||||
|
},
|
||||||
|
afterRefresh() {
|
||||||
|
},
|
||||||
|
storeToolbarState() {
|
||||||
|
},
|
||||||
|
cannotSeeMention() {
|
||||||
|
},
|
||||||
|
importQuote() {
|
||||||
|
},
|
||||||
|
onPopupMenuAction() {
|
||||||
|
},
|
||||||
|
showUploadSelector() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,35 @@
|
||||||
import WizardApplication from 'wizard/wizard';
|
import { buildResolver } from "discourse-common/resolver";
|
||||||
|
|
||||||
export default WizardApplication.extend({
|
export default Ember.Application.extend({
|
||||||
rootElement: '#custom-wizard-main'
|
rootElement: '#custom-wizard-main',
|
||||||
|
Resolver: buildResolver("wizard"),
|
||||||
|
|
||||||
|
start() {
|
||||||
|
Object.keys(requirejs._eak_seen).forEach(key => {
|
||||||
|
if (/\/initializers\//.test(key)) {
|
||||||
|
const module = requirejs(key, null, null, true);
|
||||||
|
if (!module) {
|
||||||
|
throw new Error(key + " must export an initializer.");
|
||||||
|
}
|
||||||
|
this.initializer(module.default);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(requirejs._eak_seen).forEach((key) => {
|
||||||
|
if (/\/pre\-initializers\//.test(key)) {
|
||||||
|
const module = requirejs(key, null, null, true);
|
||||||
|
if (!module) {
|
||||||
|
throw new Error(key + " must export an initializer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const init = module.default;
|
||||||
|
const oldInitialize = init.initialize;
|
||||||
|
init.initialize = () => {
|
||||||
|
oldInitialize.call(this, this.__container__, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.initializer(init);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
5
assets/javascripts/wizard/helpers/plugin-outlet.js.es6
Normale Datei
5
assets/javascripts/wizard/helpers/plugin-outlet.js.es6
Normale Datei
|
@ -0,0 +1,5 @@
|
||||||
|
import { registerUnbound } from "discourse-common/lib/helpers";
|
||||||
|
|
||||||
|
export default registerUnbound("plugin-outlet", function(attrs) {
|
||||||
|
return new Handlebars.SafeString('');
|
||||||
|
});
|
|
@ -20,10 +20,14 @@ export default {
|
||||||
const Singleton = requirejs("discourse/mixins/singleton").default;
|
const Singleton = requirejs("discourse/mixins/singleton").default;
|
||||||
const WizardFieldDropdown = requirejs('wizard/components/wizard-field-dropdown').default;
|
const WizardFieldDropdown = requirejs('wizard/components/wizard-field-dropdown').default;
|
||||||
const Store = requirejs("discourse/models/store").default;
|
const Store = requirejs("discourse/models/store").default;
|
||||||
|
const registerRawHelpers = requirejs("discourse-common/lib/raw-handlebars-helpers").registerRawHelpers;
|
||||||
|
const RawHandlebars = requirejs("discourse-common/lib/raw-handlebars").default;
|
||||||
|
|
||||||
Discourse.__container__ = app.__container__;
|
Discourse.__container__ = app.__container__;
|
||||||
Discourse.getURLWithCDN = getUrl;
|
Discourse.getURLWithCDN = getUrl;
|
||||||
Discourse.getURL = getUrl;
|
Discourse.getURL = getUrl;
|
||||||
|
|
||||||
|
registerRawHelpers(RawHandlebars, Handlebars);
|
||||||
|
|
||||||
WizardFieldDropdown.reopen({
|
WizardFieldDropdown.reopen({
|
||||||
tagName: 'span',
|
tagName: 'span',
|
||||||
|
@ -65,6 +69,8 @@ export default {
|
||||||
app.register("site:main", site);
|
app.register("site:main", site);
|
||||||
targets.forEach(t => app.inject(t, "site", "site:main"));
|
targets.forEach(t => app.inject(t, "site", "site:main"));
|
||||||
|
|
||||||
|
targets.forEach(t => app.inject(t, "appEvents", "service:app-events"));
|
||||||
|
|
||||||
site.reopenClass(Singleton);
|
site.reopenClass(Singleton);
|
||||||
site.currentProp('can_create_tag', false);
|
site.currentProp('can_create_tag', false);
|
||||||
|
|
||||||
|
@ -138,6 +144,9 @@ export default {
|
||||||
.catch(() => this.animateInvalidFields())
|
.catch(() => this.animateInvalidFields())
|
||||||
.finally(() => this.set('saving', false));
|
.finally(() => this.set('saving', false));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
keyPress(key) {
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
quit() {
|
quit() {
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
{{d-editor
|
||||||
|
tabindex="4"
|
||||||
|
value=composer.reply
|
||||||
|
placeholder=replyPlaceholder
|
||||||
|
previewUpdated=(action "previewUpdated")
|
||||||
|
markdownOptions=markdownOptions
|
||||||
|
extraButtons=(action "extraButtons")
|
||||||
|
importQuote=(action "importQuote")
|
||||||
|
showUploadModal=showUploadModal
|
||||||
|
togglePreview=(action "togglePreview")
|
||||||
|
validation=validation
|
||||||
|
loading=composer.loading
|
||||||
|
showLink=showLink
|
||||||
|
composerEvents=true
|
||||||
|
onExpandPopupMenuOptions=(action "onExpandPopupMenuOptions")
|
||||||
|
onPopupMenuAction=onPopupMenuAction
|
||||||
|
popupMenuOptions=popupMenuOptions
|
||||||
|
disabled=disableTextarea
|
||||||
|
outletArgs=(hash composer=composer editorType="composer")}}
|
|
@ -1,4 +1,14 @@
|
||||||
{{wizard-editor showPreview=showPreview value=field.value placeholder=field.placeholder}}
|
{{wizard-composer-editor replyPlaceholder=field.placeholder
|
||||||
<button class='wizard-btn primary' {{action 'togglePreview'}}>
|
composer=composer
|
||||||
|
storeToolbarState=(action "storeToolbarState")
|
||||||
|
onPopupMenuAction=(action "onPopupMenuAction")
|
||||||
|
showUploadModal=(action "showUploadSelector")
|
||||||
|
groupsMentioned=(action "groupsMentioned")
|
||||||
|
cannotSeeMention=(action "cannotSeeMention")
|
||||||
|
importQuote=(action "importQuote")
|
||||||
|
togglePreview=(action "togglePreview")
|
||||||
|
afterRefresh=(action "afterRefresh")}}
|
||||||
|
|
||||||
|
<button class='wizard-btn primary toggle-preview' {{action 'togglePreview'}}>
|
||||||
<span class="d-button-label">{{i18n togglePreviewLabel}}</span>
|
<span class="d-button-label">{{i18n togglePreviewLabel}}</span>
|
||||||
</button>
|
</button>
|
|
@ -44,6 +44,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#reply-control.show-preview .d-editor-textarea-wrapper {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-wizard #reply-control .toggle-preview {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.d-editor-textarea-wrapper,
|
.d-editor-textarea-wrapper,
|
||||||
.d-editor-preview-wrapper {
|
.d-editor-preview-wrapper {
|
||||||
background-color: $secondary;
|
background-color: $secondary;
|
||||||
|
@ -132,6 +140,7 @@
|
||||||
|
|
||||||
.d-editor-preview {
|
.d-editor-preview {
|
||||||
height: auto;
|
height: auto;
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.d-editor-plugin {
|
.d-editor-plugin {
|
||||||
|
@ -163,6 +172,10 @@
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
flex: initial;
|
flex: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
min-height: calc(200px - 32px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.d-editor-modal.hidden {
|
.d-editor-modal.hidden {
|
||||||
|
|
Laden …
In neuem Issue referenzieren