Merge pull request #6445 from opf/ckeditor-integration

Ckeditor integration branch

[ci skip]
pull/6449/head
Oliver Günther 6 years ago committed by GitHub
commit 0225163900
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/assets/javascripts/vendor/ckeditor/ckeditor.js
  2. 2
      app/assets/javascripts/vendor/ckeditor/ckeditor.js.map
  3. 43
      app/helpers/text_formatting_helper.rb
  4. 6
      app/views/messages/_form.html.erb
  5. 4
      app/views/news/_form.html.erb
  6. 2
      app/views/news/show.html.erb
  7. 2
      app/views/wiki/_page_form.html.erb
  8. 2
      frontend/src/app/components/ckeditor/ckeditor-setup.service.ts
  9. 6
      frontend/src/app/components/ckeditor/op-ckeditor-form.component.ts
  10. 3
      frontend/src/app/components/modals/editor/editor-macros.service.ts
  11. 2
      frontend/src/app/modules/common/path-helper/apiv3/apiv3-paths.ts
  12. 15
      frontend/src/app/modules/fields/edit/field-types/formattable-edit-field.ts
  13. 16
      lib/api/v3/render/render_api.rb
  14. 2
      lib/open_project/text_formatting/formats/markdown/helper.rb
  15. 3
      lib/tabular_form_builder.rb
  16. 13
      spec/features/wysiwyg/macros/child_pages_spec.rb
  17. 7
      spec/features/wysiwyg/macros/include_wiki_page_spec.rb

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -34,16 +34,13 @@ module TextFormattingHelper
:text_formatting_js_includes, :text_formatting_js_includes,
:wikitoolbar_for :wikitoolbar_for
def preview_context(object = nil) def preview_context(object, project = nil)
paths = API::V3::Utilities::PathHelper::ApiV3Path if object.new_record?
project_preview_context(object, project)
case object elsif object.is_a? Message
when News message_preview_context(object)
paths.news(object.id) else
when Message object_preview_context(object)
paths.post(object.id)
when WikiPage
paths.wiki_page(object.id)
end end
end end
@ -52,4 +49,30 @@ module TextFormattingHelper
helper_class = OpenProject::TextFormatting::Formats.rich_helper helper_class = OpenProject::TextFormatting::Formats.rich_helper
helper_class.new(self) helper_class.new(self)
end end
def project_preview_context(object, project)
relevant_project = if project
project
elsif object.respond_to?(:project) && object.project
object.project
end
return nil unless relevant_project
API::V3::Utilities::PathHelper::ApiV3Path
.project(relevant_project.id)
end
def message_preview_context(message)
API::V3::Utilities::PathHelper::ApiV3Path
.post(message.id)
end
def object_preview_context(object)
paths = API::V3::Utilities::PathHelper::ApiV3Path
if paths.respond_to?(object.class.name.underscore.singularize)
paths.send(object.class.name.underscore.singularize, object.id)
end
end
end end

@ -47,10 +47,8 @@ See docs/COPYRIGHT.rdoc for more details.
<div class="form--field -required"> <div class="form--field -required">
<% preview_object = if replying <% preview_object = if replying
f.object.parent f.object.parent
elsif f.object.persisted?
f.object
else else
nil f.object
end %> end %>
<%= f.text_area :content, <%= f.text_area :content,
@ -59,6 +57,6 @@ See docs/COPYRIGHT.rdoc for more details.
class: 'wiki-edit', class: 'wiki-edit',
container_class: '-wide', container_class: '-wide',
with_text_formatting: true, with_text_formatting: true,
preview_context: preview_context(preview_object) %> preview_context: preview_context(preview_object, @project) %>
</div> </div>
<%= render partial: 'attachments/form' %> <%= render partial: 'attachments/form' %>

@ -38,11 +38,9 @@ See docs/COPYRIGHT.rdoc for more details.
container_class: '-wide' %> container_class: '-wide' %>
</div> </div>
<div class="form--field"> <div class="form--field">
<% link_object = f.object.persisted? ? f.object : nil %>
<%= f.text_area :description, <%= f.text_area :description,
required: true, required: true,
class: 'wiki-edit wiki-toolbar', class: 'wiki-edit wiki-toolbar',
container_class: '-wide', container_class: '-wide',
with_text_formatting: true, with_text_formatting: true %>
preview_context: preview_context(link_object) %>
</div> </div>

@ -97,7 +97,7 @@ See docs/COPYRIGHT.rdoc for more details.
<%= text_area 'comment', <%= text_area 'comment',
'comments', 'comments',
class: 'wiki-edit' %> class: 'wiki-edit' %>
<%= wikitoolbar_for 'comment_comments' %> <%= wikitoolbar_for 'comment_comments', preview_context: preview_context(@news) %>
</div> </div>
<p><%= submit_tag l(:button_add), class: 'button -highlight' %></p> <p><%= submit_tag l(:button_add), class: 'button -highlight' %></p>
<% end %> <% end %>

