From 69be99b00a02cca35e5d6dd01783e37cddbf0116 Mon Sep 17 00:00:00 2001 From: wjyLearn <93930815+wjyLearn@users.noreply.github.com> Date: Sat, 20 Dec 2025 17:45:44 +0800 Subject: [PATCH] update resources\web --- resources/shaders/140/gcode.fs | 8 +- resources/web/data/text.js | 375 ++++- resources/web/flush/NozzleListTable.html | 274 ++++ resources/web/homepage3/css/dark.css | 6 +- resources/web/homepage3/css/wiki.css | 2 +- resources/web/homepage3/js/wiki.js | 161 +- resources/web/homepage3/wiki.html | 3 +- .../include/ckeditor5/LICENSE-ckeditor5.md | 55 + .../include/ckeditor5/ckeditor5-content.css | 5 + .../include/ckeditor5/ckeditor5-editor.css | 5 + resources/web/include/ckeditor5/ckeditor5.css | 6 + .../web/include/ckeditor5/ckeditor5.css.map | 1 + resources/web/include/ckeditor5/ckeditor5.js | 6 + .../web/include/ckeditor5/ckeditor5.js.map | 1 + .../web/include/ckeditor5/ckeditor5.umd.js | 1445 +++++++++++++++++ .../include/ckeditor5/ckeditor5.umd.js.map | 1 + .../include/ckeditor5/translations/af.d.ts | 8 + .../web/include/ckeditor5/translations/af.js | 5 + .../include/ckeditor5/translations/af.umd.js | 11 + .../include/ckeditor5/translations/ar.d.ts | 8 + .../web/include/ckeditor5/translations/ar.js | 5 + .../include/ckeditor5/translations/ar.umd.js | 11 + .../include/ckeditor5/translations/ast.d.ts | 8 + .../web/include/ckeditor5/translations/ast.js | 5 + .../include/ckeditor5/translations/ast.umd.js | 11 + .../include/ckeditor5/translations/az.d.ts | 8 + .../web/include/ckeditor5/translations/az.js | 5 + .../include/ckeditor5/translations/az.umd.js | 11 + .../include/ckeditor5/translations/be.d.ts | 8 + .../web/include/ckeditor5/translations/be.js | 5 + .../include/ckeditor5/translations/be.umd.js | 11 + .../include/ckeditor5/translations/bg.d.ts | 8 + .../web/include/ckeditor5/translations/bg.js | 5 + .../include/ckeditor5/translations/bg.umd.js | 11 + .../include/ckeditor5/translations/bn.d.ts | 8 + .../web/include/ckeditor5/translations/bn.js | 5 + .../include/ckeditor5/translations/bn.umd.js | 11 + .../include/ckeditor5/translations/bs.d.ts | 8 + .../web/include/ckeditor5/translations/bs.js | 5 + .../include/ckeditor5/translations/bs.umd.js | 11 + .../include/ckeditor5/translations/ca.d.ts | 8 + .../web/include/ckeditor5/translations/ca.js | 5 + .../include/ckeditor5/translations/ca.umd.js | 11 + .../include/ckeditor5/translations/cs.d.ts | 8 + .../web/include/ckeditor5/translations/cs.js | 5 + .../include/ckeditor5/translations/cs.umd.js | 11 + .../include/ckeditor5/translations/da.d.ts | 8 + .../web/include/ckeditor5/translations/da.js | 5 + .../include/ckeditor5/translations/da.umd.js | 11 + .../include/ckeditor5/translations/de-ch.d.ts | 8 + .../include/ckeditor5/translations/de-ch.js | 5 + .../ckeditor5/translations/de-ch.umd.js | 11 + .../include/ckeditor5/translations/de.d.ts | 8 + .../web/include/ckeditor5/translations/de.js | 5 + .../include/ckeditor5/translations/de.umd.js | 11 + .../include/ckeditor5/translations/el.d.ts | 8 + .../web/include/ckeditor5/translations/el.js | 5 + .../include/ckeditor5/translations/el.umd.js | 11 + .../include/ckeditor5/translations/en-au.d.ts | 8 + .../include/ckeditor5/translations/en-au.js | 5 + .../ckeditor5/translations/en-au.umd.js | 11 + .../include/ckeditor5/translations/en-gb.d.ts | 8 + .../include/ckeditor5/translations/en-gb.js | 5 + .../ckeditor5/translations/en-gb.umd.js | 11 + .../include/ckeditor5/translations/en.d.ts | 8 + .../web/include/ckeditor5/translations/en.js | 5 + .../include/ckeditor5/translations/en.umd.js | 11 + .../include/ckeditor5/translations/eo.d.ts | 8 + .../web/include/ckeditor5/translations/eo.js | 5 + .../include/ckeditor5/translations/eo.umd.js | 11 + .../include/ckeditor5/translations/es-co.d.ts | 8 + .../include/ckeditor5/translations/es-co.js | 5 + .../ckeditor5/translations/es-co.umd.js | 11 + .../include/ckeditor5/translations/es.d.ts | 8 + .../web/include/ckeditor5/translations/es.js | 5 + .../include/ckeditor5/translations/es.umd.js | 11 + .../include/ckeditor5/translations/et.d.ts | 8 + .../web/include/ckeditor5/translations/et.js | 5 + .../include/ckeditor5/translations/et.umd.js | 11 + .../include/ckeditor5/translations/eu.d.ts | 8 + .../web/include/ckeditor5/translations/eu.js | 5 + .../include/ckeditor5/translations/eu.umd.js | 11 + .../include/ckeditor5/translations/fa.d.ts | 8 + .../web/include/ckeditor5/translations/fa.js | 5 + .../include/ckeditor5/translations/fa.umd.js | 11 + .../include/ckeditor5/translations/fi.d.ts | 8 + .../web/include/ckeditor5/translations/fi.js | 5 + .../include/ckeditor5/translations/fi.umd.js | 11 + .../include/ckeditor5/translations/fr.d.ts | 8 + .../web/include/ckeditor5/translations/fr.js | 5 + .../include/ckeditor5/translations/fr.umd.js | 11 + .../include/ckeditor5/translations/gl.d.ts | 8 + .../web/include/ckeditor5/translations/gl.js | 5 + .../include/ckeditor5/translations/gl.umd.js | 11 + .../include/ckeditor5/translations/gu.d.ts | 8 + .../web/include/ckeditor5/translations/gu.js | 5 + .../include/ckeditor5/translations/gu.umd.js | 11 + .../include/ckeditor5/translations/he.d.ts | 8 + .../web/include/ckeditor5/translations/he.js | 5 + .../include/ckeditor5/translations/he.umd.js | 11 + .../include/ckeditor5/translations/hi.d.ts | 8 + .../web/include/ckeditor5/translations/hi.js | 5 + .../include/ckeditor5/translations/hi.umd.js | 11 + .../include/ckeditor5/translations/hr.d.ts | 8 + .../web/include/ckeditor5/translations/hr.js | 5 + .../include/ckeditor5/translations/hr.umd.js | 11 + .../include/ckeditor5/translations/hu.d.ts | 8 + .../web/include/ckeditor5/translations/hu.js | 5 + .../include/ckeditor5/translations/hu.umd.js | 11 + .../include/ckeditor5/translations/hy.d.ts | 8 + .../web/include/ckeditor5/translations/hy.js | 5 + .../include/ckeditor5/translations/hy.umd.js | 11 + .../include/ckeditor5/translations/id.d.ts | 8 + .../web/include/ckeditor5/translations/id.js | 5 + .../include/ckeditor5/translations/id.umd.js | 11 + .../include/ckeditor5/translations/it.d.ts | 8 + .../web/include/ckeditor5/translations/it.js | 5 + .../include/ckeditor5/translations/it.umd.js | 11 + .../include/ckeditor5/translations/ja.d.ts | 8 + .../web/include/ckeditor5/translations/ja.js | 5 + .../include/ckeditor5/translations/ja.umd.js | 11 + .../include/ckeditor5/translations/jv.d.ts | 8 + .../web/include/ckeditor5/translations/jv.js | 5 + .../include/ckeditor5/translations/jv.umd.js | 11 + .../include/ckeditor5/translations/kk.d.ts | 8 + .../web/include/ckeditor5/translations/kk.js | 5 + .../include/ckeditor5/translations/kk.umd.js | 11 + .../include/ckeditor5/translations/km.d.ts | 8 + .../web/include/ckeditor5/translations/km.js | 5 + .../include/ckeditor5/translations/km.umd.js | 11 + .../include/ckeditor5/translations/kn.d.ts | 8 + .../web/include/ckeditor5/translations/kn.js | 5 + .../include/ckeditor5/translations/kn.umd.js | 11 + .../include/ckeditor5/translations/ko.d.ts | 8 + .../web/include/ckeditor5/translations/ko.js | 5 + .../include/ckeditor5/translations/ko.umd.js | 11 + .../include/ckeditor5/translations/ku.d.ts | 8 + .../web/include/ckeditor5/translations/ku.js | 5 + .../include/ckeditor5/translations/ku.umd.js | 11 + .../include/ckeditor5/translations/lt.d.ts | 8 + .../web/include/ckeditor5/translations/lt.js | 5 + .../include/ckeditor5/translations/lt.umd.js | 11 + .../include/ckeditor5/translations/lv.d.ts | 8 + .../web/include/ckeditor5/translations/lv.js | 5 + .../include/ckeditor5/translations/lv.umd.js | 11 + .../include/ckeditor5/translations/ms.d.ts | 8 + .../web/include/ckeditor5/translations/ms.js | 5 + .../include/ckeditor5/translations/ms.umd.js | 11 + .../include/ckeditor5/translations/nb.d.ts | 8 + .../web/include/ckeditor5/translations/nb.js | 5 + .../include/ckeditor5/translations/nb.umd.js | 11 + .../include/ckeditor5/translations/ne.d.ts | 8 + .../web/include/ckeditor5/translations/ne.js | 5 + .../include/ckeditor5/translations/ne.umd.js | 11 + .../include/ckeditor5/translations/nl.d.ts | 8 + .../web/include/ckeditor5/translations/nl.js | 5 + .../include/ckeditor5/translations/nl.umd.js | 11 + .../include/ckeditor5/translations/no.d.ts | 8 + .../web/include/ckeditor5/translations/no.js | 5 + .../include/ckeditor5/translations/no.umd.js | 11 + .../include/ckeditor5/translations/oc.d.ts | 8 + .../web/include/ckeditor5/translations/oc.js | 5 + .../include/ckeditor5/translations/oc.umd.js | 11 + .../include/ckeditor5/translations/pl.d.ts | 8 + .../web/include/ckeditor5/translations/pl.js | 5 + .../include/ckeditor5/translations/pl.umd.js | 11 + .../include/ckeditor5/translations/pt-br.d.ts | 8 + .../include/ckeditor5/translations/pt-br.js | 5 + .../ckeditor5/translations/pt-br.umd.js | 11 + .../include/ckeditor5/translations/pt.d.ts | 8 + .../web/include/ckeditor5/translations/pt.js | 5 + .../include/ckeditor5/translations/pt.umd.js | 11 + .../include/ckeditor5/translations/ro.d.ts | 8 + .../web/include/ckeditor5/translations/ro.js | 5 + .../include/ckeditor5/translations/ro.umd.js | 11 + .../include/ckeditor5/translations/ru.d.ts | 8 + .../web/include/ckeditor5/translations/ru.js | 5 + .../include/ckeditor5/translations/ru.umd.js | 11 + .../include/ckeditor5/translations/si.d.ts | 8 + .../web/include/ckeditor5/translations/si.js | 5 + .../include/ckeditor5/translations/si.umd.js | 11 + .../include/ckeditor5/translations/sk.d.ts | 8 + .../web/include/ckeditor5/translations/sk.js | 5 + .../include/ckeditor5/translations/sk.umd.js | 11 + .../include/ckeditor5/translations/sl.d.ts | 8 + .../web/include/ckeditor5/translations/sl.js | 5 + .../include/ckeditor5/translations/sl.umd.js | 11 + .../include/ckeditor5/translations/sq.d.ts | 8 + .../web/include/ckeditor5/translations/sq.js | 5 + .../include/ckeditor5/translations/sq.umd.js | 11 + .../ckeditor5/translations/sr-latn.d.ts | 8 + .../include/ckeditor5/translations/sr-latn.js | 5 + .../ckeditor5/translations/sr-latn.umd.js | 11 + .../include/ckeditor5/translations/sr.d.ts | 8 + .../web/include/ckeditor5/translations/sr.js | 5 + .../include/ckeditor5/translations/sr.umd.js | 11 + .../include/ckeditor5/translations/sv.d.ts | 8 + .../web/include/ckeditor5/translations/sv.js | 5 + .../include/ckeditor5/translations/sv.umd.js | 11 + .../include/ckeditor5/translations/th.d.ts | 8 + .../web/include/ckeditor5/translations/th.js | 5 + .../include/ckeditor5/translations/th.umd.js | 11 + .../include/ckeditor5/translations/ti.d.ts | 8 + .../web/include/ckeditor5/translations/ti.js | 5 + .../include/ckeditor5/translations/ti.umd.js | 11 + .../include/ckeditor5/translations/tk.d.ts | 8 + .../web/include/ckeditor5/translations/tk.js | 5 + .../include/ckeditor5/translations/tk.umd.js | 11 + .../include/ckeditor5/translations/tr.d.ts | 8 + .../web/include/ckeditor5/translations/tr.js | 5 + .../include/ckeditor5/translations/tr.umd.js | 11 + .../include/ckeditor5/translations/tt.d.ts | 8 + .../web/include/ckeditor5/translations/tt.js | 5 + .../include/ckeditor5/translations/tt.umd.js | 11 + .../include/ckeditor5/translations/ug.d.ts | 8 + .../web/include/ckeditor5/translations/ug.js | 5 + .../include/ckeditor5/translations/ug.umd.js | 11 + .../include/ckeditor5/translations/uk.d.ts | 8 + .../web/include/ckeditor5/translations/uk.js | 5 + .../include/ckeditor5/translations/uk.umd.js | 11 + .../include/ckeditor5/translations/ur.d.ts | 8 + .../web/include/ckeditor5/translations/ur.js | 5 + .../include/ckeditor5/translations/ur.umd.js | 11 + .../include/ckeditor5/translations/uz.d.ts | 8 + .../web/include/ckeditor5/translations/uz.js | 5 + .../include/ckeditor5/translations/uz.umd.js | 11 + .../include/ckeditor5/translations/vi.d.ts | 8 + .../web/include/ckeditor5/translations/vi.js | 5 + .../include/ckeditor5/translations/vi.umd.js | 11 + .../include/ckeditor5/translations/zh-cn.d.ts | 8 + .../include/ckeditor5/translations/zh-cn.js | 5 + .../ckeditor5/translations/zh-cn.umd.js | 11 + .../include/ckeditor5/translations/zh.d.ts | 8 + .../web/include/ckeditor5/translations/zh.js | 5 + .../include/ckeditor5/translations/zh.umd.js | 11 + resources/web/include/globalapi.js | 77 + resources/web/include/shoelace_2_20_1.js | 406 +++++ resources/web/model/test.js | 40 +- resources/web/model_new/black.html | 43 + .../web/model_new/css/accessory_dropdown.css | 70 + resources/web/model_new/css/black.css | 39 + resources/web/model_new/css/dark.css | 156 ++ resources/web/model_new/css/editor.css | 260 +++ resources/web/model_new/css/gallery.css | 70 + resources/web/model_new/css/navigation.css | 50 + resources/web/model_new/css/tool.css | 23 + resources/web/model_new/editor.html | 174 ++ resources/web/model_new/img/del.svg | 3 + resources/web/model_new/img/empty.svg | 19 + resources/web/model_new/img/icon_jpg.svg | 9 + resources/web/model_new/img/icon_pdf.svg | 37 + resources/web/model_new/img/icon_png.svg | 32 + resources/web/model_new/img/icon_txt.svg | 37 + resources/web/model_new/img/icon_xcl.svg | 37 + resources/web/model_new/img/img_del.svg | 5 + resources/web/model_new/img/return.svg | 3 + resources/web/model_new/img/upload.svg | 5 + resources/web/model_new/index.css | 164 ++ resources/web/model_new/index.html | 73 + resources/web/model_new/index.js | 336 ++++ resources/web/model_new/js/ckeditor_config.js | 174 ++ resources/web/model_new/js/editor.js | 742 +++++++++ resources/web/model_new/js/gallery.js | 155 ++ resources/web/model_new/js/navigation.js | 81 + 264 files changed, 7342 insertions(+), 84 deletions(-) create mode 100644 resources/web/flush/NozzleListTable.html create mode 100644 resources/web/include/ckeditor5/LICENSE-ckeditor5.md create mode 100644 resources/web/include/ckeditor5/ckeditor5-content.css create mode 100644 resources/web/include/ckeditor5/ckeditor5-editor.css create mode 100644 resources/web/include/ckeditor5/ckeditor5.css create mode 100644 resources/web/include/ckeditor5/ckeditor5.css.map create mode 100644 resources/web/include/ckeditor5/ckeditor5.js create mode 100644 resources/web/include/ckeditor5/ckeditor5.js.map create mode 100644 resources/web/include/ckeditor5/ckeditor5.umd.js create mode 100644 resources/web/include/ckeditor5/ckeditor5.umd.js.map create mode 100644 resources/web/include/ckeditor5/translations/af.d.ts create mode 100644 resources/web/include/ckeditor5/translations/af.js create mode 100644 resources/web/include/ckeditor5/translations/af.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ar.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ar.js create mode 100644 resources/web/include/ckeditor5/translations/ar.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ast.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ast.js create mode 100644 resources/web/include/ckeditor5/translations/ast.umd.js create mode 100644 resources/web/include/ckeditor5/translations/az.d.ts create mode 100644 resources/web/include/ckeditor5/translations/az.js create mode 100644 resources/web/include/ckeditor5/translations/az.umd.js create mode 100644 resources/web/include/ckeditor5/translations/be.d.ts create mode 100644 resources/web/include/ckeditor5/translations/be.js create mode 100644 resources/web/include/ckeditor5/translations/be.umd.js create mode 100644 resources/web/include/ckeditor5/translations/bg.d.ts create mode 100644 resources/web/include/ckeditor5/translations/bg.js create mode 100644 resources/web/include/ckeditor5/translations/bg.umd.js create mode 100644 resources/web/include/ckeditor5/translations/bn.d.ts create mode 100644 resources/web/include/ckeditor5/translations/bn.js create mode 100644 resources/web/include/ckeditor5/translations/bn.umd.js create mode 100644 resources/web/include/ckeditor5/translations/bs.d.ts create mode 100644 resources/web/include/ckeditor5/translations/bs.js create mode 100644 resources/web/include/ckeditor5/translations/bs.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ca.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ca.js create mode 100644 resources/web/include/ckeditor5/translations/ca.umd.js create mode 100644 resources/web/include/ckeditor5/translations/cs.d.ts create mode 100644 resources/web/include/ckeditor5/translations/cs.js create mode 100644 resources/web/include/ckeditor5/translations/cs.umd.js create mode 100644 resources/web/include/ckeditor5/translations/da.d.ts create mode 100644 resources/web/include/ckeditor5/translations/da.js create mode 100644 resources/web/include/ckeditor5/translations/da.umd.js create mode 100644 resources/web/include/ckeditor5/translations/de-ch.d.ts create mode 100644 resources/web/include/ckeditor5/translations/de-ch.js create mode 100644 resources/web/include/ckeditor5/translations/de-ch.umd.js create mode 100644 resources/web/include/ckeditor5/translations/de.d.ts create mode 100644 resources/web/include/ckeditor5/translations/de.js create mode 100644 resources/web/include/ckeditor5/translations/de.umd.js create mode 100644 resources/web/include/ckeditor5/translations/el.d.ts create mode 100644 resources/web/include/ckeditor5/translations/el.js create mode 100644 resources/web/include/ckeditor5/translations/el.umd.js create mode 100644 resources/web/include/ckeditor5/translations/en-au.d.ts create mode 100644 resources/web/include/ckeditor5/translations/en-au.js create mode 100644 resources/web/include/ckeditor5/translations/en-au.umd.js create mode 100644 resources/web/include/ckeditor5/translations/en-gb.d.ts create mode 100644 resources/web/include/ckeditor5/translations/en-gb.js create mode 100644 resources/web/include/ckeditor5/translations/en-gb.umd.js create mode 100644 resources/web/include/ckeditor5/translations/en.d.ts create mode 100644 resources/web/include/ckeditor5/translations/en.js create mode 100644 resources/web/include/ckeditor5/translations/en.umd.js create mode 100644 resources/web/include/ckeditor5/translations/eo.d.ts create mode 100644 resources/web/include/ckeditor5/translations/eo.js create mode 100644 resources/web/include/ckeditor5/translations/eo.umd.js create mode 100644 resources/web/include/ckeditor5/translations/es-co.d.ts create mode 100644 resources/web/include/ckeditor5/translations/es-co.js create mode 100644 resources/web/include/ckeditor5/translations/es-co.umd.js create mode 100644 resources/web/include/ckeditor5/translations/es.d.ts create mode 100644 resources/web/include/ckeditor5/translations/es.js create mode 100644 resources/web/include/ckeditor5/translations/es.umd.js create mode 100644 resources/web/include/ckeditor5/translations/et.d.ts create mode 100644 resources/web/include/ckeditor5/translations/et.js create mode 100644 resources/web/include/ckeditor5/translations/et.umd.js create mode 100644 resources/web/include/ckeditor5/translations/eu.d.ts create mode 100644 resources/web/include/ckeditor5/translations/eu.js create mode 100644 resources/web/include/ckeditor5/translations/eu.umd.js create mode 100644 resources/web/include/ckeditor5/translations/fa.d.ts create mode 100644 resources/web/include/ckeditor5/translations/fa.js create mode 100644 resources/web/include/ckeditor5/translations/fa.umd.js create mode 100644 resources/web/include/ckeditor5/translations/fi.d.ts create mode 100644 resources/web/include/ckeditor5/translations/fi.js create mode 100644 resources/web/include/ckeditor5/translations/fi.umd.js create mode 100644 resources/web/include/ckeditor5/translations/fr.d.ts create mode 100644 resources/web/include/ckeditor5/translations/fr.js create mode 100644 resources/web/include/ckeditor5/translations/fr.umd.js create mode 100644 resources/web/include/ckeditor5/translations/gl.d.ts create mode 100644 resources/web/include/ckeditor5/translations/gl.js create mode 100644 resources/web/include/ckeditor5/translations/gl.umd.js create mode 100644 resources/web/include/ckeditor5/translations/gu.d.ts create mode 100644 resources/web/include/ckeditor5/translations/gu.js create mode 100644 resources/web/include/ckeditor5/translations/gu.umd.js create mode 100644 resources/web/include/ckeditor5/translations/he.d.ts create mode 100644 resources/web/include/ckeditor5/translations/he.js create mode 100644 resources/web/include/ckeditor5/translations/he.umd.js create mode 100644 resources/web/include/ckeditor5/translations/hi.d.ts create mode 100644 resources/web/include/ckeditor5/translations/hi.js create mode 100644 resources/web/include/ckeditor5/translations/hi.umd.js create mode 100644 resources/web/include/ckeditor5/translations/hr.d.ts create mode 100644 resources/web/include/ckeditor5/translations/hr.js create mode 100644 resources/web/include/ckeditor5/translations/hr.umd.js create mode 100644 resources/web/include/ckeditor5/translations/hu.d.ts create mode 100644 resources/web/include/ckeditor5/translations/hu.js create mode 100644 resources/web/include/ckeditor5/translations/hu.umd.js create mode 100644 resources/web/include/ckeditor5/translations/hy.d.ts create mode 100644 resources/web/include/ckeditor5/translations/hy.js create mode 100644 resources/web/include/ckeditor5/translations/hy.umd.js create mode 100644 resources/web/include/ckeditor5/translations/id.d.ts create mode 100644 resources/web/include/ckeditor5/translations/id.js create mode 100644 resources/web/include/ckeditor5/translations/id.umd.js create mode 100644 resources/web/include/ckeditor5/translations/it.d.ts create mode 100644 resources/web/include/ckeditor5/translations/it.js create mode 100644 resources/web/include/ckeditor5/translations/it.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ja.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ja.js create mode 100644 resources/web/include/ckeditor5/translations/ja.umd.js create mode 100644 resources/web/include/ckeditor5/translations/jv.d.ts create mode 100644 resources/web/include/ckeditor5/translations/jv.js create mode 100644 resources/web/include/ckeditor5/translations/jv.umd.js create mode 100644 resources/web/include/ckeditor5/translations/kk.d.ts create mode 100644 resources/web/include/ckeditor5/translations/kk.js create mode 100644 resources/web/include/ckeditor5/translations/kk.umd.js create mode 100644 resources/web/include/ckeditor5/translations/km.d.ts create mode 100644 resources/web/include/ckeditor5/translations/km.js create mode 100644 resources/web/include/ckeditor5/translations/km.umd.js create mode 100644 resources/web/include/ckeditor5/translations/kn.d.ts create mode 100644 resources/web/include/ckeditor5/translations/kn.js create mode 100644 resources/web/include/ckeditor5/translations/kn.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ko.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ko.js create mode 100644 resources/web/include/ckeditor5/translations/ko.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ku.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ku.js create mode 100644 resources/web/include/ckeditor5/translations/ku.umd.js create mode 100644 resources/web/include/ckeditor5/translations/lt.d.ts create mode 100644 resources/web/include/ckeditor5/translations/lt.js create mode 100644 resources/web/include/ckeditor5/translations/lt.umd.js create mode 100644 resources/web/include/ckeditor5/translations/lv.d.ts create mode 100644 resources/web/include/ckeditor5/translations/lv.js create mode 100644 resources/web/include/ckeditor5/translations/lv.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ms.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ms.js create mode 100644 resources/web/include/ckeditor5/translations/ms.umd.js create mode 100644 resources/web/include/ckeditor5/translations/nb.d.ts create mode 100644 resources/web/include/ckeditor5/translations/nb.js create mode 100644 resources/web/include/ckeditor5/translations/nb.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ne.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ne.js create mode 100644 resources/web/include/ckeditor5/translations/ne.umd.js create mode 100644 resources/web/include/ckeditor5/translations/nl.d.ts create mode 100644 resources/web/include/ckeditor5/translations/nl.js create mode 100644 resources/web/include/ckeditor5/translations/nl.umd.js create mode 100644 resources/web/include/ckeditor5/translations/no.d.ts create mode 100644 resources/web/include/ckeditor5/translations/no.js create mode 100644 resources/web/include/ckeditor5/translations/no.umd.js create mode 100644 resources/web/include/ckeditor5/translations/oc.d.ts create mode 100644 resources/web/include/ckeditor5/translations/oc.js create mode 100644 resources/web/include/ckeditor5/translations/oc.umd.js create mode 100644 resources/web/include/ckeditor5/translations/pl.d.ts create mode 100644 resources/web/include/ckeditor5/translations/pl.js create mode 100644 resources/web/include/ckeditor5/translations/pl.umd.js create mode 100644 resources/web/include/ckeditor5/translations/pt-br.d.ts create mode 100644 resources/web/include/ckeditor5/translations/pt-br.js create mode 100644 resources/web/include/ckeditor5/translations/pt-br.umd.js create mode 100644 resources/web/include/ckeditor5/translations/pt.d.ts create mode 100644 resources/web/include/ckeditor5/translations/pt.js create mode 100644 resources/web/include/ckeditor5/translations/pt.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ro.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ro.js create mode 100644 resources/web/include/ckeditor5/translations/ro.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ru.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ru.js create mode 100644 resources/web/include/ckeditor5/translations/ru.umd.js create mode 100644 resources/web/include/ckeditor5/translations/si.d.ts create mode 100644 resources/web/include/ckeditor5/translations/si.js create mode 100644 resources/web/include/ckeditor5/translations/si.umd.js create mode 100644 resources/web/include/ckeditor5/translations/sk.d.ts create mode 100644 resources/web/include/ckeditor5/translations/sk.js create mode 100644 resources/web/include/ckeditor5/translations/sk.umd.js create mode 100644 resources/web/include/ckeditor5/translations/sl.d.ts create mode 100644 resources/web/include/ckeditor5/translations/sl.js create mode 100644 resources/web/include/ckeditor5/translations/sl.umd.js create mode 100644 resources/web/include/ckeditor5/translations/sq.d.ts create mode 100644 resources/web/include/ckeditor5/translations/sq.js create mode 100644 resources/web/include/ckeditor5/translations/sq.umd.js create mode 100644 resources/web/include/ckeditor5/translations/sr-latn.d.ts create mode 100644 resources/web/include/ckeditor5/translations/sr-latn.js create mode 100644 resources/web/include/ckeditor5/translations/sr-latn.umd.js create mode 100644 resources/web/include/ckeditor5/translations/sr.d.ts create mode 100644 resources/web/include/ckeditor5/translations/sr.js create mode 100644 resources/web/include/ckeditor5/translations/sr.umd.js create mode 100644 resources/web/include/ckeditor5/translations/sv.d.ts create mode 100644 resources/web/include/ckeditor5/translations/sv.js create mode 100644 resources/web/include/ckeditor5/translations/sv.umd.js create mode 100644 resources/web/include/ckeditor5/translations/th.d.ts create mode 100644 resources/web/include/ckeditor5/translations/th.js create mode 100644 resources/web/include/ckeditor5/translations/th.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ti.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ti.js create mode 100644 resources/web/include/ckeditor5/translations/ti.umd.js create mode 100644 resources/web/include/ckeditor5/translations/tk.d.ts create mode 100644 resources/web/include/ckeditor5/translations/tk.js create mode 100644 resources/web/include/ckeditor5/translations/tk.umd.js create mode 100644 resources/web/include/ckeditor5/translations/tr.d.ts create mode 100644 resources/web/include/ckeditor5/translations/tr.js create mode 100644 resources/web/include/ckeditor5/translations/tr.umd.js create mode 100644 resources/web/include/ckeditor5/translations/tt.d.ts create mode 100644 resources/web/include/ckeditor5/translations/tt.js create mode 100644 resources/web/include/ckeditor5/translations/tt.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ug.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ug.js create mode 100644 resources/web/include/ckeditor5/translations/ug.umd.js create mode 100644 resources/web/include/ckeditor5/translations/uk.d.ts create mode 100644 resources/web/include/ckeditor5/translations/uk.js create mode 100644 resources/web/include/ckeditor5/translations/uk.umd.js create mode 100644 resources/web/include/ckeditor5/translations/ur.d.ts create mode 100644 resources/web/include/ckeditor5/translations/ur.js create mode 100644 resources/web/include/ckeditor5/translations/ur.umd.js create mode 100644 resources/web/include/ckeditor5/translations/uz.d.ts create mode 100644 resources/web/include/ckeditor5/translations/uz.js create mode 100644 resources/web/include/ckeditor5/translations/uz.umd.js create mode 100644 resources/web/include/ckeditor5/translations/vi.d.ts create mode 100644 resources/web/include/ckeditor5/translations/vi.js create mode 100644 resources/web/include/ckeditor5/translations/vi.umd.js create mode 100644 resources/web/include/ckeditor5/translations/zh-cn.d.ts create mode 100644 resources/web/include/ckeditor5/translations/zh-cn.js create mode 100644 resources/web/include/ckeditor5/translations/zh-cn.umd.js create mode 100644 resources/web/include/ckeditor5/translations/zh.d.ts create mode 100644 resources/web/include/ckeditor5/translations/zh.js create mode 100644 resources/web/include/ckeditor5/translations/zh.umd.js create mode 100644 resources/web/include/shoelace_2_20_1.js create mode 100644 resources/web/model_new/black.html create mode 100644 resources/web/model_new/css/accessory_dropdown.css create mode 100644 resources/web/model_new/css/black.css create mode 100644 resources/web/model_new/css/dark.css create mode 100644 resources/web/model_new/css/editor.css create mode 100644 resources/web/model_new/css/gallery.css create mode 100644 resources/web/model_new/css/navigation.css create mode 100644 resources/web/model_new/css/tool.css create mode 100644 resources/web/model_new/editor.html create mode 100644 resources/web/model_new/img/del.svg create mode 100644 resources/web/model_new/img/empty.svg create mode 100644 resources/web/model_new/img/icon_jpg.svg create mode 100644 resources/web/model_new/img/icon_pdf.svg create mode 100644 resources/web/model_new/img/icon_png.svg create mode 100644 resources/web/model_new/img/icon_txt.svg create mode 100644 resources/web/model_new/img/icon_xcl.svg create mode 100644 resources/web/model_new/img/img_del.svg create mode 100644 resources/web/model_new/img/return.svg create mode 100644 resources/web/model_new/img/upload.svg create mode 100644 resources/web/model_new/index.css create mode 100644 resources/web/model_new/index.html create mode 100644 resources/web/model_new/index.js create mode 100644 resources/web/model_new/js/ckeditor_config.js create mode 100644 resources/web/model_new/js/editor.js create mode 100644 resources/web/model_new/js/gallery.js create mode 100644 resources/web/model_new/js/navigation.js diff --git a/resources/shaders/140/gcode.fs b/resources/shaders/140/gcode.fs index 98d7898..074f6f3 100644 --- a/resources/shaders/140/gcode.fs +++ b/resources/shaders/140/gcode.fs @@ -14,9 +14,11 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); #define INTENSITY_AMBIENT 0.3 -const mat3 KTravel_Colors = mat3(0.505, 0.064, 0.028, - 0.219, 0.282, 0.609, - 0.112, 0.422, 0.103); +const vec3 KTravel_Colors[3] = vec3[3]( + vec3(0.505, 0.064, 0.028), + vec3(0.219, 0.282, 0.609), + vec3(0.112, 0.422, 0.103) +); uniform vec2 u_isTopLayer_hasCustomOptins; uniform mat3 normal_matrix; diff --git a/resources/web/data/text.js b/resources/web/data/text.js index df09565..1292198 100644 --- a/resources/web/data/text.js +++ b/resources/web/data/text.js @@ -115,9 +115,30 @@ var LangText={ "t121": "Search", "t122": "Search online models", "t123": "Plate", - "t124": "", "t125": "Maker’s Supply", "t126": "Loading……", + "t127": "Please add the project information", + "t128": "Edit", + "t129": "Project information", + "t130": "Accessories", + "t131": "Profile information", + "t132": "Description", + "t133": "Profile Name: ", + "t134": "Return", + "t135": "Save", + "t136": "Project Name", + "t137": "Pictures", + "t138": "Add Picture", + "t139": "Add accessories", + "t140": "Bill of Materials", + "t141": "Assembly Guide", + "t142": "Profile Pictures", + "t143": "JPG/GIF/PNG format ≤ 4MB, no more than 16 pictures, drag the picture to adjust the order;It is best to scale your image to 4:3 for the best display.", + "t144": "Invalid file type", + "t145": "File size exceeds limit", + "t146": "Upload limit exceeded", + "t147": "File upload failed", + "t148": "Add", "wk17": "QIDI Tech Academy", "wk18": "Quick Start Tutorial", "wk19": "Learn by Topic", @@ -246,6 +267,28 @@ var LangText={ "t124": " 盘", "t125": "创客宝库", "t126": "正在加载……", + "t127": "请添加项目信息", + "t128": "编辑", + "t129": "项目信息", + "t130": "配件", + "t131": "配置文件信息", + "t132": "描述", + "t133": "配置名称:", + "t134": "返回", + "t135": "保存", + "t136": "项目名称", + "t137": "图片", + "t138": "添加图片", + "t139": "添加配件", + "t140": "材料清单", + "t141": "装配指南", + "t142": "配置图片", + "t143": "JPG/GIF/PNG格式≤4MB,最多16张图片,拖动可调整顺序;建议将图片缩放为4:3以获得最佳显示效果。", + "t144": "文件类型无效", + "t145": "文件大小超出限制", + "t146": "上传次数超出限制", + "t147": "文件上传失败", + "t148": "添加", "t201": "设备连接", "t202":"请设置您的打印机连接以查看设备。", "t203":"请使用 QIDI Link APP 连接您的打印机。链接完成后,在线设备将同步到 “Link” 页面。", @@ -389,6 +432,28 @@ var LangText={ "t123": "シート材", "t124": "", "t126": "読み込み中……", + "t127": "プロジェクト情報を追加してください", + "t128": "編集", + "t129": "プロジェクト情報", + "t130": "付属品", + "t131": "プロファイル情報", + "t132": "説明", + "t133": "プロファイル名:", + "t134": "戻る", + "t135": "保存", + "t136": "プロジェクト名", + "t137": "画像", + "t138": "画像を追加", + "t139": "付属品を追加", + "t140": "部品表", + "t141": "組立ガイド", + "t142": "プロファイル画像", + "t143": "JPG/GIF/PNG 形式(4MB 以下)、最大 16 枚。ドラッグして順序を調整できます。最適な表示のため 4:3 にスケーリングしてください。", + "t144": "無効なファイルタイプです", + "t145": "ファイルサイズが制限を超えています", + "t146": "アップロード制限を超えました", + "t147": "ファイルのアップロードに失敗しました", + "t148": "追加", "wk17": "バンブーラボ アカデミー", "wk18": "クイックスタート チュートリアル", "wk19": "トピック別に学ぶ", @@ -516,6 +581,28 @@ var LangText={ "t123": "piatto", "t124": "", "t126": "Caricamento in corso……", + "t127": "Aggiungi le informazioni sul progetto", + "t128": "Modifica", + "t129": "Informazioni sul progetto", + "t130": "Accessori", + "t131": "Informazioni sul profilo", + "t132": "Descrizione", + "t133": "Nome profilo: ", + "t134": "Torna", + "t135": "Salva", + "t136": "Nome progetto", + "t137": "Immagini", + "t138": "Aggiungi immagine", + "t139": "Aggiungi accessori", + "t140": "Distinta materiali", + "t141": "Guida al montaggio", + "t142": "Immagini del profilo", + "t143": "Formato JPG/GIF/PNG ≤ 4 MB, massimo 16 immagini; trascina per modificarne l'ordine. Per la resa migliore ridimensiona a 4:3.", + "t144": "Tipo di file non valido", + "t145": "Dimensione del file oltre il limite", + "t146": "Limite di caricamento superato", + "t147": "Caricamento del file non riuscito", + "t148": "Aggiungi", "wk17": "QIDI Tech Accademia", "wk18": "Tutorial di Avvio Rapido", "wk19": "Impara per Argomento", @@ -643,6 +730,28 @@ var LangText={ "t123": "assiette", "t124": "", "t126": "Chargement en cours……", + "t127": "Veuillez ajouter les informations du projet", + "t128": "Modifier", + "t129": "Informations sur le projet", + "t130": "Accessoires", + "t131": "Informations du profil", + "t132": "Description", + "t133": "Nom du profil : ", + "t134": "Retour", + "t135": "Enregistrer", + "t136": "Nom du projet", + "t137": "Images", + "t138": "Ajouter une image", + "t139": "Ajouter des accessoires", + "t140": "Nomenclature", + "t141": "Guide d’assemblage", + "t142": "Images du profil", + "t143": "Format JPG/GIF/PNG ≤ 4 Mo, 16 images maximum. Faites glisser pour changer l’ordre ; pour un meilleur rendu, utilisez un format 4:3.", + "t144": "Type de fichier invalide", + "t145": "Taille du fichier dépassée", + "t146": "Limite de téléversement dépassée", + "t147": "Échec du téléversement", + "t148": "Ajouter", "wk17": "Académie QIDI Tech", "wk18": "Tutoriel de Démarrage Rapide", "wk19": "Apprendre par sujet", @@ -770,6 +879,28 @@ var LangText={ "t123": "Teller", "t124": "", "t126": "Laden……", + "t127": "Bitte die Projektinformationen hinzufügen", + "t128": "Bearbeiten", + "t129": "Projektinformationen", + "t130": "Zubehör", + "t131": "Profilinformationen", + "t132": "Beschreibung", + "t133": "Profilname: ", + "t134": "Zurück", + "t135": "Speichern", + "t136": "Projektname", + "t137": "Bilder", + "t138": "Bild hinzufügen", + "t139": "Zubehör hinzufügen", + "t140": "Stückliste", + "t141": "Montageanleitung", + "t142": "Profilbilder", + "t143": "JPG/GIF/PNG ≤ 4 MB, maximal 16 Bilder. Zum Anpassen der Reihenfolge ziehen; für optimale Darstellung auf 4:3 skalieren.", + "t144": "Ungültiger Dateityp", + "t145": "Dateigröße überschreitet das Limit", + "t146": "Upload-Limit überschritten", + "t147": "Datei-Upload fehlgeschlagen", + "t148": "Hinzufügen", "wk17": "QIDI Tech Akademie", "wk18": "Schnellstartanleitung", "wk19": "Lernen nach Thema", @@ -897,6 +1028,28 @@ var LangText={ "t123": "tányér", "t124": "", "t126": "Betöltés folyamatban……", + "t127": "Adja meg a projekt adatait", + "t128": "Szerkesztés", + "t129": "Projektinformációk", + "t130": "Tartozékok", + "t131": "Profilinformációk", + "t132": "Leírás", + "t133": "Profilnév: ", + "t134": "Vissza", + "t135": "Mentés", + "t136": "Projekt neve", + "t137": "Képek", + "t138": "Kép hozzáadása", + "t139": "Tartozék hozzáadása", + "t140": "Anyagjegyzék", + "t141": "Szerelési útmutató", + "t142": "Profilképek", + "t143": "JPG/GIF/PNG formátum, legfeljebb 4 MB és maximum 16 kép. A sorrend húzással módosítható; a legjobb megjelenéshez méretezd 4:3 arányra.", + "t144": "Érvénytelen fájltípus", + "t145": "A fájlméret meghaladja a korlátot", + "t146": "Feltöltési korlát túllépve", + "t147": "A fájl feltöltése sikertelen", + "t148": "Hozzáadás", "wk17": "QIDI Tech Akadémia", "wk18": "Gyorsindítási Útmutató", "wk19": "Tanulj témakörök szerint", @@ -1024,6 +1177,28 @@ var LangText={ "t123": "plato", "t124": "", "t126": "Carga en progreso……", + "t127": "Añade la información del proyecto", + "t128": "Editar", + "t129": "Información del proyecto", + "t130": "Accesorios", + "t131": "Información del perfil", + "t132": "Descripción", + "t133": "Nombre del perfil: ", + "t134": "Volver", + "t135": "Guardar", + "t136": "Nombre del proyecto", + "t137": "Imágenes", + "t138": "Añadir imagen", + "t139": "Añadir accesorios", + "t140": "Lista de materiales", + "t141": "Guía de montaje", + "t142": "Imágenes del perfil", + "t143": "Formato JPG/GIF/PNG ≤ 4 MB, máximo 16 imágenes. Arrastra para ajustar el orden; para una mejor visualización escala a 4:3.", + "t144": "Tipo de archivo no válido", + "t145": "El tamaño del archivo supera el límite", + "t146": "Se superó el límite de carga", + "t147": "Error al cargar el archivo", + "t148": "Añadir", "wk17": "Academia QIDI Tech", "wk18": "Tutorial de Inicio Rápido", "wk19": "Aprender por tema", @@ -1151,6 +1326,28 @@ var LangText={ "t123": "fat", "t124": "", "t126": "Laddning pågår……", + "t127": "Lägg till projektinformationen", + "t128": "Redigera", + "t129": "Projektinformation", + "t130": "Tillbehör", + "t131": "Profilinformation", + "t132": "Beskrivning", + "t133": "Profilnamn: ", + "t134": "Tillbaka", + "t135": "Spara", + "t136": "Projektnamn", + "t137": "Bilder", + "t138": "Lägg till bild", + "t139": "Lägg till tillbehör", + "t140": "Materiallista", + "t141": "Monteringsguide", + "t142": "Profilbilder", + "t143": "JPG/GIF/PNG-format ≤ 4 MB, högst 16 bilder. Dra för att ändra ordningen; bäst resultat fås med bildförhållandet 4:3.", + "t144": "Ogiltig filtyp", + "t145": "Filstorleken överskrider gränsen", + "t146": "Uppladdningsgränsen har överskridits", + "t147": "Filuppladdningen misslyckades", + "t148": "Lägg till", "wk17": "QIDI Tech Akademin", "wk18": "Snabbstartsguide", "wk19": "Lär dig efter ämne", @@ -1278,6 +1475,28 @@ var LangText={ "t123": "talíř", "t124": "", "t126": "Načtení probíhá……", + "t127": "Přidejte informace o projektu", + "t128": "Upravit", + "t129": "Informace o projektu", + "t130": "Příslušenství", + "t131": "Informace o profilu", + "t132": "Popis", + "t133": "Název profilu: ", + "t134": "Zpět", + "t135": "Uložit", + "t136": "Název projektu", + "t137": "Obrázky", + "t138": "Přidat obrázek", + "t139": "Přidat příslušenství", + "t140": "Seznam materiálu", + "t141": "Montážní příručka", + "t142": "Obrázky profilu", + "t143": "Formát JPG/GIF/PNG ≤ 4 MB, maximálně 16 obrázků. Přetažením upravíte pořadí; pro nejlepší zobrazení použijte poměr 4:3.", + "t144": "Neplatný typ souboru", + "t145": "Velikost souboru překračuje limit", + "t146": "Byl překročen limit nahrávání", + "t147": "Nahrání souboru se nezdařilo", + "t148": "Přidat", "wk17": "QIDI Tech Akademie", "wk18": "Rychlý Návod k Použití", "wk19": "Učit se podle tématu", @@ -1405,6 +1624,28 @@ var LangText={ "t123": "bord", "t124": "", "t126": "Laden in uitvoering……", + "t127": "Voeg de projectinformatie toe", + "t128": "Bewerken", + "t129": "Projectinformatie", + "t130": "Accessoires", + "t131": "Profielinformatie", + "t132": "Beschrijving", + "t133": "Profielnaam: ", + "t134": "Terug", + "t135": "Opslaan", + "t136": "Projectnaam", + "t137": "Afbeeldingen", + "t138": "Afbeelding toevoegen", + "t139": "Accessoires toevoegen", + "t140": "Stuklijst", + "t141": "Montagehandleiding", + "t142": "Profielfoto's", + "t143": "JPG/GIF/PNG-formaat ≤ 4 MB, maximaal 16 afbeeldingen. Sleep om de volgorde te wijzigen; schaal bij voorkeur naar 4:3 voor het beste resultaat.", + "t144": "Ongeldig bestandstype", + "t145": "Bestandsgrootte overschrijdt de limiet", + "t146": "Uploadlimiet overschreden", + "t147": "Bestandsupload mislukt", + "t148": "Toevoegen", "wk17": "QIDI Tech Academie", "wk18": "Snelstartgids", "wk19": "Leren per Onderwerp", @@ -1532,6 +1773,28 @@ var LangText={ "t123": "тарілка", "t124": "", "t126": "Завантаження триває……", + "t127": "Додайте інформацію про проєкт", + "t128": "Редагувати", + "t129": "Відомості про проєкт", + "t130": "Аксесуари", + "t131": "Відомості про профіль", + "t132": "Опис", + "t133": "Назва профілю: ", + "t134": "Повернутися", + "t135": "Зберегти", + "t136": "Назва проєкту", + "t137": "Зображення", + "t138": "Додати зображення", + "t139": "Додати аксесуари", + "t140": "Специфікація матеріалів", + "t141": "Інструкція зі складання", + "t142": "Зображення профілю", + "t143": "Формат JPG/GIF/PNG ≤ 4 МБ, не більше 16 зображень. Перетягніть, щоб змінити порядок; для найкращого вигляду використовуйте співвідношення 4:3.", + "t144": "Неприпустимий тип файлу", + "t145": "Розмір файлу перевищує ліміт", + "t146": "Перевищено ліміт завантажень", + "t147": "Не вдалося завантажити файл", + "t148": "Додати", "wk17": "Академія QIDI Tech", "wk18": "Керівництво з Швидкого Початку", "wk19": "Навчання за Темою", @@ -1659,6 +1922,28 @@ var LangText={ "t123": "тарелка", "t124": "", "t126": "Загрузка идёт……", + "t127": "Добавьте сведения о проекте", + "t128": "Редактировать", + "t129": "Сведения о проекте", + "t130": "Аксессуары", + "t131": "Сведения о профиле", + "t132": "Описание", + "t133": "Имя профиля: ", + "t134": "Назад", + "t135": "Сохранить", + "t136": "Название проекта", + "t137": "Изображения", + "t138": "Добавить изображение", + "t139": "Добавить аксессуары", + "t140": "Ведомость материалов", + "t141": "Руководство по сборке", + "t142": "Изображения профиля", + "t143": "Формат JPG/GIF/PNG ≤ 4 МБ, не более 16 изображений. Перетащите для изменения порядка; для лучшего отображения масштабируйте до 4:3.", + "t144": "Неверный тип файла", + "t145": "Размер файла превышает ограничение", + "t146": "Превышен лимит загрузок", + "t147": "Не удалось загрузить файл", + "t148": "Добавить", "wk17": "Академия QIDI Tech", "wk18": "Руководство по быстрому запуску", "wk19": "Обучение по теме", @@ -1786,6 +2071,28 @@ var LangText={ "t123": "tabak", "t124": "", "t126": "Yükleme devam ediyor……", + "t127": "Lütfen proje bilgilerini ekleyin", + "t128": "Düzenle", + "t129": "Proje bilgileri", + "t130": "Aksesuarlar", + "t131": "Profil bilgileri", + "t132": "Açıklama", + "t133": "Profil Adı: ", + "t134": "Geri", + "t135": "Kaydet", + "t136": "Proje Adı", + "t137": "Görseller", + "t138": "Görsel Ekle", + "t139": "Aksesuar ekle", + "t140": "Malzeme Listesi", + "t141": "Montaj Kılavuzu", + "t142": "Profil Görselleri", + "t143": "JPG/GIF/PNG formatı ≤ 4 MB, en fazla 16 görsel. Sıralamayı sürükleyerek düzenleyin; en iyi görünüm için görüntüleri 4:3 oranına ölçekleyin.", + "t144": "Geçersiz dosya türü", + "t145": "Dosya boyutu sınırı aşıldı", + "t146": "Yükleme sınırı aşıldı", + "t147": "Dosya yükleme başarısız", + "t148": "Ekle", "wk17": "QIDI Tech Akademisi", "wk18": "Hızlı Başlangıç Kılavuzu", "wk19": "Konuya Göre Öğrenme", @@ -1913,6 +2220,28 @@ var LangText={ "t123": "prato", "t124": "", "t126": "Carregamento em andamento……", + "t127": "Adicione as informações do projeto", + "t128": "Editar", + "t129": "Informações do projeto", + "t130": "Acessórios", + "t131": "Informações do perfil", + "t132": "Descrição", + "t133": "Nome do perfil: ", + "t134": "Voltar", + "t135": "Salvar", + "t136": "Nome do projeto", + "t137": "Imagens", + "t138": "Adicionar imagem", + "t139": "Adicionar acessórios", + "t140": "Lista de materiais", + "t141": "Guia de montagem", + "t142": "Imagens do perfil", + "t143": "Formato JPG/GIF/PNG ≤ 4 MB, no máximo 16 imagens. Arraste para ajustar a ordem; para a melhor visualização, redimensione para 4:3.", + "t144": "Tipo de arquivo inválido", + "t145": "Tamanho do arquivo excede o limite", + "t146": "Limite de upload excedido", + "t147": "Falha no upload do arquivo", + "t148": "Adicionar", "wk17": "Academia QIDI Tech", "wk18": "Tutorial de Início Rápido", "wk19": "Aprendizado por Tópico", @@ -2040,6 +2369,28 @@ var LangText={ "t123": "접시", "t124": "", "t126": "로딩 중……", + "t127": "프로젝트 정보를 추가하세요", + "t128": "편집", + "t129": "프로젝트 정보", + "t130": "액세서리", + "t131": "프로필 정보", + "t132": "설명", + "t133": "프로필 이름: ", + "t134": "돌아가기", + "t135": "저장", + "t136": "프로젝트 이름", + "t137": "이미지", + "t138": "이미지 추가", + "t139": "액세서리 추가", + "t140": "자재 명세서", + "t141": "조립 가이드", + "t142": "프로필 이미지", + "t143": "JPG/GIF/PNG 형식, 4MB 이하, 최대 16장. 드래그하여 순서를 조정하고, 최적 표시를 위해 4:3 비율로 조정하세요.", + "t144": "잘못된 파일 형식입니다", + "t145": "파일 크기가 제한을 초과했습니다", + "t146": "업로드 제한을 초과했습니다", + "t147": "파일 업로드에 실패했습니다", + "t148": "추가", "wk17": "밤부랩 아카데미", "wk18": "빠른 시작 튜토리얼", "wk19": "주제별 학습", @@ -2167,6 +2518,28 @@ var LangText={ "t123": "talerz", "t124": "", "t126": "Ładowanie trwa……", + "t127": "Dodaj informacje o projekcie", + "t128": "Edytuj", + "t129": "Informacje o projekcie", + "t130": "Akcesoria", + "t131": "Informacje o profilu", + "t132": "Opis", + "t133": "Nazwa profilu: ", + "t134": "Powrót", + "t135": "Zapisz", + "t136": "Nazwa projektu", + "t137": "Obrazy", + "t138": "Dodaj obraz", + "t139": "Dodaj akcesoria", + "t140": "Zestawienie materiałów", + "t141": "Instrukcja montażu", + "t142": "Obrazy profilu", + "t143": "Format JPG/GIF/PNG ≤ 4 MB, maksymalnie 16 obrazów. Przeciągnij, aby zmienić kolejność; dla najlepszego efektu ustaw proporcje 4:3.", + "t144": "Nieprawidłowy typ pliku", + "t145": "Rozmiar pliku przekracza limit", + "t146": "Przekroczono limit przesyłania", + "t147": "Przesyłanie pliku nie powiodło się", + "t148": "Dodaj", "wk17": "Akademia QIDI Tech", "wk18": "Samouczek Szybkiego Startu", "wk19": "Nauka według Tematu", diff --git a/resources/web/flush/NozzleListTable.html b/resources/web/flush/NozzleListTable.html new file mode 100644 index 0000000..d8654ee --- /dev/null +++ b/resources/web/flush/NozzleListTable.html @@ -0,0 +1,274 @@ + + + + + + 喷嘴选择表格 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
选用的喷嘴直径可用喷嘴
左边右边
+ + + 不可用2个
+ + + ✔️3个
+ + + 不可用1个
+ + + + diff --git a/resources/web/homepage3/css/dark.css b/resources/web/homepage3/css/dark.css index 835a3dd..309af99 100644 --- a/resources/web/homepage3/css/dark.css +++ b/resources/web/homepage3/css/dark.css @@ -192,13 +192,13 @@ html,body /*wiki.css*/ .topicBlock{ - background-color: #000000; + background-color: #242428; } .card{ - background-color: #737373; + background-color: #333337; } .topicCard{ - background-color: #737373; + background-color: #333337; } \ No newline at end of file diff --git a/resources/web/homepage3/css/wiki.css b/resources/web/homepage3/css/wiki.css index f7f05a6..9e8bc4e 100644 --- a/resources/web/homepage3/css/wiki.css +++ b/resources/web/homepage3/css/wiki.css @@ -157,7 +157,7 @@ body{ background-clip: content-box; background-size: cover; cursor: pointer; - opacity: 0.2; + opacity: 0.4; pointer-events: auto; } .switchBtn:hover{ diff --git a/resources/web/homepage3/js/wiki.js b/resources/web/homepage3/js/wiki.js index c44f28b..a60e3fc 100644 --- a/resources/web/homepage3/js/wiki.js +++ b/resources/web/homepage3/js/wiki.js @@ -1,45 +1,50 @@ -var cardData = [ - { - "title": "Q2", - "img": "img/printer_q2.png", - "link": "Q2" - }, - { - "title": "X-Plus 4", - "img": "img/printer_xplus4.png", - "link": "X-Plus4" - }, - { - "title": "Q1 Pro", - "img": "img/printer_q1pro.png", - "link": "Q1-Pro" - }, - { - "title": "X-Max 3", - "img": "img/printer_xmax3.png", - "link": "X-Max3" - }, - { - "title": "X-Plus 3", - "img": "img/printer_xplus3.png", - "link": "X-Plus3" - }, - { - "title": "X-Smart 3", - "img": "img/printer_xsmart3.png", - "link": "X-Smart3" - }, - { - "title": "QIDI Studio", - "img": "img/QIDIStudio.png", - "link": "software/qidi-studio" - }, -]; +var cardData = { + "code":"000000", + "msg":"operation.successful", + "traceId":"71ce01ec38ce414692a88a2fd44e7330.18616.17631185286483121", + "data":[ + { + "id":"Q2", + "printerType":"Q2", + "img":"img/printer_q2.png" + }, + { + "id":"X-Plus4", + "printerType":"X-Plus 4", + "img":"img/printer_xplus4.png" + }, + { + "id":"Q1-Pro", + "printerType":"Q1 Pro", + "img":"img/printer_q1pro.png" + }, + { + "id":"X-Max3", + "printerType":"X-Max 3", + "img":"img/printer_xmax3.png" + }, + { + "id": "X-Plus3", + "printerType":"X-Plus 3", + "img":"img/printer_xplus3.png" + }, + { + "id":"X-Smart3", + "printerType":"X-Smart 3", + "img":"img/printer_xsmart3.png" + }, + { + "id":"software/qidi-studio", + "printerType":"QIDI Studio", + "img":"img/QIDIStudio.png" + } + ] +}; // var youtubeData = [ // { // "id": "8TQCRVS72Us", -// "title": "Intro: Overview of Bambu Studio" +// "title": "Intro: Overview of QIDI Studio" // }, // { // "id": "AdHUVQiVeDI", @@ -214,9 +219,9 @@ var topicData = [ "zhcn-title": "打印设置", "children": [ // { - // "title": "Special Slicing Mode in Bambu Studio", - // "zhcn-title": "Bambu Studio 特殊切片模式", - // "link": "software/bambu-studio/special-slicing-modes" + // "title": "Special Slicing Mode in QIDI Studio", + // "zhcn-title": "QIDI Studio 特殊切片模式", + // "link": "software/qidi-studio/special-slicing-modes" // }, { "title": "How to Create Custom Preset", @@ -229,7 +234,7 @@ var topicData = [ "link": "software/qidi-studio/print-settings/seam" }, { - "title": "Support settings in Bambu Studio", + "title": "Support settings in QIDI Studio", "zhcn-title": "支撑耗材与支撑功能的介绍", "link": "software/qidi-studio/print-settings/support" }, @@ -247,12 +252,12 @@ var topicData = [ // { // "title": "Flow Rate Calibration", // "zhcn-title": "流量比例", - // "link": "software/bambu-studio/calibration_flow_rate" + // "link": "software/qidi-studio/calibration_flow_rate" // }, // { // "title": "Flow Dynamics Calibration", // "zhcn-title": "动态流量校准", - // "link": "software/bambu-studio/calibration_pa" + // "link": "software/qidi-studio/calibration_pa" // } { "title": "Calibration", @@ -291,7 +296,7 @@ var video_prev; var video_next; function OnInit() { - createCardHTML(); + getAcademyData(); createVideoHTML(); if (IsChinese()) $("#tutorial_block").hide(); @@ -375,11 +380,24 @@ function updateSearchResult(result) { //--------------- Academy Cards ------------------- -function createCardHTML() { - for (let i = 0; i < cardData.length; i++) { - let html = `
- -
${cardData[i].title}
+function getAcademyData() { + var tSend={}; + tSend['sequence_id']=Math.round(new Date() / 1000); + tSend['command']="get_academy_list"; + tSend['data']={}; + if(IsChinese()) { + tSend['data']['region'] = "mainland"; + }else { + tSend['data']['region'] = "oversea"; + } + SendWXMessage( JSON.stringify(tSend) ); +} + +function createCardHTML(data) { + for (let i = 0; i < data.length; i++) { + let html = `
+ +
${data[i].printerType}
`; $('#academy_Card_Content').append(html) } @@ -418,7 +436,7 @@ function scrollByStep(dir) { $('#academy_content').stop(true, false).animate({ scrollLeft: target }, 260, 'swing', updateButtons); } -function openAcademyUrl(path) +function openAcademyUrl(id) { let open_url = ""; if (IsChinese()){ @@ -427,7 +445,7 @@ function openAcademyUrl(path) let strLang=langStringTransfer(); open_url = "https://wiki.qidi3d.com/en/"; } - open_url += path; + open_url += id; OpenUrlInLocalBrowser(open_url); } @@ -496,7 +514,7 @@ function createTopicHTML() { title = topicData[i].title; } let html = `
-
${title}
+
${title}
'\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\tname: 'spotify',\n\t\t\t\t\turl: [\n\t\t\t\t\t\t/^open\\.spotify\\.com\\/(artist\\/\\w+)/,\n\t\t\t\t\t\t/^open\\.spotify\\.com\\/(album\\/\\w+)/,\n\t\t\t\t\t\t/^open\\.spotify\\.com\\/(track\\/\\w+)/\n\t\t\t\t\t],\n\t\t\t\t\thtml: match => {\n\t\t\t\t\t\tconst id = match[ 1 ];\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t'
' +\n\t\t\t\t\t\t\t\t`' +\n\t\t\t\t\t\t\t'
'\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\tname: 'youtube',\n\t\t\t\t\turl: [\n\t\t\t\t\t\t/^(?:m\\.)?youtube\\.com\\/watch\\?v=([\\w-]+)(?:&t=(\\d+))?/,\n\t\t\t\t\t\t/^(?:m\\.)?youtube\\.com\\/shorts\\/([\\w-]+)(?:\\?t=(\\d+))?/,\n\t\t\t\t\t\t/^(?:m\\.)?youtube\\.com\\/v\\/([\\w-]+)(?:\\?t=(\\d+))?/,\n\t\t\t\t\t\t/^youtube\\.com\\/embed\\/([\\w-]+)(?:\\?start=(\\d+))?/,\n\t\t\t\t\t\t/^youtu\\.be\\/([\\w-]+)(?:\\?t=(\\d+))?/\n\t\t\t\t\t],\n\t\t\t\t\thtml: match => {\n\t\t\t\t\t\tconst id = match[ 1 ];\n\t\t\t\t\t\tconst time = match[ 2 ];\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t'
' +\n\t\t\t\t\t\t\t\t`' +\n\t\t\t\t\t\t\t'
'\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\tname: 'vimeo',\n\t\t\t\t\turl: [\n\t\t\t\t\t\t/^vimeo\\.com\\/(\\d+)/,\n\t\t\t\t\t\t/^vimeo\\.com\\/[^/]+\\/[^/]+\\/video\\/(\\d+)/,\n\t\t\t\t\t\t/^vimeo\\.com\\/album\\/[^/]+\\/video\\/(\\d+)/,\n\t\t\t\t\t\t/^vimeo\\.com\\/channels\\/[^/]+\\/(\\d+)/,\n\t\t\t\t\t\t/^vimeo\\.com\\/groups\\/[^/]+\\/videos\\/(\\d+)/,\n\t\t\t\t\t\t/^vimeo\\.com\\/ondemand\\/[^/]+\\/(\\d+)/,\n\t\t\t\t\t\t/^player\\.vimeo\\.com\\/video\\/(\\d+)/\n\t\t\t\t\t],\n\t\t\t\t\thtml: match => {\n\t\t\t\t\t\tconst id = match[ 1 ];\n\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t'
' +\n\t\t\t\t\t\t\t\t`' +\n\t\t\t\t\t\t\t'
'\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\t{\n\t\t\t\t\tname: 'instagram',\n\t\t\t\t\turl: [\n\t\t\t\t\t\t/^instagram\\.com\\/p\\/(\\w+)/,\n\t\t\t\t\t\t/^instagram\\.com\\/reel\\/(\\w+)/\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'twitter',\n\t\t\t\t\turl: [\n\t\t\t\t\t\t/^twitter\\.com/,\n\t\t\t\t\t\t/^x\\.com/\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'googleMaps',\n\t\t\t\t\turl: [\n\t\t\t\t\t\t/^google\\.com\\/maps/,\n\t\t\t\t\t\t/^goo\\.gl\\/maps/,\n\t\t\t\t\t\t/^maps\\.google\\.com/,\n\t\t\t\t\t\t/^maps\\.app\\.goo\\.gl/\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'flickr',\n\t\t\t\t\turl: /^flickr\\.com/\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: 'facebook',\n\t\t\t\t\turl: /^facebook\\.com/\n\t\t\t\t}\n\t\t\t]\n\t\t} as MediaEmbedConfig );\n\n\t\tthis.registry = new MediaRegistry( editor.locale, editor.config.get( 'mediaEmbed' )! );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic init(): void {\n\t\tconst editor = this.editor;\n\t\tconst schema = editor.model.schema;\n\t\tconst t = editor.t;\n\t\tconst conversion = editor.conversion;\n\t\tconst renderMediaPreview = editor.config.get( 'mediaEmbed.previewsInData' );\n\t\tconst elementName = editor.config.get( 'mediaEmbed.elementName' )!;\n\n\t\tconst registry = this.registry;\n\n\t\teditor.commands.add( 'mediaEmbed', new MediaEmbedCommand( editor ) );\n\n\t\t// Configure the schema.\n\t\tschema.register( 'media', {\n\t\t\tinheritAllFrom: '$blockObject',\n\t\t\tallowAttributes: [ 'url' ]\n\t\t} );\n\n\t\t// Model -> Data\n\t\tconversion.for( 'dataDowncast' ).elementToStructure( {\n\t\t\tmodel: 'media',\n\t\t\tview: ( modelElement, { writer } ) => {\n\t\t\t\tconst url = modelElement.getAttribute( 'url' ) as string;\n\n\t\t\t\treturn createMediaFigureElement( writer, registry, url, {\n\t\t\t\t\telementName,\n\t\t\t\t\trenderMediaPreview: !!url && renderMediaPreview\n\t\t\t\t} );\n\t\t\t}\n\t\t} );\n\n\t\t// Model -> Data (url -> data-oembed-url)\n\t\tconversion.for( 'dataDowncast' ).add(\n\t\t\tmodelToViewUrlAttributeConverter( registry, {\n\t\t\t\telementName,\n\t\t\t\trenderMediaPreview\n\t\t\t} ) );\n\n\t\t// Model -> View (element)\n\t\tconversion.for( 'editingDowncast' ).elementToStructure( {\n\t\t\tmodel: 'media',\n\t\t\tview: ( modelElement, { writer } ) => {\n\t\t\t\tconst url = modelElement.getAttribute( 'url' ) as string;\n\t\t\t\tconst figure = createMediaFigureElement( writer, registry, url, {\n\t\t\t\t\telementName,\n\t\t\t\t\trenderForEditingView: true\n\t\t\t\t} );\n\n\t\t\t\treturn toMediaWidget( figure, writer, t( 'media widget' ) );\n\t\t\t}\n\t\t} );\n\n\t\t// Model -> View (url -> data-oembed-url)\n\t\tconversion.for( 'editingDowncast' ).add(\n\t\t\tmodelToViewUrlAttributeConverter( registry, {\n\t\t\t\telementName,\n\t\t\t\trenderForEditingView: true\n\t\t\t} ) );\n\n\t\t// View -> Model (data-oembed-url -> url)\n\t\tconversion.for( 'upcast' )\n\t\t\t// Upcast semantic media.\n\t\t\t.elementToElement( {\n\t\t\t\tview: element => [ 'oembed', elementName ].includes( element.name ) && element.getAttribute( 'url' ) ?\n\t\t\t\t\t{ name: true } :\n\t\t\t\t\tnull,\n\t\t\t\tmodel: ( viewMedia, { writer } ) => {\n\t\t\t\t\tconst url = viewMedia.getAttribute( 'url' ) as string;\n\n\t\t\t\t\tif ( registry.hasMedia( url ) ) {\n\t\t\t\t\t\treturn writer.createElement( 'media', { url } );\n\t\t\t\t\t}\n\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t} )\n\t\t\t// Upcast non-semantic media.\n\t\t\t.elementToElement( {\n\t\t\t\tview: {\n\t\t\t\t\tname: 'div',\n\t\t\t\t\tattributes: {\n\t\t\t\t\t\t'data-oembed-url': true\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tmodel: ( viewMedia, { writer } ) => {\n\t\t\t\t\tconst url = viewMedia.getAttribute( 'data-oembed-url' ) as string;\n\n\t\t\t\t\tif ( registry.hasMedia( url ) ) {\n\t\t\t\t\t\treturn writer.createElement( 'media', { url } );\n\t\t\t\t\t}\n\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t} )\n\t\t\t// Consume `
` elements, that were left after upcast.\n\t\t\t.add( dispatcher => {\n\t\t\t\tconst converter: GetCallback = ( evt, data, conversionApi ) => {\n\t\t\t\t\tif ( !conversionApi.consumable.consume( data.viewItem, { name: true, classes: 'media' } ) ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst { modelRange, modelCursor } = conversionApi.convertChildren( data.viewItem, data.modelCursor );\n\n\t\t\t\t\tdata.modelRange = modelRange;\n\t\t\t\t\tdata.modelCursor = modelCursor;\n\n\t\t\t\t\tconst modelElement = first( modelRange!.getItems() );\n\n\t\t\t\t\tif ( !modelElement ) {\n\t\t\t\t\t\t// Revert consumed figure so other features can convert it.\n\t\t\t\t\t\tconversionApi.consumable.revert( data.viewItem, { name: true, classes: 'media' } );\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\tdispatcher.on( 'element:figure', converter );\n\t\t\t} );\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module media-embed/automediaembed\n */\n\nimport { type Editor, Plugin } from 'ckeditor5/src/core.js';\nimport { ModelLiveRange, ModelLivePosition } from 'ckeditor5/src/engine.js';\nimport { Clipboard, type ClipboardPipeline } from 'ckeditor5/src/clipboard.js';\nimport { Delete } from 'ckeditor5/src/typing.js';\nimport { Undo, type UndoCommand } from 'ckeditor5/src/undo.js';\nimport { global } from 'ckeditor5/src/utils.js';\n\nimport { MediaEmbedEditing } from './mediaembedediting.js';\nimport { insertMedia } from './utils.js';\nimport { type MediaEmbedCommand } from './mediaembedcommand.js';\n\nconst URL_REGEXP = /^(?:http(s)?:\\/\\/)?[\\w-]+\\.[\\w-.~:/?#[\\]@!$&'()*+,;=%]+$/;\n\n/**\n * The auto-media embed plugin. It recognizes media links in the pasted content and embeds\n * them shortly after they are injected into the document.\n */\nexport class AutoMediaEmbed extends Plugin {\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get requires() {\n\t\treturn [ Clipboard, Delete, Undo ] as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get pluginName() {\n\t\treturn 'AutoMediaEmbed' as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static override get isOfficialPlugin(): true {\n\t\treturn true;\n\t}\n\n\t/**\n\t * The paste–to–embed `setTimeout` ID. Stored as a property to allow\n\t * cleaning of the timeout.\n\t */\n\tprivate _timeoutId: number | null;\n\n\t/**\n\t * The position where the `` element will be inserted after the timeout,\n\t * determined each time the new content is pasted into the document.\n\t */\n\tprivate _positionToInsert: ModelLivePosition | null;\n\n\t/**\n\t * @inheritDoc\n\t */\n\tconstructor( editor: Editor ) {\n\t\tsuper( editor );\n\n\t\tthis._timeoutId = null;\n\t\tthis._positionToInsert = null;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic init(): void {\n\t\tconst editor = this.editor;\n\t\tconst modelDocument = editor.model.document;\n\n\t\t// We need to listen on `Clipboard#inputTransformation` because we need to save positions of selection.\n\t\t// After pasting, the content between those positions will be checked for a URL that could be transformed\n\t\t// into media.\n\t\tconst clipboardPipeline: ClipboardPipeline = editor.plugins.get( 'ClipboardPipeline' );\n\t\tthis.listenTo( clipboardPipeline, 'inputTransformation', () => {\n\t\t\tconst firstRange = modelDocument.selection.getFirstRange()!;\n\n\t\t\tconst leftLivePosition = ModelLivePosition.fromPosition( firstRange.start );\n\t\t\tleftLivePosition.stickiness = 'toPrevious';\n\n\t\t\tconst rightLivePosition = ModelLivePosition.fromPosition( firstRange.end );\n\t\t\trightLivePosition.stickiness = 'toNext';\n\n\t\t\tmodelDocument.once( 'change:data', () => {\n\t\t\t\tthis._embedMediaBetweenPositions( leftLivePosition, rightLivePosition );\n\n\t\t\t\tleftLivePosition.detach();\n\t\t\t\trightLivePosition.detach();\n\t\t\t}, { priority: 'high' } );\n\t\t} );\n\n\t\tconst undoCommand: UndoCommand = editor.commands.get( 'undo' )!;\n\t\tundoCommand.on( 'execute', () => {\n\t\t\tif ( this._timeoutId ) {\n\t\t\t\tglobal.window.clearTimeout( this._timeoutId );\n\t\t\t\tthis._positionToInsert!.detach();\n\n\t\t\t\tthis._timeoutId = null;\n\t\t\t\tthis._positionToInsert = null;\n\t\t\t}\n\t\t}, { priority: 'high' } );\n\t}\n\n\t/**\n\t * Analyzes the part of the document between provided positions in search for a URL representing media.\n\t * When the URL is found, it is automatically converted into media.\n\t *\n\t * @param leftPosition Left position of the selection.\n\t * @param rightPosition Right position of the selection.\n\t */\n\tprivate _embedMediaBetweenPositions( leftPosition: ModelLivePosition, rightPosition: ModelLivePosition ): void {\n\t\tconst editor = this.editor;\n\t\tconst mediaRegistry = editor.plugins.get( MediaEmbedEditing ).registry;\n\t\t// TODO: Use marker instead of ModelLiveRange & LivePositions.\n\t\tconst urlRange = new ModelLiveRange( leftPosition, rightPosition );\n\t\tconst walker = urlRange.getWalker( { ignoreElementEnd: true } );\n\n\t\tlet url = '';\n\n\t\tfor ( const node of walker ) {\n\t\t\tif ( node.item.is( '$textProxy' ) ) {\n\t\t\t\turl += node.item.data;\n\t\t\t}\n\t\t}\n\n\t\turl = url.trim();\n\n\t\t// If the URL does not match to universal URL regexp, let's skip that.\n\t\tif ( !url.match( URL_REGEXP ) ) {\n\t\t\turlRange.detach();\n\n\t\t\treturn;\n\t\t}\n\n\t\t// If the URL represents a media, let's use it.\n\t\tif ( !mediaRegistry.hasMedia( url ) ) {\n\t\t\turlRange.detach();\n\n\t\t\treturn;\n\t\t}\n\n\t\tconst mediaEmbedCommand: MediaEmbedCommand = editor.commands.get( 'mediaEmbed' )!;\n\n\t\t// Do not anything if media element cannot be inserted at the current position (#47).\n\t\tif ( !mediaEmbedCommand.isEnabled ) {\n\t\t\turlRange.detach();\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Position won't be available in the `setTimeout` function so let's clone it.\n\t\tthis._positionToInsert = ModelLivePosition.fromPosition( leftPosition );\n\n\t\t// This action mustn't be executed if undo was called between pasting and auto-embedding.\n\t\tthis._timeoutId = global.window.setTimeout( () => {\n\t\t\teditor.model.change( writer => {\n\t\t\t\tthis._timeoutId = null;\n\n\t\t\t\twriter.remove( urlRange );\n\t\t\t\turlRange.detach();\n\n\t\t\t\tlet insertionPosition: ModelLivePosition | null = null;\n\n\t\t\t\t// Check if position where the media element should be inserted is still valid.\n\t\t\t\t// Otherwise leave it as undefined to use document.selection - default behavior of model.insertContent().\n\t\t\t\tif ( this._positionToInsert!.root.rootName !== '$graveyard' ) {\n\t\t\t\t\tinsertionPosition = this._positionToInsert;\n\t\t\t\t}\n\n\t\t\t\tinsertMedia( editor.model, url, insertionPosition, false );\n\n\t\t\t\tthis._positionToInsert!.detach();\n\t\t\t\tthis._positionToInsert = null;\n\t\t\t} );\n\n\t\t\teditor.plugins.get( Delete ).requestUndoOnBackspace();\n\t\t}, 100 );\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module media-embed/ui/mediaformview\n */\n\nimport {\n\ttype InputTextView,\n\tLabeledFieldView,\n\tView,\n\tcreateLabeledInputText,\n\tsubmitHandler\n} from 'ckeditor5/src/ui.js';\nimport { FocusTracker, KeystrokeHandler, type Locale } from 'ckeditor5/src/utils.js';\n\n// See: #8833.\n// eslint-disable-next-line ckeditor5-rules/ckeditor-imports\nimport '@ckeditor/ckeditor5-ui/theme/components/responsive-form/responsiveform.css';\nimport '../../theme/mediaform.css';\n\n/**\n * The media form view controller class.\n *\n * See {@link module:media-embed/ui/mediaformview~MediaFormView}.\n */\nexport class MediaFormView extends View {\n\t/**\n\t * Tracks information about the DOM focus in the form.\n\t */\n\tpublic readonly focusTracker: FocusTracker;\n\n\t/**\n\t * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.\n\t */\n\tpublic readonly keystrokes: KeystrokeHandler;\n\n\t/**\n\t * The value of the URL input.\n\t */\n\tdeclare public mediaURLInputValue: string;\n\n\t/**\n\t * The URL input view.\n\t */\n\tpublic urlInputView: LabeledFieldView;\n\n\t/**\n\t * An array of form validators used by {@link #isValid}.\n\t */\n\tprivate readonly _validators: Array<( v: MediaFormView ) => string | undefined>;\n\n\t/**\n\t * The default info text for the {@link #urlInputView}.\n\t */\n\tprivate _urlInputViewInfoDefault?: string;\n\n\t/**\n\t * The info text with an additional tip for the {@link #urlInputView},\n\t * displayed when the input has some value.\n\t */\n\tprivate _urlInputViewInfoTip?: string;\n\n\t/**\n\t * @param validators Form validators used by {@link #isValid}.\n\t * @param locale The localization services instance.\n\t */\n\tconstructor( validators: Array<( v: MediaFormView ) => string | undefined>, locale: Locale ) {\n\t\tsuper( locale );\n\n\t\tthis.focusTracker = new FocusTracker();\n\t\tthis.keystrokes = new KeystrokeHandler();\n\t\tthis.set( 'mediaURLInputValue', '' );\n\t\tthis.urlInputView = this._createUrlInput();\n\n\t\tthis._validators = validators;\n\n\t\tthis.setTemplate( {\n\t\t\ttag: 'form',\n\n\t\t\tattributes: {\n\t\t\t\tclass: [\n\t\t\t\t\t'ck',\n\t\t\t\t\t'ck-media-form',\n\t\t\t\t\t'ck-responsive-form'\n\t\t\t\t],\n\n\t\t\t\ttabindex: '-1'\n\t\t\t},\n\n\t\t\tchildren: [\n\t\t\t\tthis.urlInputView\n\t\t\t]\n\t\t} );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic override render(): void {\n\t\tsuper.render();\n\n\t\tsubmitHandler( {\n\t\t\tview: this\n\t\t} );\n\n\t\t// Register the view in the focus tracker.\n\t\tthis.focusTracker.add( this.urlInputView.element! );\n\n\t\t// Start listening for the keystrokes coming from #element.\n\t\tthis.keystrokes.listenTo( this.element! );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic override destroy(): void {\n\t\tsuper.destroy();\n\n\t\tthis.focusTracker.destroy();\n\t\tthis.keystrokes.destroy();\n\t}\n\n\t/**\n\t * Focuses the {@link #urlInputView}.\n\t */\n\tpublic focus(): void {\n\t\tthis.urlInputView.focus();\n\t}\n\n\t/**\n\t * The native DOM `value` of the {@link #urlInputView} element.\n\t *\n\t * **Note**: Do not confuse it with the {@link module:ui/inputtext/inputtextview~InputTextView#value}\n\t * which works one way only and may not represent the actual state of the component in the DOM.\n\t */\n\tpublic get url(): string {\n\t\treturn this.urlInputView.fieldView.element!.value.trim();\n\t}\n\n\tpublic set url( url: string ) {\n\t\tthis.urlInputView.fieldView.value = url.trim();\n\t}\n\n\t/**\n\t * Validates the form and returns `false` when some fields are invalid.\n\t */\n\tpublic isValid(): boolean {\n\t\tthis.resetFormStatus();\n\n\t\tfor ( const validator of this._validators ) {\n\t\t\tconst errorText = validator( this );\n\n\t\t\t// One error per field is enough.\n\t\t\tif ( errorText ) {\n\t\t\t\t// Apply updated error.\n\t\t\t\tthis.urlInputView.errorText = errorText;\n\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Cleans up the supplementary error and information text of the {@link #urlInputView}\n\t * bringing them back to the state when the form has been displayed for the first time.\n\t *\n\t * See {@link #isValid}.\n\t */\n\tpublic resetFormStatus(): void {\n\t\tthis.urlInputView.errorText = null;\n\t\tthis.urlInputView.infoText = this._urlInputViewInfoDefault!;\n\t}\n\n\t/**\n\t * Creates a labeled input view.\n\t *\n\t * @returns Labeled input view instance.\n\t */\n\tprivate _createUrlInput(): LabeledFieldView {\n\t\tconst t = this.locale!.t;\n\n\t\tconst labeledInput = new LabeledFieldView( this.locale, createLabeledInputText );\n\t\tconst inputField = labeledInput.fieldView;\n\n\t\tthis._urlInputViewInfoDefault = t( 'Paste the media URL in the input.' );\n\t\tthis._urlInputViewInfoTip = t( 'Tip: Paste the URL into the content to embed faster.' );\n\n\t\tlabeledInput.label = t( 'Media URL' );\n\t\tlabeledInput.infoText = this._urlInputViewInfoDefault;\n\n\t\tinputField.inputMode = 'url';\n\t\tinputField.on( 'input', () => {\n\t\t\t// Display the tip text only when there is some value. Otherwise fall back to the default info text.\n\t\t\tlabeledInput.infoText = inputField.element!.value ? this._urlInputViewInfoTip! : this._urlInputViewInfoDefault!;\n\t\t\tthis.mediaURLInputValue = inputField.element!.value.trim();\n\t\t} );\n\n\t\treturn labeledInput;\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module media-embed/mediaembedui\n */\n\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport { IconMedia } from 'ckeditor5/src/icons.js';\nimport { ButtonView, CssTransitionDisablerMixin, MenuBarMenuListItemButtonView, Dialog } from 'ckeditor5/src/ui.js';\n\nimport { MediaFormView } from './ui/mediaformview.js';\nimport { MediaEmbedEditing } from './mediaembedediting.js';\nimport type { LocaleTranslate } from 'ckeditor5/src/utils.js';\nimport { type MediaRegistry } from './mediaregistry.js';\n\n/**\n * The media embed UI plugin.\n */\nexport class MediaEmbedUI extends Plugin {\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get requires() {\n\t\treturn [ MediaEmbedEditing, Dialog ] as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get pluginName() {\n\t\treturn 'MediaEmbedUI' as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static override get isOfficialPlugin(): true {\n\t\treturn true;\n\t}\n\n\tprivate _formView: MediaFormView | undefined;\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic init(): void {\n\t\tconst editor = this.editor;\n\n\t\teditor.ui.componentFactory.add( 'mediaEmbed', () => {\n\t\t\tconst t = this.editor.locale.t;\n\t\t\tconst button = this._createDialogButton( ButtonView );\n\n\t\t\tbutton.tooltip = true;\n\t\t\tbutton.label = t( 'Insert media' );\n\n\t\t\treturn button;\n\t\t} );\n\n\t\teditor.ui.componentFactory.add( 'menuBar:mediaEmbed', () => {\n\t\t\tconst t = this.editor.locale.t;\n\t\t\tconst button = this._createDialogButton( MenuBarMenuListItemButtonView );\n\n\t\t\tbutton.label = t( 'Media' );\n\n\t\t\treturn button;\n\t\t} );\n\t}\n\n\t/**\n\t * Creates a button for menu bar that will show media embed dialog.\n\t */\n\tprivate _createDialogButton( ButtonClass: T ): InstanceType {\n\t\tconst editor = this.editor;\n\t\tconst buttonView = new ButtonClass( editor.locale ) as InstanceType;\n\t\tconst command = editor.commands.get( 'mediaEmbed' )!;\n\t\tconst dialogPlugin = this.editor.plugins.get( 'Dialog' );\n\n\t\tbuttonView.icon = IconMedia;\n\n\t\tbuttonView.bind( 'isEnabled' ).to( command, 'isEnabled' );\n\n\t\tbuttonView.on( 'execute', () => {\n\t\t\tif ( dialogPlugin.id === 'mediaEmbed' ) {\n\t\t\t\tdialogPlugin.hide();\n\t\t\t} else {\n\t\t\t\tthis._showDialog();\n\t\t\t}\n\t\t} );\n\n\t\treturn buttonView;\n\t}\n\n\tprivate _showDialog() {\n\t\tconst editor = this.editor;\n\t\tconst dialog = editor.plugins.get( 'Dialog' );\n\t\tconst command = editor.commands.get( 'mediaEmbed' )!;\n\t\tconst t = editor.locale.t;\n\n\t\tconst isMediaSelected = command.value !== undefined;\n\n\t\tif ( !this._formView ) {\n\t\t\tconst registry = editor.plugins.get( MediaEmbedEditing ).registry;\n\n\t\t\tthis._formView = new ( CssTransitionDisablerMixin( MediaFormView ) )( getFormValidators( editor.t, registry ), editor.locale );\n\t\t\tthis._formView.on( 'submit', () => this._handleSubmitForm() );\n\t\t}\n\n\t\tdialog.show( {\n\t\t\tid: 'mediaEmbed',\n\t\t\ttitle: t( 'Media embed' ),\n\t\t\tcontent: this._formView,\n\t\t\tisModal: true,\n\t\t\tonShow: () => {\n\t\t\t\tthis._formView!.url = command.value || '';\n\t\t\t\tthis._formView!.resetFormStatus();\n\t\t\t\tthis._formView!.urlInputView.fieldView.select();\n\t\t\t},\n\t\t\tactionButtons: [\n\t\t\t\t{\n\t\t\t\t\tlabel: t( 'Cancel' ),\n\t\t\t\t\twithText: true,\n\t\t\t\t\tonExecute: () => dialog.hide()\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tlabel: isMediaSelected ? t( 'Save' ) : t( 'Insert' ),\n\t\t\t\t\tclass: 'ck-button-action',\n\t\t\t\t\twithText: true,\n\t\t\t\t\tonExecute: () => this._handleSubmitForm()\n\t\t\t\t}\n\t\t\t]\n\t\t} );\n\t}\n\n\tprivate _handleSubmitForm() {\n\t\tconst editor = this.editor;\n\t\tconst dialog = editor.plugins.get( 'Dialog' );\n\n\t\tif ( this._formView!.isValid() ) {\n\t\t\teditor.execute( 'mediaEmbed', this._formView!.url );\n\t\t\tdialog.hide();\n\t\t\teditor.editing.view.focus();\n\t\t}\n\t}\n}\n\nfunction getFormValidators( t: LocaleTranslate, registry: MediaRegistry ): Array<( v: MediaFormView ) => string | undefined> {\n\treturn [\n\t\tform => {\n\t\t\tif ( !form.url.length ) {\n\t\t\t\treturn t( 'The URL must not be empty.' );\n\t\t\t}\n\t\t},\n\t\tform => {\n\t\t\tif ( !registry.hasMedia( form.url ) ) {\n\t\t\t\treturn t( 'This media URL is not supported.' );\n\t\t\t}\n\t\t}\n\t];\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module media-embed/mediaembed\n */\n\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport { Widget } from 'ckeditor5/src/widget.js';\n\nimport { MediaEmbedEditing } from './mediaembedediting.js';\nimport { AutoMediaEmbed } from './automediaembed.js';\nimport { MediaEmbedUI } from './mediaembedui.js';\n\nimport '../theme/mediaembed.css';\n\n/**\n * The media embed plugin.\n *\n * For a detailed overview, check the {@glink features/media-embed Media Embed feature documentation}.\n *\n * This is a \"glue\" plugin which loads the following plugins:\n *\n * * The {@link module:media-embed/mediaembedediting~MediaEmbedEditing media embed editing feature},\n * * The {@link module:media-embed/mediaembedui~MediaEmbedUI media embed UI feature} and\n * * The {@link module:media-embed/automediaembed~AutoMediaEmbed auto-media embed feature}.\n */\nexport class MediaEmbed extends Plugin {\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get requires() {\n\t\treturn [ MediaEmbedEditing, MediaEmbedUI, AutoMediaEmbed, Widget ] as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get pluginName() {\n\t\treturn 'MediaEmbed' as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static override get isOfficialPlugin(): true {\n\t\treturn true;\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module media-embed/mediaembedtoolbar\n */\n\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport { WidgetToolbarRepository } from 'ckeditor5/src/widget.js';\n\nimport { getSelectedMediaViewWidget } from './utils.js';\n\nimport './mediaembedconfig.js';\n\n/**\n * The media embed toolbar plugin. It creates a toolbar for media embed that shows up when the media element is selected.\n *\n * Instances of toolbar components (e.g. buttons) are created based on the\n * {@link module:media-embed/mediaembedconfig~MediaEmbedConfig#toolbar `media.toolbar` configuration option}.\n */\nexport class MediaEmbedToolbar extends Plugin {\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get requires() {\n\t\treturn [ WidgetToolbarRepository ] as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get pluginName() {\n\t\treturn 'MediaEmbedToolbar' as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static override get isOfficialPlugin(): true {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic afterInit(): void {\n\t\tconst editor = this.editor;\n\t\tconst t = editor.t;\n\t\tconst widgetToolbarRepository = editor.plugins.get( WidgetToolbarRepository );\n\t\twidgetToolbarRepository.register( 'mediaEmbed', {\n\t\t\tariaLabel: t( 'Media toolbar' ),\n\t\t\titems: editor.config.get( 'mediaEmbed.toolbar' ) || [],\n\t\t\tgetRelatedElement: getSelectedMediaViewWidget\n\t\t} );\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module mention/mentioncommand\n */\n\nimport { Command, type Editor } from 'ckeditor5/src/core.js';\nimport type { ModelRange } from 'ckeditor5/src/engine.js';\nimport { CKEditorError, toMap } from 'ckeditor5/src/utils.js';\n\nimport { _addMentionAttributes } from './mentionediting.js';\n\nconst BRACKET_PAIRS = {\n\t'(': ')',\n\t'[': ']',\n\t'{': '}'\n} as const;\n\n/**\n * The mention command.\n *\n * The command is registered by {@link module:mention/mentionediting~MentionEditing} as `'mention'`.\n *\n * To insert a mention into a range, execute the command and specify a mention object with a range to replace:\n *\n * ```ts\n * const focus = editor.model.document.selection.focus;\n *\n * // It will replace one character before the selection focus with the '#1234' text\n * // with the mention attribute filled with passed attributes.\n * editor.execute( 'mention', {\n * \tmarker: '#',\n * \tmention: {\n * \t\tid: '#1234',\n * \t\tname: 'Foo',\n * \t\ttitle: 'Big Foo'\n * \t},\n * \trange: editor.model.createRange( focus.getShiftedBy( -1 ), focus )\n * } );\n *\n * // It will replace one character before the selection focus with the 'The \"Big Foo\"' text\n * // with the mention attribute filled with passed attributes.\n * editor.execute( 'mention', {\n * \tmarker: '#',\n * \tmention: {\n * \t\tid: '#1234',\n * \t\tname: 'Foo',\n * \t\ttitle: 'Big Foo'\n * \t},\n * \ttext: 'The \"Big Foo\"',\n * \trange: editor.model.createRange( focus.getShiftedBy( -1 ), focus )\n * } );\n *\t```\n */\nexport class MentionCommand extends Command {\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic constructor( editor: Editor ) {\n\t\tsuper( editor );\n\n\t\t// Since this command may pass range in execution parameters, it should be checked directly in execute block.\n\t\tthis._isEnabledBasedOnSelection = false;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic override refresh(): void {\n\t\tconst model = this.editor.model;\n\t\tconst doc = model.document;\n\n\t\tthis.isEnabled = model.schema.checkAttributeInSelection( doc.selection, 'mention' );\n\t}\n\n\t/**\n\t * Executes the command.\n\t *\n\t * @param options Options for the executed command.\n\t * @param options.mention The mention object to insert. When a string is passed, it will be used to create a plain\n\t * object with the name attribute that equals the passed string.\n\t * @param options.marker The marker character (e.g. `'@'`).\n\t * @param options.text The text of the inserted mention. Defaults to the full mention string composed from `marker` and\n\t * `mention` string or `mention.id` if an object is passed.\n\t * @param options.range The range to replace.\n\t * Note that the replaced range might be shorter than the inserted text with the mention attribute.\n\t * @fires execute\n\t */\n\tpublic override execute( options: {\n\t\tmention: string | { id: string; [ key: string ]: unknown };\n\t\tmarker: string;\n\t\ttext?: string;\n\t\trange?: ModelRange;\n\t} ): void {\n\t\tconst model = this.editor.model;\n\t\tconst document = model.document;\n\t\tconst selection = document.selection;\n\n\t\tconst mentionData = typeof options.mention == 'string' ? { id: options.mention } : options.mention;\n\t\tconst mentionID = mentionData.id;\n\n\t\tconst range = options.range || selection.getFirstRange();\n\n\t\t// Don't execute command if range is in non-editable place.\n\t\tif ( !model.canEditAt( range ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst mentionText = options.text || mentionID;\n\n\t\tconst mention = _addMentionAttributes( { _text: mentionText, id: mentionID }, mentionData );\n\n\t\tif ( !mentionID.startsWith( options.marker ) ) {\n\t\t\t/**\n\t\t\t * The feed item ID must start with the marker character(s).\n\t\t\t *\n\t\t\t * Correct mention feed setting:\n\t\t\t *\n\t\t\t * ```ts\n\t\t\t * mentions: [\n\t\t\t * \t{\n\t\t\t * \t\tmarker: '@',\n\t\t\t * \t\tfeed: [ '@Ann', '@Barney', ... ]\n\t\t\t * \t}\n\t\t\t * ]\n\t\t\t * ```\n\t\t\t *\n\t\t\t * Incorrect mention feed setting:\n\t\t\t *\n\t\t\t * ```ts\n\t\t\t * mentions: [\n\t\t\t * \t{\n\t\t\t * \t\tmarker: '@',\n\t\t\t * \t\tfeed: [ 'Ann', 'Barney', ... ]\n\t\t\t * \t}\n\t\t\t * ]\n\t\t\t * ```\n\t\t\t *\n\t\t\t * See {@link module:mention/mentionconfig~MentionConfig}.\n\t\t\t *\n\t\t\t * @error mentioncommand-incorrect-id\n\t\t\t */\n\t\t\tthrow new CKEditorError(\n\t\t\t\t'mentioncommand-incorrect-id',\n\t\t\t\tthis\n\t\t\t);\n\t\t}\n\n\t\tmodel.change( writer => {\n\t\t\tconst currentAttributes = toMap( selection.getAttributes() );\n\t\t\tconst attributesWithMention = new Map( currentAttributes.entries() );\n\n\t\t\tattributesWithMention.set( 'mention', mention );\n\n\t\t\t// Replace a range with the text with a mention.\n\t\t\tconst insertionRange = model.insertContent( writer.createText( mentionText, attributesWithMention ), range );\n\t\t\tconst nodeBefore = insertionRange.start.nodeBefore;\n\t\t\tconst nodeAfter = insertionRange.end.nodeAfter;\n\n\t\t\tconst isFollowedByWhiteSpace = nodeAfter && nodeAfter.is( '$text' ) && nodeAfter.data.startsWith( ' ' );\n\t\t\tlet isInsertedInBrackets = false;\n\n\t\t\tif ( nodeBefore && nodeAfter && nodeBefore.is( '$text' ) && nodeAfter.is( '$text' ) ) {\n\t\t\t\tconst precedingCharacter = nodeBefore.data.slice( -1 );\n\t\t\t\tconst isPrecededByOpeningBracket = precedingCharacter in BRACKET_PAIRS;\n\t\t\t\tconst isFollowedByBracketClosure = isPrecededByOpeningBracket && nodeAfter.data.startsWith(\n\t\t\t\t\tBRACKET_PAIRS[ precedingCharacter as keyof typeof BRACKET_PAIRS ]\n\t\t\t\t);\n\n\t\t\t\tisInsertedInBrackets = isPrecededByOpeningBracket && isFollowedByBracketClosure;\n\t\t\t}\n\n\t\t\t// Don't add a white space if either of the following is true:\n\t\t\t// * there's already one after the mention;\n\t\t\t// * the mention was inserted in the empty matching brackets.\n\t\t\t// https://github.com/ckeditor/ckeditor5/issues/4651\n\t\t\tif ( !isInsertedInBrackets && !isFollowedByWhiteSpace ) {\n\t\t\t\tmodel.insertContent( writer.createText( ' ', currentAttributes ), range!.start.getShiftedBy( mentionText.length ) );\n\t\t\t}\n\t\t} );\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module mention/mentionediting\n */\n\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport type {\n\tModelElement,\n\tModelText,\n\tModelWriter,\n\tModelDocument,\n\tViewAttributeElement,\n\tDowncastConversionApi,\n\tDowncastDispatcher,\n\tModelPosition,\n\tModelSchema,\n\tDowncastAttributeEvent,\n\tModelItem\n} from 'ckeditor5/src/engine.js';\nimport { uid } from 'ckeditor5/src/utils.js';\n\nimport { MentionCommand } from './mentioncommand.js';\nimport type { MentionAttribute } from './mention.js';\n\n/**\n * The mention editing feature.\n *\n * It introduces the {@link module:mention/mentioncommand~MentionCommand command} and the `mention`\n * attribute in the {@link module:engine/model/model~Model model} which renders in the {@link module:engine/view/view view}\n * as a ``.\n */\nexport class MentionEditing extends Plugin {\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get pluginName() {\n\t\treturn 'MentionEditing' as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static override get isOfficialPlugin(): true {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic init(): void {\n\t\tconst editor = this.editor;\n\t\tconst model = editor.model;\n\t\tconst doc = model.document;\n\n\t\t// Allow the mention attribute on all text nodes.\n\t\tmodel.schema.extend( '$text', { allowAttributes: 'mention' } );\n\n\t\t// Upcast conversion.\n\t\teditor.conversion.for( 'upcast' ).elementToAttribute( {\n\t\t\tview: {\n\t\t\t\tname: 'span',\n\t\t\t\tattributes: 'data-mention',\n\t\t\t\tclasses: 'mention'\n\t\t\t},\n\t\t\tmodel: {\n\t\t\t\tkey: 'mention',\n\t\t\t\tvalue: ( viewElement: ModelElement ) => _toMentionAttribute( viewElement )\n\t\t\t}\n\t\t} );\n\n\t\t// Downcast conversion.\n\t\teditor.conversion.for( 'downcast' ).attributeToElement( {\n\t\t\tmodel: 'mention',\n\t\t\tview: createViewMentionElement\n\t\t} );\n\t\teditor.conversion.for( 'downcast' ).add( preventPartialMentionDowncast );\n\n\t\tdoc.registerPostFixer( writer => removePartialMentionPostFixer( writer, doc, model.schema ) );\n\t\tdoc.registerPostFixer( writer => extendAttributeOnMentionPostFixer( writer, doc ) );\n\t\tdoc.registerPostFixer( writer => selectionMentionAttributePostFixer( writer, doc ) );\n\n\t\teditor.commands.add( 'mention', new MentionCommand( editor ) );\n\t}\n}\n\n/**\n * @internal\n */\nexport function _addMentionAttributes(\n\tbaseMentionData: { id: string; _text: string },\n\tdata?: Record\n): MentionAttribute {\n\treturn Object.assign( { uid: uid() }, baseMentionData, data || {} );\n}\n\n/**\n * Creates a mention attribute value from the provided view element and optional data.\n *\n * This function is exposed as\n * {@link module:mention/mention~Mention#toMentionAttribute `editor.plugins.get( 'Mention' ).toMentionAttribute()`}.\n *\n * @internal\n */\nexport function _toMentionAttribute(\n\tviewElementOrMention: ModelElement,\n\tdata?: Record\n): MentionAttribute | undefined {\n\tconst dataMention = viewElementOrMention.getAttribute( 'data-mention' ) as string;\n\n\tconst textNode = viewElementOrMention.getChild( 0 ) as ModelText;\n\n\t// Do not convert empty mentions.\n\tif ( !textNode ) {\n\t\treturn;\n\t}\n\n\tconst baseMentionData = {\n\t\tid: dataMention,\n\t\t_text: textNode.data\n\t};\n\n\treturn _addMentionAttributes( baseMentionData, data );\n}\n\n/**\n * A converter that blocks partial mention from being converted.\n *\n * This converter is registered with 'highest' priority in order to consume mention attribute before it is converted by\n * any other converters. This converter only consumes partial mention - those whose `_text` attribute is not equal to text with mention\n * attribute. This may happen when copying part of mention text.\n */\nfunction preventPartialMentionDowncast( dispatcher: DowncastDispatcher ) {\n\tdispatcher.on( 'attribute:mention', ( evt, data, conversionApi ) => {\n\t\tconst mention = data.attributeNewValue as MentionAttribute;\n\n\t\tif ( !data.item.is( '$textProxy' ) || !mention ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst start = data.range.start;\n\t\tconst textNode = start.textNode || start.nodeAfter as ModelText;\n\n\t\tif ( textNode!.data != mention._text ) {\n\t\t\t// Consume item to prevent partial mention conversion.\n\t\t\tconversionApi.consumable.consume( data.item, evt.name );\n\t\t}\n\t}, { priority: 'highest' } );\n}\n\n/**\n * Creates a mention element from the mention data.\n */\nfunction createViewMentionElement( mention: MentionAttribute, { writer }: DowncastConversionApi ): ViewAttributeElement | undefined {\n\tif ( !mention ) {\n\t\treturn;\n\t}\n\n\tconst attributes = {\n\t\tclass: 'mention',\n\t\t'data-mention': mention.id\n\t};\n\n\tconst options = {\n\t\tid: mention.uid,\n\t\tpriority: 20\n\t};\n\n\treturn writer.createAttributeElement( 'span', attributes, options );\n}\n\n/**\n * Model post-fixer that disallows typing with selection when the selection is placed after the text node with the mention attribute or\n * before a text node with mention attribute.\n */\nfunction selectionMentionAttributePostFixer( writer: ModelWriter, doc: ModelDocument ): boolean {\n\tconst selection = doc.selection;\n\tconst focus = selection.focus;\n\n\tif ( selection.isCollapsed && selection.hasAttribute( 'mention' ) && shouldNotTypeWithMentionAt( focus! ) ) {\n\t\twriter.removeSelectionAttribute( 'mention' );\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/**\n * Helper function to detect if mention attribute should be removed from selection.\n * This check makes only sense if the selection has mention attribute.\n *\n * The mention attribute should be removed from a selection when selection focus is placed:\n * a) after a text node\n * b) the position is at parents start - the selection will set attributes from node after.\n */\nfunction shouldNotTypeWithMentionAt( position: ModelPosition ): boolean {\n\tconst isAtStart = position.isAtStart;\n\tconst isAfterAMention = position.nodeBefore && position.nodeBefore.is( '$text' );\n\n\treturn isAfterAMention || isAtStart;\n}\n\n/**\n * Model post-fixer that removes the mention attribute from the modified text node.\n */\nfunction removePartialMentionPostFixer( writer: ModelWriter, doc: ModelDocument, schema: ModelSchema ): boolean {\n\tconst changes = doc.differ.getChanges();\n\n\tlet wasChanged = false;\n\n\tfor ( const change of changes ) {\n\t\tif ( change.type == 'attribute' ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Checks the text node on the current position.\n\t\tconst position = change.position;\n\n\t\tif ( change.name == '$text' ) {\n\t\t\tconst nodeAfterInsertedTextNode = position.textNode && position.textNode.nextSibling;\n\n\t\t\t// Checks the text node where the change occurred.\n\t\t\twasChanged = checkAndFix( position.textNode, writer ) || wasChanged;\n\n\t\t\t// Occurs on paste inside a text node with mention.\n\t\t\twasChanged = checkAndFix( nodeAfterInsertedTextNode, writer ) || wasChanged;\n\t\t\twasChanged = checkAndFix( position.nodeBefore, writer ) || wasChanged;\n\t\t\twasChanged = checkAndFix( position.nodeAfter, writer ) || wasChanged;\n\t\t}\n\n\t\t// Checks text nodes in inserted elements (might occur when splitting a paragraph or pasting content inside text with mention).\n\t\tif ( change.name != '$text' && change.type == 'insert' ) {\n\t\t\tconst insertedNode = position.nodeAfter as ModelElement;\n\n\t\t\tfor ( const item of writer.createRangeIn( insertedNode! ).getItems() ) {\n\t\t\t\twasChanged = checkAndFix( item, writer ) || wasChanged;\n\t\t\t}\n\t\t}\n\n\t\t// Inserted inline elements might break mention.\n\t\tif ( change.type == 'insert' && schema.isInline( change.name ) ) {\n\t\t\tconst nodeAfterInserted = position.nodeAfter && position.nodeAfter.nextSibling;\n\n\t\t\twasChanged = checkAndFix( position.nodeBefore, writer ) || wasChanged;\n\t\t\twasChanged = checkAndFix( nodeAfterInserted, writer ) || wasChanged;\n\t\t}\n\t}\n\n\treturn wasChanged;\n}\n\n/**\n * This post-fixer will extend the attribute applied on the part of the mention so the whole text node of the mention will have\n * the added attribute.\n */\nfunction extendAttributeOnMentionPostFixer( writer: ModelWriter, doc: ModelDocument ): boolean {\n\tconst changes = doc.differ.getChanges();\n\n\tlet wasChanged = false;\n\n\tfor ( const change of changes ) {\n\t\tif ( change.type === 'attribute' && change.attributeKey != 'mention' ) {\n\t\t\t// Checks the node on the left side of the range...\n\t\t\tconst nodeBefore = change.range.start.nodeBefore;\n\t\t\t// ... and on the right side of the range.\n\t\t\tconst nodeAfter = change.range.end.nodeAfter;\n\n\t\t\tfor ( const node of [ nodeBefore, nodeAfter ] ) {\n\t\t\t\tif ( isBrokenMentionNode( node ) && node!.getAttribute( change.attributeKey ) != change.attributeNewValue ) {\n\t\t\t\t\twriter.setAttribute( change.attributeKey, change.attributeNewValue, node! );\n\n\t\t\t\t\twasChanged = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn wasChanged;\n}\n\n/**\n * Checks if a node has a correct mention attribute if present.\n * Returns `true` if the node is text and has a mention attribute whose text does not match the expected mention text.\n */\nfunction isBrokenMentionNode( node: ModelItem | null ): boolean {\n\tif ( !node || !( node.is( '$text' ) || node.is( '$textProxy' ) ) || !node.hasAttribute( 'mention' ) ) {\n\t\treturn false;\n\t}\n\n\tconst text = node.data;\n\tconst mention = node.getAttribute( 'mention' ) as MentionAttribute;\n\n\tconst expectedText = mention._text;\n\n\treturn text != expectedText;\n}\n\n/**\n * Fixes a mention on a text node if it needs a fix.\n */\nfunction checkAndFix( textNode: ModelItem | null, writer: ModelWriter ): boolean {\n\tif ( isBrokenMentionNode( textNode ) ) {\n\t\twriter.removeAttribute( 'mention', textNode! );\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module mention/ui/mentionsview\n */\n\nimport { ListView } from 'ckeditor5/src/ui.js';\nimport { Rect, type Locale } from 'ckeditor5/src/utils.js';\n\nimport { type MentionListItemView } from './mentionlistitemview.js';\n\nimport '../../theme/mentionui.css';\n\n/**\n * The mention ui view.\n */\nexport class MentionsView extends ListView {\n\tpublic selected: MentionListItemView | undefined;\n\n\tpublic position: string | undefined;\n\n\t/**\n\t * @inheritDoc\n\t */\n\tconstructor( locale: Locale ) {\n\t\tsuper( locale );\n\n\t\tthis.extendTemplate( {\n\t\t\tattributes: {\n\t\t\t\tclass: [\n\t\t\t\t\t'ck-mentions'\n\t\t\t\t],\n\n\t\t\t\ttabindex: '-1'\n\t\t\t}\n\t\t} );\n\t}\n\n\t/**\n\t * {@link #select Selects} the first item.\n\t */\n\tpublic selectFirst(): void {\n\t\tthis.select( 0 );\n\t}\n\n\t/**\n\t * Selects next item to the currently {@link #select selected}.\n\t *\n\t * If the last item is already selected, it will select the first item.\n\t */\n\tpublic selectNext(): void {\n\t\tconst item = this.selected;\n\t\tconst index = this.items.getIndex( item! );\n\n\t\tthis.select( index + 1 );\n\t}\n\n\t/**\n\t * Selects previous item to the currently {@link #select selected}.\n\t *\n\t * If the first item is already selected, it will select the last item.\n\t */\n\tpublic selectPrevious(): void {\n\t\tconst item = this.selected;\n\t\tconst index = this.items.getIndex( item! );\n\n\t\tthis.select( index - 1 );\n\t}\n\n\t/**\n\t * Marks item at a given index as selected.\n\t *\n\t * Handles selection cycling when passed index is out of bounds:\n\t * - if the index is lower than 0, it will select the last item,\n\t * - if the index is higher than the last item index, it will select the first item.\n\t *\n\t * @param index Index of an item to be marked as selected.\n\t */\n\tpublic select( index: number ): void {\n\t\tlet indexToGet = 0;\n\n\t\tif ( index > 0 && index < this.items.length ) {\n\t\t\tindexToGet = index;\n\t\t} else if ( index < 0 ) {\n\t\t\tindexToGet = this.items.length - 1;\n\t\t}\n\n\t\tconst item = this.items.get( indexToGet ) as MentionListItemView;\n\n\t\t// Return early if item is already selected.\n\t\tif ( this.selected === item ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remove highlight of previously selected item.\n\t\tif ( this.selected ) {\n\t\t\tthis.selected.removeHighlight();\n\t\t}\n\n\t\titem.highlight();\n\t\tthis.selected = item;\n\n\t\t// Scroll the mentions view to the selected element.\n\t\tif ( !this._isItemVisibleInScrolledArea( item ) ) {\n\t\t\tthis.element!.scrollTop = item.element!.offsetTop;\n\t\t}\n\t}\n\n\t/**\n\t * Triggers the `execute` event on the {@link #select selected} item.\n\t */\n\tpublic executeSelected(): void {\n\t\tthis.selected!.fire( 'execute' );\n\t}\n\n\t/**\n\t * Checks if an item is visible in the scrollable area.\n\t *\n\t * The item is considered visible when:\n\t * - its top boundary is inside the scrollable rect\n\t * - its bottom boundary is inside the scrollable rect (the whole item must be visible)\n\t */\n\tprivate _isItemVisibleInScrolledArea( item: MentionListItemView ) {\n\t\treturn new Rect( this.element! ).contains( new Rect( item.element! ) );\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module mention/ui/domwrapperview\n */\n\nimport { View } from 'ckeditor5/src/ui.js';\nimport type { Locale } from 'ckeditor5/src/utils.js';\n\n/**\n * This class wraps DOM element as a CKEditor5 UI View.\n *\n * It allows to render any DOM element and use it in mentions list.\n */\nexport class MentionDomWrapperView extends View {\n\t/**\n\t * The DOM element for which wrapper was created.\n\t */\n\tpublic domElement: HTMLElement;\n\n\t/**\n\t * Controls whether the dom wrapper view is \"on\". This is in line with {@link module:ui/button/button~Button#isOn} property.\n\t *\n\t * @observable\n\t * @default true\n\t */\n\tdeclare public isOn: boolean;\n\n\t/**\n\t * Creates an instance of {@link module:mention/ui/domwrapperview~MentionDomWrapperView} class.\n\t *\n\t * Also see {@link #render}.\n\t */\n\tconstructor( locale: Locale, domElement: HTMLElement ) {\n\t\tsuper( locale );\n\n\t\t// Disable template rendering on this view.\n\t\tthis.template = undefined;\n\n\t\tthis.domElement = domElement;\n\n\t\t// Render dom wrapper as a button.\n\t\tthis.domElement.classList.add( 'ck-button' );\n\n\t\tthis.set( 'isOn', false );\n\n\t\t// Handle isOn state as in buttons.\n\t\tthis.on( 'change:isOn', ( evt, name, isOn ) => {\n\t\t\tif ( isOn ) {\n\t\t\t\tthis.domElement.classList.add( 'ck-on' );\n\t\t\t\tthis.domElement.classList.remove( 'ck-off' );\n\t\t\t} else {\n\t\t\t\tthis.domElement.classList.add( 'ck-off' );\n\t\t\t\tthis.domElement.classList.remove( 'ck-on' );\n\t\t\t}\n\t\t} );\n\n\t\t// Pass click event as execute event.\n\t\tthis.listenTo( this.domElement, 'click', () => {\n\t\t\tthis.fire( 'execute' );\n\t\t} );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic override render(): void {\n\t\tsuper.render();\n\n\t\tthis.element = this.domElement;\n\t}\n\n\t/**\n\t * Focuses the DOM element.\n\t */\n\tpublic focus(): void {\n\t\tthis.domElement.focus();\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module mention/ui/mentionlistitemview\n */\n\nimport { ListItemView } from 'ckeditor5/src/ui.js';\n\nimport type { MentionFeedItem } from '../mentionconfig.js';\n\nimport { type MentionDomWrapperView } from './domwrapperview.js';\n\nexport class MentionListItemView extends ListItemView {\n\tpublic item!: MentionFeedItem;\n\n\tpublic marker!: string;\n\n\tpublic highlight(): void {\n\t\tconst child = this.children.first as MentionDomWrapperView;\n\n\t\tchild.isOn = true;\n\t}\n\n\tpublic removeHighlight(): void {\n\t\tconst child = this.children.first as MentionDomWrapperView;\n\n\t\tchild.isOn = false;\n\t}\n}\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module mention/mentionui\n */\n\nimport {\n\tPlugin,\n\ttype Editor\n} from 'ckeditor5/src/core.js';\n\nimport type {\n\tViewDocumentKeyDownEvent,\n\tMarker,\n\tModelPosition\n} from 'ckeditor5/src/engine.js';\n\nimport {\n\tButtonView,\n\tContextualBalloon,\n\tclickOutsideHandler\n} from 'ckeditor5/src/ui.js';\n\nimport {\n\tCKEditorError,\n\tCollection,\n\tRect,\n\tenv,\n\tkeyCodes,\n\tlogWarning,\n\ttype DomOptimalPositionOptions\n} from 'ckeditor5/src/utils.js';\n\nimport { TextWatcher, type TextWatcherMatchedEvent } from 'ckeditor5/src/typing.js';\n\nimport { debounce } from 'es-toolkit/compat';\n\nimport { MentionsView } from './ui/mentionsview.js';\nimport { MentionDomWrapperView } from './ui/domwrapperview.js';\nimport { MentionListItemView } from './ui/mentionlistitemview.js';\n\nimport type {\n\tMentionFeedbackCallback,\n\tMentionFeed,\n\tMentionFeedItem,\n\tMentionItemRenderer,\n\tMentionFeedObjectItem\n} from './mentionconfig.js';\n\nconst VERTICAL_SPACING = 3;\n\n// The key codes that mention UI handles when it is open (without commit keys).\nconst defaultHandledKeyCodes = [\n\tkeyCodes.arrowup,\n\tkeyCodes.arrowdown,\n\tkeyCodes.esc\n];\n\n// Dropdown commit key codes.\nconst defaultCommitKeyCodes = [\n\tkeyCodes.enter,\n\tkeyCodes.tab\n];\n\n/**\n * The mention UI feature.\n */\nexport class MentionUI extends Plugin {\n\t/**\n\t * The mention view.\n\t */\n\tprivate readonly _mentionsView: MentionsView;\n\n\t/**\n\t * Stores mention feeds configurations.\n\t */\n\tprivate _mentionsConfigurations: Map;\n\n\t/**\n\t * The contextual balloon plugin instance.\n\t */\n\tprivate _balloon: ContextualBalloon | undefined;\n\n\tprivate _items = new Collection<{ item: MentionFeedObjectItem; marker: string }>();\n\n\tprivate _lastRequested?: string;\n\n\t/**\n\t * Debounced feed requester. It uses `es-toolkit#debounce` method to delay function call.\n\t */\n\tprivate _requestFeedDebounced: ( marker: string, feedText: string ) => void;\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get pluginName() {\n\t\treturn 'MentionUI' as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static override get isOfficialPlugin(): true {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get requires() {\n\t\treturn [ ContextualBalloon ] as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tconstructor( editor: Editor ) {\n\t\tsuper( editor );\n\n\t\tthis._mentionsView = this._createMentionView();\n\t\tthis._mentionsConfigurations = new Map();\n\t\tthis._requestFeedDebounced = debounce( this._requestFeed, 100 );\n\n\t\teditor.config.define( 'mention', { feeds: [] } );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic init(): void {\n\t\tconst editor = this.editor;\n\n\t\tconst commitKeys = editor.config.get( 'mention.commitKeys' ) || defaultCommitKeyCodes;\n\t\tconst handledKeyCodes = defaultHandledKeyCodes.concat( commitKeys );\n\n\t\tthis._balloon = editor.plugins.get( ContextualBalloon );\n\n\t\t// Key listener that handles navigation in mention view.\n\t\teditor.editing.view.document.on( 'keydown', ( evt, data ) => {\n\t\t\tif ( isHandledKey( data.keyCode ) && this._isUIVisible ) {\n\t\t\t\tdata.preventDefault();\n\t\t\t\tevt.stop(); // Required for Enter key overriding.\n\n\t\t\t\tif ( data.keyCode == keyCodes.arrowdown ) {\n\t\t\t\t\tthis._mentionsView.selectNext();\n\t\t\t\t}\n\n\t\t\t\tif ( data.keyCode == keyCodes.arrowup ) {\n\t\t\t\t\tthis._mentionsView.selectPrevious();\n\t\t\t\t}\n\n\t\t\t\tif ( commitKeys.includes( data.keyCode ) ) {\n\t\t\t\t\tthis._mentionsView.executeSelected();\n\t\t\t\t}\n\n\t\t\t\tif ( data.keyCode == keyCodes.esc ) {\n\t\t\t\t\tthis._hideUIAndRemoveMarker();\n\t\t\t\t}\n\t\t\t}\n\t\t}, { priority: 'highest' } ); // Required to override the Enter key.\n\n\t\t// Close the dropdown upon clicking outside of the plugin UI.\n\t\tclickOutsideHandler( {\n\t\t\temitter: this._mentionsView,\n\t\t\tactivator: () => this._isUIVisible,\n\t\t\tcontextElements: () => [ this._balloon!.view.element! ],\n\t\t\tcallback: () => this._hideUIAndRemoveMarker()\n\t\t} );\n\n\t\tconst feeds = editor.config.get( 'mention.feeds' )!;\n\n\t\tfor ( const mentionDescription of feeds ) {\n\t\t\tconst { feed, marker, dropdownLimit } = mentionDescription;\n\n\t\t\tif ( !isValidMentionMarker( marker ) ) {\n\t\t\t\t/**\n\t\t\t\t * The marker must be a single character.\n\t\t\t\t *\n\t\t\t\t * Correct markers: `'@'`, `'#'`.\n\t\t\t\t *\n\t\t\t\t * Incorrect markers: `'$$'`, `'[@'`.\n\t\t\t\t *\n\t\t\t\t * See {@link module:mention/mentionconfig~MentionConfig}.\n\t\t\t\t *\n\t\t\t\t * @error mentionconfig-incorrect-marker\n\t\t\t\t * @param {string} marker Configured marker\n\t\t\t\t */\n\t\t\t\tthrow new CKEditorError( 'mentionconfig-incorrect-marker', null, { marker } );\n\t\t\t}\n\n\t\t\tconst feedCallback = typeof feed == 'function' ? feed.bind( this.editor ) : createFeedCallback( feed );\n\t\t\tconst itemRenderer = mentionDescription.itemRenderer;\n\t\t\tconst definition = { marker, feedCallback, itemRenderer, dropdownLimit };\n\n\t\t\tthis._mentionsConfigurations.set( marker, definition );\n\t\t}\n\n\t\tthis._setupTextWatcher( feeds );\n\t\tthis.listenTo( editor, 'change:isReadOnly', () => {\n\t\t\tthis._hideUIAndRemoveMarker();\n\t\t} );\n\t\tthis.on( 'requestFeed:response', ( evt, data ) => this._handleFeedResponse( data ) );\n\t\tthis.on( 'requestFeed:error', () => this._hideUIAndRemoveMarker() );\n\n\t\t/**\n\t\t * Checks if a given key code is handled by the mention UI.\n\t\t */\n\t\tfunction isHandledKey( keyCode: number ): boolean {\n\t\t\treturn handledKeyCodes.includes( keyCode );\n\t\t}\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic override destroy(): void {\n\t\tsuper.destroy();\n\n\t\t// Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).\n\t\tthis._mentionsView.destroy();\n\t}\n\n\t/**\n\t * Returns true when {@link #_mentionsView} is in the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon} and it is\n\t * currently visible.\n\t */\n\tprivate get _isUIVisible(): boolean {\n\t\treturn this._balloon!.visibleView === this._mentionsView;\n\t}\n\n\t/**\n\t * Creates the {@link #_mentionsView}.\n\t */\n\tprivate _createMentionView(): MentionsView {\n\t\tconst locale = this.editor.locale;\n\n\t\tconst mentionsView = new MentionsView( locale );\n\n\t\tmentionsView.items.bindTo( this._items ).using( data => {\n\t\t\tconst { item, marker } = data;\n\n\t\t\tconst { dropdownLimit: markerDropdownLimit } = this._mentionsConfigurations.get( marker )!;\n\n\t\t\t// Set to 10 by default for backwards compatibility. See: #10479\n\t\t\tconst dropdownLimit = markerDropdownLimit || this.editor.config.get( 'mention.dropdownLimit' ) || 10;\n\n\t\t\tif ( mentionsView.items.length >= dropdownLimit ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tconst listItemView = new MentionListItemView( locale );\n\n\t\t\tconst view = this._renderItem( item, marker );\n\t\t\tview.delegate( 'execute' ).to( listItemView );\n\n\t\t\tlistItemView.children.add( view );\n\t\t\tlistItemView.item = item;\n\t\t\tlistItemView.marker = marker;\n\n\t\t\tlistItemView.on( 'execute', () => {\n\t\t\t\tmentionsView.fire( 'execute', {\n\t\t\t\t\titem,\n\t\t\t\t\tmarker\n\t\t\t\t} );\n\t\t\t} );\n\n\t\t\treturn listItemView;\n\t\t} );\n\n\t\tmentionsView.on( 'execute', ( evt, data ) => {\n\t\t\tconst editor = this.editor;\n\t\t\tconst model = editor.model;\n\n\t\t\tconst item = data.item;\n\t\t\tconst marker = data.marker;\n\n\t\t\tconst mentionMarker = editor.model.markers.get( 'mention' );\n\n\t\t\t// Create a range on matched text.\n\t\t\tconst end = model.createPositionAt( model.document.selection.focus! );\n\t\t\tconst start = model.createPositionAt( mentionMarker!.getStart() );\n\t\t\tconst range = model.createRange( start, end );\n\n\t\t\tthis._hideUIAndRemoveMarker();\n\n\t\t\teditor.execute( 'mention', {\n\t\t\t\tmention: item,\n\t\t\t\ttext: item.text,\n\t\t\t\tmarker,\n\t\t\t\trange\n\t\t\t} );\n\n\t\t\teditor.editing.view.focus();\n\t\t} );\n\n\t\treturn mentionsView;\n\t}\n\n\t/**\n\t * Returns item renderer for the marker.\n\t */\n\tprivate _getItemRenderer( marker: string ): MentionItemRenderer | undefined {\n\t\tconst { itemRenderer } = this._mentionsConfigurations.get( marker )!;\n\n\t\treturn itemRenderer;\n\t}\n\n\t/**\n\t * Requests a feed from a configured callbacks.\n\t */\n\tprivate _requestFeed( marker: string, feedText: string ): void {\n\t\t// @if CK_DEBUG_MENTION // console.log( '%c[Feed]%c Requesting for', 'color: blue', 'color: black', `\"${ feedText }\"` );\n\n\t\t// Store the last requested feed - it is used to discard any out-of order requests.\n\t\tthis._lastRequested = feedText;\n\n\t\tconst { feedCallback } = this._mentionsConfigurations.get( marker )!;\n\t\tconst feedResponse = feedCallback( feedText );\n\n\t\tconst isAsynchronous = feedResponse instanceof Promise;\n\n\t\t// For synchronous feeds (e.g. callbacks, arrays) fire the response event immediately.\n\t\tif ( !isAsynchronous ) {\n\t\t\tthis.fire( 'requestFeed:response', { feed: feedResponse, marker, feedText } );\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle the asynchronous responses.\n\t\tfeedResponse\n\t\t\t.then( response => {\n\t\t\t\t// Check the feed text of this response with the last requested one so either:\n\t\t\t\tif ( this._lastRequested == feedText ) {\n\t\t\t\t\t// It is the same and fire the response event.\n\t\t\t\t\tthis.fire( 'requestFeed:response', { feed: response, marker, feedText } );\n\t\t\t\t} else {\n\t\t\t\t\t// It is different - most probably out-of-order one, so fire the discarded event.\n\t\t\t\t\tthis.fire( 'requestFeed:discarded', { feed: response, marker, feedText } );\n\t\t\t\t}\n\t\t\t} )\n\t\t\t.catch( error => {\n\t\t\t\tthis.fire( 'requestFeed:error', { error } );\n\n\t\t\t\t/**\n\t\t\t\t * The callback used for obtaining mention autocomplete feed thrown and error and the mention UI was hidden or\n\t\t\t\t * not displayed at all.\n\t\t\t\t *\n\t\t\t\t * @error mention-feed-callback-error\n\t\t\t\t */\n\t\t\t\tlogWarning( 'mention-feed-callback-error', { marker } );\n\t\t\t} );\n\t}\n\n\t/**\n\t * Registers a text watcher for the marker.\n\t */\n\tprivate _setupTextWatcher( feeds: Array ): TextWatcher {\n\t\tconst editor = this.editor;\n\n\t\tconst feedsWithPattern: Array = feeds.map( feed => ( {\n\t\t\t...feed,\n\t\t\tpattern: createRegExp( feed.marker, feed.minimumCharacters || 0 )\n\t\t} ) );\n\n\t\tconst watcher = new TextWatcher( editor.model, createTestCallback( feedsWithPattern ) );\n\n\t\twatcher.on( 'matched', ( evt, data ) => {\n\t\t\tconst markerDefinition = getLastValidMarkerInText( feedsWithPattern, data.text );\n\t\t\tconst selection = editor.model.document.selection;\n\t\t\tconst focus = selection.focus;\n\t\t\tconst markerPosition = editor.model.createPositionAt( focus!.parent, markerDefinition!.position );\n\n\t\t\tif ( isPositionInExistingMention( focus! ) || isMarkerInExistingMention( markerPosition ) ) {\n\t\t\t\tthis._hideUIAndRemoveMarker();\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst feedText = requestFeedText( markerDefinition, data.text );\n\t\t\tconst matchedTextLength = markerDefinition!.marker.length + feedText.length;\n\n\t\t\t// Create a marker range.\n\t\t\tconst start = focus!.getShiftedBy( -matchedTextLength );\n\t\t\tconst end = focus!.getShiftedBy( -feedText.length );\n\n\t\t\tconst markerRange = editor.model.createRange( start, end );\n\n\t\t\t// @if CK_DEBUG_MENTION // console.group( '%c[TextWatcher]%c matched', 'color: red', 'color: black', `\"${ feedText }\"` );\n\t\t\t// @if CK_DEBUG_MENTION // console.log( 'data#text', `\"${ data.text }\"` );\n\t\t\t// @if CK_DEBUG_MENTION // console.log( 'data#range', data.range.start.path, data.range.end.path );\n\t\t\t// @if CK_DEBUG_MENTION // console.log( 'marker definition', markerDefinition );\n\t\t\t// @if CK_DEBUG_MENTION // console.log( 'marker range', markerRange.start.path, markerRange.end.path );\n\n\t\t\tif ( checkIfStillInCompletionMode( editor ) ) {\n\t\t\t\tconst mentionMarker = editor.model.markers.get( 'mention' )!;\n\n\t\t\t\t// Update the marker - user might've moved the selection to other mention trigger.\n\t\t\t\teditor.model.change( writer => {\n\t\t\t\t\t// @if CK_DEBUG_MENTION // console.log( '%c[Editing]%c Updating the marker.', 'color: purple', 'color: black' );\n\n\t\t\t\t\twriter.updateMarker( mentionMarker, { range: markerRange } );\n\t\t\t\t} );\n\t\t\t} else {\n\t\t\t\teditor.model.change( writer => {\n\t\t\t\t\t// @if CK_DEBUG_MENTION // console.log( '%c[Editing]%c Adding the marker.', 'color: purple', 'color: black' );\n\n\t\t\t\t\twriter.addMarker( 'mention', { range: markerRange, usingOperation: false, affectsData: false } );\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\tthis._requestFeedDebounced( markerDefinition!.marker, feedText );\n\n\t\t\t// @if CK_DEBUG_MENTION // console.groupEnd();\n\t\t} );\n\n\t\twatcher.on( 'unmatched', () => {\n\t\t\tthis._hideUIAndRemoveMarker();\n\t\t} );\n\n\t\tconst mentionCommand = editor.commands.get( 'mention' )!;\n\t\twatcher.bind( 'isEnabled' ).to( mentionCommand );\n\n\t\treturn watcher;\n\t}\n\n\t/**\n\t * Handles the feed response event data.\n\t */\n\tprivate _handleFeedResponse( data: RequestFeedResponseEvent['args'][0] ) {\n\t\tconst { feed, marker } = data;\n\n\t\t// eslint-disable-next-line @stylistic/max-len\n\t\t// @if CK_DEBUG_MENTION // console.log( `%c[Feed]%c Response for \"${ data.feedText }\" (${ feed.length })`, 'color: blue', 'color: black', feed );\n\n\t\t// If the marker is not in the document happens when the selection had changed and the 'mention' marker was removed.\n\t\tif ( !checkIfStillInCompletionMode( this.editor ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset the view.\n\t\tthis._items.clear();\n\n\t\tfor ( const feedItem of feed ) {\n\t\t\tconst item = typeof feedItem != 'object' ? { id: feedItem, text: feedItem } : feedItem;\n\n\t\t\tthis._items.add( { item, marker } );\n\t\t}\n\n\t\tconst mentionMarker = this.editor.model.markers.get( 'mention' );\n\n\t\tif ( this._items.length ) {\n\t\t\tthis._showOrUpdateUI( mentionMarker! );\n\t\t} else {\n\t\t\t// Do not show empty mention UI.\n\t\t\tthis._hideUIAndRemoveMarker();\n\t\t}\n\t}\n\n\t/**\n\t * Shows the mentions balloon. If the panel is already visible, it will reposition it.\n\t */\n\tprivate _showOrUpdateUI( markerMarker: Marker ): void {\n\t\tif ( this._isUIVisible ) {\n\t\t\t// @if CK_DEBUG_MENTION // console.log( '%c[UI]%c Updating position.', 'color: green', 'color: black' );\n\n\t\t\t// Update balloon position as the mention list view may change its size.\n\t\t\tthis._balloon!.updatePosition( this._getBalloonPanelPositionData( markerMarker, this._mentionsView!.position ) );\n\t\t} else {\n\t\t\t// @if CK_DEBUG_MENTION // console.log( '%c[UI]%c Showing the UI.', 'color: green', 'color: black' );\n\n\t\t\tthis._balloon!.add( {\n\t\t\t\tview: this._mentionsView,\n\t\t\t\tposition: this._getBalloonPanelPositionData( markerMarker, this._mentionsView.position ),\n\t\t\t\tsingleViewMode: true,\n\t\t\t\tballoonClassName: 'ck-mention-balloon'\n\t\t\t} );\n\t\t}\n\n\t\tthis._mentionsView.position = this._balloon!.view.position;\n\t\tthis._mentionsView.selectFirst();\n\t}\n\n\t/**\n\t * Hides the mentions balloon and removes the 'mention' marker from the markers collection.\n\t */\n\tprivate _hideUIAndRemoveMarker(): void {\n\t\t// Remove the mention view from balloon before removing marker - it is used by balloon position target().\n\t\tif ( this._balloon!.hasView( this._mentionsView ) ) {\n\t\t\t// @if CK_DEBUG_MENTION // console.log( '%c[UI]%c Hiding the UI.', 'color: green', 'color: black' );\n\n\t\t\tthis._balloon!.remove( this._mentionsView );\n\t\t}\n\n\t\tif ( checkIfStillInCompletionMode( this.editor ) ) {\n\t\t\t// @if CK_DEBUG_MENTION // console.log( '%c[Editing]%c Removing marker.', 'color: purple', 'color: black' );\n\n\t\t\tthis.editor.model.change( writer => writer.removeMarker( 'mention' ) );\n\t\t}\n\n\t\t// Make the last matched position on panel view undefined so the #_getBalloonPanelPositionData() method will return all positions\n\t\t// on the next call.\n\t\tthis._mentionsView.position = undefined;\n\t}\n\n\t/**\n\t * Renders a single item in the autocomplete list.\n\t */\n\tprivate _renderItem( item: MentionFeedObjectItem, marker: string ): MentionDomWrapperView | ButtonView {\n\t\tconst editor = this.editor;\n\n\t\tlet view;\n\t\tlet label = item.id;\n\n\t\tconst renderer = this._getItemRenderer( marker );\n\n\t\tif ( renderer ) {\n\t\t\tconst renderResult = renderer( item );\n\n\t\t\tif ( typeof renderResult != 'string' ) {\n\t\t\t\tview = new MentionDomWrapperView( editor.locale, renderResult );\n\t\t\t} else {\n\t\t\t\tlabel = renderResult;\n\t\t\t}\n\t\t}\n\n\t\tif ( !view ) {\n\t\t\tconst buttonView = new ButtonView( editor.locale );\n\n\t\t\tbuttonView.label = label;\n\t\t\tbuttonView.withText = true;\n\n\t\t\tview = buttonView;\n\t\t}\n\n\t\treturn view;\n\t}\n\n\t/**\n\t * Creates a position options object used to position the balloon panel.\n\t *\n\t * @param mentionMarker\n\t * @param preferredPosition The name of the last matched position name.\n\t */\n\tprivate _getBalloonPanelPositionData(\n\t\tmentionMarker: Marker,\n\t\tpreferredPosition: MentionsView['position']\n\t): Partial {\n\t\tconst editor = this.editor;\n\t\tconst editing = editor.editing;\n\t\tconst domConverter = editing.view.domConverter;\n\t\tconst mapper = editing.mapper;\n\t\tconst uiLanguageDirection = editor.locale.uiLanguageDirection;\n\n\t\treturn {\n\t\t\ttarget: () => {\n\t\t\t\tlet modelRange = mentionMarker.getRange();\n\n\t\t\t\t// Target the UI to the model selection range - the marker has been removed so probably the UI will not be shown anyway.\n\t\t\t\t// The logic is used by ContextualBalloon to display another panel in the same place.\n\t\t\t\tif ( modelRange.start.root.rootName == '$graveyard' ) {\n\t\t\t\t\tmodelRange = editor.model.document.selection.getFirstRange()!;\n\t\t\t\t}\n\n\t\t\t\tconst viewRange = mapper.toViewRange( modelRange );\n\t\t\t\tconst rangeRects = Rect.getDomRangeRects( domConverter.viewRangeToDom( viewRange ) );\n\n\t\t\t\treturn rangeRects.pop()!;\n\t\t\t},\n\t\t\tlimiter: () => {\n\t\t\t\tconst view = this.editor.editing.view;\n\t\t\t\tconst viewDocument = view.document;\n\t\t\t\tconst editableElement = viewDocument.selection.editableElement;\n\n\t\t\t\tif ( editableElement ) {\n\t\t\t\t\treturn view.domConverter.mapViewToDom( editableElement.root ) as HTMLElement;\n\t\t\t\t}\n\n\t\t\t\treturn null;\n\t\t\t},\n\t\t\tpositions: getBalloonPanelPositions( preferredPosition, uiLanguageDirection )\n\t\t};\n\t}\n}\n\n/**\n * Returns the balloon positions data callbacks.\n */\nfunction getBalloonPanelPositions(\n\tpreferredPosition: MentionsView['position'],\n\tuiLanguageDirection: string\n): DomOptimalPositionOptions['positions'] {\n\tconst positions: Record = {\n\t\t// Positions the panel to the southeast of the caret rectangle.\n\t\t'caret_se': ( targetRect: Rect ) => {\n\t\t\treturn {\n\t\t\t\ttop: targetRect.bottom + VERTICAL_SPACING,\n\t\t\t\tleft: targetRect.right,\n\t\t\t\tname: 'caret_se',\n\t\t\t\tconfig: {\n\t\t\t\t\twithArrow: false\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\n\t\t// Positions the panel to the northeast of the caret rectangle.\n\t\t'caret_ne': ( targetRect: Rect, balloonRect: Rect ) => {\n\t\t\treturn {\n\t\t\t\ttop: targetRect.top - balloonRect.height - VERTICAL_SPACING,\n\t\t\t\tleft: targetRect.right,\n\t\t\t\tname: 'caret_ne',\n\t\t\t\tconfig: {\n\t\t\t\t\twithArrow: false\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\n\t\t// Positions the panel to the southwest of the caret rectangle.\n\t\t'caret_sw': ( targetRect: Rect, balloonRect: Rect ) => {\n\t\t\treturn {\n\t\t\t\ttop: targetRect.bottom + VERTICAL_SPACING,\n\t\t\t\tleft: targetRect.right - balloonRect.width,\n\t\t\t\tname: 'caret_sw',\n\t\t\t\tconfig: {\n\t\t\t\t\twithArrow: false\n\t\t\t\t}\n\t\t\t};\n\t\t},\n\n\t\t// Positions the panel to the northwest of the caret rect.\n\t\t'caret_nw': ( targetRect: Rect, balloonRect: Rect ) => {\n\t\t\treturn {\n\t\t\t\ttop: targetRect.top - balloonRect.height - VERTICAL_SPACING,\n\t\t\t\tleft: targetRect.right - balloonRect.width,\n\t\t\t\tname: 'caret_nw',\n\t\t\t\tconfig: {\n\t\t\t\t\twithArrow: false\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t};\n\n\t// Returns only the last position if it was matched to prevent the panel from jumping after the first match.\n\tif ( Object.prototype.hasOwnProperty.call( positions, preferredPosition! ) ) {\n\t\treturn [\n\t\t\tpositions[ preferredPosition! ]\n\t\t];\n\t}\n\n\t// By default, return all position callbacks ordered depending on the UI language direction.\n\treturn uiLanguageDirection !== 'rtl' ? [\n\t\tpositions.caret_se,\n\t\tpositions.caret_sw,\n\t\tpositions.caret_ne,\n\t\tpositions.caret_nw\n\t] : [\n\t\tpositions.caret_sw,\n\t\tpositions.caret_se,\n\t\tpositions.caret_nw,\n\t\tpositions.caret_ne\n\t];\n}\n\n/**\n * Returns a marker definition of the last valid occurring marker in a given string.\n * If there is no valid marker in a string, it returns undefined.\n *\n * Example of returned object:\n *\n * ```ts\n * {\n * \tmarker: '@',\n * \tposition: 4,\n * \tminimumCharacters: 0\n * }\n * ````\n *\n * @param feedsWithPattern Registered feeds in editor for mention plugin with created RegExp for matching marker.\n * @param text String to find the marker in\n * @returns Matched marker's definition\n */\nfunction getLastValidMarkerInText(\n\tfeedsWithPattern: Array,\n\ttext: string\n): MarkerDefinition {\n\tlet lastValidMarker: any;\n\n\tfor ( const feed of feedsWithPattern ) {\n\t\tconst currentMarkerLastIndex = text.lastIndexOf( feed.marker );\n\n\t\tif ( currentMarkerLastIndex > 0 && !text.substring( currentMarkerLastIndex - 1 ).match( feed.pattern ) ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif ( !lastValidMarker || currentMarkerLastIndex >= lastValidMarker.position ) {\n\t\t\tlastValidMarker = {\n\t\t\t\tmarker: feed.marker,\n\t\t\t\tposition: currentMarkerLastIndex,\n\t\t\t\tminimumCharacters: feed.minimumCharacters,\n\t\t\t\tpattern: feed.pattern\n\t\t\t};\n\t\t}\n\t}\n\n\treturn lastValidMarker;\n}\n\n/**\n * Creates a RegExp pattern for the marker.\n *\n * Function has to be exported to achieve 100% code coverage.\n *\n * @internal\n */\nexport function createRegExp( marker: string, minimumCharacters: number ): RegExp {\n\tconst numberOfCharacters = minimumCharacters == 0 ? '*' : `{${ minimumCharacters },}`;\n\tconst openAfterCharacters = env.features.isRegExpUnicodePropertySupported ? '\\\\p{Ps}\\\\p{Pi}\"\\'' : '\\\\(\\\\[{\"\\'';\n\tconst mentionCharacters = '.';\n\n\t// I wanted to make an util out of it, but since this regexp uses \"u\" flag, it became difficult.\n\t// When \"u\" flag is used, the regexp has \"strict\" escaping rules, i.e. if you try to escape a character that does not need\n\t// to be escaped, RegExp() will throw. It made it difficult to write a generic util, because different characters are\n\t// allowed in different context. For example, escaping \"-\" sometimes was correct, but sometimes it threw an error.\n\tmarker = marker.replace( /[.*+?^${}()\\-|[\\]\\\\]/g, '\\\\$&' );\n\n\t// The pattern consists of 3 groups:\n\t//\n\t// - 0 (non-capturing): Opening sequence - start of the line, space or an opening punctuation character like \"(\" or \"\\\"\",\n\t// - 1: The marker character(s),\n\t// - 2: Mention input (taking the minimal length into consideration to trigger the UI),\n\t//\n\t// The pattern matches up to the caret (end of string switch - $).\n\t// (0: opening sequence )(1: marker )(2: typed mention )$\n\tconst pattern = `(?:^|[ ${ openAfterCharacters }])(${ marker })(${ mentionCharacters }${ numberOfCharacters })$`;\n\n\treturn new RegExp( pattern, 'u' );\n}\n\n/**\n * Creates a test callback for the marker to be used in the text watcher instance.\n *\n * @param feedsWithPattern Feeds of mention plugin configured in editor with RegExp to match marker in text\n */\nfunction createTestCallback( feedsWithPattern: Array ): ( text: string ) => boolean {\n\tconst textMatcher = ( text: string ) => {\n\t\tconst markerDefinition = getLastValidMarkerInText( feedsWithPattern, text );\n\n\t\tif ( !markerDefinition ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tlet splitStringFrom = 0;\n\n\t\tif ( markerDefinition.position !== 0 ) {\n\t\t\tsplitStringFrom = markerDefinition.position - 1;\n\t\t}\n\n\t\tconst textToTest = text.substring( splitStringFrom );\n\n\t\treturn markerDefinition.pattern.test( textToTest );\n\t};\n\n\treturn textMatcher;\n}\n\n/**\n * Creates a text matcher from the marker.\n */\nfunction requestFeedText( markerDefinition: MarkerDefinition, text: string ): string {\n\tlet splitStringFrom = 0;\n\n\tif ( markerDefinition.position !== 0 ) {\n\t\tsplitStringFrom = markerDefinition.position - 1;\n\t}\n\n\tconst regExp = createRegExp( markerDefinition.marker, 0 );\n\tconst textToMatch = text.substring( splitStringFrom );\n\tconst match = textToMatch.match( regExp )!;\n\n\treturn match[ 2 ];\n}\n\n/**\n * The default feed callback.\n */\nfunction createFeedCallback( feedItems: Array ) {\n\treturn ( feedText: string ) => {\n\t\tconst filteredItems = feedItems\n\t\t\t// Make the default mention feed case-insensitive.\n\t\t\t.filter( item => {\n\t\t\t\t// Item might be defined as object.\n\t\t\t\tconst itemId = typeof item == 'string' ? item : String( item.id );\n\n\t\t\t\t// The default feed is case insensitive.\n\t\t\t\treturn itemId.toLowerCase().includes( feedText.toLowerCase() );\n\t\t\t} );\n\t\treturn filteredItems;\n\t};\n}\n\n/**\n * Checks if position in inside or right after a text with a mention.\n */\nfunction isPositionInExistingMention( position: ModelPosition ): boolean | null {\n\t// The text watcher listens only to changed range in selection - so the selection attributes are not yet available\n\t// and you cannot use selection.hasAttribute( 'mention' ) just yet.\n\t// See https://github.com/ckeditor/ckeditor5-engine/issues/1723.\n\tconst hasMention = position.textNode && position.textNode.hasAttribute( 'mention' );\n\n\tconst nodeBefore = position.nodeBefore;\n\n\treturn hasMention || nodeBefore && nodeBefore.is( '$text' ) && nodeBefore.hasAttribute( 'mention' );\n}\n\n/**\n * Checks if the closest marker offset is at the beginning of a mention.\n *\n * See https://github.com/ckeditor/ckeditor5/issues/11400.\n */\nfunction isMarkerInExistingMention( markerPosition: ModelPosition ): boolean | null {\n\tconst nodeAfter = markerPosition.nodeAfter;\n\n\treturn nodeAfter && nodeAfter.is( '$text' ) && nodeAfter.hasAttribute( 'mention' );\n}\n\n/**\n * Checks if string is a valid mention marker.\n */\nfunction isValidMentionMarker( marker: string ): boolean {\n\treturn !!marker;\n}\n\n/**\n * Checks the mention plugins is in completion mode (e.g. when typing is after a valid mention string like @foo).\n */\nfunction checkIfStillInCompletionMode( editor: Editor ): boolean {\n\treturn editor.model.markers.has( 'mention' );\n}\n\ntype RequestFeedResponse = {\n\n\t/**\n\t * Autocomplete items\n\t */\n\tfeed: Array;\n\n\t/**\n\t * The character which triggers autocompletion for mention.\n\t */\n\tmarker: string;\n\n\t/**\n\t * The text for which feed items were requested.\n\t */\n\tfeedText: string;\n};\n\ntype RequestFeedError = {\n\n\t/**\n\t * The error that was caught.\n\t */\n\terror: ErrorEvent;\n};\n\n/**\n * Fired whenever requested feed has a response.\n */\ntype RequestFeedResponseEvent = {\n\tname: 'requestFeed:response';\n\targs: [ RequestFeedResponse ];\n};\n\n/**\n * Fired whenever the requested feed was discarded. This happens when the response was delayed and\n * other feed was already requested.\n */\ntype RequestFeedDiscardedEvent = {\n\tname: 'requestFeed:discarded';\n\targs: [ RequestFeedResponse ];\n};\n\n/**\n * Fired whenever the requested {@link module:mention/mentionconfig~MentionFeed#feed} promise fails with error.\n */\ntype RequestFeedErrorEvent = {\n\tname: 'requestFeed:error';\n\targs: [ RequestFeedError ];\n};\n\ntype Definition = {\n\tmarker: string;\n\tfeedCallback: MentionFeedbackCallback;\n\titemRenderer?: MentionItemRenderer;\n\tdropdownLimit?: number;\n};\n\ntype MarkerDefinition = {\n\tmarker: string;\n\tminimumCharacters?: number;\n\tpattern: RegExp;\n\tposition: number;\n};\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module mention/mention\n */\n\nimport { Plugin } from 'ckeditor5/src/core.js';\nimport type { ModelElement } from 'ckeditor5/src/engine.js';\n\nimport { MentionEditing, _toMentionAttribute } from './mentionediting.js';\nimport { MentionUI } from './mentionui.js';\n\nimport '../theme/mention.css';\n\n/**\n * The mention plugin.\n *\n * For a detailed overview, check the {@glink features/mentions Mention feature} guide.\n */\nexport class Mention extends Plugin {\n\t/**\n\t * Creates a mention attribute value from the provided view element and additional data.\n\t *\n\t * ```ts\n\t * editor.plugins.get( 'Mention' ).toMentionAttribute( viewElement, { userId: '1234' } );\n\t *\n\t * // For a view element: @John Doe\n\t * // it will return:\n\t * // { id: '@joe', userId: '1234', uid: '7a7bc7...', _text: '@John Doe' }\n\t * ```\n\t *\n\t * @param data Additional data to be stored in the mention attribute.\n\t */\n\tpublic toMentionAttribute>(\n\t\tviewElement: ModelElement,\n\t\tdata: MentionData\n\t): ( MentionAttribute & MentionData ) | undefined;\n\n\t/**\n\t * Creates a mention attribute value from the provided view element.\n\t *\n\t * ```ts\n\t * editor.plugins.get( 'Mention' ).toMentionAttribute( viewElement );\n\t *\n\t * // For a view element: @John Doe\n\t * // it will return:\n\t * // { id: '@joe', uid: '7a7bc7...', _text: '@John Doe' }\n\t * ```\n\t */\n\tpublic toMentionAttribute( viewElement: ModelElement ): MentionAttribute | undefined;\n\n\tpublic toMentionAttribute( viewElement: ModelElement, data?: Record ): MentionAttribute | undefined {\n\t\treturn _toMentionAttribute( viewElement, data );\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get pluginName() {\n\t\treturn 'Mention' as const;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static override get isOfficialPlugin(): true {\n\t\treturn true;\n\t}\n\n\t/**\n\t * @inheritDoc\n\t */\n\tpublic static get requires() {\n\t\treturn [ MentionEditing, MentionUI ] as const;\n\t}\n}\n\n/**\n * Represents a mention in the model.\n *\n * See {@link module:mention/mention~Mention#toMentionAttribute `Mention#toMentionAttribute()`}.\n */\nexport type MentionAttribute = {\n\n\t/**\n\t * The ID of a mention. It identifies the mention item in the mention feed. There can be multiple mentions\n\t * in the document with the same ID (e.g. the same hashtag being mentioned).\n\t */\n\tid: string;\n\n\t/**\n\t * A unique ID of this mention instance. Should be passed as an `option.id` when using\n\t * {@link module:engine/view/downcastwriter~ViewDowncastWriter#createAttributeElement writer.createAttributeElement()}.\n\t */\n\tuid: string;\n\n\t/**\n\t * Helper property that stores the text of the inserted mention. Used for detecting a broken mention\n\t * in the editing area.\n\t *\n\t * @internal\n\t */\n\t_text: string;\n};\n","/**\n * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.\n * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options\n */\n\n/**\n * @module minimap/minimapiframeview\n */\n\nimport { IframeView } from 'ckeditor5/src/ui.js';\nimport { toUnit, type Locale } from 'ckeditor5/src/utils.js';\nimport type { MinimapViewOptions } from './minimapview.js';\n\nconst toPx = /* #__PURE__ */ toUnit( 'px' );\n\n/**\n * The internal `