diff --git a/app/assets/javascripts/components/saved_annotations/edit_saved_annotation.ts b/app/assets/javascripts/components/saved_annotations/edit_saved_annotation.ts deleted file mode 100644 index dbaeecd367..0000000000 --- a/app/assets/javascripts/components/saved_annotations/edit_saved_annotation.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { customElement, property } from "lit/decorators.js"; -import { html, TemplateResult } from "lit"; -import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; -import { SavedAnnotation, savedAnnotationState } from "state/SavedAnnotations"; -import "./saved_annotation_form"; -import { modalMixin } from "components/modal_mixin"; - -/** - * This component represents an edit button for a saved annotation - * It also contains the edit form within a modal, which will be shown upon clicking the button - * - * @element d-edit-saved-annotation - * - * @prop {SavedAnnotation} savedAnnotation - the saved annotation to be edited - */ -@customElement("d-edit-saved-annotation") -export class EditSavedAnnotation extends modalMixin(ShadowlessLitElement) { - @property({ type: Object }) - savedAnnotation: SavedAnnotation; - - @property({ state: true }) - errors: string[]; - - async updateSavedAnnotation(): Promise { - try { - await savedAnnotationState.update(this.savedAnnotation.id, { - saved_annotation: this.savedAnnotation - }); - this.errors = undefined; - this.hideModal(); - } catch (errors) { - this.errors = errors; - } - } - - async deleteSavedAnnotation(): Promise { - if (confirm(I18n.t("js.saved_annotation.delete.confirm"))) { - await savedAnnotationState.delete(this.savedAnnotation.id); - this.hideModal(); - } - } - - get filledModalTemplate(): TemplateResult { - return this.modalTemplate(html` - ${I18n.t("js.saved_annotation.edit.title")} - `, html` - ${this.errors !== undefined ? html` -
-

${I18n.t("js.saved_annotation.edit.errors", { count: this.errors.length })}

-
    - ${this.errors.map(error => html` -
  • ${error}
  • `)} -
-
- ` : ""} - this.savedAnnotation = e.detail} - > - `, html` - - - - - `); - } - - render(): TemplateResult { - return html` - this.showModal()} - > - - `; - } -} diff --git a/app/assets/javascripts/components/saved_annotations/saved_annotation_icon.ts b/app/assets/javascripts/components/saved_annotations/saved_annotation_icon.ts index 17f4aebeea..a23f08d9a9 100644 --- a/app/assets/javascripts/components/saved_annotations/saved_annotation_icon.ts +++ b/app/assets/javascripts/components/saved_annotations/saved_annotation_icon.ts @@ -26,9 +26,11 @@ export class SavedAnnotationIcon extends ShadowlessLitElement { render(): TemplateResult { return isBetaCourse() && this.isAlreadyLinked && this.savedAnnotation!= undefined ? html` - + + + ` : html``; } } diff --git a/app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts b/app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts index dcccb80528..0b7bab9fae 100644 --- a/app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts +++ b/app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts @@ -2,7 +2,6 @@ import { customElement, property } from "lit/decorators.js"; import { html, TemplateResult } from "lit"; import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; import { Pagination, SavedAnnotation, savedAnnotationState } from "state/SavedAnnotations"; -import "./edit_saved_annotation"; import "components/pagination"; import { searchQueryState } from "state/SearchQuery"; @@ -67,14 +66,13 @@ export class SavedAnnotationList extends ShadowlessLitElement { ${I18n.t("js.saved_annotation.user")} ${I18n.t("js.saved_annotation.course")} ${I18n.t("js.saved_annotation.exercise")} - `} ${this.savedAnnotations.map(sa => html` - ${sa.title} + ${sa.title}

${I18n.t("js.saved_annotation.list.annotations_count", { count: sa.annotations_count })}

${ this.small ? "" : html` @@ -83,9 +81,6 @@ export class SavedAnnotationList extends ShadowlessLitElement { ${sa.course.name} ${sa.exercise.name} `} - - - `)} diff --git a/app/assets/javascripts/state/SavedAnnotations.ts b/app/assets/javascripts/state/SavedAnnotations.ts index f1bbef29ff..1b7c215d90 100644 --- a/app/assets/javascripts/state/SavedAnnotations.ts +++ b/app/assets/javascripts/state/SavedAnnotations.ts @@ -10,6 +10,7 @@ export type SavedAnnotation = { title: string, id: number, annotation_text: string, + url?: string, user?: { name: string, url: string }, exercise?: { name: string, url: string }, course?: { name: string, url: string } @@ -64,29 +65,6 @@ class SavedAnnotationState extends State { return savedAnnotation.id; } - async update(id: number, data: { saved_annotation: SavedAnnotation }): Promise { - const url = `${URL}/${id}`; - const response = await fetch(url, { - method: "put", - body: JSON.stringify(data), - headers: { "Content-type": "application/json" }, - }); - if (response.status === 422) { - const errors = await response.json(); - throw errors; - } - const savedAnnotation: SavedAnnotation = await response.json(); - this.invalidate(savedAnnotation.id, savedAnnotation); - } - - async delete(id: number): Promise { - const url = `${URL}/${id}`; - await fetch(url, { - method: "delete", - }); - this.invalidate(id); - } - getList(params?: Map, arrayParams?: Map): Array | undefined { const url = addParametersToUrl(`${URL}.json`, params, arrayParams); delayerByURL.get(url)(() => { diff --git a/app/assets/stylesheets/bootstrap_css_variable_overrides.css.scss b/app/assets/stylesheets/bootstrap_css_variable_overrides.css.scss index 2b8e394edd..099639ef46 100644 --- a/app/assets/stylesheets/bootstrap_css_variable_overrides.css.scss +++ b/app/assets/stylesheets/bootstrap_css_variable_overrides.css.scss @@ -78,6 +78,7 @@ --bs-link-color-rgb: var(--d-primary-rgb); --bs-link-decoration: none; --bs-link-hover-color: var(--d-on-primary-container); + --bs-link-hover-color-rgb: var(--d-on-primary-container-rgb); --bs-code-color: var(--d-secondary); --bs-highlight-bg: var(--d-surface-variant); diff --git a/app/assets/stylesheets/theme/m3-theme-dark.css.scss b/app/assets/stylesheets/theme/m3-theme-dark.css.scss index 470f934abf..40bdba365a 100644 --- a/app/assets/stylesheets/theme/m3-theme-dark.css.scss +++ b/app/assets/stylesheets/theme/m3-theme-dark.css.scss @@ -10,6 +10,7 @@ --d-on-primary: #{$primary-20}; --d-on-primary-container: #{$primary-90}; --d-primary-rgb: #{color.red($primary-80)}, #{color.green($primary-80)}, #{color.blue($primary-80)}; + --d-on-primary-container-rgb: #{color.red($primary-90)}, #{color.green($primary-90)}, #{color.blue($primary-90)}; --d-banner: #{$primary-20}; --d-on-banner: #{$primary-90}; diff --git a/app/assets/stylesheets/theme/m3-theme-light.css.scss b/app/assets/stylesheets/theme/m3-theme-light.css.scss index 7f2532fd5a..69ecbc3ab2 100644 --- a/app/assets/stylesheets/theme/m3-theme-light.css.scss +++ b/app/assets/stylesheets/theme/m3-theme-light.css.scss @@ -12,6 +12,7 @@ --d-on-primary: #{$primary-100}; --d-on-primary-container: #{$primary-10}; --d-primary-rgb: #{color.red($primary-40)}, #{color.green($primary-40)}, #{color.blue($primary-40)}; + --d-on-primary-container-rgb: #{color.red($primary-10)}, #{color.green($primary-10)}, #{color.blue($primary-10)}; --d-banner: #{$primary-30}; --d-on-banner: #{$primary-90}; diff --git a/app/controllers/saved_annotations_controller.rb b/app/controllers/saved_annotations_controller.rb index 2ed9041a78..4f21aec283 100644 --- a/app/controllers/saved_annotations_controller.rb +++ b/app/controllers/saved_annotations_controller.rb @@ -1,6 +1,6 @@ class SavedAnnotationsController < ApplicationController set_pagination_headers :saved_annotations, only: [:index] - before_action :set_saved_annotation, only: %i[show update destroy] + before_action :set_saved_annotation, only: %i[show update destroy edit] has_scope :by_user, as: 'user_id' has_scope :by_course, as: 'course_id' @@ -9,12 +9,28 @@ class SavedAnnotationsController < ApplicationController def index authorize SavedAnnotation + @title = I18n.t('saved_annotations.index.title') + @crumbs = [[I18n.t('saved_annotations.index.title'), saved_annotations_path]] @saved_annotations = apply_scopes(policy_scope(SavedAnnotation.all)) .includes(:course).includes(:user).includes(:exercise) .paginate(page: parse_pagination_param(params[:page]), per_page: parse_pagination_param(params[:per_page])) end - def show; end + def show + respond_to do |format| + format.html do + @title = @saved_annotation.title + @crumbs = [[I18n.t('saved_annotations.index.title'), saved_annotations_path], [@saved_annotation.title, saved_annotation_path(@saved_annotation)]] + @submissions = @saved_annotation.submissions.paginate(page: parse_pagination_param(params[:page])) + end + format.json + end + end + + def edit + @title = I18n.t('saved_annotations.edit.title') + @crumbs = [[I18n.t('saved_annotations.index.title'), saved_annotations_path], [@saved_annotation.title, saved_annotation_path(@saved_annotation)], [I18n.t('saved_annotations.edit.title'), '#']] + end def create annotation = Annotation.find(params[:from]) @@ -35,14 +51,22 @@ def update respond_to do |format| if @saved_annotation.update(permitted_attributes(SavedAnnotation)) format.json { render :show, status: :ok, location: @saved_annotation } + format.html do + redirect_to saved_annotation_path(@saved_annotation) + end else format.json { render json: @saved_annotation.errors.full_messages, status: :unprocessable_entity } + format.html do + @crumbs = [[I18n.t('saved_annotations.index.title'), saved_annotations_path], [@saved_annotation.title, saved_annotation_path(@saved_annotation)], [I18n.t('saved_annotations.edit.title'), '#']] + render :edit + end end end end def destroy @saved_annotation.destroy + redirect_to saved_annotations_url end private diff --git a/app/models/saved_annotation.rb b/app/models/saved_annotation.rb index 03e179bd97..9ae9d21f30 100644 --- a/app/models/saved_annotation.rb +++ b/app/models/saved_annotation.rb @@ -21,6 +21,7 @@ class SavedAnnotation < ApplicationRecord belongs_to :course has_many :annotations, dependent: :nullify + has_many :submissions, through: :annotations scope :by_user, ->(user_id) { where user_id: user_id } scope :by_course, ->(course_id) { where course_id: course_id } diff --git a/app/policies/saved_annotation_policy.rb b/app/policies/saved_annotation_policy.rb index 0378db844e..c146d37258 100644 --- a/app/policies/saved_annotation_policy.rb +++ b/app/policies/saved_annotation_policy.rb @@ -45,6 +45,10 @@ def show? user_admin_of_beta_course? && record_in_beta_course? && record&.user_id == user.id end + def edit? + update? + end + def update? # EDIT AFTER CLOSED BETA user_admin_of_beta_course? && record_in_beta_course? && record&.user_id == user.id diff --git a/app/views/saved_annotations/edit.html.erb b/app/views/saved_annotations/edit.html.erb new file mode 100644 index 0000000000..c0efdba484 --- /dev/null +++ b/app/views/saved_annotations/edit.html.erb @@ -0,0 +1,45 @@ +
+
+
+
+

<%= t ".title" %>

+
+
+
<%= t ".warning_no_annotations_changed" %>
+ <%= form_for(@saved_annotation, html: { class: 'form-horizontal' }) do |f| %> + <% if @saved_annotation.errors.any? %> +
+

<%= t('errors.validation_errors', count: @saved_annotation.errors.count) %>

+
    + <% @saved_annotation.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
+
+ <% end %> +
+ <%= f.label :title, class: "col-sm-3 col-form-label" %> +
<%= f.text_field :title, class: "form-control" %>
+
+
+ <%= f.label :annotation_text, class: "col-sm-3 col-form-label" %> +
<%= f.text_area :annotation_text, class: "form-control" %>
+ <%= t ".markdown_html" %> +
+ <% end %> +
+
+ <% if policy(@saved_annotation).destroy? %> + <%= link_to @saved_annotation, method: :delete, data: { confirm: t("general.are_you_sure") }, class: "btn btn-filled with-icon d-btn-danger" do %> + + <%= t ".destroy" %> + <% end %> + <% end %> + +
+
+
+
diff --git a/app/views/saved_annotations/index.json.jbuilder b/app/views/saved_annotations/index.json.jbuilder index c8ba4cb781..c743865058 100644 --- a/app/views/saved_annotations/index.json.jbuilder +++ b/app/views/saved_annotations/index.json.jbuilder @@ -1,5 +1,6 @@ json.array! @saved_annotations do |saved_annotation| json.extract! saved_annotation, :id, :title, :annotation_text, :user_id, :exercise_id, :course_id, :created_at, :updated_at, :annotations_count + json.url saved_annotation_url(saved_annotation) json.user do json.name saved_annotation.user.full_name json.url user_url(saved_annotation.user) diff --git a/app/views/saved_annotations/show.html.erb b/app/views/saved_annotations/show.html.erb new file mode 100644 index 0000000000..1e4302c044 --- /dev/null +++ b/app/views/saved_annotations/show.html.erb @@ -0,0 +1,29 @@ +
+
+
+
+

<%= @saved_annotation.title %>

+ <% if policy(@saved_annotation).edit? %> +
+ <%= render 'fab_link', url: edit_saved_annotation_path(@saved_annotation), icon: 'pencil' %> +
+ <% end %> +
+
+

<%= @saved_annotation.title %>

+
<%= markdown @saved_annotation.annotation_text %>
+ <%= t ".usage_info_html", + exercise_path: course_activity_path(@saved_annotation.course ,@saved_annotation.exercise), + exercise_name: @saved_annotation.exercise.name, + course_path: course_path(@saved_annotation.course), + course_name: @saved_annotation.course.name + %>
+ <%= t ".count_info_html", count: @saved_annotation.annotations_count %> +
+
+

<%= t ".linked_submissions" %>

+ <%= render partial: 'submissions/submissions_table', locals: {submissions: @submissions, exercise: @saved_annotation.exercise, course: @saved_annotation.course, user: nil} %> +
+
+
+
diff --git a/app/views/saved_annotations/show.json.jbuilder b/app/views/saved_annotations/show.json.jbuilder index d14df66518..e4f39d0c54 100644 --- a/app/views/saved_annotations/show.json.jbuilder +++ b/app/views/saved_annotations/show.json.jbuilder @@ -1 +1,2 @@ json.extract! @saved_annotation, :id, :title, :annotation_text, :user_id, :exercise_id, :course_id, :created_at, :updated_at +json.url saved_annotation_url(@saved_annotation) diff --git a/config/locales/models/en.yml b/config/locales/models/en.yml index cf03cc68c6..27086273a8 100644 --- a/config/locales/models/en.yml +++ b/config/locales/models/en.yml @@ -208,3 +208,10 @@ en: institution_id: Institution ID institution: Institution style: Bootstrap style + saved_annotation: + title: Title + annotation_text: Text + course: Course + exercise: Exercise + annotations_count: Number of usages + diff --git a/config/locales/models/nl.yml b/config/locales/models/nl.yml index ca28e196de..2b74d68389 100644 --- a/config/locales/models/nl.yml +++ b/config/locales/models/nl.yml @@ -209,3 +209,9 @@ nl: institution_id: ID van onderwijsinstelling institution: Onderwijsinstelling style: Bootstrap-stijl + saved_annotation: + title: Titel + annotation_text: Tekst + course: Cursus + exercise: Oefening + annotations_count: "Aantal keer gebruikt" diff --git a/config/locales/views/saved_annotations/en.yml b/config/locales/views/saved_annotations/en.yml index 05ee661f1e..bbb1270882 100644 --- a/config/locales/views/saved_annotations/en.yml +++ b/config/locales/views/saved_annotations/en.yml @@ -2,3 +2,13 @@ en: saved_annotations: index: title: Saved comments + show: + linked_submissions: Linked submissions + usage_info_html: This comment can be reused on all submissions for the exercise %{exercise_name} in the course %{course_name}. + count_info_html: This comment is used %{count} times. + edit: + title: Edit saved comment + markdown_html: Markdown is supported. + warning_no_annotations_changed: Existing comments will not be updated. These changes will only be applied to new comments. + destroy: Delete + save: Save diff --git a/config/locales/views/saved_annotations/nl.yml b/config/locales/views/saved_annotations/nl.yml index f68cddbe4e..a8f821e55a 100644 --- a/config/locales/views/saved_annotations/nl.yml +++ b/config/locales/views/saved_annotations/nl.yml @@ -2,3 +2,13 @@ nl: saved_annotations: index: title: Opgeslagen opmerkingen + show: + linked_submissions: Gekoppelde oplossingen + usage_info_html: Deze opmerking kan worden hergebruikt op alle ingediende oplossingen voor de oefening %{exercise_name} in de cursus %{course_name}. + count_info_html: Deze opmerking wordt %{count} keer gebruikt. + edit: + title: Bewerk opgeslagen opmerking + markdown_html: Markdown wordt ondersteund. + warning_no_annotations_changed: Bestaande opmerkingen zullen niet worden aangepast. Deze wijzigingen zullen enkel worden toegepast op nieuwe opmerkingen. + destroy: Verwijderen + save: Opslaan diff --git a/config/routes.rb b/config/routes.rb index 8cb475bfea..acdfd00869 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -174,7 +174,7 @@ resources :annotations, only: %i[index show create update destroy] - resources :saved_annotations, only: %i[index show create update destroy] + resources :saved_annotations, only: %i[index show create update destroy edit] get 'questions', to: 'annotations#question_index' diff --git a/test/controllers/saved_annotation_controller_test.rb b/test/controllers/saved_annotation_controller_test.rb index 60a76eb438..35d89c8974 100644 --- a/test/controllers/saved_annotation_controller_test.rb +++ b/test/controllers/saved_annotation_controller_test.rb @@ -3,7 +3,7 @@ class SavedAnnotationControllerTest < ActionDispatch::IntegrationTest extend CRUDTest - crud_helpers SavedAnnotation, attrs: %i[title annotation_text], format: :json + crud_helpers SavedAnnotation, attrs: %i[title annotation_text] def setup @course = create :course, id: 10 # COURSE ID CAN BE REMOVED AFTER CLOSED BETA @@ -13,7 +13,7 @@ def setup sign_in @user end - test_crud_actions only: %i[show destroy index update], except: %i[update_redirect destroy_redirect] + test_crud_actions only: %i[show destroy index update edit] test 'should be able to create from existing annotation' do annotation = create :annotation, submission: create(:submission, course: @course), user: @user diff --git a/test/system/saved_annotation_test.rb b/test/system/saved_annotation_test.rb index 72b48706e2..b93ee5fe6a 100644 --- a/test/system/saved_annotation_test.rb +++ b/test/system/saved_annotation_test.rb @@ -51,7 +51,7 @@ class SavedAnnotationsTest < ApplicationSystemTestCase within '.annotation' do assert_text initial # assert linked icon - assert_css 'i.mdi-link-variant' + assert_css 'i.mdi-comment-bookmark-outline' end sign_out @staff end @@ -103,7 +103,7 @@ class SavedAnnotationsTest < ApplicationSystemTestCase within '.annotation' do assert_text sa.annotation_text # assert linked icon - assert_css 'i.mdi-link-variant' + assert_css 'i.mdi-comment-bookmark-outline' end sign_out @staff end