diff --git a/app/components/admin/text_transform_actions/sandbox_component.html.erb b/app/components/admin/text_transform_actions/sandbox_component.html.erb
new file mode 100644
index 000000000000..5c4c7423f18d
--- /dev/null
+++ b/app/components/admin/text_transform_actions/sandbox_component.html.erb
@@ -0,0 +1,138 @@
+<%#-- copyright
+OpenProject is an open source project management software.
+Copyright (C) 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.
+
+++#%>
+
+<%=
+ render(Primer::Beta::BorderBox.new(mt: 5, data: controller_data, test_selector: "ai-text-transform-sandbox")) do |box|
+ box.with_header do |header|
+ header.with_title(tag: :h2) { label(:title) }
+ end
+
+ box.with_body do
+ flex_layout(direction: :column) do |layout|
+ layout.with_row(mb: 3) do
+ render(Primer::Beta::Text.new(tag: :p, color: :muted)) { label(:description) }
+ end
+
+ layout.with_row(mb: 3) do
+ helpers.primer_form_with(url: "#", method: :post, data: form_data) do |f|
+ render(Admin::TextTransformActions::SandboxForm.new(f, types:))
+ end
+ end
+
+ layout.with_row(mb: 3) do
+ flex_layout(align_items: :center) do |controls|
+ controls.with_column(mr: 2) do
+ render(
+ Primer::Beta::Button.new(
+ scheme: :primary,
+ type: :button,
+ data: target(:execute).merge(action: "ai-text-transform-sandbox#execute")
+ )
+ ) { label(:execute) }
+ end
+ controls.with_column(mr: 3) do
+ render(
+ Primer::Beta::Button.new(
+ scheme: :danger,
+ type: :button,
+ disabled: true,
+ data: target(:cancel).merge(action: "ai-text-transform-sandbox#cancel")
+ )
+ ) { label(:cancel) }
+ end
+ controls.with_column(mr: 2, hidden: true, data: target(:spinner)) do
+ render(Primer::Beta::Spinner.new(size: :small))
+ end
+ controls.with_column(mr: 2) do
+ render(Primer::Beta::Label.new(scheme: :secondary, data: target(:status))) { label(:status_idle) }
+ end
+ controls.with_column do
+ render(Primer::Beta::Text.new(tag: :span, font_family: :mono, color: :muted, data: target(:timer))) { "" }
+ end
+ end
+ end
+
+ layout.with_row(mb: 3) do
+ render(Primer::Box.new(border: true, border_radius: 2, p: 3, bg: :subtle)) do
+ content_tag(
+ :pre,
+ "",
+ class: "text-mono f5",
+ style: "white-space: pre-wrap; margin: 0; min-height: 6rem;",
+ data: target(:output)
+ )
+ end
+ end
+
+ layout.with_row do
+ render(Primer::Alpha::Dialog.new(id: DIALOG_ID, title: label(:details_title), size: :xlarge)) do |dialog|
+ dialog.with_show_button(scheme: :default) { label(:details) }
+ dialog.with_header(variant: :large)
+ dialog.with_body do
+ flex_layout(direction: :column) do |details|
+ details.with_row(mb: 3) do
+ render(Primer::Beta::Heading.new(tag: :h3, font_size: 5, mb: 2)) { label(:run_section) }
+ end
+ details.with_row(mb: 4) do
+ content_tag(:dl, "", class: "text-mono f6", data: target(:runMeta))
+ end
+ details.with_row(mb: 3) do
+ render(Primer::Beta::Heading.new(tag: :h3, font_size: 5, mb: 2)) { label(:system_prompt_section) }
+ end
+ details.with_row(mb: 4) do
+ render(Primer::Box.new(border: true, border_radius: 2, p: 3, bg: :subtle)) do
+ content_tag(:pre, "", class: "text-mono f6", style: "white-space: pre-wrap; margin: 0;", data: target(:systemPrompt))
+ end
+ end
+ details.with_row(mb: 3) do
+ render(Primer::Beta::Heading.new(tag: :h3, font_size: 5, mb: 2)) { label(:events_section) }
+ end
+ details.with_row do
+ content_tag(:table, class: "text-mono f6 width-full") do
+ concat(
+ content_tag(:thead) do
+ content_tag(:tr) do
+ safe_join(%i[event_seq event_kind event_payload event_time].map { |key| content_tag(:th, label(key), class: "text-left p-2 border-bottom") })
+ end
+ end
+ )
+ concat(content_tag(:tbody, "", data: target(:eventsBody)))
+ end
+ end
+ end
+ end
+ dialog.with_footer(show_divider: true) do
+ render(Primer::Beta::Button.new(data: { close_dialog_id: DIALOG_ID })) { I18n.t(:button_close) }
+ end
+ end
+ end
+ end
+ end
+ end
+%>
diff --git a/app/components/admin/text_transform_actions/sandbox_component.rb b/app/components/admin/text_transform_actions/sandbox_component.rb
new file mode 100644
index 000000000000..e427870dde3b
--- /dev/null
+++ b/app/components/admin/text_transform_actions/sandbox_component.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+#-- copyright
+# OpenProject is an open source project management software.
+# Copyright (C) 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.
+#++
+
+module Admin
+ module TextTransformActions
+ # Prototype only: lets an administrator run the action against arbitrary text
+ # through the execute API and watch the run being polled.
+ class SandboxComponent < ApplicationComponent
+ include OpPrimer::ComponentHelpers
+ include API::V3::Utilities::PathHelper
+
+ DIALOG_ID = "ai-text-transform-sandbox-details"
+
+ alias action model
+
+ private
+
+ def controller_data
+ {
+ controller: "ai-text-transform-sandbox",
+ ai_text_transform_sandbox_url_value: api_v3_paths.ai_text_transform_runs,
+ ai_text_transform_sandbox_action_id_value: action.id
+ }
+ end
+
+ def form_data
+ {
+ controller: "show-when-value-selected",
+ action: "submit->ai-text-transform-sandbox#submit"
+ }
+ end
+
+ def target(name)
+ { ai_text_transform_sandbox_target: name }
+ end
+
+ def label(key)
+ I18n.t("admin.text_transform_actions.sandbox.#{key}")
+ end
+
+ def types
+ Type.order(:position)
+ end
+ end
+ end
+end
diff --git a/app/components/ai/text_transforms/result_pane_component.html.erb b/app/components/ai/text_transforms/result_pane_component.html.erb
index 605cbd03731f..e31cef43856a 100644
--- a/app/components/ai/text_transforms/result_pane_component.html.erb
+++ b/app/components/ai/text_transforms/result_pane_component.html.erb
@@ -26,13 +26,15 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
-<%= component_wrapper(data: { test_selector: "ai-text-transform-result" }) do %>
+<%= component_wrapper(data: wrapper_data) do %>
<%=
render(
Primer::BaseComponent.new(
tag: :div,
role: :dialog,
aria: { label: label(:title) },
+ tabindex: -1,
+ data: target(:pane),
position: :fixed,
display: :flex,
direction: :column,
@@ -42,11 +44,11 @@ See COPYRIGHT and LICENSE files for more details.
safe_join(
[
render(Primer::Beta::BorderBox.new(flex: 1, overflow: :hidden, display: :flex, direction: :column, box_shadow: :extra_large)) do |box|
- box.with_header(classes: "op-ai-result-pane--handle") do
+ box.with_header(classes: "op-ai-result-pane--handle", data: action(beginDrag: :pointerdown)) do
flex_layout(align_items: :center, justify_content: :space_between) do |header|
header.with_column { render(Title.new(pane:)) }
header.with_column do
- render(Primer::Beta::CloseButton.new("aria-label": label(:close)))
+ render(Primer::Beta::CloseButton.new("aria-label": label(:close), data: action(close: :click)))
end
end
end
@@ -58,7 +60,7 @@ See COPYRIGHT and LICENSE files for more details.
Primer::BaseComponent.new(
tag: :span,
classes: "op-ai-result-pane--resize op-ai-result-pane--resize_#{edge}",
- data: { edge: },
+ data: { edge:, **action(beginResize: :pointerdown) },
"aria-hidden": true
)
) do
diff --git a/app/components/ai/text_transforms/result_pane_component.rb b/app/components/ai/text_transforms/result_pane_component.rb
index ffc6b9003448..3e1d0e0bf2ff 100644
--- a/app/components/ai/text_transforms/result_pane_component.rb
+++ b/app/components/ai/text_transforms/result_pane_component.rb
@@ -49,6 +49,15 @@ def initialize(pane:)
private
attr_reader :pane
+
+ def wrapper_data
+ {
+ test_selector: "ai-text-transform-result",
+ controller: "ai-text-transform-pane",
+ ai_text_transform_pane_close_form_value: CLOSE_FORM_ID,
+ **action(close: "keydown.esc", keepInViewport: "resize@window")
+ }
+ end
end
end
end
diff --git a/app/components/ai/text_transforms/result_pane_component.sass b/app/components/ai/text_transforms/result_pane_component.sass
index 204ed7cd2a1a..0fa138a86c9d 100644
--- a/app/components/ai/text_transforms/result_pane_component.sass
+++ b/app/components/ai/text_transforms/result_pane_component.sass
@@ -11,6 +11,7 @@
&--grip
cursor: grab
user-select: none
+ touch-action: none
&--grip
display: inline-flex
@@ -23,6 +24,7 @@
position: absolute
display: flex
line-height: 0
+ touch-action: none
&_left,
&_right
diff --git a/app/components/ai/text_transforms/result_pane_component/body.html.erb b/app/components/ai/text_transforms/result_pane_component/body.html.erb
index 4f2f850f7e69..71a651ca1332 100644
--- a/app/components/ai/text_transforms/result_pane_component/body.html.erb
+++ b/app/components/ai/text_transforms/result_pane_component/body.html.erb
@@ -35,9 +35,9 @@ See COPYRIGHT and LICENSE files for more details.
end
end
if done?
- body.with_row(mb: 3, hidden: true, test_selector: "ai-text-transform-pane-apply-failed") do
+ body.with_row(mb: 3, hidden: true, test_selector: "ai-text-transform-pane-apply-failed", data: target(:applyFailed)) do
render(Primer::Alpha::Banner.new(scheme: :danger)) do
- render(Primer::Beta::Text.new(tag: :span)) { "" }
+ render(Primer::Beta::Text.new(tag: :span, data: target(:applyFailedMessage))) { "" }
end
end
end
@@ -47,7 +47,7 @@ See COPYRIGHT and LICENSE files for more details.
end
end
if done?
- body.with_row { content_tag(:template, pane.text) }
+ body.with_row { content_tag(:template, pane.text, data: target(:raw)) }
end
end
%>
diff --git a/app/components/ai/text_transforms/result_pane_component/body.rb b/app/components/ai/text_transforms/result_pane_component/body.rb
index 4d4ba833352f..2304b3758ca1 100644
--- a/app/components/ai/text_transforms/result_pane_component/body.rb
+++ b/app/components/ai/text_transforms/result_pane_component/body.rb
@@ -56,7 +56,7 @@ def done?
end
def output
- helpers.format_text(pane.text, object: pane.work_package)
+ helpers.format_text(pane.text, object: pane.work_package, project: pane.project)
end
end
end
diff --git a/app/components/ai/text_transforms/result_pane_component/footer.html.erb b/app/components/ai/text_transforms/result_pane_component/footer.html.erb
index 9b81049170f5..ded026c2c1cf 100644
--- a/app/components/ai/text_transforms/result_pane_component/footer.html.erb
+++ b/app/components/ai/text_transforms/result_pane_component/footer.html.erb
@@ -27,9 +27,13 @@ See COPYRIGHT and LICENSE files for more details.
++#%>
<%= component_wrapper do %>
+ <%= content_tag(:span, nil, hidden: true, data: state_data) %>
+ <%= form(id: CLOSE_FORM_ID, url: helpers.ai_text_transform_pane_path(pane.uuid), method: :delete) %>
+ <%= form(id: APPLY_FORM_ID, url: helpers.apply_ai_text_transform_pane_path(pane.uuid), method: :post) if state == "done" %>
+ <%= form(id: RETRY_FORM_ID, url: helpers.ai_text_transform_panes_path, method: :post, fields: form_params.merge(retry_of: pane.uuid)) if retryable? %>
<%=
flex_layout(align_items: :center, classes: "op-ai-result-pane--footer") do |bar|
- bar.with_column(mr: 2, classes: "op-ai-result-pane--grip") do
+ bar.with_column(mr: 2, classes: "op-ai-result-pane--grip", data: action(beginDrag: :pointerdown)) do
render(Primer::Beta::Octicon.new(icon: :grabber, color: :muted))
end
bar.with_column(flex: 1) do
@@ -40,30 +44,30 @@ See COPYRIGHT and LICENSE files for more details.
render(Primer::Beta::Text.new(color: :muted)) { label(:generating) }
end
footer.with_column do
- render(Primer::Beta::Button.new) { label(:cancel) }
+ render(Primer::Beta::Button.new(data: action(close: :click))) { label(:cancel) }
end
end
when "done"
flex_layout(align_items: :center) do |footer|
footer.with_column(flex: 1) do
- render(Primer::Beta::Button.new(scheme: :invisible)) { label(:copy) }
+ render(Primer::Beta::ClipboardCopyButton.new(scheme: :invisible, value: pane.text, "aria-label": label(:copy)))
end
footer.with_column(mr: 2) do
- render(Primer::Beta::Button.new) { label(:discard) }
+ render(Primer::Beta::Button.new(data: action(close: :click))) { label(:discard) }
end
footer.with_column do
- render(Primer::Beta::Button.new(scheme: :primary)) { label(:replace) }
+ render(Primer::Beta::Button.new(scheme: :primary, type: :submit, form: APPLY_FORM_ID)) { label(:replace) }
end
end
else
flex_layout(align_items: :center, justify_content: :flex_end) do |footer|
if pane.run
footer.with_column(mr: 2) do
- render(Primer::Beta::Button.new) { label(:try_again) }
+ render(Primer::Beta::Button.new(type: :submit, form: RETRY_FORM_ID)) { label(:try_again) }
end
end
footer.with_column do
- render(Primer::Beta::Button.new) { label(:close) }
+ render(Primer::Beta::Button.new(data: action(close: :click))) { label(:close) }
end
end
end
diff --git a/app/components/ai/text_transforms/result_pane_component/footer.rb b/app/components/ai/text_transforms/result_pane_component/footer.rb
index 1234588044f9..9911768bac3c 100644
--- a/app/components/ai/text_transforms/result_pane_component/footer.rb
+++ b/app/components/ai/text_transforms/result_pane_component/footer.rb
@@ -30,7 +30,37 @@
module AI
module TextTransforms
class ResultPaneComponent
+ # Demo glue (AI-126 demo branch, until AI-175 adds its component part): the forms the
+ # footer buttons submit and the state element the pane controller polls with.
class Footer < Section
+ private
+
+ def state_data
+ {
+ controller: "ai-text-transform-pane-run",
+ ai_text_transform_pane_run_state_value: state,
+ ai_text_transform_pane_run_run_value: pane.uuid,
+ ai_text_transform_pane_run_seq_value: pane.last_seq,
+ ai_text_transform_pane_run_request_id_value: pane.request_id,
+ ai_text_transform_pane_run_poll_url_value: helpers.ai_text_transform_pane_path(pane.uuid, form_params),
+ ai_text_transform_pane_run_editor_gone_value: label(:editor_gone),
+ ai_text_transform_pane_run_selection_gone_value: label(:selection_gone)
+ }
+ end
+
+ def form_params
+ { request_id: pane.request_id, scope: pane.scope }.merge(pane.context_ids).compact
+ end
+
+ def retryable?
+ pane.run && %w[failed stopped].include?(state)
+ end
+
+ def form(id:, url:, method:, fields: form_params)
+ helpers.form_with(url:, method:, id:, data: { turbo_stream: true }) do
+ safe_join(fields.map { |name, value| helpers.hidden_field_tag(name, value, id: nil) })
+ end
+ end
end
end
end
diff --git a/app/components/ai/text_transforms/result_pane_helpers.rb b/app/components/ai/text_transforms/result_pane_helpers.rb
index 2fe14ff6770c..c8c150ae02f2 100644
--- a/app/components/ai/text_transforms/result_pane_helpers.rb
+++ b/app/components/ai/text_transforms/result_pane_helpers.rb
@@ -39,6 +39,14 @@ module ResultPaneHelpers
def label(key)
I18n.t("ai.text_transform.pane.#{key}")
end
+
+ def target(name)
+ { ai_text_transform_pane_target: name }
+ end
+
+ def action(**events_by_name)
+ { action: events_by_name.map { |name, event| "#{event}->ai-text-transform-pane##{name}" }.join(" ") }
+ end
end
end
end
diff --git a/app/components/ai/text_transforms/result_pane_state.rb b/app/components/ai/text_transforms/result_pane_state.rb
index 7706018c5314..86fff337a932 100644
--- a/app/components/ai/text_transforms/result_pane_state.rb
+++ b/app/components/ai/text_transforms/result_pane_state.rb
@@ -30,17 +30,18 @@
module AI
module TextTransforms
class ResultPaneState
- attr_reader :run, :scope, :request_id, :work_package, :context_ids, :rejection
+ attr_reader :run, :scope, :request_id, :work_package, :project, :context_ids, :rejection
def self.rejected(message:, label:, scope:, request_id:, context_ids: {})
new(run: nil, scope:, request_id:, label:, context_ids:, rejection: message)
end
- def initialize(run:, scope:, request_id:, work_package: nil, context_ids: {}, label: nil, rejection: nil)
+ def initialize(run:, scope:, request_id:, work_package: nil, project: nil, context_ids: {}, label: nil, rejection: nil)
@run = run
@scope = scope
@request_id = request_id
@work_package = work_package
+ @project = project
@context_ids = context_ids
@label = label
@rejection = rejection
diff --git a/app/contracts/ai/text_transforms/pane_params_contract.rb b/app/contracts/ai/text_transforms/pane_params_contract.rb
new file mode 100644
index 000000000000..b037a828a4ad
--- /dev/null
+++ b/app/contracts/ai/text_transforms/pane_params_contract.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+#-- copyright
+# OpenProject is an open source project management software.
+# Copyright (C) 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, see o.offset){if(n>s){let r={type:"attribute",offset:s,howMany:n-s,count:this._changeCount++};this._handleChange(r,i),i.push(r)}t.nodesToHandle=o.offset-t.offset,t.howMany=t.nodesToHandle}else t.offset>=o.offset&&t.offsets?(t.nodesToHandle=n-s,t.offset=s):t.nodesToHandle=0);if(o.type=="remove"&&t.offseto.offset){if(n>s){let r={type:"attribute",offset:s,howMany:n-s,count:this._changeCount++};this._handleChange(r,i),i.push(r)}t.nodesToHandle=o.offset-t.offset,t.howMany=t.nodesToHandle}else t.offset>=o.offset&&t.offsets?(t.nodesToHandle=n-s,t.offset=s):t.nodesToHandle=0);if(o.type=="remove"&&t.offset1&&(i=o),e(i))};return"conversion"in e&&(t.conversion=e.conversion),t}function Dx(e){let t=function(...i){let o=i[0];if(o==null)return o;o.length>1&&(i=o);let n=e(i);if(typeof n=="object")for(let{length:s}=n,r=0;re>i?i:e1&&(i=o),e(i))};return"conversion"in e&&(t.conversion=e.conversion),t}function Nx(e){let t=function(...i){let o=i[0];if(o==null)return o;o.length>1&&(i=o);let n=e(i);if(typeof n=="object")for(let{length:s}=n,r=0;re>i?i:e<br> element)"),keystroke:"Shift+Enter"}]})}};function uE(e){let t=new Set;for(let o of e.model.document.differ.getChanges())if(o.type=="insert"||o.type=="remove"){/* v8 ignore else -- @preserve */o.position.parent.is("element")&&t.add(o.position.parent)}else if(o.type=="attribute"){/* v8 ignore else -- @preserve */o.range.start.parent.is("element")&&t.add(o.range.start.parent)}let i=!1;for(let o of t)for(let n of o.getChildren()){if(!n.is("element","softBreak"))continue;let s=n.nextSibling;if(!s||s.is("element"))continue;let r=Array.from(n.getAttributes()).filter(([a,l])=>!dE(s,a,l));for(let[a]of r)e.removeAttribute(a,n),i=!0}return i}function dE(e,t,i){return e?.getAttribute(t)===i}/**
+*/var Jw=class extends w{static get pluginName(){return"ShiftEnter"}static get isOfficialPlugin(){return!0}init(){let e=this.editor,t=e.model.schema,i=e.conversion,o=e.editing.view,n=o.document,s=this.editor.t;t.register("softBreak",{allowWhere:"$text",allowAttributesOf:"$text",isInline:!0}),i.for("upcast").elementToElement({model:"softBreak",view:"br"}),i.for("downcast").elementToElement({model:"softBreak",view:(r,{writer:a})=>a.createEmptyElement("br")}),o.addObserver(Gw),e.commands.add("shiftEnter",new cE(e)),e.model.document.registerPostFixer(r=>hE(r)),this.listenTo(n,"enter",(r,a)=>{n.isComposing||a.preventDefault(),a.isSoft&&(e.execute("shiftEnter"),o.scrollToTheSelection())},{priority:"low"}),e.accessibility.addKeystrokeInfos({keystrokes:[{label:s("Insert a soft break (a <br> element)"),keystroke:"Shift+Enter"}]})}};function hE(e){let t=new Set;for(let o of e.model.document.differ.getChanges())if(o.type=="insert"||o.type=="remove"){/* v8 ignore else -- @preserve */o.position.parent.is("element")&&t.add(o.position.parent)}else if(o.type=="attribute"){/* v8 ignore else -- @preserve */o.range.start.parent.is("element")&&t.add(o.range.start.parent)}let i=!1;for(let o of t)for(let n of o.getChildren()){if(!n.is("element","softBreak"))continue;let s=n.nextSibling;if(!s||s.is("element"))continue;let r=Array.from(n.getAttributes()).filter(([a,l])=>!fE(s,a,l));for(let[a]of r)e.removeAttribute(a,n),i=!0}return i}function fE(e,t,i){return e?.getAttribute(t)===i}/**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*//**
@@ -1151,50 +1151,50 @@ Original error: ${i.name}: ${i.message}`:"";return e+s+r+a}function U0(e,t){let
*//**
* @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
-*/var hE=q(),fE=class extends hE{_stack=[];add(e,t){let i=this._stack,o=i[0];this._insertDescriptor(e);let n=i[0];o!==n&&!kh(o,n)&&this.fire("change:top",{oldDescriptor:o,newDescriptor:n,writer:t})}remove(e,t){let i=this._stack,o=i[0];this._removeDescriptor(e);let n=i[0];o!==n&&!kh(o,n)&&this.fire("change:top",{oldDescriptor:o,newDescriptor:n,writer:t})}_insertDescriptor(e){let t=this._stack,i=t.findIndex(n=>n.id===e.id);if(kh(e,t[i]))return;i>-1&&t.splice(i,1);let o=0;for(;t[o]&&mE(t[o],e);)o++;t.splice(o,0,e)}_removeDescriptor(e){let t=this._stack,i=t.findIndex(o=>o.id===e);i>-1&&t.splice(i,1)}};function kh(e,t){return e&&t&&e.priority==t.priority&&zl(e.classes)==zl(t.classes)}function mE(e,t){return e.priority>t.priority?!0:e.priority
").replace(/\r?\n/g,"
").replace(/\t/g," ").replace(/^\s/," ").replace(/\s$/," ").replace(/\s\s/g," "),(e.includes("
")||e.includes("
"))&&(e=`
${e}
`),e}/** +*/function dA(e){return e=e.replace(/&/g,"&").replace(//g,">").replace(/\r?\n\r?\n/g,"").replace(/\r?\n/g,"
").replace(/\t/g," ").replace(/^\s/," ").replace(/\s$/," ").replace(/\s\s/g," "),(e.includes("
")||e.includes("
"))&&(e=`
${e}
`),e}/** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options -*/var Sr=class extends $e{domEventType=["paste","copy","cut","drop","dragover","dragstart","dragend","dragenter","dragleave"];constructor(e){super(e);let t=this.document;this.listenTo(t,"paste",i("clipboardInput"),{priority:"low"}),this.listenTo(t,"drop",i("clipboardInput"),{priority:"low"}),this.listenTo(t,"dragover",i("dragging"),{priority:"low"});function i(o){return(n,s)=>{s.preventDefault();let r=s.dropRange?[s.dropRange]:null,a=s.dataTransfer,l=new pt(t,o),c="";a.getData("text/html")?c=a.getData("text/html"):a.getData("text/plain")&&(c=cA(a.getData("text/plain"))),t.fire(l,{dataTransfer:a,content:c,method:n.name,targetRanges:r,target:s.target,domEvent:s.domEvent}),l.stop.called&&s.stopPropagation()}}}onDomEvent(e){let t="clipboardData"in e?e.clipboardData:e.dataTransfer,i=e.type=="drop"||e.type=="paste",o={dataTransfer:new wd(t,{cacheFiles:i})};if(e.type=="drop"||e.type=="dragover"){let n=Oa(e);o.dropRange=n&&this.view.domConverter.domRangeToView(n)}this.fire(e.type,e,o)}};/** +*/var Sr=class extends $e{domEventType=["paste","copy","cut","drop","dragover","dragstart","dragend","dragenter","dragleave"];constructor(e){super(e);let t=this.document;this.listenTo(t,"paste",i("clipboardInput"),{priority:"low"}),this.listenTo(t,"drop",i("clipboardInput"),{priority:"low"}),this.listenTo(t,"dragover",i("dragging"),{priority:"low"});function i(o){return(n,s)=>{s.preventDefault();let r=s.dropRange?[s.dropRange]:null,a=s.dataTransfer,l=new bt(t,o),c="";a.getData("text/html")?c=a.getData("text/html"):a.getData("text/plain")&&(c=dA(a.getData("text/plain"))),t.fire(l,{dataTransfer:a,content:c,method:n.name,targetRanges:r,target:s.target,domEvent:s.domEvent}),l.stop.called&&s.stopPropagation()}}}onDomEvent(e){let t="clipboardData"in e?e.clipboardData:e.dataTransfer,i=e.type=="drop"||e.type=="paste",o={dataTransfer:new _d(t,{cacheFiles:i})};if(e.type=="drop"||e.type=="dragover"){let n=Da(e);o.dropRange=n&&this.view.domConverter.domRangeToView(n)}this.fire(e.type,e,o)}};/** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options -*/function uA(e){return e.replace(/(\s+)<\/span>/g,(t,i)=>i.length==1?" ":i).replace(//g,"")}/** +*/function hA(e){return e.replace(/(\s+)<\/span>/g,(t,i)=>i.length==1?" ":i).replace(//g,"")}/** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options -*/var g2=["figcaption","li"],p2=["ol","ul"];function v2(e,t){if(t.is("$text")||t.is("$textProxy"))return t.data;if(t.is("element","img")&&t.hasAttribute("alt"))return t.getAttribute("alt");if(t.is("element","br"))return` -`;let i="",o=null;for(let n of t.getChildren())i+=dA(n,o)+v2(e,n),o=n;if(t.is("rawElement")){let n=document.implementation.createHTMLDocument("").createElement("div");t.render(n,e),i+=k2(n)}return i}function k2(e){let t="";if(e.nodeType===Node.TEXT_NODE)return e.textContent;if(e.tagName==="BR")return` -`;for(let i of e.childNodes)t+=k2(i);return t}function dA(e,t){return t?e.is("element","li")&&!e.isEmpty&&e.getChild(0).is("containerElement")||p2.includes(e.name)&&p2.includes(t.name)?` +*/var p2=["figcaption","li"],b2=["ol","ul"];function k2(e,t){if(t.is("$text")||t.is("$textProxy"))return t.data;if(t.is("element","img")&&t.hasAttribute("alt"))return t.getAttribute("alt");if(t.is("element","br"))return` +`;let i="",o=null;for(let n of t.getChildren())i+=fA(n,o)+k2(e,n),o=n;if(t.is("rawElement")){let n=document.implementation.createHTMLDocument("").createElement("div");t.render(n,e),i+=C2(n)}return i}function C2(e){let t="";if(e.nodeType===Node.TEXT_NODE)return e.textContent;if(e.tagName==="BR")return` +`;for(let i of e.childNodes)t+=C2(i);return t}function fA(e,t){return t?e.is("element","li")&&!e.isEmpty&&e.getChild(0).is("containerElement")||b2.includes(e.name)&&b2.includes(t.name)?` -`:!e.is("containerElement")&&!t.is("containerElement")?"":g2.includes(e.name)||g2.includes(t.name)?` +`:!e.is("containerElement")&&!t.is("containerElement")?"":p2.includes(e.name)||p2.includes(t.name)?` `:e.is("element")&&e.getCustomProperty("dataPipeline:transparentRendering")||t.is("element")&&t.getCustomProperty("dataPipeline:transparentRendering")?"":` `:""}/** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options -*/var ss=class extends y{_markersToCopy=new Map;static get pluginName(){return"ClipboardMarkersUtils"}static get isOfficialPlugin(){return!0}_registerMarkerToCopy(e,t){this._markersToCopy.set(e,t)}_copySelectedFragmentWithMarkers(e,t,i=o=>o.model.getSelectedContent(o.model.document.selection)){return this.editor.model.change(o=>{let n=o.model.document.selection;o.setSelection(t);let s=this._insertFakeMarkersIntoSelection(o,o.model.document.selection,e),r=i(o),a=this._removeFakeMarkersInsideElement(o,r);for(let[l,c]of Object.entries(s)){a[l]||=o.createRangeIn(r);for(let u of c)o.remove(u)}r.markers.clear();for(let[l,c]of Object.entries(a))r.markers.set(l,c);return o.setSelection(n),r})}_pasteMarkersIntoTransformedElement(e,t){let i=this._getPasteMarkersFromRangeMap(e);return this.editor.model.change(o=>{let n=this._insertFakeMarkersElements(o,i),s=t(o),r=this._removeFakeMarkersInsideElement(o,s);for(let a of Object.values(n).flat())o.remove(a);for(let[a,l]of Object.entries(r))o.model.markers.has(a)||o.addMarker(a,{usingOperation:!0,affectsData:!0,range:l});return s})}_pasteFragmentWithMarkers(e){let t=this._getPasteMarkersFromRangeMap(e.markers);e.markers.clear();for(let i of t)e.markers.set(i.name,i.range);return this.editor.model.insertContent(e)}_forceMarkersCopy(e,t,i={allowedActions:"all",copyPartiallySelected:!0,duplicateOnPaste:!0}){let o=this._markersToCopy.get(e);this._markersToCopy.set(e,i),t(),o?this._markersToCopy.set(e,o):this._markersToCopy.delete(e)}_isMarkerCopyable(e,t){let i=this._getMarkerClipboardConfig(e);if(!i)return!1;if(!t)return!0;let{allowedActions:o}=i;return o==="all"||o.includes(t)}_hasMarkerConfiguration(e){return!!this._getMarkerClipboardConfig(e)}_getMarkerClipboardConfig(e){let[t]=e.split(":");return this._markersToCopy.get(t)||null}_insertFakeMarkersIntoSelection(e,t,i){let o=this._getCopyableMarkersFromSelection(e,t,i);return this._insertFakeMarkersElements(e,o)}_getCopyableMarkersFromSelection(e,t,i){let o=Array.from(t.getRanges()),n=new Set(o.flatMap(r=>Array.from(e.model.markers.getMarkersIntersectingRange(r)))),s=r=>{if(!this._isMarkerCopyable(r.name,i))return!1;let{copyPartiallySelected:a}=this._getMarkerClipboardConfig(r.name);if(!a){let l=r.getRange();return o.some(c=>c.containsRange(l,!0))}return!0};return Array.from(n).filter(s).map(r=>({name:i==="dragstart"?this._getUniqueMarkerName(r.name):r.name,range:r.getRange()}))}_getPasteMarkersFromRangeMap(e,t=null){let{model:i}=this.editor;return(e instanceof Map?Array.from(e.entries()):Object.entries(e)).flatMap(([o,n])=>{if(!this._hasMarkerConfiguration(o))return[{name:o,range:n}];if(this._isMarkerCopyable(o,t)){let s=this._getMarkerClipboardConfig(o),r=i.markers.has(o)&&i.markers.get(o).getRange().root.rootName==="$graveyard";return(s.duplicateOnPaste||r)&&(o=this._getUniqueMarkerName(o)),[{name:o,range:n}]}return[]})}_insertFakeMarkersElements(e,t){let i={},o=t.flatMap(n=>{let{start:s,end:r}=n.range;return[{position:s,marker:n,type:"start"},{position:r,marker:n,type:"end"}]}).sort(({position:n},{position:s})=>n.isBefore(s)?1:-1);for(let{position:n,marker:s,type:r}of o){let a=e.createElement("$marker",{"data-name":s.name,"data-type":r});i[s.name]||(i[s.name]=[]),i[s.name].push(a),e.insert(a,n)}return i}_removeFakeMarkersInsideElement(e,t){let i=this._getAllFakeMarkersFromElement(e,t).reduce((o,n)=>{let s=n.markerElement&&e.createPositionBefore(n.markerElement),r=o[n.name],a=!1;return r?.start&&r?.end&&(this._getMarkerClipboardConfig(n.name).duplicateOnPaste?o[this._getUniqueMarkerName(n.name)]=o[n.name]:a=!0,r=null),a||(o[n.name]={...r,[n.type]:s}),n.markerElement&&e.remove(n.markerElement),o},{});return uu(i,o=>new T(o.start||e.createPositionFromPath(t,[0]),o.end||e.createPositionAt(t,"end")))}_getAllFakeMarkersFromElement(e,t){let i=Array.from(e.createRangeIn(t)).flatMap(({item:s})=>s.is("element","$marker")?[{markerElement:s,name:s.getAttribute("data-name"),type:s.getAttribute("data-type")}]:[]),o=[],n=[];for(let s of i)s.type==="end"&&(i.some(r=>r.name===s.name&&r.type==="start")||o.push({markerElement:null,name:s.name,type:"start"})),s.type==="start"&&(i.some(r=>r.name===s.name&&r.type==="end")||n.unshift({markerElement:null,name:s.name,type:"end"}));return[...o,...i,...n]}_getUniqueMarkerName(e){let t=e.split(":"),i=ne().substring(1,6);return t.length===3?`${t.slice(0,2).join(":")}:${i}`:`${t.join(":")}:${i}`}};/** +*/var ss=class extends w{_markersToCopy=new Map;static get pluginName(){return"ClipboardMarkersUtils"}static get isOfficialPlugin(){return!0}_registerMarkerToCopy(e,t){this._markersToCopy.set(e,t)}_copySelectedFragmentWithMarkers(e,t,i=o=>o.model.getSelectedContent(o.model.document.selection)){return this.editor.model.change(o=>{let n=o.model.document.selection;o.setSelection(t);let s=this._insertFakeMarkersIntoSelection(o,o.model.document.selection,e),r=i(o),a=this._removeFakeMarkersInsideElement(o,r);for(let[l,c]of Object.entries(s)){a[l]||=o.createRangeIn(r);for(let u of c)o.remove(u)}r.markers.clear();for(let[l,c]of Object.entries(a))r.markers.set(l,c);return o.setSelection(n),r})}_pasteMarkersIntoTransformedElement(e,t){let i=this._getPasteMarkersFromRangeMap(e);return this.editor.model.change(o=>{let n=this._insertFakeMarkersElements(o,i),s=t(o),r=this._removeFakeMarkersInsideElement(o,s);for(let a of Object.values(n).flat())o.remove(a);for(let[a,l]of Object.entries(r))o.model.markers.has(a)||o.addMarker(a,{usingOperation:!0,affectsData:!0,range:l});return s})}_pasteFragmentWithMarkers(e){let t=this._getPasteMarkersFromRangeMap(e.markers);e.markers.clear();for(let i of t)e.markers.set(i.name,i.range);return this.editor.model.insertContent(e)}_forceMarkersCopy(e,t,i={allowedActions:"all",copyPartiallySelected:!0,duplicateOnPaste:!0}){let o=this._markersToCopy.get(e);this._markersToCopy.set(e,i),t(),o?this._markersToCopy.set(e,o):this._markersToCopy.delete(e)}_isMarkerCopyable(e,t){let i=this._getMarkerClipboardConfig(e);if(!i)return!1;if(!t)return!0;let{allowedActions:o}=i;return o==="all"||o.includes(t)}_hasMarkerConfiguration(e){return!!this._getMarkerClipboardConfig(e)}_getMarkerClipboardConfig(e){let[t]=e.split(":");return this._markersToCopy.get(t)||null}_insertFakeMarkersIntoSelection(e,t,i){let o=this._getCopyableMarkersFromSelection(e,t,i);return this._insertFakeMarkersElements(e,o)}_getCopyableMarkersFromSelection(e,t,i){let o=Array.from(t.getRanges()),n=new Set(o.flatMap(r=>Array.from(e.model.markers.getMarkersIntersectingRange(r)))),s=r=>{if(!this._isMarkerCopyable(r.name,i))return!1;let{copyPartiallySelected:a}=this._getMarkerClipboardConfig(r.name);if(!a){let l=r.getRange();return o.some(c=>c.containsRange(l,!0))}return!0};return Array.from(n).filter(s).map(r=>({name:i==="dragstart"?this._getUniqueMarkerName(r.name):r.name,range:r.getRange()}))}_getPasteMarkersFromRangeMap(e,t=null){let{model:i}=this.editor;return(e instanceof Map?Array.from(e.entries()):Object.entries(e)).flatMap(([o,n])=>{if(!this._hasMarkerConfiguration(o))return[{name:o,range:n}];if(this._isMarkerCopyable(o,t)){let s=this._getMarkerClipboardConfig(o),r=i.markers.has(o)&&i.markers.get(o).getRange().root.rootName==="$graveyard";return(s.duplicateOnPaste||r)&&(o=this._getUniqueMarkerName(o)),[{name:o,range:n}]}return[]})}_insertFakeMarkersElements(e,t){let i={},o=t.flatMap(n=>{let{start:s,end:r}=n.range;return[{position:s,marker:n,type:"start"},{position:r,marker:n,type:"end"}]}).sort(({position:n},{position:s})=>n.isBefore(s)?1:-1);for(let{position:n,marker:s,type:r}of o){let a=e.createElement("$marker",{"data-name":s.name,"data-type":r});i[s.name]||(i[s.name]=[]),i[s.name].push(a),e.insert(a,n)}return i}_removeFakeMarkersInsideElement(e,t){let i=this._getAllFakeMarkersFromElement(e,t).reduce((o,n)=>{let s=n.markerElement&&e.createPositionBefore(n.markerElement),r=o[n.name],a=!1;return r?.start&&r?.end&&(this._getMarkerClipboardConfig(n.name).duplicateOnPaste?o[this._getUniqueMarkerName(n.name)]=o[n.name]:a=!0,r=null),a||(o[n.name]={...r,[n.type]:s}),n.markerElement&&e.remove(n.markerElement),o},{});return hu(i,o=>new T(o.start||e.createPositionFromPath(t,[0]),o.end||e.createPositionAt(t,"end")))}_getAllFakeMarkersFromElement(e,t){let i=Array.from(e.createRangeIn(t)).flatMap(({item:s})=>s.is("element","$marker")?[{markerElement:s,name:s.getAttribute("data-name"),type:s.getAttribute("data-type")}]:[]),o=[],n=[];for(let s of i)s.type==="end"&&(i.some(r=>r.name===s.name&&r.type==="start")||o.push({markerElement:null,name:s.name,type:"start"})),s.type==="start"&&(i.some(r=>r.name===s.name&&r.type==="end")||n.unshift({markerElement:null,name:s.name,type:"end"}));return[...o,...i,...n]}_getUniqueMarkerName(e){let t=e.split(":"),i=ne().substring(1,6);return t.length===3?`${t.slice(0,2).join(":")}:${i}`:`${t.join(":")}:${i}`}};/** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options -*/var Be=class extends y{static get pluginName(){return"ClipboardPipeline"}static get isOfficialPlugin(){return!0}static get requires(){return[ss]}init(){this.editor.editing.view.addObserver(Sr),this._setupPasteDrop(),this._setupCopyCut()}_fireOutputTransformationEvent(e,t,i){let o=this.editor.plugins.get("ClipboardMarkersUtils");this.editor.model.enqueueChange({isUndoable:i==="cut"},()=>{let n=o._copySelectedFragmentWithMarkers(i,t);this.fire("outputTransformation",{dataTransfer:e,content:n,method:i})})}_setupPasteDrop(){let e=this.editor,t=e.model,i=e.editing.view,o=i.document,n=this.editor.plugins.get("ClipboardMarkersUtils");this.listenTo(o,"clipboardInput",(s,r)=>{r.method=="paste"&&!e.model.canEditAt(e.model.document.selection)&&s.stop()},{priority:"highest"}),this.listenTo(o,"clipboardInput",(s,r)=>{let a=r.dataTransfer,l=new pt(this,"inputTransformation"),c=a.getData("application/ckeditor5-editor-id")||null,u=typeof r.content=="string"?this.editor.data.htmlProcessor.toView(uA(r.content)):r.content;this.fire(l,{content:u,dataTransfer:a,sourceEditorId:c,extraContent:r.extraContent,targetRanges:r.targetRanges,method:r.method}),l.stop.called&&s.stop(),i.scrollToTheSelection()},{priority:"low"}),this.listenTo(this,"inputTransformation",(s,r)=>{if(r.content.isEmpty)return;let a=this.editor.data.toModel(r.content,"$clipboardHolder");a.childCount!=0&&(s.stop(),t.change(()=>{this.fire("contentInsertion",{content:a,method:r.method,sourceEditorId:r.sourceEditorId,dataTransfer:r.dataTransfer,targetRanges:r.targetRanges})}))},{priority:"low"}),this.listenTo(this,"contentInsertion",(s,r)=>{r.resultRange=n._pasteFragmentWithMarkers(r.content)},{priority:"low"})}_setupCopyCut(){let e=this.editor,t=e.model.document,i=e.editing.view.document,o=(n,s)=>{let r=s.dataTransfer;s.preventDefault(),this._fireOutputTransformationEvent(r,t.selection,n.name)};this.listenTo(i,"copy",o,{priority:"low"}),this.listenTo(i,"cut",(n,s)=>{e.model.canEditAt(e.model.document.selection)?o(n,s):s.preventDefault()},{priority:"low"}),this.listenTo(this,"outputTransformation",(n,s)=>{let r=e.data.toView(s.content,{isClipboardPipeline:!0});i.fire("clipboardOutput",{dataTransfer:s.dataTransfer,content:r,method:s.method})},{priority:"low"}),this.listenTo(i,"clipboardOutput",(n,s)=>{s.content.isEmpty||(s.dataTransfer.setData("text/html",this.editor.data.htmlProcessor.toData(s.content)),s.dataTransfer.setData("text/plain",v2(e.data.htmlProcessor.domConverter,s.content)),s.dataTransfer.setData("application/ckeditor5-editor-id",this.editor.id)),s.method=="cut"&&e.model.deleteContent(t.selection)},{priority:"low"})}};/** +*/var Be=class extends w{static get pluginName(){return"ClipboardPipeline"}static get isOfficialPlugin(){return!0}static get requires(){return[ss]}init(){this.editor.editing.view.addObserver(Sr),this._setupPasteDrop(),this._setupCopyCut()}_fireOutputTransformationEvent(e,t,i){let o=this.editor.plugins.get("ClipboardMarkersUtils");this.editor.model.enqueueChange({isUndoable:i==="cut"},()=>{let n=o._copySelectedFragmentWithMarkers(i,t);this.fire("outputTransformation",{dataTransfer:e,content:n,method:i})})}_setupPasteDrop(){let e=this.editor,t=e.model,i=e.editing.view,o=i.document,n=this.editor.plugins.get("ClipboardMarkersUtils");this.listenTo(o,"clipboardInput",(s,r)=>{r.method=="paste"&&!e.model.canEditAt(e.model.document.selection)&&s.stop()},{priority:"highest"}),this.listenTo(o,"clipboardInput",(s,r)=>{let a=r.dataTransfer,l=new bt(this,"inputTransformation"),c=a.getData("application/ckeditor5-editor-id")||null,u=typeof r.content=="string"?this.editor.data.htmlProcessor.toView(hA(r.content)):r.content;this.fire(l,{content:u,dataTransfer:a,sourceEditorId:c,extraContent:r.extraContent,targetRanges:r.targetRanges,method:r.method}),l.stop.called&&s.stop(),i.scrollToTheSelection()},{priority:"low"}),this.listenTo(this,"inputTransformation",(s,r)=>{if(r.content.isEmpty)return;let a=this.editor.data.toModel(r.content,"$clipboardHolder");a.childCount!=0&&(s.stop(),t.change(()=>{this.fire("contentInsertion",{content:a,method:r.method,sourceEditorId:r.sourceEditorId,dataTransfer:r.dataTransfer,targetRanges:r.targetRanges})}))},{priority:"low"}),this.listenTo(this,"contentInsertion",(s,r)=>{r.resultRange=n._pasteFragmentWithMarkers(r.content)},{priority:"low"})}_setupCopyCut(){let e=this.editor,t=e.model.document,i=e.editing.view.document,o=(n,s)=>{let r=s.dataTransfer;s.preventDefault(),this._fireOutputTransformationEvent(r,t.selection,n.name)};this.listenTo(i,"copy",o,{priority:"low"}),this.listenTo(i,"cut",(n,s)=>{e.model.canEditAt(e.model.document.selection)?o(n,s):s.preventDefault()},{priority:"low"}),this.listenTo(this,"outputTransformation",(n,s)=>{let r=e.data.toView(s.content,{isClipboardPipeline:!0});i.fire("clipboardOutput",{dataTransfer:s.dataTransfer,content:r,method:s.method})},{priority:"low"}),this.listenTo(i,"clipboardOutput",(n,s)=>{s.content.isEmpty||(s.dataTransfer.setData("text/html",this.editor.data.htmlProcessor.toData(s.content)),s.dataTransfer.setData("text/plain",k2(e.data.htmlProcessor.domConverter,s.content)),s.dataTransfer.setData("application/ckeditor5-editor-id",this.editor.id)),s.method=="cut"&&e.model.deleteContent(t.selection)},{priority:"low"})}};/** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options -*//* istanbul ignore file -- @preserve */var xh=po("px"),hA=class extends R{constructor(){super();let e=this.bindTemplate;this.set({isVisible:!1,left:null,top:null,width:null}),this.setTemplate({tag:"div",attributes:{class:["ck","ck-clipboard-drop-target-line",e.if("isVisible","ck-hidden",t=>!t)],style:{left:e.to("left",t=>xh(t)),top:e.to("top",t=>xh(t)),width:e.to("width",t=>xh(t))}}})}};/** +*//* istanbul ignore file -- @preserve */var Eh=bo("px"),mA=class extends R{constructor(){super();let e=this.bindTemplate;this.set({isVisible:!1,left:null,top:null,width:null}),this.setTemplate({tag:"div",attributes:{class:["ck","ck-clipboard-drop-target-line",e.if("isVisible","ck-hidden",t=>!t)],style:{left:e.to("left",t=>Eh(t)),top:e.to("top",t=>Eh(t)),width:e.to("width",t=>Eh(t))}}})}};/** * @license Copyright (c) 2003-2026, CKSource Holding sp. z o.o. All rights reserved. * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options -*/var Ul=class extends y{removeDropMarkerDelayed=Os(()=>this.removeDropMarker(),40);_updateDropMarkerThrottled=ai(e=>this._updateDropMarker(e),40);_reconvertMarkerThrottled=ai(()=>{this.editor.model.markers.has("drop-target")&&this.editor.editing.reconvertMarker("drop-target")},0);_dropTargetLineView=new hA;_domEmitter=new(pe());_scrollables=new Map;static get pluginName(){return"DragDropTarget"}static get isOfficialPlugin(){return!0}init(){this._setupDropMarker()}destroy(){this._domEmitter.stopListening();for(let{resizeObserver:e}of this._scrollables.values())e.destroy();return this._updateDropMarkerThrottled.cancel(),this.removeDropMarkerDelayed.cancel(),this._reconvertMarkerThrottled.cancel(),super.destroy()}updateDropMarker(e,t,i,o,n,s){this.removeDropMarkerDelayed.cancel();let r=b2(this.editor,e,t,i,o,n,s);/* istanbul ignore next -- @preserve */return r?s&&s.containsRange(r)?(this.removeDropMarker(),null):r&&!this.editor.model.canEditAt(r)?(this.removeDropMarker(),null):(this._updateDropMarkerThrottled(r),r):null}getFinalDropRange(e,t,i,o,n,s){let r=b2(this.editor,e,t,i,o,n,s);return this.removeDropMarker(),r}removeDropMarker(){let e=this.editor.model;this.removeDropMarkerDelayed.cancel(),this._updateDropMarkerThrottled.cancel(),this._dropTargetLineView.isVisible=!1,e.markers.has("drop-target")&&e.change(t=>{t.removeMarker("drop-target")})}_setupDropMarker(){let e=this.editor;e.ui.view.body.add(this._dropTargetLineView),e.conversion.for("editingDowncast").markerToHighlight({model:"drop-target",view:{classes:["ck-clipboard-drop-target-range"]}}),e.conversion.for("editingDowncast").markerToElement({model:"drop-target",view:(t,{writer:i})=>{if(e.model.schema.checkChild(t.markerRange.start,"$text"))return this._dropTargetLineView.isVisible=!1,this._createDropTargetPosition(i);t.markerRange.isCollapsed?this._updateDropTargetLine(t.markerRange):this._dropTargetLineView.isVisible=!1}})}_updateDropMarker(e){let t=this.editor,i=t.model.markers;t.model.change(o=>{i.has("drop-target")?i.get("drop-target").getRange().isEqual(e)||o.updateMarker("drop-target",{range:e}):o.addMarker("drop-target",{range:e,usingOperation:!1,affectsData:!1})})}_createDropTargetPosition(e){return e.createUIElement("span",{class:"ck ck-clipboard-drop-target-position"},function(t){let i=this.toDomElement(t);return i.append("\u2060",t.createElement("span"),"\u2060"),i})}_updateDropTargetLine(e){let t=this.editor.editing,i=e.start.nodeBefore,o=e.start.nodeAfter,n=e.start.parent,s=i?t.mapper.toViewElement(i):null,r=s?t.view.domConverter.mapViewToDom(s):null,a=o?t.mapper.toViewElement(o):null,l=a?t.view.domConverter.mapViewToDom(a):null,c=t.mapper.toViewElement(n);if(!c)return;let u=t.view.domConverter.mapViewToDom(c),d=this._getScrollableRect(c),{scrollX:h,scrollY:f}=S.window,m=r?new F(r):null,g=l?new F(l):null,b=new F(u).excludeScrollbarsAndBorders(),k=m?m.bottom:b.top,v=g?g.top:b.bottom,_=S.window.getComputedStyle(u),C=k<=v?(k+v)/2:v;if(d.top