diff --git a/frontend/src/app/features/hal/resources/meeting-resource.ts b/frontend/src/app/features/hal/resources/meeting-resource.ts new file mode 100644 index 000000000000..4d2260038394 --- /dev/null +++ b/frontend/src/app/features/hal/resources/meeting-resource.ts @@ -0,0 +1,40 @@ +// -- copyright +// OpenProject is an open source project management software. +// Copyright (C) 2012-2024 the OpenProject GmbH +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License version 3. +// +// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +// Copyright (C) 2006-2013 Jean-Philippe Lang +// Copyright (C) 2010-2013 the ChiliProject Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// See COPYRIGHT and LICENSE files for more details. +//++ + +import { HalResource } from 'core-app/features/hal/resources/hal-resource'; +import { Attachable } from 'core-app/features/hal/resources/mixins/attachable-mixin'; + +export interface MeetingResourceLinks { + addAttachment(attachment:HalResource):Promise; +} + +class MeetingBaseResource extends HalResource { + public $links:MeetingResourceLinks; +} + +export const MeetingResource = Attachable(MeetingBaseResource); diff --git a/frontend/src/app/features/hal/services/hal-resource.config.ts b/frontend/src/app/features/hal/services/hal-resource.config.ts index 7b20feb79745..c2a26efbe542 100644 --- a/frontend/src/app/features/hal/services/hal-resource.config.ts +++ b/frontend/src/app/features/hal/services/hal-resource.config.ts @@ -56,13 +56,16 @@ import { HalResourceFactoryConfigInterface, HalResourceService, } from 'core-app/features/hal/services/hal-resource.service'; -import { QueryFilterInstanceSchemaResource } from 'core-app/features/hal/resources/query-filter-instance-schema-resource'; +import { + QueryFilterInstanceSchemaResource, +} from 'core-app/features/hal/resources/query-filter-instance-schema-resource'; import { ErrorResource } from 'core-app/features/hal/resources/error-resource'; import { SchemaDependencyResource } from 'core-app/features/hal/resources/schema-dependency-resource'; import { WorkPackageCollectionResource } from 'core-app/features/hal/resources/wp-collection-resource'; import { RelationResource } from 'core-app/features/hal/resources/relation-resource'; import { QueryFilterResource } from 'core-app/features/hal/resources/query-filter-resource'; import { SchemaResource } from 'core-app/features/hal/resources/schema-resource'; +import { MeetingResource } from 'core-app/features/hal/resources/meeting-resource'; const halResourceDefaultConfig:{ [typeName:string]:HalResourceFactoryConfigInterface } = { WorkPackage: { @@ -179,6 +182,9 @@ const halResourceDefaultConfig:{ [typeName:string]:HalResourceFactoryConfigInter WikiPage: { cls: WikiPageResource, }, + Meeting: { + cls: MeetingResource, + }, MeetingContent: { cls: MeetingContentResource, }, diff --git a/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.component.ts b/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.component.ts index 35b6a8a26497..653d3888f25d 100644 --- a/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.component.ts +++ b/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.component.ts @@ -70,6 +70,8 @@ export class CkeditorAugmentedTextareaComponent extends UntilDestroyedMixin impl @Input() public editorType:ICKEditorType = 'full'; + @Input() public showAttachments = true; + // Which template to include public element:HTMLElement; diff --git a/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.html b/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.html index c822b1c3265a..771c286e38fa 100644 --- a/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.html +++ b/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.html @@ -10,7 +10,7 @@
{{ text.attachments }} diff --git a/lib/primer/open_project/forms/dsl/rich_text_area_input.rb b/lib/primer/open_project/forms/dsl/rich_text_area_input.rb index fa2c1dc46bc3..5cf58d9abd28 100644 --- a/lib/primer/open_project/forms/dsl/rich_text_area_input.rb +++ b/lib/primer/open_project/forms/dsl/rich_text_area_input.rb @@ -7,15 +7,16 @@ module Dsl class RichTextAreaInput < Primer::Forms::Dsl::Input attr_reader :name, :label - def initialize(name:, label:, **system_arguments) + def initialize(name:, label:, rich_text_options:, **system_arguments) @name = name @label = label + @rich_text_options = rich_text_options super(**system_arguments) end def to_component - RichTextArea.new(input: self) + RichTextArea.new(input: self, rich_text_options: @rich_text_options) end def type diff --git a/lib/primer/open_project/forms/rich_text_area.html.erb b/lib/primer/open_project/forms/rich_text_area.html.erb index 3b4747379769..a7b9357ff79b 100644 --- a/lib/primer/open_project/forms/rich_text_area.html.erb +++ b/lib/primer/open_project/forms/rich_text_area.html.erb @@ -3,10 +3,12 @@ <%= builder.text_area(@input.name, **@input.input_arguments) %> <% end %> <%= angular_component_tag 'opce-ckeditor-augmented-textarea', - inputs: { - textareaSelector: "##{builder.field_id(@input.name)}", - macros: 'resource', - turboMode: true - } + inputs: @rich_text_options.reverse_merge( + { + textareaSelector: "##{builder.field_id(@input.name)}", + macros: 'resource', + turboMode: true + } + ) %> <% end %> diff --git a/lib/primer/open_project/forms/rich_text_area.rb b/lib/primer/open_project/forms/rich_text_area.rb index 79a50b7fab98..87f4220f9406 100644 --- a/lib/primer/open_project/forms/rich_text_area.rb +++ b/lib/primer/open_project/forms/rich_text_area.rb @@ -9,9 +9,10 @@ class RichTextArea < Primer::Forms::BaseComponent delegate :builder, :form, to: :@input - def initialize(input:) + def initialize(input:, rich_text_options:) super() @input = input + @rich_text_options = rich_text_options end end end diff --git a/modules/meeting/app/components/meetings/show_component.html.erb b/modules/meeting/app/components/meetings/show_component.html.erb index d2ef57e35ced..51e203c6a62f 100644 --- a/modules/meeting/app/components/meetings/show_component.html.erb +++ b/modules/meeting/app/components/meetings/show_component.html.erb @@ -22,6 +22,11 @@ agenda.with_row do render(MeetingAgendaItems::NewButtonComponent.new(meeting: @meeting)) end + + agenda.with_row do + resource = ::API::V3::Meetings::MeetingRepresenter.new(@meeting, current_user: User.current, embed_links: false) + helpers.list_attachments(resource) + end end end diff --git a/modules/meeting/app/forms/meeting_agenda_item/notes.rb b/modules/meeting/app/forms/meeting_agenda_item/notes.rb index 0b605d8f3ba8..5ca18b7adc79 100644 --- a/modules/meeting/app/forms/meeting_agenda_item/notes.rb +++ b/modules/meeting/app/forms/meeting_agenda_item/notes.rb @@ -27,15 +27,25 @@ #++ class MeetingAgendaItem::Notes < ApplicationForm + delegate :object, to: :@builder + form do |agenda_item_form| agenda_item_form.rich_text_area( name: :notes, label: MeetingAgendaItem.human_attribute_name(:notes), - disabled: @disabled + disabled: @disabled, + rich_text_options: { + resource:, + } ) end def initialize(disabled: false) @disabled = disabled end + + def resource + API::V3::Meetings::MeetingRepresenter + .new(object.meeting, current_user: User.current, embed_links: false) + end end diff --git a/modules/meeting/spec/features/structured_meetings/attachment_upload_spec.rb b/modules/meeting/spec/features/structured_meetings/attachment_upload_spec.rb new file mode 100644 index 000000000000..a1cad1c3e48b --- /dev/null +++ b/modules/meeting/spec/features/structured_meetings/attachment_upload_spec.rb @@ -0,0 +1,69 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2024 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +require 'spec_helper' +require 'features/page_objects/notification' +require_relative '../../support/pages/structured_meeting/show' + +RSpec.describe 'Upload attachment to meetings', :js do + let(:user) do + create(:user, + member_with_permissions: { project => %i[view_meetings edit_meetings manage_agendas] }) + end + let(:project) { create(:project) } + let(:attachments) { Components::Attachments.new } + let(:image_fixture) { UploadedFile.load_from('spec/fixtures/files/image.png') } + let(:editor) { Components::WysiwygEditor.new '#content', 'opce-ckeditor-augmented-textarea' } + let(:wiki_page_content) { project.wiki.pages.first.text } + + let(:meeting) { create(:structured_meeting, project: project)} + let(:show_page) { Pages::StructuredMeeting::Show.new(meeting) } + + before do + login_as(user) + end + + it 'can upload an image to new and existing meeting agenda item via drag & drop in editor' do + show_page.visit! + + show_page.add_agenda_item(save: false) do + click_link_or_button 'Notes' + end + + # adding an image + editor.drag_attachment image_fixture.path, 'Image uploaded the first time' + + editor.attachments_list.expect_attached('image.png') + editor.wait_until_upload_progress_toaster_cleared + + click_link_or_button 'Save' + expect(page).to have_css('#meeting-agenda-items-list-component img', count: 1) + expect(page).to have_content('Image uploaded the first time') + editor.attachments_list.expect_attached('image.png') + end +end diff --git a/modules/meeting/spec/support/pages/structured_meeting/show.rb b/modules/meeting/spec/support/pages/structured_meeting/show.rb index 14e43b50cf11..cfd23e03b0bf 100644 --- a/modules/meeting/spec/support/pages/structured_meeting/show.rb +++ b/modules/meeting/spec/support/pages/structured_meeting/show.rb @@ -36,7 +36,7 @@ def expect_empty expect(page).to have_no_css('[id^="meeting-agenda-items-item-component"]') end - def add_agenda_item(type: MeetingAgendaItem, &) + def add_agenda_item(type: MeetingAgendaItem, save: true, &) page.within("#meeting-agenda-items-new-button-component") do click_button I18n.t(:button_add) click_link type.model_name.human @@ -44,7 +44,7 @@ def add_agenda_item(type: MeetingAgendaItem, &) in_agenda_form do yield - click_button 'Save' + click_button('Save') if save end end diff --git a/spec/support/components/wysiwyg/wysiwyg_editor.rb b/spec/support/components/wysiwyg/wysiwyg_editor.rb index 74e20ec81ba4..57ce75c2d460 100644 --- a/spec/support/components/wysiwyg/wysiwyg_editor.rb +++ b/spec/support/components/wysiwyg/wysiwyg_editor.rb @@ -6,10 +6,10 @@ class WysiwygEditor attr_reader :context_selector, :attachments, :attachments_list - def initialize(context = '#content') + def initialize(context = '#content', attachment_list_selector = 'ckeditor-augmented-textarea') @context_selector = context @attachments = ::Components::Attachments.new - @attachments_list = ::Components::AttachmentsList.new("#{context} ckeditor-augmented-textarea") + @attachments_list = ::Components::AttachmentsList.new("#{context} #{attachment_list_selector}") end def container