@ -27,7 +27,7 @@
accesskey: accesskey(:edit), accesskey: accesskey(:edit),
with_text_formatting: true, with_text_formatting: true,
resource: ::API::V3::WikiPages::WikiPageRepresenter.new(@page, current_user: current_user), resource: ::API::V3::WikiPages::WikiPageRepresenter.new(@page, current_user: current_user),
preview_context: preview_context(@page) %> preview_context: preview_context(@page, @project) %>
</div> </div>
<div class="form--field"> <div class="form--field">

@ -20,6 +20,8 @@ export interface ICKEditorContext {
removePlugins?:string[]; removePlugins?:string[];
// Set of enabled macro plugins or false to disable all // Set of enabled macro plugins or false to disable all
macros?:false|string[]; macros?:false|string[];
// context link to append on preview requests
previewContext?:string;
} }
declare global { declare global {

@ -84,9 +84,13 @@ export class OpCkeditorFormComponent implements OnInit {
this.wrappedTextArea = this.formElement.find(this.textareaSelector); this.wrappedTextArea = this.formElement.find(this.textareaSelector);
this.wrappedTextArea.hide(); this.wrappedTextArea.hide();
const wrapper = this.$element.find(`.${ckEditorReplacementClass}`); const wrapper = this.$element.find(`.${ckEditorReplacementClass}`);
const context = { resource: this.resource,
previewContext: this.previewContext };
const editorPromise = this.ckEditorSetup const editorPromise = this.ckEditorSetup
.create('classic', wrapper[0], { resource: this.resource }) .create('classic',
wrapper[0],
context)
.then(this.setup.bind(this)); .then(this.setup.bind(this));
this.$element.data('editor', editorPromise); this.$element.data('editor', editorPromise);

@ -61,7 +61,8 @@ export class EditorMacrosService {
*/ */
public configureWikiPageInclude(page:string):Promise<string> { public configureWikiPageInclude(page:string):Promise<string> {
return new Promise<string>((resolve, _) => { return new Promise<string>((resolve, _) => {
const modal = this.opModalService.show(WikiIncludePageMacroModal, { page: page }); const pageValue = page || '';
const modal = this.opModalService.show(WikiIncludePageMacroModal, { page: pageValue });
modal.closingEvent.subscribe((modal:WikiIncludePageMacroModal) => { modal.closingEvent.subscribe((modal:WikiIncludePageMacroModal) => {
if (modal.changed) { if (modal.changed) {
resolve(modal.page); resolve(modal.page);

@ -92,7 +92,7 @@ export class ApiV3Paths {
let base = this.apiV3Base + '/render/markdown'; let base = this.apiV3Base + '/render/markdown';
if (context) { if (context) {
return base + `?link=${context}`; return base + `?context=${context}`;
} else { } else {
return base; return base;
} }

@ -70,8 +70,13 @@ export class FormattableEditField extends EditField {
public setupMarkdownEditor(container:HTMLElement) { public setupMarkdownEditor(container:HTMLElement) {
const element = container.querySelector('.op-ckeditor-element') as HTMLElement; const element = container.querySelector('.op-ckeditor-element') as HTMLElement;
const context = { resource: this.resource,
previewContext: this.previewContext };
this.ckEditorSetup this.ckEditorSetup
.create('balloon', element, { resource: this.resource }) .create('balloon',
element,
context)
.then((editor:ICKEditorInstance) => { .then((editor:ICKEditorInstance) => {
this.ckeditor = editor; this.ckeditor = editor;
if (this.rawValue) { if (this.rawValue) {
@ -89,6 +94,14 @@ export class FormattableEditField extends EditField {
} ); } );
} }
private get previewContext() {
if (this.resource.isNew && this.resource.project) {
return this.resource.project.href;
} else if (!this.resource.isNew) {
return this.pathHelper.api.v3.work_packages.id(this.resource.id).path;
}
}
public reset() { public reset() {
this.ckeditor.setData(this.rawValue); this.ckeditor.setData(this.rawValue);
} }

@ -36,8 +36,8 @@ module API
resources :render do resources :render do
helpers do helpers do
SUPPORTED_CONTEXT_NAMESPACES = %w(work_packages projects news posts).freeze SUPPORTED_CONTEXT_NAMESPACES = %w(work_packages projects news posts wiki_pages).freeze
SUPPORTED_MEDIA_TYPE = 'text/plain' SUPPORTED_MEDIA_TYPE = 'text/plain'.freeze
def allowed_content_types def allowed_content_types
[SUPPORTED_MEDIA_TYPE] [SUPPORTED_MEDIA_TYPE]
@ -46,7 +46,7 @@ module API
def check_content_type def check_content_type
actual = request.content_type actual = request.content_type
unless actual && actual.starts_with?(SUPPORTED_MEDIA_TYPE) unless actual&.starts_with?(SUPPORTED_MEDIA_TYPE)
bad_type = actual || I18n.t('api_v3.errors.missing_content_type') bad_type = actual || I18n.t('api_v3.errors.missing_content_type')
message = I18n.t('api_v3.errors.invalid_content_type', message = I18n.t('api_v3.errors.invalid_content_type',
content_type: SUPPORTED_MEDIA_TYPE, content_type: SUPPORTED_MEDIA_TYPE,
@ -82,14 +82,12 @@ module API
def try_context_object def try_context_object
if params[:context] if params[:context]
context = parse_context context = parse_context
namespace = context[:namespace]
klass = case context[:namespace] klass = if namespace == 'posts'
when 'work_packages'
WorkPackage
when 'news'
News
when 'posts'
Message Message
elsif SUPPORTED_CONTEXT_NAMESPACES.without('posts').include?(namespace)
namespace.camelcase.singularize.constantize
end end
return unless klass return unless klass

@ -58,7 +58,7 @@ module OpenProject::TextFormatting::Formats
view_context.content_tag 'op-ckeditor-form', view_context.content_tag 'op-ckeditor-form',
'', '',
'textarea-selector': "##{field_id}", 'textarea-selector': "##{field_id}",
'preview-context': context[:preview], 'preview-context': context[:preview_context],
'data-resource': resource.to_json 'data-resource': resource.to_json
end end
end end

@ -59,8 +59,7 @@ class TabularFormBuilder < ActionView::Helpers::FormBuilder
if options[:with_text_formatting] if options[:with_text_formatting]
# use either the provided id or fetch the one created by rails # use either the provided id or fetch the one created by rails
id = options[:id] || input.match(/<[^>]* id="(\w+)"[^>]*>/)[1] id = options[:id] || input.match(/<[^>]* id="(\w+)"[^>]*>/)[1]
context_object = object.persisted? ? object : nil options[:preview_context] ||= preview_context(object)
options[:preview_context] ||= preview_context(context_object)
input.concat text_formatting_wrapper id, options input.concat text_formatting_wrapper id, options
end end

@ -94,6 +94,10 @@ describe 'Wysiwyg child pages spec',
# Find widget, click to show toolbar # Find widget, click to show toolbar
placeholder = find('.macro.-child_pages') placeholder = find('.macro.-child_pages')
# Placeholder states `this page` and no `Include parent`
expect(placeholder).to have_text('this page')
expect(placeholder).not_to have_text('Include parent')
# Edit widget and cancel again # Edit widget and cancel again
placeholder.click placeholder.click
page.find('.ck-balloon-panel .ck-button', visible: :all, text: 'Edit').click page.find('.ck-balloon-panel .ck-button', visible: :all, text: 'Edit').click
@ -110,6 +114,9 @@ describe 'Wysiwyg child pages spec',
# Save widget # Save widget
find('.op-modal--submit-button').click find('.op-modal--submit-button').click
# Placeholder states `parent-page` and no `Include parent`
expect(placeholder).to have_text('parent-page')
expect(placeholder).not_to have_text('Include parent')
end end
# Save wiki page # Save wiki page
@ -126,7 +133,7 @@ describe 'Wysiwyg child pages spec',
find('.toolbar .icon-edit').click find('.toolbar .icon-edit').click
end end
editor.in_editor do |container, editable| editor.in_editor do |_container, _editable|
# Find widget, click to show toolbar # Find widget, click to show toolbar
placeholder = find('.macro.-child_pages') placeholder = find('.macro.-child_pages')
@ -138,6 +145,10 @@ describe 'Wysiwyg child pages spec',
# Save widget # Save widget
find('.op-modal--submit-button').click find('.op-modal--submit-button').click
# Placeholder states `parent-page` and `Include parent`
expect(placeholder).to have_text('parent-page')
expect(placeholder).to have_text('Include parent')
end end
# Save wiki page # Save wiki page

@ -96,10 +96,13 @@ describe 'Wysiwyg include wiki page spec',
find('.op-modal--submit-button').click find('.op-modal--submit-button').click
# Find widget, click to show toolbar # Find widget, click to show toolbar
modal = find('.macro.-wiki_page_include') placeholder = find('.macro.-wiki_page_include')
# Placeholder states `included`
expect(placeholder).to have_text('included')
# Edit widget again # Edit widget again
modal.click placeholder.click
page.find('.ck-balloon-panel .ck-button', visible: :all, text: 'Edit').click page.find('.ck-balloon-panel .ck-button', visible: :all, text: 'Edit').click
expect(page).to have_field('selected-page', with: 'included') expect(page).to have_field('selected-page', with: 'included')
find('.op-modal--cancel-button').click find('.op-modal--cancel-button').click

Loading…
Cancel
Save