feat: update major UI
This commit is contained in:
parent
ca11d06341
commit
a6e5e7e319
|
|
@ -38,11 +38,12 @@ export const metadata: Metadata = {
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default async function RootLayout({
|
||||||
children,
|
children,
|
||||||
params: { locale },
|
params,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
params: { locale: string };
|
params: Promise<{ locale: string }>;
|
||||||
}>) {
|
}>) {
|
||||||
|
const { locale } = await params;
|
||||||
const messages = await getMessages();
|
const messages = await getMessages();
|
||||||
const direction = getLangDir(locale);
|
const direction = getLangDir(locale);
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "./[locale]/globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({
|
||||||
|
subsets: ["latin"],
|
||||||
|
display: 'swap',
|
||||||
|
preload: true,
|
||||||
|
fallback: ['system-ui', 'arial']
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "NetidHub",
|
||||||
|
description: "NetidHub Platform",
|
||||||
|
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="in">
|
||||||
|
<body className={`${inter.className} dashcode-app`}>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import AutoRedirect from '@/components/auto-redirect';
|
||||||
|
|
||||||
|
export default function RootPage() {
|
||||||
|
return <AutoRedirect />;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useRouter } from '@/components/navigation';
|
||||||
|
|
||||||
|
export default function AutoRedirect() {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Get current pathname without locale
|
||||||
|
const pathname = window.location.pathname;
|
||||||
|
const segments = pathname.split('/').filter(Boolean);
|
||||||
|
|
||||||
|
// Check if first segment is a locale
|
||||||
|
const locales = ['in', 'en', 'ar'];
|
||||||
|
const hasLocale = segments.length > 0 && locales.includes(segments[0]);
|
||||||
|
|
||||||
|
if (!hasLocale) {
|
||||||
|
// Redirect to default locale with current path
|
||||||
|
const newPath = `/in${pathname}`;
|
||||||
|
router.replace(newPath);
|
||||||
|
}
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
'use client';
|
|
||||||
|
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import { CKEditor } from '@ckeditor/ckeditor5-react';
|
|
||||||
import { ClassicEditor } from 'ckeditor5';
|
|
||||||
|
|
||||||
// Dynamically import CKEditor to avoid SSR issues
|
|
||||||
const DynamicCKEditor = dynamic(() => Promise.resolve(CKEditor), {
|
|
||||||
ssr: false,
|
|
||||||
loading: () => <div>Loading editor...</div>
|
|
||||||
});
|
|
||||||
|
|
||||||
interface EditorProps {
|
|
||||||
data?: string;
|
|
||||||
onChange?: (data: string) => void;
|
|
||||||
config?: any;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Editor({ data = '', onChange, config = {}, disabled = false }: EditorProps) {
|
|
||||||
return (
|
|
||||||
<div className="ckeditor-wrapper">
|
|
||||||
<DynamicCKEditor
|
|
||||||
editor={ClassicEditor}
|
|
||||||
data={data}
|
|
||||||
onChange={(event, editor) => {
|
|
||||||
const data = editor.getData();
|
|
||||||
onChange?.(data);
|
|
||||||
}}
|
|
||||||
config={{
|
|
||||||
toolbar: [
|
|
||||||
'heading', '|',
|
|
||||||
'bold', 'italic', 'link', '|',
|
|
||||||
'bulletedList', 'numberedList', '|',
|
|
||||||
'outdent', 'indent', '|',
|
|
||||||
'blockQuote', 'insertTable', '|',
|
|
||||||
'undo', 'redo'
|
|
||||||
],
|
|
||||||
...config
|
|
||||||
}}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import SimpleEditor from './simple-editor';
|
||||||
|
|
||||||
|
// Dynamic import untuk CKEditor dengan error handling
|
||||||
|
const CKEditorWrapper = dynamic(
|
||||||
|
() => import('./ckeditor-wrapper'),
|
||||||
|
{
|
||||||
|
ssr: false,
|
||||||
|
loading: () => (
|
||||||
|
<div className="flex items-center justify-center p-8 border border-gray-200 rounded-lg">
|
||||||
|
<div className="text-gray-500">Loading editor...</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
interface EditorProps {
|
||||||
|
data?: string;
|
||||||
|
onChange?: (data: string) => void;
|
||||||
|
onReady?: (editor: any) => void;
|
||||||
|
onBlur?: (event: any, editor: any) => void;
|
||||||
|
onFocus?: (event: any, editor: any) => void;
|
||||||
|
config?: any;
|
||||||
|
disabled?: boolean;
|
||||||
|
className?: string;
|
||||||
|
fallbackToSimple?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Editor({
|
||||||
|
data = '',
|
||||||
|
onChange,
|
||||||
|
onReady,
|
||||||
|
onBlur,
|
||||||
|
onFocus,
|
||||||
|
config = {},
|
||||||
|
disabled = false,
|
||||||
|
className = '',
|
||||||
|
fallbackToSimple = true
|
||||||
|
}: EditorProps) {
|
||||||
|
const [useSimpleEditor, setUseSimpleEditor] = useState(false);
|
||||||
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check if CKEditor is available
|
||||||
|
const checkCKEditor = async () => {
|
||||||
|
try {
|
||||||
|
await import('@ckeditor/ckeditor5-react');
|
||||||
|
await import('ckeditor5');
|
||||||
|
setUseSimpleEditor(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('CKEditor not available, falling back to simple editor:', error);
|
||||||
|
if (fallbackToSimple) {
|
||||||
|
setUseSimpleEditor(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isMounted) {
|
||||||
|
checkCKEditor();
|
||||||
|
}
|
||||||
|
}, [isMounted, fallbackToSimple]);
|
||||||
|
|
||||||
|
if (!isMounted) {
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center justify-center p-8 border border-gray-200 rounded-lg ${className}`}>
|
||||||
|
<div className="text-gray-500">Loading editor...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useSimpleEditor) {
|
||||||
|
return (
|
||||||
|
<SimpleEditor
|
||||||
|
data={data}
|
||||||
|
onChange={onChange}
|
||||||
|
className={className}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CKEditorWrapper
|
||||||
|
data={data}
|
||||||
|
onChange={onChange}
|
||||||
|
onReady={onReady}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onFocus={onFocus}
|
||||||
|
config={config}
|
||||||
|
disabled={disabled}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useRef, useEffect } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Bold,
|
||||||
|
Italic,
|
||||||
|
Underline,
|
||||||
|
List,
|
||||||
|
ListOrdered,
|
||||||
|
Quote,
|
||||||
|
Link,
|
||||||
|
Undo,
|
||||||
|
Redo,
|
||||||
|
Type,
|
||||||
|
AlignLeft,
|
||||||
|
AlignCenter,
|
||||||
|
AlignRight
|
||||||
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
interface SimpleEditorProps {
|
||||||
|
data?: string;
|
||||||
|
onChange?: (data: string) => void;
|
||||||
|
placeholder?: string;
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SimpleEditor({
|
||||||
|
data = '',
|
||||||
|
onChange,
|
||||||
|
placeholder = 'Start typing...',
|
||||||
|
className = '',
|
||||||
|
disabled = false
|
||||||
|
}: SimpleEditorProps) {
|
||||||
|
const [content, setContent] = useState(data);
|
||||||
|
const editorRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsMounted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setContent(data);
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const handleInput = () => {
|
||||||
|
if (editorRef.current) {
|
||||||
|
const html = editorRef.current.innerHTML;
|
||||||
|
setContent(html);
|
||||||
|
onChange?.(html);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const execCommand = (command: string, value?: string) => {
|
||||||
|
document.execCommand(command, false, value);
|
||||||
|
editorRef.current?.focus();
|
||||||
|
handleInput();
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertLink = () => {
|
||||||
|
const url = prompt('Enter URL:');
|
||||||
|
if (url) {
|
||||||
|
execCommand('createLink', url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toolbarButtons = [
|
||||||
|
{ command: 'bold', icon: Bold, label: 'Bold' },
|
||||||
|
{ command: 'italic', icon: Italic, label: 'Italic' },
|
||||||
|
{ command: 'underline', icon: Underline, label: 'Underline' },
|
||||||
|
{ command: 'insertUnorderedList', icon: List, label: 'Bullet List' },
|
||||||
|
{ command: 'insertOrderedList', icon: ListOrdered, label: 'Numbered List' },
|
||||||
|
{ command: 'formatBlock', icon: Quote, label: 'Quote', value: 'blockquote' },
|
||||||
|
{ command: 'justifyLeft', icon: AlignLeft, label: 'Align Left' },
|
||||||
|
{ command: 'justifyCenter', icon: AlignCenter, label: 'Align Center' },
|
||||||
|
{ command: 'justifyRight', icon: AlignRight, label: 'Align Right' },
|
||||||
|
{ command: 'undo', icon: Undo, label: 'Undo' },
|
||||||
|
{ command: 'redo', icon: Redo, label: 'Redo' },
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isMounted) {
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center justify-center p-8 border border-gray-200 rounded-lg ${className}`}>
|
||||||
|
<div className="text-gray-500">Loading editor...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`border border-gray-200 rounded-lg overflow-hidden ${className}`}>
|
||||||
|
{/* Toolbar */}
|
||||||
|
<div className="flex flex-wrap items-center gap-1 p-2 bg-gray-50 border-b border-gray-200">
|
||||||
|
{toolbarButtons.map((button) => {
|
||||||
|
const IconComponent = button.icon;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={button.command}
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
if (button.command === 'createLink') {
|
||||||
|
insertLink();
|
||||||
|
} else {
|
||||||
|
execCommand(button.command, button.value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
disabled={disabled}
|
||||||
|
title={button.label}
|
||||||
|
>
|
||||||
|
<IconComponent className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={insertLink}
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
disabled={disabled}
|
||||||
|
title="Insert Link"
|
||||||
|
>
|
||||||
|
<Link className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Editor Content */}
|
||||||
|
<div
|
||||||
|
ref={editorRef}
|
||||||
|
contentEditable={!disabled}
|
||||||
|
onInput={handleInput}
|
||||||
|
className="min-h-[200px] p-4 focus:outline-none"
|
||||||
|
style={{ minHeight: '200px' }}
|
||||||
|
dangerouslySetInnerHTML={{ __html: content }}
|
||||||
|
data-placeholder={placeholder}
|
||||||
|
suppressContentEditableWarning={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -85,7 +85,7 @@ export default function LocalSwitcher() {
|
||||||
<SelectItem value="in" className="border-none">
|
<SelectItem value="in" className="border-none">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Image
|
<Image
|
||||||
src="/images/all-img/flag-3.png"
|
src="/flag-3.png"
|
||||||
alt="flag"
|
alt="flag"
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
|
|
@ -99,7 +99,7 @@ export default function LocalSwitcher() {
|
||||||
<SelectItem value="en" className="border-none">
|
<SelectItem value="en" className="border-none">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Image
|
<Image
|
||||||
src="/images/all-img/flag-1.png"
|
src="/flag-1.png"
|
||||||
alt="flag"
|
alt="flag"
|
||||||
width={24}
|
width={24}
|
||||||
height={24}
|
height={24}
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ const ProfileInfo = () => {
|
||||||
{detail !== undefined ? (
|
{detail !== undefined ? (
|
||||||
<div className="flex items-center gap-3 text-default-800">
|
<div className="flex items-center gap-3 text-default-800">
|
||||||
<Image
|
<Image
|
||||||
src={"/assets/avatar-profile.png"}
|
src={"/avatar-profile.png"}
|
||||||
alt={"Image"}
|
alt={"Image"}
|
||||||
width={36}
|
width={36}
|
||||||
height={36}
|
height={36}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ const MenuItem = ({ href, label, icon, active, id, collapsed }: MenuItemProps) =
|
||||||
const { hovered } = hoverConfig;
|
const { hovered } = hoverConfig;
|
||||||
const isDesktop = useMediaQuery("(min-width: 1280px)");
|
const isDesktop = useMediaQuery("(min-width: 1280px)");
|
||||||
const [mobileMenuConfig, setMobileMenuConfig] = useMobileMenuConfig();
|
const [mobileMenuConfig, setMobileMenuConfig] = useMobileMenuConfig();
|
||||||
|
|
||||||
const { transform, transition, setNodeRef, isDragging, attributes, listeners } = useSortable({
|
const { transform, transition, setNodeRef, isDragging, attributes, listeners } = useSortable({
|
||||||
id: id,
|
id: id,
|
||||||
|
|
||||||
|
|
@ -54,12 +55,13 @@ const MenuItem = ({ href, label, icon, active, id, collapsed }: MenuItemProps) =
|
||||||
<Button
|
<Button
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
style={style}
|
style={style}
|
||||||
variant={active ? "default" : "ghost"}
|
variant="ghost"
|
||||||
color={active ? "default" : "secondary"}
|
color="secondary"
|
||||||
fullWidth
|
fullWidth
|
||||||
className={cn('', {
|
className={cn('', {
|
||||||
'justify-start text-sm font-medium capitalize group hover:md:px-8 h-auto py-2.5 md:px-3 px-3': !collapsed,
|
'justify-start text-sm font-medium capitalize group hover:md:px-8 h-auto py-2.5 md:px-3 px-3': !collapsed,
|
||||||
'hover:ring-transparent hover:ring-offset-0': !active
|
'hover:ring-transparent hover:ring-offset-0': !active,
|
||||||
|
'bg-blue-600 text-white hover:bg-blue-700': active
|
||||||
})}
|
})}
|
||||||
asChild
|
asChild
|
||||||
size={collapsed ? "icon" : "default"}
|
size={collapsed ? "icon" : "default"}
|
||||||
|
|
@ -99,13 +101,13 @@ const MenuItem = ({ href, label, icon, active, id, collapsed }: MenuItemProps) =
|
||||||
if (config.sidebar === 'compact' && isDesktop) {
|
if (config.sidebar === 'compact' && isDesktop) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
|
variant="ghost"
|
||||||
variant={active ? "default" : "ghost"}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
color={active ? "default" : "secondary"}
|
color="secondary"
|
||||||
className="flex-col h-auto py-1.5 px-3.5 capitalize font-semibold"
|
className={cn("flex-col h-auto py-1.5 px-3.5 capitalize font-semibold", {
|
||||||
|
'bg-blue-600 text-white hover:bg-blue-700': active
|
||||||
|
})}
|
||||||
asChild
|
asChild
|
||||||
|
|
||||||
>
|
>
|
||||||
<Link href={href}>
|
<Link href={href}>
|
||||||
<Icon icon={icon} className={cn('h-6 w-6 mb-1 ')} />
|
<Icon icon={icon} className={cn('h-6 w-6 mb-1 ')} />
|
||||||
|
|
@ -125,21 +127,22 @@ const MenuItem = ({ href, label, icon, active, id, collapsed }: MenuItemProps) =
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setMobileMenuConfig({ ...mobileMenuConfig, isOpen: false })}
|
onClick={() => setMobileMenuConfig({ ...mobileMenuConfig, isOpen: false })}
|
||||||
variant={active ? "default" : "ghost"}
|
variant="ghost"
|
||||||
fullWidth
|
fullWidth
|
||||||
color={active ? "default" : "secondary"}
|
color="secondary"
|
||||||
className={cn('', {
|
className={cn('', {
|
||||||
'justify-start text-sm font-medium capitalize h-auto py-2.5 md:px-3 px-3': !collapsed || hovered,
|
'justify-start text-sm font-medium capitalize h-auto py-2.5 md:px-3 px-3': !collapsed,
|
||||||
'hover:ring-transparent hover:ring-offset-0': !active
|
'hover:ring-transparent hover:ring-offset-0': !active,
|
||||||
|
'bg-secondary text-white hover:text-secondary-foreground': active
|
||||||
})}
|
})}
|
||||||
asChild
|
asChild
|
||||||
size={(collapsed && !hovered) ? "icon" : "default"}
|
size={collapsed ? "icon" : "default"}
|
||||||
>
|
>
|
||||||
<Link href={href}>
|
<Link href={href}>
|
||||||
<Icon icon={icon} className={cn('h-5 w-5 ', {
|
<Icon icon={icon} className={cn('h-5 w-5 ', {
|
||||||
'me-4': !collapsed || hovered
|
'me-4': !collapsed
|
||||||
})} />
|
})} />
|
||||||
{(!collapsed || hovered) && (
|
{!collapsed && (
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"max-w-[200px] truncate",
|
"max-w-[200px] truncate",
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ const SidebarContent = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
onMouseEnter={() => config.sidebar === 'draggable' && setHoverConfig({ hovered: true })}
|
onMouseEnter={() => setHoverConfig({ hovered: true })}
|
||||||
onMouseLeave={() => config.sidebar === 'draggable' && setHoverConfig({ hovered: false })}
|
onMouseLeave={() => setHoverConfig({ hovered: false })}
|
||||||
|
|
||||||
className={cn('fixed z-50 w-[248px] bg-sidebar shadow-base block', sidebarTheme, {
|
className={cn('fixed z-50 w-[248px] bg-sidebar shadow-base block', sidebarTheme, {
|
||||||
'w-[72px]': config.collapsed && config.sidebar !== 'compact',
|
'w-[72px]': config.collapsed && config.sidebar !== 'compact',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Auto Locale Routing
|
||||||
|
|
||||||
|
Sistem auto routing locale telah diimplementasikan untuk memastikan bahwa semua URL otomatis memiliki prefix locale.
|
||||||
|
|
||||||
|
## 🚀 **Cara Kerja:**
|
||||||
|
|
||||||
|
### **1. Middleware (Server-side)**
|
||||||
|
- **File**: `middleware.ts`
|
||||||
|
- **Fungsi**: Menangani redirect di server-side sebelum halaman dimuat
|
||||||
|
- **Logika**:
|
||||||
|
- Jika URL tidak memiliki prefix locale (`/in`, `/en`, `/ar`)
|
||||||
|
- Maka akan otomatis redirect ke `/in` + path yang diminta
|
||||||
|
- Contoh: `/admin/dashboard` → `/in/admin/dashboard`
|
||||||
|
|
||||||
|
### **2. Auto Redirect Component (Client-side)**
|
||||||
|
- **File**: `components/auto-redirect.tsx`
|
||||||
|
- **Fungsi**: Fallback untuk client-side redirect jika middleware tidak bekerja
|
||||||
|
- **Logika**: Menggunakan `useRouter` untuk redirect di browser
|
||||||
|
|
||||||
|
### **3. Root Pages**
|
||||||
|
- **File**: `app/page.tsx` dan `app/layout.tsx`
|
||||||
|
- **Fungsi**: Menangani akses ke root path tanpa locale
|
||||||
|
- **Logika**: Menggunakan `AutoRedirect` component
|
||||||
|
|
||||||
|
## 📋 **Contoh Redirect:**
|
||||||
|
|
||||||
|
| URL yang diakses | URL hasil redirect |
|
||||||
|
|------------------|-------------------|
|
||||||
|
| `/` | `/in` |
|
||||||
|
| `/admin/dashboard` | `/in/admin/dashboard` |
|
||||||
|
| `/content/image` | `/in/content/image` |
|
||||||
|
| `/settings` | `/in/settings` |
|
||||||
|
|
||||||
|
## ⚙️ **Konfigurasi:**
|
||||||
|
|
||||||
|
### **Default Locale:**
|
||||||
|
```typescript
|
||||||
|
const defaultLocale = "in";
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Supported Locales:**
|
||||||
|
```typescript
|
||||||
|
const locales = ["in", "en", "ar"];
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Middleware Matcher:**
|
||||||
|
```typescript
|
||||||
|
matcher: ['/((?!api|_next|_static|.*\\..*).*)']
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 **Fitur:**
|
||||||
|
|
||||||
|
- ✅ **Server-side redirect** (lebih cepat)
|
||||||
|
- ✅ **Client-side fallback** (lebih reliable)
|
||||||
|
- ✅ **Skip redirect** untuk API routes dan static files
|
||||||
|
- ✅ **Preserve query parameters** saat redirect
|
||||||
|
- ✅ **SEO friendly** dengan proper HTTP redirect codes
|
||||||
|
|
||||||
|
## 🎯 **Keuntungan:**
|
||||||
|
|
||||||
|
1. **User Experience**: User tidak perlu mengetik prefix locale
|
||||||
|
2. **SEO**: Search engine akan selalu mendapat URL dengan locale
|
||||||
|
3. **Consistency**: Semua URL memiliki format yang konsisten
|
||||||
|
4. **Fallback**: Jika server-side redirect gagal, client-side akan menangani
|
||||||
|
5. **Performance**: Redirect terjadi sebelum halaman dimuat
|
||||||
|
|
||||||
|
## 🚨 **Catatan Penting:**
|
||||||
|
|
||||||
|
- Middleware akan menangani sebagian besar kasus
|
||||||
|
- Auto Redirect component hanya sebagai fallback
|
||||||
|
- Pastikan semua route sudah ada di `app/[locale]/` directory
|
||||||
|
- Test dengan berbagai URL untuk memastikan redirect bekerja
|
||||||
|
|
@ -307,7 +307,11 @@ export const useOTPVerification = () => {
|
||||||
throw new Error("OTP must be exactly 6 digits");
|
throw new Error("OTP must be exactly 6 digits");
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await verifyOTPByUsername(username, otp);
|
const data = {
|
||||||
|
username: username,
|
||||||
|
otpCode: otp,
|
||||||
|
}
|
||||||
|
const response = await verifyOTPByUsername(data);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
throw new Error(response.message || "OTP verification failed");
|
throw new Error(response.message || "OTP verification failed");
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
import { useState, useCallback, useEffect } from "react";
|
import { useState, useCallback, useEffect } from "react";
|
||||||
import { useRouter } from "@/components/navigation";
|
import { useRouter } from "@/components/navigation";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
import {
|
import {
|
||||||
LoginFormData,
|
LoginFormData,
|
||||||
ProfileData,
|
ProfileData,
|
||||||
|
|
@ -83,8 +84,6 @@ export const useAuth = (): AuthContextType => {
|
||||||
// Attempt login
|
// Attempt login
|
||||||
const response = await doLogin({
|
const response = await doLogin({
|
||||||
...credentials,
|
...credentials,
|
||||||
grantType: AUTH_CONSTANTS.GRANT_TYPE,
|
|
||||||
clientId: AUTH_CONSTANTS.CLIENT_ID,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
|
|
@ -92,7 +91,7 @@ export const useAuth = (): AuthContextType => {
|
||||||
throw new Error("Invalid username or password");
|
throw new Error("Invalid username or password");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { access_token, refresh_token } = response?.data || {};
|
const { access_token, refresh_token } = response?.data?.data || {};
|
||||||
|
|
||||||
if (!access_token || !refresh_token) {
|
if (!access_token || !refresh_token) {
|
||||||
throw new Error("Invalid response from server");
|
throw new Error("Invalid response from server");
|
||||||
|
|
@ -103,45 +102,78 @@ export const useAuth = (): AuthContextType => {
|
||||||
|
|
||||||
// Get user profile
|
// Get user profile
|
||||||
const profileResponse = await getProfile(access_token);
|
const profileResponse = await getProfile(access_token);
|
||||||
const profile: ProfileData = profileResponse?.data?.data;
|
const profile = profileResponse?.data?.data;
|
||||||
|
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
throw new Error("Failed to fetch user profile");
|
throw new Error("Failed to fetch user profile");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate user eligibility
|
const dateTime: any = new Date();
|
||||||
// if (!isUserEligible(profile)) {
|
|
||||||
// clearAllCookies();
|
|
||||||
// warning(
|
|
||||||
// "Akun Anda tidak dapat digunakan untuk masuk ke MediaHub Polri",
|
|
||||||
// "/auth"
|
|
||||||
// );
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Set profile cookies
|
const newTime: any = dateTime.getTime() + 10 * 60 * 1000;
|
||||||
setProfileCookies(profile);
|
|
||||||
|
Cookies.set("access_token", response?.data?.data?.access_token, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("refresh_token", response?.data?.data?.refresh_token, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("time_refresh", newTime, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("is_first_login", "true", {
|
||||||
|
secure: true,
|
||||||
|
sameSite: "strict",
|
||||||
|
});
|
||||||
|
// await saveActivity(
|
||||||
|
// {
|
||||||
|
// activityTypeId: 1,
|
||||||
|
// url: "https://dev.mikulnews.com/auth",
|
||||||
|
// userId: profile?.data?.data?.id,
|
||||||
|
// },
|
||||||
|
// response?.data?.data?.access_token
|
||||||
|
// );
|
||||||
|
Cookies.set("profile_picture", profile?.profilePictureUrl, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("uie", profile?.id, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("ufne", profile?.fullname, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("ulie", profile?.userLevelGroup, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("username", profile?.username, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("urie", profile?.roleId, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("roleName", profile?.roleName, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("masterPoldaId", profile?.masterPoldaId, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("ulne", profile?.userLevelId, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("urce", profile?.roleCode, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("email", profile?.email, {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
Cookies.set("status", "login", {
|
||||||
|
expires: 1,
|
||||||
|
});
|
||||||
|
|
||||||
// Reset rate limiter on successful login
|
// Reset rate limiter on successful login
|
||||||
loginRateLimiter.resetAttempts(credentials.username);
|
loginRateLimiter.resetAttempts(credentials.username);
|
||||||
|
|
||||||
// Navigate based on user role
|
router.push("/admin/dashboard");
|
||||||
const navigationPath = getNavigationPath(
|
|
||||||
profile.roleId,
|
|
||||||
profile.userLevel?.id,
|
|
||||||
profile.userLevel?.parentLevelId
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update state
|
|
||||||
setState({
|
|
||||||
isAuthenticated: true,
|
|
||||||
user: profile,
|
|
||||||
loading: false,
|
|
||||||
error: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Navigate to appropriate dashboard
|
|
||||||
window.location.href = navigationPath;
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const errorMessage = error?.message || "Login failed";
|
const errorMessage = error?.message || "Login failed";
|
||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
|
|
@ -203,7 +235,7 @@ export const useEmailValidation = () => {
|
||||||
throw new Error(response?.message || "Email validation failed");
|
throw new Error(response?.message || "Email validation failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = response?.data?.message;
|
const message = response?.data?.messages[0];
|
||||||
|
|
||||||
switch (message) {
|
switch (message) {
|
||||||
case "Continue to setup email":
|
case "Continue to setup email":
|
||||||
|
|
@ -306,8 +338,11 @@ export const useOTPVerification = () => {
|
||||||
if (otp.length !== 6) {
|
if (otp.length !== 6) {
|
||||||
throw new Error("OTP must be exactly 6 digits");
|
throw new Error("OTP must be exactly 6 digits");
|
||||||
}
|
}
|
||||||
|
const data = {
|
||||||
const response = await verifyOTPByUsername(username, otp);
|
username: username,
|
||||||
|
otpCode: otp,
|
||||||
|
}
|
||||||
|
const response = await verifyOTPByUsername(data);
|
||||||
|
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
throw new Error(response.message || "OTP verification failed");
|
throw new Error(response.message || "OTP verification failed");
|
||||||
|
|
|
||||||
|
|
@ -2,30 +2,47 @@ import createMiddleware from "next-intl/middleware";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { locales } from "@/config";
|
import { locales } from "@/config";
|
||||||
|
|
||||||
// export default async function middleware(request: NextRequest) {
|
export default async function middleware(request: NextRequest) {
|
||||||
// // Step 1: Use the incoming request (example)
|
const { pathname } = request.nextUrl;
|
||||||
// const defaultLocale = "in";
|
const defaultLocale = "in";
|
||||||
// // const defaultLocale = request.headers.get("dashcode-locale") || "in";
|
|
||||||
|
|
||||||
// // Step 2: Create and call the next-intl middleware (example)
|
// Check if the pathname already has a locale
|
||||||
// const handleI18nRouting = createMiddleware({
|
const pathnameHasLocale = locales.some(
|
||||||
// locales: ["in", "en"],
|
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
|
||||||
// defaultLocale: "in",
|
);
|
||||||
// });
|
|
||||||
// const response = handleI18nRouting(request);
|
|
||||||
|
|
||||||
// // Step 3: Alter the response (example)
|
// If pathname doesn't have a locale, redirect to default locale
|
||||||
// response.headers.set("dashcode-locale", defaultLocale);
|
if (!pathnameHasLocale) {
|
||||||
|
// Skip redirect for API routes, static files, and Next.js internals
|
||||||
|
if (
|
||||||
|
pathname.startsWith('/api/') ||
|
||||||
|
pathname.startsWith('/_next/') ||
|
||||||
|
pathname.startsWith('/favicon') ||
|
||||||
|
pathname.includes('.') ||
|
||||||
|
pathname.startsWith('/public/')
|
||||||
|
) {
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
// return response;
|
// Redirect to default locale
|
||||||
// }
|
const newUrl = new URL(`/${defaultLocale}${pathname}`, request.url);
|
||||||
|
return NextResponse.redirect(newUrl);
|
||||||
|
}
|
||||||
|
|
||||||
export default createMiddleware({
|
// Use next-intl middleware for internationalized routes
|
||||||
|
const handleI18nRouting = createMiddleware({
|
||||||
locales: locales,
|
locales: locales,
|
||||||
defaultLocale: "in",
|
defaultLocale: defaultLocale,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return handleI18nRouting(request);
|
||||||
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
// Match only internationalized pathnames
|
// Match all pathnames except for
|
||||||
matcher: ["/", "/(in|en|ar)/:path*"],
|
// - API routes
|
||||||
|
// - _next (Next.js internals)
|
||||||
|
// - _static (inside /public)
|
||||||
|
// - all root files inside /public (e.g. favicon.ico)
|
||||||
|
matcher: ['/((?!api|_next|_static|.*\\..*).*)']
|
||||||
};
|
};
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -9,7 +9,8 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ckeditor5": "^41.0.0",
|
"ckeditor5": "^42.0.0",
|
||||||
|
"@ckeditor/ckeditor5-react": "^10.0.0",
|
||||||
"@dnd-kit/core": "^6.1.0",
|
"@dnd-kit/core": "^6.1.0",
|
||||||
"@dnd-kit/modifiers": "^7.0.0",
|
"@dnd-kit/modifiers": "^7.0.0",
|
||||||
"@dnd-kit/sortable": "^8.0.0",
|
"@dnd-kit/sortable": "^8.0.0",
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 756 B |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -1,180 +0,0 @@
|
||||||
Changelog
|
|
||||||
=========
|
|
||||||
|
|
||||||
All changes in the package are documented in the main repository. See: https://github.com/ckeditor/ckeditor5/blob/master/CHANGELOG.md.
|
|
||||||
|
|
||||||
Changes for the past releases are available below.
|
|
||||||
|
|
||||||
## [19.0.0](https://github.com/ckeditor/ckeditor5-clipboard/compare/v18.0.0...v19.0.0) (April 29, 2020)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Handled `paste` and `drop` events no longer propagate up the DOM tree. Closes [ckeditor/ckeditor5#6464](https://github.com/ckeditor/ckeditor5/issues/6464). ([70aa7ba](https://github.com/ckeditor/ckeditor5-clipboard/commit/70aa7ba))
|
|
||||||
|
|
||||||
|
|
||||||
## [18.0.0](https://github.com/ckeditor/ckeditor5-clipboard/compare/v17.0.0...v18.0.0) (March 19, 2020)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [17.0.0](https://github.com/ckeditor/ckeditor5-clipboard/compare/v16.0.0...v17.0.0) (February 19, 2020)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [16.0.0](https://github.com/ckeditor/ckeditor5-clipboard/compare/v15.0.0...v16.0.0) (December 4, 2019)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [15.0.0](https://github.com/ckeditor/ckeditor5-clipboard/compare/v12.0.2...v15.0.0) (October 23, 2019)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [12.0.2](https://github.com/ckeditor/ckeditor5-clipboard/compare/v12.0.1...v12.0.2) (August 26, 2019)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* The issue tracker for this package was moved to https://github.com/ckeditor/ckeditor5/issues. See [ckeditor/ckeditor5#1988](https://github.com/ckeditor/ckeditor5/issues/1988). ([b1782bb](https://github.com/ckeditor/ckeditor5-clipboard/commit/b1782bb))
|
|
||||||
|
|
||||||
|
|
||||||
## [12.0.1](https://github.com/ckeditor/ckeditor5-clipboard/compare/v12.0.0...v12.0.1) (July 10, 2019)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [12.0.0](https://github.com/ckeditor/ckeditor5-clipboard/compare/v11.0.2...v12.0.0) (July 4, 2019)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* New lines pasted as a plain text will always create a new paragraph. Closes [ckeditor/ckeditor5#1727](https://github.com/ckeditor/ckeditor5/issues/1727). ([f0eb3a0](https://github.com/ckeditor/ckeditor5-clipboard/commit/f0eb3a0))
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* From now on, every new line pasted in the editor as a plain text, will create a new paragraph. Read more at [ckeditor/ckeditor5#1727](https://github.com/ckeditor/ckeditor5/issues/1727).
|
|
||||||
|
|
||||||
|
|
||||||
## [11.0.2](https://github.com/ckeditor/ckeditor5-clipboard/compare/v11.0.1...v11.0.2) (June 6, 2019)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [11.0.1](https://github.com/ckeditor/ckeditor5-clipboard/compare/v11.0.0...v11.0.1) (April 4, 2019)
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
* The DOM `drop` event will not bubble up if the `clipboardInput` event was handled. Closes [ckeditor/ckeditor5-upload#92](https://github.com/ckeditor/ckeditor5-upload/issues/92). ([5d14697](https://github.com/ckeditor/ckeditor5-clipboard/commit/5d14697))
|
|
||||||
|
|
||||||
|
|
||||||
## [11.0.0](https://github.com/ckeditor/ckeditor5-clipboard/compare/v10.0.4...v11.0.0) (February 28, 2019)
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* Upgraded minimal versions of Node to `8.0.0` and npm to `5.7.1`. See: [ckeditor/ckeditor5#1507](https://github.com/ckeditor/ckeditor5/issues/1507). ([612ea3c](https://github.com/ckeditor/ckeditor5-cloud-services/commit/612ea3c))
|
|
||||||
|
|
||||||
|
|
||||||
## [10.0.4](https://github.com/ckeditor/ckeditor5-clipboard/compare/v10.0.3...v10.0.4) (December 5, 2018)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [10.0.3](https://github.com/ckeditor/ckeditor5-clipboard/compare/v10.0.2...v10.0.3) (October 8, 2018)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* The `Clipboard#inputTransformation` event is now emitted with an additional `dataTransfer` object. Closes [#54](https://github.com/ckeditor/ckeditor5-clipboard/issues/54) . ([f3589b4](https://github.com/ckeditor/ckeditor5-clipboard/commit/f3589b4))
|
|
||||||
|
|
||||||
|
|
||||||
## [10.0.2](https://github.com/ckeditor/ckeditor5-clipboard/compare/v10.0.1...v10.0.2) (July 18, 2018)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [10.0.1](https://github.com/ckeditor/ckeditor5-clipboard/compare/v10.0.0...v10.0.1) (June 21, 2018)
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
* Disabled the entire clipboard input pipeline when the editor is read-only. Closes [#48](https://github.com/ckeditor/ckeditor5-clipboard/issues/48). ([b40ec4b](https://github.com/ckeditor/ckeditor5-clipboard/commit/b40ec4b))
|
|
||||||
* When pasting a plain text, single new line characters should be converted to `<br>`s. Closes [ckeditor/ckeditor5#766](https://github.com/ckeditor/ckeditor5/issues/766). ([be21676](https://github.com/ckeditor/ckeditor5-clipboard/commit/be21676))
|
|
||||||
|
|
||||||
|
|
||||||
## [10.0.0](https://github.com/ckeditor/ckeditor5-clipboard/compare/v1.0.0-beta.4...v10.0.0) (April 25, 2018)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Changed the license to GPL2+ only. See [ckeditor/ckeditor5#991](https://github.com/ckeditor/ckeditor5/issues/991). ([8c092af](https://github.com/ckeditor/ckeditor5-clipboard/commit/8c092af))
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* The license under which CKEditor 5 is released has been changed from a triple GPL, LGPL and MPL license to a GPL2+ only. See [ckeditor/ckeditor5#991](https://github.com/ckeditor/ckeditor5/issues/991) for more information.
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-beta.4](https://github.com/ckeditor/ckeditor5-clipboard/compare/v1.0.0-beta.2...v1.0.0-beta.4) (April 19, 2018)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-beta.2](https://github.com/ckeditor/ckeditor5-clipboard/compare/v1.0.0-beta.1...v1.0.0-beta.2) (April 10, 2018)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-beta.1](https://github.com/ckeditor/ckeditor5-clipboard/compare/v1.0.0-alpha.2...v1.0.0-beta.1) (March 15, 2018)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-alpha.2](https://github.com/ckeditor/ckeditor5-clipboard/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (November 14, 2017)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-alpha.1](https://github.com/ckeditor/ckeditor5-clipboard/compare/v0.7.0...v1.0.0-alpha.1) (October 3, 2017)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [0.7.0](https://github.com/ckeditor/ckeditor5-clipboard/compare/v0.6.0...v0.7.0) (September 3, 2017)
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
* Whitespaces around inline elements will not be lost upon pasting. Closes [#24](https://github.com/ckeditor/ckeditor5-clipboard/issues/24). ([5888743](https://github.com/ckeditor/ckeditor5-clipboard/commit/5888743))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Added the `'dragover'` event to ClipboardObserver. ([00c7567](https://github.com/ckeditor/ckeditor5-clipboard/commit/00c7567))
|
|
||||||
* Added `dropRange` to the `drop` event and `targetRanges` to the `clipboardInput` event. Closes [#29](https://github.com/ckeditor/ckeditor5-clipboard/issues/29). ([86daed9](https://github.com/ckeditor/ckeditor5-clipboard/commit/86daed9))
|
|
||||||
* Disable pasting and cutting when the editor is read-only. Closes [#26](https://github.com/ckeditor/ckeditor5-clipboard/issues/26). ([0ba74d5](https://github.com/ckeditor/ckeditor5-clipboard/commit/0ba74d5))
|
|
||||||
* The viewport will be scrolled to the selection on paste. See ckeditor/ckeditor5-engine#660. ([9a0e20f](https://github.com/ckeditor/ckeditor5-clipboard/commit/9a0e20f))
|
|
||||||
|
|
||||||
|
|
||||||
## [0.6.0](https://github.com/ckeditor/ckeditor5-clipboard/compare/v0.5.0...v0.6.0) (May 7, 2017)
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
* Plain text data is now available in the clipboard when copying or cutting the editor contents. Closes [#11](https://github.com/ckeditor/ckeditor5-clipboard/issues/11). ([8a01e0f](https://github.com/ckeditor/ckeditor5-clipboard/commit/8a01e0f))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Introduced `DataTransfer#files` property. Change the clipboard input pipeline. Closes [#16](https://github.com/ckeditor/ckeditor5-clipboard/issues/16). ([e4e7e10](https://github.com/ckeditor/ckeditor5-clipboard/commit/e4e7e10))
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* The `clipboardInput` event now contains only the `dataTransfer` property (`content` was removed). The separate `inputTransformation` event was introduced for the content transformations.
|
|
||||||
|
|
||||||
|
|
||||||
## [0.5.0](https://github.com/ckeditor/ckeditor5-clipboard/compare/v0.4.1...v0.5.0) (April 5, 2017)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Aligned use of the `DataController` to the latest API. Closes [#14](https://github.com/ckeditor/ckeditor5-clipboard/issues/14). ([8f98e2b](https://github.com/ckeditor/ckeditor5-clipboard/commit/8f98e2b))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Named existing plugin(s). ([3d37f53](https://github.com/ckeditor/ckeditor5-clipboard/commit/3d37f53))
|
|
||||||
|
|
||||||
|
|
||||||
## [0.4.1](https://github.com/ckeditor/ckeditor5-clipboard/compare/v0.4.0...v0.4.1) (March 6, 2017)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
Software License Agreement
|
|
||||||
==========================
|
|
||||||
|
|
||||||
**CKEditor 5 clipboard feature** – https://github.com/ckeditor/ckeditor5-clipboard <br>
|
|
||||||
Copyright (c) 2003–2024, [CKSource Holding sp. z o.o.](https://cksource.com) All rights reserved.
|
|
||||||
|
|
||||||
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
|
|
||||||
|
|
||||||
Sources of Intellectual Property Included in CKEditor
|
|
||||||
-----------------------------------------------------
|
|
||||||
|
|
||||||
Where not otherwise indicated, all CKEditor content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource with their express permission.
|
|
||||||
|
|
||||||
The following libraries are included in CKEditor under the [MIT license](https://opensource.org/licenses/MIT):
|
|
||||||
|
|
||||||
* Lodash - Copyright (c) JS Foundation and other contributors https://js.foundation/. Based on Underscore.js, copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors http://underscorejs.org/.
|
|
||||||
|
|
||||||
Trademarks
|
|
||||||
----------
|
|
||||||
|
|
||||||
**CKEditor** is a trademark of [CKSource Holding sp. z o.o.](https://cksource.com) All other brand and product names are trademarks, registered trademarks, or service marks of their respective holders.
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
CKEditor 5 clipboard feature
|
|
||||||
========================================
|
|
||||||
|
|
||||||
[](https://www.npmjs.com/package/@ckeditor/ckeditor5-clipboard)
|
|
||||||
[](https://coveralls.io/github/ckeditor/ckeditor5?branch=master)
|
|
||||||
[](https://app.travis-ci.com/github/ckeditor/ckeditor5)
|
|
||||||
|
|
||||||
This package implements the clipboard (copy, cut, paste) support for CKEditor 5.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
See the [`@ckeditor/ckeditor5-clipboard` package](https://ckeditor.com/docs/ckeditor5/latest/api/clipboard.html) page in [CKEditor 5 documentation](https://ckeditor.com/docs/ckeditor5/latest/).
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the `LICENSE.md` file or [https://ckeditor.com/legal/ckeditor-oss-license](https://ckeditor.com/legal/ckeditor-oss-license).
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"Copy selected content": "Keystroke description for assistive technologies: keystroke for copying selected content.",
|
|
||||||
"Paste content": "Keystroke description for assistive technologies: keystroke for pasting content.",
|
|
||||||
"Paste content as plain text": "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Arabic (https://app.transifex.com/ckeditor/teams/11143/ar/)\n"
|
|
||||||
"Language: ar\n"
|
|
||||||
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "انسخْ المحتوى المحدد"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "الصقْ المحتوى"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "الصقْ المحتوى كنص عادي"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Bulgarian (https://app.transifex.com/ckeditor/teams/11143/bg/)\n"
|
|
||||||
"Language: bg\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Копиране на избраното съдържание"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Поставяне на съдържанието"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Поставяне на съдържанието като обикновен текст"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Bengali (https://app.transifex.com/ckeditor/teams/11143/bn/)\n"
|
|
||||||
"Language: bn\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "সিলেক্ট করা কন্টেন্ট কপি করুন"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "কন্টেন্ট পেস্ট করুন"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "প্লেইন টেক্সট হিসেবে কন্টেন্ট পেস্ট করুন"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Catalan (https://app.transifex.com/ckeditor/teams/11143/ca/)\n"
|
|
||||||
"Language: ca\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Copia el contingut seleccionat"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Enganxa el contingut"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Enganxa el contingut com a text pla"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Czech (https://app.transifex.com/ckeditor/teams/11143/cs/)\n"
|
|
||||||
"Language: cs\n"
|
|
||||||
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Zkopírovat vybraný obsah"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Vložit obsah"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Vyložit obsah jako prostý text"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Danish (https://app.transifex.com/ckeditor/teams/11143/da/)\n"
|
|
||||||
"Language: da\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kopier markeret indhold"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Indsæt indhold"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Indsæt indhold som ren tekst"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: German (https://app.transifex.com/ckeditor/teams/11143/de/)\n"
|
|
||||||
"Language: de\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Markierten Inhalt kopieren"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Inhalt einfügen"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Inhalt als Klartext einfügen"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Greek (https://app.transifex.com/ckeditor/teams/11143/el/)\n"
|
|
||||||
"Language: el\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Αντιγραφή επιλεγμένου περιεχομένου"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Επικόλληση περιεχομένου"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Επικόλληση περιεχομένου ως απλό κείμενο"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language: \n"
|
|
||||||
"Language-Team: \n"
|
|
||||||
"Plural-Forms: \n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Copy selected content"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Paste content"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Paste content as plain text"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Spanish (Colombia) (https://app.transifex.com/ckeditor/teams/11143/es_CO/)\n"
|
|
||||||
"Language: es_CO\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Copiar contenido seleccionado"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Pegar contenido"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Pegar contenido como texto plano"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Spanish (https://app.transifex.com/ckeditor/teams/11143/es/)\n"
|
|
||||||
"Language: es\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Copia el contenido seleccionado"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Pega el contenido"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Pega el contenido como texto sin formato"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Estonian (https://app.transifex.com/ckeditor/teams/11143/et/)\n"
|
|
||||||
"Language: et\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kopeeri valitud sisu"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Kleebi sisu"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Kleebi sisu lihttekstina"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Finnish (https://app.transifex.com/ckeditor/teams/11143/fi/)\n"
|
|
||||||
"Language: fi\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kopioi valittu sisältö"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Liitä sisältö"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Liitä sisältö pelkkänä tekstinä"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: French (https://app.transifex.com/ckeditor/teams/11143/fr/)\n"
|
|
||||||
"Language: fr\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Copier le contenu sélectionné"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Coller le contenu"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Coller le contenu sous forme de texte brut"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Hebrew (https://app.transifex.com/ckeditor/teams/11143/he/)\n"
|
|
||||||
"Language: he\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=(n == 1 && n % 1 == 0) ? 0 : (n == 2 && n % 1 == 0) ? 1: 2;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "העתקת התוכן שנבחר"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "הדבקת תוכן"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "הדבקת תוכן כטקסט רגיל"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Hindi (https://app.transifex.com/ckeditor/teams/11143/hi/)\n"
|
|
||||||
"Language: hi\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "सेलेक्ट किए गए कॉन्टेंट को कॉपी करें"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "कॉन्टेंट पेस्ट करें"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "कॉन्टेंट को प्लेन टेक्स्ट के रूप में पेस्ट करें"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Croatian (https://app.transifex.com/ckeditor/teams/11143/hr/)\n"
|
|
||||||
"Language: hr\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kopiraj odabrani sadržaj"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Zalijepi sadržaj"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Zalijepi sadržaj kao čisti tekst"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Hungarian (https://app.transifex.com/ckeditor/teams/11143/hu/)\n"
|
|
||||||
"Language: hu\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kiválasztott tartalom másolása"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Tartalom beillesztése"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Tartalom másolása egyszerű szövegként"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Indonesian (https://app.transifex.com/ckeditor/teams/11143/id/)\n"
|
|
||||||
"Language: id\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Salin konten yang dipilih"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Tempelkan konten"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Tempelkan konten sebagai teks biasa"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Italian (https://app.transifex.com/ckeditor/teams/11143/it/)\n"
|
|
||||||
"Language: it\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Copia il contenuto selezionato"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Incolla il contenuto"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Incolla il contenuto come testo normale"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Japanese (https://app.transifex.com/ckeditor/teams/11143/ja/)\n"
|
|
||||||
"Language: ja\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "選択したコンテンツをコピーする"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "コンテンツを貼り付ける"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "コンテンツをプレーンテキストとして貼り付ける"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Korean (https://app.transifex.com/ckeditor/teams/11143/ko/)\n"
|
|
||||||
"Language: ko\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "선택된 콘텐츠 복사"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "콘텐츠 붙여넣기"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "콘텐츠를 일반 텍스트로 붙여넣기"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Lithuanian (https://app.transifex.com/ckeditor/teams/11143/lt/)\n"
|
|
||||||
"Language: lt\n"
|
|
||||||
"Plural-Forms: nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kopijuoti pasirinktą turinį"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Įklijuoti turinį"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Įklijuoti turinį kaip paprastą tekstą"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Latvian (https://app.transifex.com/ckeditor/teams/11143/lv/)\n"
|
|
||||||
"Language: lv\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kopēt atlasīto saturu"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Ielīmēt saturu"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Ielīmēt saturu kā tekstu"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Malay (https://app.transifex.com/ckeditor/teams/11143/ms/)\n"
|
|
||||||
"Language: ms\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Salin kandungan yang dipilih"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Tampal kandungan"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Tampal kandungan sebagai teks kosong"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Dutch (https://app.transifex.com/ckeditor/teams/11143/nl/)\n"
|
|
||||||
"Language: nl\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kopieer geselecteerde inhoud"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Plak inhoud"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Plak inhoud als onbewerkte tekst"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Norwegian (https://app.transifex.com/ckeditor/teams/11143/no/)\n"
|
|
||||||
"Language: no\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kopier valgt innhold"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Lim inn innhold"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Lim inn innhold som vanlig tekst"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Polish (https://app.transifex.com/ckeditor/teams/11143/pl/)\n"
|
|
||||||
"Language: pl\n"
|
|
||||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kopiuje zaznaczoną zawartość"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Wkleja zawartość"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Wkleja zawartość jako zwykły tekst"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Portuguese (Brazil) (https://app.transifex.com/ckeditor/teams/11143/pt_BR/)\n"
|
|
||||||
"Language: pt_BR\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Copiar conteúdo selecionado"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Colar conteúdo"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Colar conteúdo como texto simples"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Portuguese (https://app.transifex.com/ckeditor/teams/11143/pt/)\n"
|
|
||||||
"Language: pt\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Copiar o conteúdo selecionado"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Colar o conteúdo"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Colar o conteúdo como texto sem formatação"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Romanian (https://app.transifex.com/ckeditor/teams/11143/ro/)\n"
|
|
||||||
"Language: ro\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Copiază conținutul selectat"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Lipește conținut"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Lipește conținutul ca text simplu"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Russian (https://app.transifex.com/ckeditor/teams/11143/ru/)\n"
|
|
||||||
"Language: ru\n"
|
|
||||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Копировать выбранное содержание"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Вставить содержание"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Вставить содержанрие в виде обычного текста"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Slovak (https://app.transifex.com/ckeditor/teams/11143/sk/)\n"
|
|
||||||
"Language: sk\n"
|
|
||||||
"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Skopírovať vybraný obsah"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Prilepiť obsah"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Prilepiť obsah iba ako text"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Serbian (https://app.transifex.com/ckeditor/teams/11143/sr/)\n"
|
|
||||||
"Language: sr\n"
|
|
||||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kopiraj odabrani sadržaj"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Nalepi sadržaj"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Nalepi sadržaj kao običan tekst"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Swedish (https://app.transifex.com/ckeditor/teams/11143/sv/)\n"
|
|
||||||
"Language: sv\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Kopiera markerat innehåll"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Klistra in innehåll"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Klistra in innehåll som vanlig text"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Thai (https://app.transifex.com/ckeditor/teams/11143/th/)\n"
|
|
||||||
"Language: th\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "คัดลอกเนื้อหาที่เลือกเอาไว้"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "วางเนื้อหา"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "วางเนื้อหาเป็นข้อความธรรมดา"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Turkish (https://app.transifex.com/ckeditor/teams/11143/tr/)\n"
|
|
||||||
"Language: tr\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Seçilen içeriği kopyala"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "İçeriği yapıştır"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "İçeriği düz metin olarak yapıştır"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Ukrainian (https://app.transifex.com/ckeditor/teams/11143/uk/)\n"
|
|
||||||
"Language: uk\n"
|
|
||||||
"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Копіювати вибраний вміст"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Вставити вміст"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Вставити вміст як простий текст"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Vietnamese (https://app.transifex.com/ckeditor/teams/11143/vi/)\n"
|
|
||||||
"Language: vi\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "Sao chép nội dung đã chọn"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "Dán nội dung"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "Dán nội dung dưới dạng văn bản thuần túy"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Chinese (China) (https://app.transifex.com/ckeditor/teams/11143/zh_CN/)\n"
|
|
||||||
"Language: zh_CN\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "复制选定的内容"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "粘贴内容"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "将内容粘贴为纯文本"
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
#
|
|
||||||
# !!! IMPORTANT !!!
|
|
||||||
#
|
|
||||||
# Before you edit this file, please keep in mind that contributing to the project
|
|
||||||
# translations is possible ONLY via the Transifex online service.
|
|
||||||
#
|
|
||||||
# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
|
|
||||||
#
|
|
||||||
# To learn more, check out the official contributor's guide:
|
|
||||||
# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
|
|
||||||
#
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Language-Team: Chinese (Taiwan) (https://app.transifex.com/ckeditor/teams/11143/zh_TW/)\n"
|
|
||||||
"Language: zh_TW\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for copying selected content."
|
|
||||||
msgid "Copy selected content"
|
|
||||||
msgstr "複製所選內容"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content."
|
|
||||||
msgid "Paste content"
|
|
||||||
msgstr "貼上內容"
|
|
||||||
|
|
||||||
msgctxt "Keystroke description for assistive technologies: keystroke for pasting content as plain text."
|
|
||||||
msgid "Paste content as plain text"
|
|
||||||
msgstr "將內容貼上為純文字"
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@ckeditor/ckeditor5-clipboard",
|
|
||||||
"version": "41.3.1",
|
|
||||||
"description": "Clipboard integration feature for CKEditor 5.",
|
|
||||||
"keywords": [
|
|
||||||
"ckeditor",
|
|
||||||
"ckeditor5",
|
|
||||||
"ckeditor 5",
|
|
||||||
"ckeditor5-feature",
|
|
||||||
"ckeditor5-plugin",
|
|
||||||
"ckeditor5-dll"
|
|
||||||
],
|
|
||||||
"type": "module",
|
|
||||||
"main": "src/index.js",
|
|
||||||
"dependencies": {
|
|
||||||
"@ckeditor/ckeditor5-core": "41.3.1",
|
|
||||||
"@ckeditor/ckeditor5-engine": "41.3.1",
|
|
||||||
"@ckeditor/ckeditor5-ui": "41.3.1",
|
|
||||||
"@ckeditor/ckeditor5-utils": "41.3.1",
|
|
||||||
"@ckeditor/ckeditor5-widget": "41.3.1",
|
|
||||||
"lodash-es": "4.17.21"
|
|
||||||
},
|
|
||||||
"author": "CKSource (http://cksource.com/)",
|
|
||||||
"license": "GPL-2.0-or-later",
|
|
||||||
"homepage": "https://ckeditor.com/ckeditor-5",
|
|
||||||
"bugs": "https://github.com/ckeditor/ckeditor5/issues",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/ckeditor/ckeditor5.git",
|
|
||||||
"directory": "packages/ckeditor5-clipboard"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"lang",
|
|
||||||
"src/**/*.js",
|
|
||||||
"src/**/*.d.ts",
|
|
||||||
"theme",
|
|
||||||
"ckeditor5-metadata.json",
|
|
||||||
"CHANGELOG.md"
|
|
||||||
],
|
|
||||||
"types": "src/index.d.ts"
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
import type { Clipboard, ClipboardPipeline, PastePlainText, DragDrop, DragDropTarget, DragDropBlockToolbar, ClipboardMarkersUtils } from './index.js';
|
|
||||||
declare module '@ckeditor/ckeditor5-core' {
|
|
||||||
interface PluginsMap {
|
|
||||||
[Clipboard.pluginName]: Clipboard;
|
|
||||||
[ClipboardPipeline.pluginName]: ClipboardPipeline;
|
|
||||||
[ClipboardMarkersUtils.pluginName]: ClipboardMarkersUtils;
|
|
||||||
[PastePlainText.pluginName]: PastePlainText;
|
|
||||||
[DragDrop.pluginName]: DragDrop;
|
|
||||||
[DragDropTarget.pluginName]: DragDropTarget;
|
|
||||||
[DragDropBlockToolbar.pluginName]: DragDropBlockToolbar;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
export {};
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/clipboard
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import ClipboardPipeline from './clipboardpipeline.js';
|
|
||||||
import DragDrop from './dragdrop.js';
|
|
||||||
import PastePlainText from './pasteplaintext.js';
|
|
||||||
import ClipboardMarkersUtils from './clipboardmarkersutils.js';
|
|
||||||
/**
|
|
||||||
* The clipboard feature.
|
|
||||||
*
|
|
||||||
* Read more about the clipboard integration in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.
|
|
||||||
*
|
|
||||||
* This is a "glue" plugin which loads the following plugins:
|
|
||||||
* * {@link module:clipboard/clipboardpipeline~ClipboardPipeline}
|
|
||||||
* * {@link module:clipboard/dragdrop~DragDrop}
|
|
||||||
* * {@link module:clipboard/pasteplaintext~PastePlainText}
|
|
||||||
*/
|
|
||||||
export default class Clipboard extends Plugin {
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName(): "Clipboard";
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get requires(): readonly [typeof ClipboardMarkersUtils, typeof ClipboardPipeline, typeof DragDrop, typeof PastePlainText];
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init(): void;
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/clipboard
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import ClipboardPipeline from './clipboardpipeline.js';
|
|
||||||
import DragDrop from './dragdrop.js';
|
|
||||||
import PastePlainText from './pasteplaintext.js';
|
|
||||||
import ClipboardMarkersUtils from './clipboardmarkersutils.js';
|
|
||||||
/**
|
|
||||||
* The clipboard feature.
|
|
||||||
*
|
|
||||||
* Read more about the clipboard integration in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.
|
|
||||||
*
|
|
||||||
* This is a "glue" plugin which loads the following plugins:
|
|
||||||
* * {@link module:clipboard/clipboardpipeline~ClipboardPipeline}
|
|
||||||
* * {@link module:clipboard/dragdrop~DragDrop}
|
|
||||||
* * {@link module:clipboard/pasteplaintext~PastePlainText}
|
|
||||||
*/
|
|
||||||
export default class Clipboard extends Plugin {
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName() {
|
|
||||||
return 'Clipboard';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get requires() {
|
|
||||||
return [ClipboardMarkersUtils, ClipboardPipeline, DragDrop, PastePlainText];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
const editor = this.editor;
|
|
||||||
const t = this.editor.t;
|
|
||||||
// Add the information about the keystrokes to the accessibility database.
|
|
||||||
editor.accessibility.addKeystrokeInfos({
|
|
||||||
keystrokes: [
|
|
||||||
{
|
|
||||||
label: t('Copy selected content'),
|
|
||||||
keystroke: 'CTRL+C'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('Paste content'),
|
|
||||||
keystroke: 'CTRL+V'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('Paste content as plain text'),
|
|
||||||
keystroke: 'CTRL+SHIFT+V'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,200 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
import { Plugin, type NonEmptyArray } from '@ckeditor/ckeditor5-core';
|
|
||||||
import { Range, type DocumentFragment, type Element, type DocumentSelection, type Selection, type Writer } from '@ckeditor/ckeditor5-engine';
|
|
||||||
/**
|
|
||||||
* Part of the clipboard logic. Responsible for collecting markers from selected fragments
|
|
||||||
* and restoring them with proper positions in pasted elements.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export default class ClipboardMarkersUtils extends Plugin {
|
|
||||||
/**
|
|
||||||
* Map of marker names that can be copied.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
private _markersToCopy;
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName(): "ClipboardMarkersUtils";
|
|
||||||
/**
|
|
||||||
* Registers marker name as copyable in clipboard pipeline.
|
|
||||||
*
|
|
||||||
* @param markerName Name of marker that can be copied.
|
|
||||||
* @param config Configuration that describes what can be performed on specified marker.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_registerMarkerToCopy(markerName: string, config: ClipboardMarkerConfiguration): void;
|
|
||||||
/**
|
|
||||||
* Performs copy markers on provided selection and paste it to fragment returned from `getCopiedFragment`.
|
|
||||||
*
|
|
||||||
* 1. Picks all markers in provided selection.
|
|
||||||
* 2. Inserts fake markers to document.
|
|
||||||
* 3. Gets copied selection fragment from document.
|
|
||||||
* 4. Removes fake elements from fragment and document.
|
|
||||||
* 5. Inserts markers in the place of removed fake markers.
|
|
||||||
*
|
|
||||||
* Due to selection modification, when inserting items, `getCopiedFragment` must *always* operate on `writer.model.document.selection'.
|
|
||||||
* Do not use any other custom selection object within callback, as this will lead to out-of-bounds exceptions in rare scenarios.
|
|
||||||
*
|
|
||||||
* @param action Type of clipboard action.
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param selection Selection to be checked.
|
|
||||||
* @param getCopiedFragment Callback that performs copy of selection and returns it as fragment.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_copySelectedFragmentWithMarkers(action: ClipboardMarkerRestrictedAction, selection: Selection | DocumentSelection, getCopiedFragment?: (writer: Writer) => DocumentFragment): DocumentFragment;
|
|
||||||
/**
|
|
||||||
* Performs paste of markers on already pasted element.
|
|
||||||
*
|
|
||||||
* 1. Inserts fake markers that are present in fragment element (such fragment will be processed in `getPastedDocumentElement`).
|
|
||||||
* 2. Calls `getPastedDocumentElement` and gets element that is inserted into root model.
|
|
||||||
* 3. Removes all fake markers present in transformed element.
|
|
||||||
* 4. Inserts new markers with removed fake markers ranges into pasted fragment.
|
|
||||||
*
|
|
||||||
* There are multiple edge cases that have to be considered before calling this function:
|
|
||||||
*
|
|
||||||
* * `markers` are inserted into the same element that must be later transformed inside `getPastedDocumentElement`.
|
|
||||||
* * Fake marker elements inside `getPastedDocumentElement` can be cloned, but their ranges cannot overlap.
|
|
||||||
* * If `duplicateOnPaste` is `true` in marker config then associated marker ID is regenerated before pasting.
|
|
||||||
*
|
|
||||||
* @param action Type of clipboard action.
|
|
||||||
* @param markers Object that maps marker name to corresponding range.
|
|
||||||
* @param getPastedDocumentElement Getter used to get target markers element.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_pasteMarkersIntoTransformedElement(markers: Record<string, Range> | Map<string, Range>, getPastedDocumentElement: (writer: Writer) => Element): Element;
|
|
||||||
/**
|
|
||||||
* Pastes document fragment with markers to document.
|
|
||||||
* If `duplicateOnPaste` is `true` in marker config then associated markers IDs
|
|
||||||
* are regenerated before pasting to avoid markers duplications in content.
|
|
||||||
*
|
|
||||||
* @param fragment Document fragment that should contain already processed by pipeline markers.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_pasteFragmentWithMarkers(fragment: DocumentFragment): Range;
|
|
||||||
/**
|
|
||||||
* In some situations we have to perform copy on selected fragment with certain markers. This function allows to temporarily bypass
|
|
||||||
* restrictions on markers that we want to copy.
|
|
||||||
*
|
|
||||||
* This function executes `executor()` callback. For the duration of the callback, if the clipboard pipeline is used to copy
|
|
||||||
* content, markers with the specified name will be copied to the clipboard as well.
|
|
||||||
*
|
|
||||||
* @param markerName Which markers should be copied.
|
|
||||||
* @param executor Callback executed.
|
|
||||||
* @param config Optional configuration flags used to copy (such like partial copy flag).
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_forceMarkersCopy(markerName: string, executor: VoidFunction, config?: ClipboardMarkerConfiguration): void;
|
|
||||||
/**
|
|
||||||
* Checks if marker can be copied.
|
|
||||||
*
|
|
||||||
* @param markerName Name of checked marker.
|
|
||||||
* @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_isMarkerCopyable(markerName: string, action: ClipboardMarkerRestrictedAction | null): boolean;
|
|
||||||
/**
|
|
||||||
* Checks if marker has any clipboard copy behavior configuration.
|
|
||||||
*
|
|
||||||
* @param markerName Name of checked marker.
|
|
||||||
*/
|
|
||||||
_hasMarkerConfiguration(markerName: string): boolean;
|
|
||||||
/**
|
|
||||||
* Returns marker's configuration flags passed during registration.
|
|
||||||
*
|
|
||||||
* @param markerName Name of marker that should be returned.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_getMarkerClipboardConfig(markerName: string): ClipboardMarkerConfiguration | null;
|
|
||||||
/**
|
|
||||||
* First step of copying markers. It looks for markers intersecting with given selection and inserts `$marker` elements
|
|
||||||
* at positions where document markers start or end. This way `$marker` elements can be easily copied together with
|
|
||||||
* the rest of the content of the selection.
|
|
||||||
*
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param selection Selection to be checked.
|
|
||||||
* @param action Type of clipboard action.
|
|
||||||
*/
|
|
||||||
private _insertFakeMarkersIntoSelection;
|
|
||||||
/**
|
|
||||||
* Returns array of markers that can be copied in specified selection.
|
|
||||||
*
|
|
||||||
* If marker cannot be copied partially (according to `copyPartiallySelected` configuration flag) and
|
|
||||||
* is not present entirely in any selection range then it will be skipped.
|
|
||||||
*
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param selection Selection which will be checked.
|
|
||||||
* @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
|
|
||||||
*/
|
|
||||||
private _getCopyableMarkersFromSelection;
|
|
||||||
/**
|
|
||||||
* Picks all markers from markers map that can be pasted.
|
|
||||||
* If `duplicateOnPaste` is `true`, it regenerates their IDs to ensure uniqueness.
|
|
||||||
* If marker is not registered, it will be kept in the array anyway.
|
|
||||||
*
|
|
||||||
* @param markers Object that maps marker name to corresponding range.
|
|
||||||
* @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
|
|
||||||
*/
|
|
||||||
private _getPasteMarkersFromRangeMap;
|
|
||||||
/**
|
|
||||||
* Inserts specified array of fake markers elements to document and assigns them `type` and `name` attributes.
|
|
||||||
* Fake markers elements are used to calculate position of markers on pasted fragment that were transformed during
|
|
||||||
* steps between copy and paste.
|
|
||||||
*
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param markers Array of markers that will be inserted.
|
|
||||||
*/
|
|
||||||
private _insertFakeMarkersElements;
|
|
||||||
/**
|
|
||||||
* Removes all `$marker` elements from the given document fragment.
|
|
||||||
*
|
|
||||||
* Returns an object where keys are marker names, and values are ranges corresponding to positions
|
|
||||||
* where `$marker` elements were inserted.
|
|
||||||
*
|
|
||||||
* If the document fragment had only one `$marker` element for given marker (start or end) the other boundary is set automatically
|
|
||||||
* (to the end or start of the document fragment, respectively).
|
|
||||||
*
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param rootElement The element to be checked.
|
|
||||||
*/
|
|
||||||
private _removeFakeMarkersInsideElement;
|
|
||||||
/**
|
|
||||||
* Returns array that contains list of fake markers with corresponding `$marker` elements.
|
|
||||||
*
|
|
||||||
* For each marker, there can be two `$marker` elements or only one (if the document fragment contained
|
|
||||||
* only the beginning or only the end of a marker).
|
|
||||||
*
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param rootElement The element to be checked.
|
|
||||||
*/
|
|
||||||
private _getAllFakeMarkersFromElement;
|
|
||||||
/**
|
|
||||||
* When copy of markers occurs we have to make sure that pasted markers have different names
|
|
||||||
* than source markers. This functions helps with assigning unique part to marker name to
|
|
||||||
* prevent duplicated markers error.
|
|
||||||
*
|
|
||||||
* @param name Name of marker
|
|
||||||
*/
|
|
||||||
private _getUniqueMarkerName;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Specifies which action is performed during clipboard event.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export type ClipboardMarkerRestrictedAction = 'copy' | 'cut' | 'dragstart';
|
|
||||||
/**
|
|
||||||
* Specifies behavior of markers during clipboard actions.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export type ClipboardMarkerConfiguration = {
|
|
||||||
allowedActions: NonEmptyArray<ClipboardMarkerRestrictedAction> | 'all';
|
|
||||||
copyPartiallySelected?: boolean;
|
|
||||||
duplicateOnPaste?: boolean;
|
|
||||||
};
|
|
||||||
|
|
@ -1,499 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/clipboardmarkersutils
|
|
||||||
*/
|
|
||||||
import { mapValues } from 'lodash-es';
|
|
||||||
import { uid } from '@ckeditor/ckeditor5-utils';
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import { Range } from '@ckeditor/ckeditor5-engine';
|
|
||||||
/**
|
|
||||||
* Part of the clipboard logic. Responsible for collecting markers from selected fragments
|
|
||||||
* and restoring them with proper positions in pasted elements.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export default class ClipboardMarkersUtils extends Plugin {
|
|
||||||
constructor() {
|
|
||||||
super(...arguments);
|
|
||||||
/**
|
|
||||||
* Map of marker names that can be copied.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
this._markersToCopy = new Map();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName() {
|
|
||||||
return 'ClipboardMarkersUtils';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Registers marker name as copyable in clipboard pipeline.
|
|
||||||
*
|
|
||||||
* @param markerName Name of marker that can be copied.
|
|
||||||
* @param config Configuration that describes what can be performed on specified marker.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_registerMarkerToCopy(markerName, config) {
|
|
||||||
this._markersToCopy.set(markerName, config);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Performs copy markers on provided selection and paste it to fragment returned from `getCopiedFragment`.
|
|
||||||
*
|
|
||||||
* 1. Picks all markers in provided selection.
|
|
||||||
* 2. Inserts fake markers to document.
|
|
||||||
* 3. Gets copied selection fragment from document.
|
|
||||||
* 4. Removes fake elements from fragment and document.
|
|
||||||
* 5. Inserts markers in the place of removed fake markers.
|
|
||||||
*
|
|
||||||
* Due to selection modification, when inserting items, `getCopiedFragment` must *always* operate on `writer.model.document.selection'.
|
|
||||||
* Do not use any other custom selection object within callback, as this will lead to out-of-bounds exceptions in rare scenarios.
|
|
||||||
*
|
|
||||||
* @param action Type of clipboard action.
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param selection Selection to be checked.
|
|
||||||
* @param getCopiedFragment Callback that performs copy of selection and returns it as fragment.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_copySelectedFragmentWithMarkers(action, selection, getCopiedFragment = writer => writer.model.getSelectedContent(writer.model.document.selection)) {
|
|
||||||
return this.editor.model.change(writer => {
|
|
||||||
const oldSelection = writer.model.document.selection;
|
|
||||||
// In some scenarios, such like in drag & drop, passed `selection` parameter is not actually
|
|
||||||
// the same `selection` as the `writer.model.document.selection` which means that `_insertFakeMarkersToSelection`
|
|
||||||
// is not affecting passed `selection` `start` and `end` positions but rather modifies `writer.model.document.selection`.
|
|
||||||
//
|
|
||||||
// It is critical due to fact that when we have selection that starts [ 0, 0 ] and ends at [ 1, 0 ]
|
|
||||||
// and after inserting fake marker it will point to such marker instead of new widget position at start: [ 1, 0 ] end: [2, 0 ].
|
|
||||||
// `writer.insert` modifies only original `writer.model.document.selection`.
|
|
||||||
writer.setSelection(selection);
|
|
||||||
const sourceSelectionInsertedMarkers = this._insertFakeMarkersIntoSelection(writer, writer.model.document.selection, action);
|
|
||||||
const fragment = getCopiedFragment(writer);
|
|
||||||
const fakeMarkersRangesInsideRange = this._removeFakeMarkersInsideElement(writer, fragment);
|
|
||||||
// <fake-marker> [Foo] Bar</fake-marker>
|
|
||||||
// ^ ^
|
|
||||||
// In `_insertFakeMarkersIntoSelection` call we inserted fake marker just before first element.
|
|
||||||
// The problem is that the first element can be start position of selection so insertion fake-marker
|
|
||||||
// before such element shifts selection (so selection that was at [0, 0] now is at [0, 1]).
|
|
||||||
// It means that inserted fake-marker is no longer present inside such selection and is orphaned.
|
|
||||||
// This function checks special case of such problem. Markers that are orphaned at the start position
|
|
||||||
// and end position in the same time. Basically it means that they overlaps whole element.
|
|
||||||
for (const [markerName, elements] of Object.entries(sourceSelectionInsertedMarkers)) {
|
|
||||||
fakeMarkersRangesInsideRange[markerName] || (fakeMarkersRangesInsideRange[markerName] = writer.createRangeIn(fragment));
|
|
||||||
for (const element of elements) {
|
|
||||||
writer.remove(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fragment.markers.clear();
|
|
||||||
for (const [markerName, range] of Object.entries(fakeMarkersRangesInsideRange)) {
|
|
||||||
fragment.markers.set(markerName, range);
|
|
||||||
}
|
|
||||||
// Revert back selection to previous one.
|
|
||||||
writer.setSelection(oldSelection);
|
|
||||||
return fragment;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Performs paste of markers on already pasted element.
|
|
||||||
*
|
|
||||||
* 1. Inserts fake markers that are present in fragment element (such fragment will be processed in `getPastedDocumentElement`).
|
|
||||||
* 2. Calls `getPastedDocumentElement` and gets element that is inserted into root model.
|
|
||||||
* 3. Removes all fake markers present in transformed element.
|
|
||||||
* 4. Inserts new markers with removed fake markers ranges into pasted fragment.
|
|
||||||
*
|
|
||||||
* There are multiple edge cases that have to be considered before calling this function:
|
|
||||||
*
|
|
||||||
* * `markers` are inserted into the same element that must be later transformed inside `getPastedDocumentElement`.
|
|
||||||
* * Fake marker elements inside `getPastedDocumentElement` can be cloned, but their ranges cannot overlap.
|
|
||||||
* * If `duplicateOnPaste` is `true` in marker config then associated marker ID is regenerated before pasting.
|
|
||||||
*
|
|
||||||
* @param action Type of clipboard action.
|
|
||||||
* @param markers Object that maps marker name to corresponding range.
|
|
||||||
* @param getPastedDocumentElement Getter used to get target markers element.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_pasteMarkersIntoTransformedElement(markers, getPastedDocumentElement) {
|
|
||||||
const pasteMarkers = this._getPasteMarkersFromRangeMap(markers);
|
|
||||||
return this.editor.model.change(writer => {
|
|
||||||
// Inserts fake markers into source fragment / element that is later transformed inside `getPastedDocumentElement`.
|
|
||||||
const sourceFragmentFakeMarkers = this._insertFakeMarkersElements(writer, pasteMarkers);
|
|
||||||
// Modifies document fragment (for example, cloning table cells) and then inserts it into the document.
|
|
||||||
const transformedElement = getPastedDocumentElement(writer);
|
|
||||||
// Removes markers in pasted and transformed fragment in root document.
|
|
||||||
const removedFakeMarkers = this._removeFakeMarkersInsideElement(writer, transformedElement);
|
|
||||||
// Cleans up fake markers inserted into source fragment (that one before transformation which is not pasted).
|
|
||||||
for (const element of Object.values(sourceFragmentFakeMarkers).flat()) {
|
|
||||||
writer.remove(element);
|
|
||||||
}
|
|
||||||
// Inserts to root document fake markers.
|
|
||||||
for (const [markerName, range] of Object.entries(removedFakeMarkers)) {
|
|
||||||
if (!writer.model.markers.has(markerName)) {
|
|
||||||
writer.addMarker(markerName, {
|
|
||||||
usingOperation: true,
|
|
||||||
affectsData: true,
|
|
||||||
range
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return transformedElement;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Pastes document fragment with markers to document.
|
|
||||||
* If `duplicateOnPaste` is `true` in marker config then associated markers IDs
|
|
||||||
* are regenerated before pasting to avoid markers duplications in content.
|
|
||||||
*
|
|
||||||
* @param fragment Document fragment that should contain already processed by pipeline markers.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_pasteFragmentWithMarkers(fragment) {
|
|
||||||
const pasteMarkers = this._getPasteMarkersFromRangeMap(fragment.markers);
|
|
||||||
fragment.markers.clear();
|
|
||||||
for (const copyableMarker of pasteMarkers) {
|
|
||||||
fragment.markers.set(copyableMarker.name, copyableMarker.range);
|
|
||||||
}
|
|
||||||
return this.editor.model.insertContent(fragment);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* In some situations we have to perform copy on selected fragment with certain markers. This function allows to temporarily bypass
|
|
||||||
* restrictions on markers that we want to copy.
|
|
||||||
*
|
|
||||||
* This function executes `executor()` callback. For the duration of the callback, if the clipboard pipeline is used to copy
|
|
||||||
* content, markers with the specified name will be copied to the clipboard as well.
|
|
||||||
*
|
|
||||||
* @param markerName Which markers should be copied.
|
|
||||||
* @param executor Callback executed.
|
|
||||||
* @param config Optional configuration flags used to copy (such like partial copy flag).
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_forceMarkersCopy(markerName, executor, config = {
|
|
||||||
allowedActions: 'all',
|
|
||||||
copyPartiallySelected: true,
|
|
||||||
duplicateOnPaste: true
|
|
||||||
}) {
|
|
||||||
const before = this._markersToCopy.get(markerName);
|
|
||||||
this._markersToCopy.set(markerName, config);
|
|
||||||
executor();
|
|
||||||
if (before) {
|
|
||||||
this._markersToCopy.set(markerName, before);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._markersToCopy.delete(markerName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks if marker can be copied.
|
|
||||||
*
|
|
||||||
* @param markerName Name of checked marker.
|
|
||||||
* @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_isMarkerCopyable(markerName, action) {
|
|
||||||
const config = this._getMarkerClipboardConfig(markerName);
|
|
||||||
if (!config) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// If there is no action provided then only presence of marker is checked.
|
|
||||||
if (!action) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const { allowedActions } = config;
|
|
||||||
return allowedActions === 'all' || allowedActions.includes(action);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Checks if marker has any clipboard copy behavior configuration.
|
|
||||||
*
|
|
||||||
* @param markerName Name of checked marker.
|
|
||||||
*/
|
|
||||||
_hasMarkerConfiguration(markerName) {
|
|
||||||
return !!this._getMarkerClipboardConfig(markerName);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns marker's configuration flags passed during registration.
|
|
||||||
*
|
|
||||||
* @param markerName Name of marker that should be returned.
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_getMarkerClipboardConfig(markerName) {
|
|
||||||
const [markerNamePrefix] = markerName.split(':');
|
|
||||||
return this._markersToCopy.get(markerNamePrefix) || null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* First step of copying markers. It looks for markers intersecting with given selection and inserts `$marker` elements
|
|
||||||
* at positions where document markers start or end. This way `$marker` elements can be easily copied together with
|
|
||||||
* the rest of the content of the selection.
|
|
||||||
*
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param selection Selection to be checked.
|
|
||||||
* @param action Type of clipboard action.
|
|
||||||
*/
|
|
||||||
_insertFakeMarkersIntoSelection(writer, selection, action) {
|
|
||||||
const copyableMarkers = this._getCopyableMarkersFromSelection(writer, selection, action);
|
|
||||||
return this._insertFakeMarkersElements(writer, copyableMarkers);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns array of markers that can be copied in specified selection.
|
|
||||||
*
|
|
||||||
* If marker cannot be copied partially (according to `copyPartiallySelected` configuration flag) and
|
|
||||||
* is not present entirely in any selection range then it will be skipped.
|
|
||||||
*
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param selection Selection which will be checked.
|
|
||||||
* @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
|
|
||||||
*/
|
|
||||||
_getCopyableMarkersFromSelection(writer, selection, action) {
|
|
||||||
const selectionRanges = Array.from(selection.getRanges());
|
|
||||||
// Picks all markers in provided ranges. Ensures that there are no duplications if
|
|
||||||
// there are multiple ranges that intersects with the same marker.
|
|
||||||
const markersInRanges = new Set(selectionRanges.flatMap(selectionRange => Array.from(writer.model.markers.getMarkersIntersectingRange(selectionRange))));
|
|
||||||
const isSelectionMarkerCopyable = (marker) => {
|
|
||||||
// Check if marker exists in configuration and provided action can be performed on it.
|
|
||||||
const isCopyable = this._isMarkerCopyable(marker.name, action);
|
|
||||||
if (!isCopyable) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// Checks if configuration disallows to copy marker only if part of its content is selected.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
// <marker-a> Hello [ World ] </marker-a>
|
|
||||||
// ^ selection
|
|
||||||
//
|
|
||||||
// In this scenario `marker-a` won't be copied because selection doesn't overlap its content entirely.
|
|
||||||
const { copyPartiallySelected } = this._getMarkerClipboardConfig(marker.name);
|
|
||||||
if (!copyPartiallySelected) {
|
|
||||||
const markerRange = marker.getRange();
|
|
||||||
return selectionRanges.some(selectionRange => selectionRange.containsRange(markerRange, true));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
return Array
|
|
||||||
.from(markersInRanges)
|
|
||||||
.filter(isSelectionMarkerCopyable)
|
|
||||||
.map((copyableMarker) => {
|
|
||||||
// During `dragstart` event original marker is still present in tree.
|
|
||||||
// It is removed after the clipboard drop event, so none of the copied markers are inserted at the end.
|
|
||||||
// It happens because there already markers with specified `marker.name` when clipboard is trying to insert data
|
|
||||||
// and it aborts inserting.
|
|
||||||
const name = action === 'dragstart' ? this._getUniqueMarkerName(copyableMarker.name) : copyableMarker.name;
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
range: copyableMarker.getRange()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Picks all markers from markers map that can be pasted.
|
|
||||||
* If `duplicateOnPaste` is `true`, it regenerates their IDs to ensure uniqueness.
|
|
||||||
* If marker is not registered, it will be kept in the array anyway.
|
|
||||||
*
|
|
||||||
* @param markers Object that maps marker name to corresponding range.
|
|
||||||
* @param action Type of clipboard action. If null then checks only if marker is registered as copyable.
|
|
||||||
*/
|
|
||||||
_getPasteMarkersFromRangeMap(markers, action = null) {
|
|
||||||
const { model } = this.editor;
|
|
||||||
const entries = markers instanceof Map ? Array.from(markers.entries()) : Object.entries(markers);
|
|
||||||
return entries.flatMap(([markerName, range]) => {
|
|
||||||
if (!this._hasMarkerConfiguration(markerName)) {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: markerName,
|
|
||||||
range
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if (this._isMarkerCopyable(markerName, action)) {
|
|
||||||
const copyMarkerConfig = this._getMarkerClipboardConfig(markerName);
|
|
||||||
const isInGraveyard = model.markers.has(markerName) &&
|
|
||||||
model.markers.get(markerName).getRange().root.rootName === '$graveyard';
|
|
||||||
if (copyMarkerConfig.duplicateOnPaste || isInGraveyard) {
|
|
||||||
markerName = this._getUniqueMarkerName(markerName);
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
name: markerName,
|
|
||||||
range
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Inserts specified array of fake markers elements to document and assigns them `type` and `name` attributes.
|
|
||||||
* Fake markers elements are used to calculate position of markers on pasted fragment that were transformed during
|
|
||||||
* steps between copy and paste.
|
|
||||||
*
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param markers Array of markers that will be inserted.
|
|
||||||
*/
|
|
||||||
_insertFakeMarkersElements(writer, markers) {
|
|
||||||
const mappedMarkers = {};
|
|
||||||
const sortedMarkers = markers
|
|
||||||
.flatMap(marker => {
|
|
||||||
const { start, end } = marker.range;
|
|
||||||
return [
|
|
||||||
{ position: start, marker, type: 'start' },
|
|
||||||
{ position: end, marker, type: 'end' }
|
|
||||||
];
|
|
||||||
})
|
|
||||||
// Markers position is sorted backwards to ensure that the insertion of fake markers will not change
|
|
||||||
// the position of the next markers.
|
|
||||||
.sort(({ position: posA }, { position: posB }) => posA.isBefore(posB) ? 1 : -1);
|
|
||||||
for (const { position, marker, type } of sortedMarkers) {
|
|
||||||
const fakeMarker = writer.createElement('$marker', {
|
|
||||||
'data-name': marker.name,
|
|
||||||
'data-type': type
|
|
||||||
});
|
|
||||||
if (!mappedMarkers[marker.name]) {
|
|
||||||
mappedMarkers[marker.name] = [];
|
|
||||||
}
|
|
||||||
mappedMarkers[marker.name].push(fakeMarker);
|
|
||||||
writer.insert(fakeMarker, position);
|
|
||||||
}
|
|
||||||
return mappedMarkers;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Removes all `$marker` elements from the given document fragment.
|
|
||||||
*
|
|
||||||
* Returns an object where keys are marker names, and values are ranges corresponding to positions
|
|
||||||
* where `$marker` elements were inserted.
|
|
||||||
*
|
|
||||||
* If the document fragment had only one `$marker` element for given marker (start or end) the other boundary is set automatically
|
|
||||||
* (to the end or start of the document fragment, respectively).
|
|
||||||
*
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param rootElement The element to be checked.
|
|
||||||
*/
|
|
||||||
_removeFakeMarkersInsideElement(writer, rootElement) {
|
|
||||||
const fakeMarkersElements = this._getAllFakeMarkersFromElement(writer, rootElement);
|
|
||||||
const fakeMarkersRanges = fakeMarkersElements.reduce((acc, fakeMarker) => {
|
|
||||||
const position = fakeMarker.markerElement && writer.createPositionBefore(fakeMarker.markerElement);
|
|
||||||
let prevFakeMarker = acc[fakeMarker.name];
|
|
||||||
// Handle scenario when tables clone cells with the same fake node. Example:
|
|
||||||
//
|
|
||||||
// <cell><fake-marker-a></cell> <cell><fake-marker-a></cell> <cell><fake-marker-a></cell>
|
|
||||||
// ^ cloned ^ cloned
|
|
||||||
//
|
|
||||||
// The easiest way to bypass this issue is to rename already existing in map nodes and
|
|
||||||
// set them new unique name.
|
|
||||||
let skipAssign = false;
|
|
||||||
if (prevFakeMarker && prevFakeMarker.start && prevFakeMarker.end) {
|
|
||||||
const config = this._getMarkerClipboardConfig(fakeMarker.name);
|
|
||||||
if (config.duplicateOnPaste) {
|
|
||||||
acc[this._getUniqueMarkerName(fakeMarker.name)] = acc[fakeMarker.name];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
skipAssign = true;
|
|
||||||
}
|
|
||||||
prevFakeMarker = null;
|
|
||||||
}
|
|
||||||
if (!skipAssign) {
|
|
||||||
acc[fakeMarker.name] = {
|
|
||||||
...prevFakeMarker,
|
|
||||||
[fakeMarker.type]: position
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (fakeMarker.markerElement) {
|
|
||||||
writer.remove(fakeMarker.markerElement);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
// We cannot construct ranges directly in previous reduce because element ranges can overlap.
|
|
||||||
// In other words lets assume we have such scenario:
|
|
||||||
// <fake-marker-start /> <paragraph /> <fake-marker-2-start /> <fake-marker-end /> <fake-marker-2-end />
|
|
||||||
//
|
|
||||||
// We have to remove `fake-marker-start` firstly and then remove `fake-marker-2-start`.
|
|
||||||
// Removal of `fake-marker-2-start` affects `fake-marker-end` position so we cannot create
|
|
||||||
// connection between `fake-marker-start` and `fake-marker-end` without iterating whole set firstly.
|
|
||||||
return mapValues(fakeMarkersRanges, range => new Range(range.start || writer.createPositionFromPath(rootElement, [0]), range.end || writer.createPositionAt(rootElement, 'end')));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns array that contains list of fake markers with corresponding `$marker` elements.
|
|
||||||
*
|
|
||||||
* For each marker, there can be two `$marker` elements or only one (if the document fragment contained
|
|
||||||
* only the beginning or only the end of a marker).
|
|
||||||
*
|
|
||||||
* @param writer An instance of the model writer.
|
|
||||||
* @param rootElement The element to be checked.
|
|
||||||
*/
|
|
||||||
_getAllFakeMarkersFromElement(writer, rootElement) {
|
|
||||||
const foundFakeMarkers = Array
|
|
||||||
.from(writer.createRangeIn(rootElement))
|
|
||||||
.flatMap(({ item }) => {
|
|
||||||
if (!item.is('element', '$marker')) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const name = item.getAttribute('data-name');
|
|
||||||
const type = item.getAttribute('data-type');
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
markerElement: item,
|
|
||||||
name,
|
|
||||||
type
|
|
||||||
}
|
|
||||||
];
|
|
||||||
});
|
|
||||||
const prependFakeMarkers = [];
|
|
||||||
const appendFakeMarkers = [];
|
|
||||||
for (const fakeMarker of foundFakeMarkers) {
|
|
||||||
if (fakeMarker.type === 'end') {
|
|
||||||
// <fake-marker> [ phrase</fake-marker> phrase ]
|
|
||||||
// ^
|
|
||||||
// Handle case when marker is just before start of selection.
|
|
||||||
// Only end marker is inside selection.
|
|
||||||
const hasMatchingStartMarker = foundFakeMarkers.some(otherFakeMarker => otherFakeMarker.name === fakeMarker.name && otherFakeMarker.type === 'start');
|
|
||||||
if (!hasMatchingStartMarker) {
|
|
||||||
prependFakeMarkers.push({
|
|
||||||
markerElement: null,
|
|
||||||
name: fakeMarker.name,
|
|
||||||
type: 'start'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fakeMarker.type === 'start') {
|
|
||||||
// [<fake-marker>phrase]</fake-marker>
|
|
||||||
// ^
|
|
||||||
// Handle case when fake marker is after selection.
|
|
||||||
// Only start marker is inside selection.
|
|
||||||
const hasMatchingEndMarker = foundFakeMarkers.some(otherFakeMarker => otherFakeMarker.name === fakeMarker.name && otherFakeMarker.type === 'end');
|
|
||||||
if (!hasMatchingEndMarker) {
|
|
||||||
appendFakeMarkers.unshift({
|
|
||||||
markerElement: null,
|
|
||||||
name: fakeMarker.name,
|
|
||||||
type: 'end'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
...prependFakeMarkers,
|
|
||||||
...foundFakeMarkers,
|
|
||||||
...appendFakeMarkers
|
|
||||||
];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* When copy of markers occurs we have to make sure that pasted markers have different names
|
|
||||||
* than source markers. This functions helps with assigning unique part to marker name to
|
|
||||||
* prevent duplicated markers error.
|
|
||||||
*
|
|
||||||
* @param name Name of marker
|
|
||||||
*/
|
|
||||||
_getUniqueMarkerName(name) {
|
|
||||||
const parts = name.split(':');
|
|
||||||
const newId = uid().substring(1, 6);
|
|
||||||
// It looks like the marker already is UID marker so in this scenario just swap
|
|
||||||
// last part of marker name and assign new UID.
|
|
||||||
//
|
|
||||||
// example: comment:{ threadId }:{ id } => comment:{ threadId }:{ newId }
|
|
||||||
if (parts.length === 3) {
|
|
||||||
return `${parts.slice(0, 2).join(':')}:${newId}`;
|
|
||||||
}
|
|
||||||
// Assign new segment to marker name with id.
|
|
||||||
//
|
|
||||||
// example: comment => comment:{ newId }
|
|
||||||
return `${parts.join(':')}:${newId}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,312 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
import { DataTransfer, DomEventObserver, type DomEventData, type EditingView, type ViewDocumentFragment, type ViewElement, type ViewRange } from '@ckeditor/ckeditor5-engine';
|
|
||||||
/**
|
|
||||||
* Clipboard events observer.
|
|
||||||
*
|
|
||||||
* Fires the following events:
|
|
||||||
*
|
|
||||||
* * {@link module:engine/view/document~Document#event:clipboardInput},
|
|
||||||
* * {@link module:engine/view/document~Document#event:paste},
|
|
||||||
* * {@link module:engine/view/document~Document#event:copy},
|
|
||||||
* * {@link module:engine/view/document~Document#event:cut},
|
|
||||||
* * {@link module:engine/view/document~Document#event:drop},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragover},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragging},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragstart},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragend},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragenter},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragleave}.
|
|
||||||
*
|
|
||||||
* **Note**: This observer is not available by default (ckeditor5-engine does not add it on its own).
|
|
||||||
* To make it available, it needs to be added to {@link module:engine/view/document~Document} by using
|
|
||||||
* the {@link module:engine/view/view~View#addObserver `View#addObserver()`} method. Alternatively, you can load the
|
|
||||||
* {@link module:clipboard/clipboard~Clipboard} plugin which adds this observer automatically (because it uses it).
|
|
||||||
*/
|
|
||||||
export default class ClipboardObserver extends DomEventObserver<'paste' | 'copy' | 'cut' | 'drop' | 'dragover' | 'dragstart' | 'dragend' | 'dragenter' | 'dragleave', ClipboardEventData> {
|
|
||||||
readonly domEventType: readonly ["paste", "copy", "cut", "drop", "dragover", "dragstart", "dragend", "dragenter", "dragleave"];
|
|
||||||
constructor(view: EditingView);
|
|
||||||
onDomEvent(domEvent: ClipboardEvent | DragEvent): void;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The data of 'paste', 'copy', 'cut', 'drop', 'dragover', 'dragstart', 'dragend', 'dragenter' and 'dragleave' events.
|
|
||||||
*/
|
|
||||||
export interface ClipboardEventData {
|
|
||||||
/**
|
|
||||||
* The data transfer instance.
|
|
||||||
*/
|
|
||||||
dataTransfer: DataTransfer;
|
|
||||||
/**
|
|
||||||
* The position into which the content is dropped.
|
|
||||||
*/
|
|
||||||
dropRange?: ViewRange | null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Fired as a continuation of the {@link module:engine/view/document~Document#event:paste} and
|
|
||||||
* {@link module:engine/view/document~Document#event:drop} events.
|
|
||||||
*
|
|
||||||
* It is a part of the {@glink framework/deep-dive/clipboard#input-pipeline clipboard input pipeline}.
|
|
||||||
*
|
|
||||||
* This event carries a `dataTransfer` object which comes from the clipboard and whose content should be processed
|
|
||||||
* and inserted into the editor.
|
|
||||||
*
|
|
||||||
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
|
|
||||||
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
|
|
||||||
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
|
|
||||||
* the observer must be added manually.
|
|
||||||
*
|
|
||||||
* @see module:clipboard/clipboardobserver~ClipboardObserver
|
|
||||||
* @see module:clipboard/clipboard~Clipboard
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#clipboardInput
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentClipboardInputEvent = {
|
|
||||||
name: 'clipboardInput';
|
|
||||||
args: [data: DomEventData<ClipboardEvent | DragEvent> & ClipboardInputEventData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* The value of the {@link module:engine/view/document~Document#event:paste},
|
|
||||||
* {@link module:engine/view/document~Document#event:copy} and {@link module:engine/view/document~Document#event:cut} events.
|
|
||||||
*
|
|
||||||
* In order to access the clipboard data, use the `dataTransfer` property.
|
|
||||||
*/
|
|
||||||
export interface ClipboardInputEventData {
|
|
||||||
/**
|
|
||||||
* Data transfer instance.
|
|
||||||
*/
|
|
||||||
dataTransfer: DataTransfer;
|
|
||||||
/**
|
|
||||||
* Whether the event was triggered by a paste or a drop operation.
|
|
||||||
*/
|
|
||||||
method: 'paste' | 'drop';
|
|
||||||
/**
|
|
||||||
* The tree view element representing the target.
|
|
||||||
*/
|
|
||||||
target: ViewElement;
|
|
||||||
/**
|
|
||||||
* The ranges which are the target of the operation (usually – into which the content should be inserted).
|
|
||||||
* If the clipboard input was triggered by a paste operation, this property is not set. If by a drop operation,
|
|
||||||
* then it is the drop position (which can be different than the selection at the moment of the drop).
|
|
||||||
*/
|
|
||||||
targetRanges: Array<ViewRange> | null;
|
|
||||||
/**
|
|
||||||
* The content of clipboard input.
|
|
||||||
*/
|
|
||||||
content?: ViewDocumentFragment;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Fired when the user drags the content over one of the editing roots of the editor.
|
|
||||||
*
|
|
||||||
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
|
|
||||||
*
|
|
||||||
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
|
|
||||||
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
|
|
||||||
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
|
|
||||||
* the observer must be added manually.
|
|
||||||
*
|
|
||||||
* @see module:engine/view/document~Document#event:clipboardInput
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#dragover
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentDragOverEvent = {
|
|
||||||
name: 'dragover';
|
|
||||||
args: [data: DomEventData<DragEvent> & ClipboardEventData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Fired when the user dropped the content into one of the editing roots of the editor.
|
|
||||||
*
|
|
||||||
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
|
|
||||||
*
|
|
||||||
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
|
|
||||||
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
|
|
||||||
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
|
|
||||||
* the observer must be added manually.
|
|
||||||
*
|
|
||||||
* @see module:engine/view/document~Document#event:clipboardInput
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#drop
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentDropEvent = {
|
|
||||||
name: 'drop';
|
|
||||||
args: [data: DomEventData<DragEvent> & ClipboardEventData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Fired when the user pasted the content into one of the editing roots of the editor.
|
|
||||||
*
|
|
||||||
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
|
|
||||||
*
|
|
||||||
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
|
|
||||||
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
|
|
||||||
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
|
|
||||||
* the observer must be added manually.
|
|
||||||
*
|
|
||||||
* @see module:engine/view/document~Document#event:clipboardInput
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#paste
|
|
||||||
* @param {module:clipboard/clipboardobserver~ClipboardEventData} data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentPasteEvent = {
|
|
||||||
name: 'paste';
|
|
||||||
args: [data: DomEventData<ClipboardEvent> & ClipboardEventData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Fired when the user copied the content from one of the editing roots of the editor.
|
|
||||||
*
|
|
||||||
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
|
|
||||||
*
|
|
||||||
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
|
|
||||||
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
|
|
||||||
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
|
|
||||||
* the observer must be added manually.
|
|
||||||
*
|
|
||||||
* @see module:clipboard/clipboardobserver~ClipboardObserver
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#copy
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentCopyEvent = {
|
|
||||||
name: 'copy';
|
|
||||||
args: [data: DomEventData<ClipboardEvent> & ClipboardEventData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Fired when the user cut the content from one of the editing roots of the editor.
|
|
||||||
*
|
|
||||||
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
|
|
||||||
*
|
|
||||||
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
|
|
||||||
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
|
|
||||||
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
|
|
||||||
* the observer must be added manually.
|
|
||||||
*
|
|
||||||
* @see module:clipboard/clipboardobserver~ClipboardObserver
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#cut
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentCutEvent = {
|
|
||||||
name: 'cut';
|
|
||||||
args: [data: DomEventData<ClipboardEvent> & ClipboardEventData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Fired as a continuation of the {@link module:engine/view/document~Document#event:dragover} event.
|
|
||||||
*
|
|
||||||
* It is a part of the {@glink framework/deep-dive/clipboard#input-pipeline clipboard input pipeline}.
|
|
||||||
*
|
|
||||||
* This event carries a `dataTransfer` object which comes from the clipboard and whose content should be processed
|
|
||||||
* and inserted into the editor.
|
|
||||||
*
|
|
||||||
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
|
|
||||||
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
|
|
||||||
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
|
|
||||||
* the observer must be added manually.
|
|
||||||
*
|
|
||||||
* @see module:clipboard/clipboardobserver~ClipboardObserver
|
|
||||||
* @see module:clipboard/clipboard~Clipboard
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#dragging
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentDraggingEvent = {
|
|
||||||
name: 'dragging';
|
|
||||||
args: [data: DomEventData<DragEvent> & DraggingEventData];
|
|
||||||
};
|
|
||||||
export interface DraggingEventData {
|
|
||||||
/**
|
|
||||||
* The data transfer instance.
|
|
||||||
*/
|
|
||||||
dataTransfer: DataTransfer;
|
|
||||||
/**
|
|
||||||
* Whether the event was triggered by a paste or a drop operation.
|
|
||||||
*/
|
|
||||||
method: 'dragover';
|
|
||||||
/**
|
|
||||||
* The tree view element representing the target.
|
|
||||||
*/
|
|
||||||
target: Element;
|
|
||||||
/**
|
|
||||||
* Ranges which are the target of the operation (usually – into which the content should be inserted).
|
|
||||||
* It is the drop position (which can be different than the selection at the moment of drop).
|
|
||||||
*/
|
|
||||||
targetRanges: Array<ViewRange> | null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Fired when the user starts dragging the content in one of the editing roots of the editor.
|
|
||||||
*
|
|
||||||
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
|
|
||||||
*
|
|
||||||
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
|
|
||||||
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
|
|
||||||
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
|
|
||||||
* the observer must be added manually.
|
|
||||||
*
|
|
||||||
* @see module:engine/view/document~Document#event:clipboardInput
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#dragstart
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentDragStartEvent = {
|
|
||||||
name: 'dragstart';
|
|
||||||
args: [data: DomEventData<DragEvent> & ClipboardEventData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Fired when the user ended dragging the content.
|
|
||||||
*
|
|
||||||
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
|
|
||||||
*
|
|
||||||
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
|
|
||||||
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
|
|
||||||
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
|
|
||||||
* the observer must be added manually.
|
|
||||||
*
|
|
||||||
* @see module:engine/view/document~Document#event:clipboardInput
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#dragend
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentDragEndEvent = {
|
|
||||||
name: 'dragend';
|
|
||||||
args: [data: DomEventData<DragEvent> & ClipboardEventData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Fired when the user drags the content into one of the editing roots of the editor.
|
|
||||||
*
|
|
||||||
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
|
|
||||||
*
|
|
||||||
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
|
|
||||||
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
|
|
||||||
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
|
|
||||||
* the observer must be added manually.
|
|
||||||
*
|
|
||||||
* @see module:engine/view/document~Document#event:clipboardInput
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#dragenter
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentDragEnterEvent = {
|
|
||||||
name: 'dragenter';
|
|
||||||
args: [data: DomEventData<DragEvent> & ClipboardEventData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Fired when the user drags the content out of one of the editing roots of the editor.
|
|
||||||
*
|
|
||||||
* Introduced by {@link module:clipboard/clipboardobserver~ClipboardObserver}.
|
|
||||||
*
|
|
||||||
* **Note**: This event is not available by default. To make it available, {@link module:clipboard/clipboardobserver~ClipboardObserver}
|
|
||||||
* needs to be added to the {@link module:engine/view/document~Document} by using the {@link module:engine/view/view~View#addObserver}
|
|
||||||
* method. This is usually done by the {@link module:clipboard/clipboard~Clipboard} plugin, but if for some reason it is not loaded,
|
|
||||||
* the observer must be added manually.
|
|
||||||
*
|
|
||||||
* @see module:engine/view/document~Document#event:clipboardInput
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#dragleave
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentDragLeaveEvent = {
|
|
||||||
name: 'dragleave';
|
|
||||||
args: [data: DomEventData<DragEvent> & ClipboardEventData];
|
|
||||||
};
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/clipboardobserver
|
|
||||||
*/
|
|
||||||
import { EventInfo } from '@ckeditor/ckeditor5-utils';
|
|
||||||
import { DataTransfer, DomEventObserver } from '@ckeditor/ckeditor5-engine';
|
|
||||||
/**
|
|
||||||
* Clipboard events observer.
|
|
||||||
*
|
|
||||||
* Fires the following events:
|
|
||||||
*
|
|
||||||
* * {@link module:engine/view/document~Document#event:clipboardInput},
|
|
||||||
* * {@link module:engine/view/document~Document#event:paste},
|
|
||||||
* * {@link module:engine/view/document~Document#event:copy},
|
|
||||||
* * {@link module:engine/view/document~Document#event:cut},
|
|
||||||
* * {@link module:engine/view/document~Document#event:drop},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragover},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragging},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragstart},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragend},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragenter},
|
|
||||||
* * {@link module:engine/view/document~Document#event:dragleave}.
|
|
||||||
*
|
|
||||||
* **Note**: This observer is not available by default (ckeditor5-engine does not add it on its own).
|
|
||||||
* To make it available, it needs to be added to {@link module:engine/view/document~Document} by using
|
|
||||||
* the {@link module:engine/view/view~View#addObserver `View#addObserver()`} method. Alternatively, you can load the
|
|
||||||
* {@link module:clipboard/clipboard~Clipboard} plugin which adds this observer automatically (because it uses it).
|
|
||||||
*/
|
|
||||||
export default class ClipboardObserver extends DomEventObserver {
|
|
||||||
constructor(view) {
|
|
||||||
super(view);
|
|
||||||
this.domEventType = [
|
|
||||||
'paste', 'copy', 'cut', 'drop', 'dragover', 'dragstart', 'dragend', 'dragenter', 'dragleave'
|
|
||||||
];
|
|
||||||
const viewDocument = this.document;
|
|
||||||
this.listenTo(viewDocument, 'paste', handleInput('clipboardInput'), { priority: 'low' });
|
|
||||||
this.listenTo(viewDocument, 'drop', handleInput('clipboardInput'), { priority: 'low' });
|
|
||||||
this.listenTo(viewDocument, 'dragover', handleInput('dragging'), { priority: 'low' });
|
|
||||||
function handleInput(type) {
|
|
||||||
return (evt, data) => {
|
|
||||||
data.preventDefault();
|
|
||||||
const targetRanges = data.dropRange ? [data.dropRange] : null;
|
|
||||||
const eventInfo = new EventInfo(viewDocument, type);
|
|
||||||
viewDocument.fire(eventInfo, {
|
|
||||||
dataTransfer: data.dataTransfer,
|
|
||||||
method: evt.name,
|
|
||||||
targetRanges,
|
|
||||||
target: data.target,
|
|
||||||
domEvent: data.domEvent
|
|
||||||
});
|
|
||||||
// If CKEditor handled the input, do not bubble the original event any further.
|
|
||||||
// This helps external integrations recognize that fact and act accordingly.
|
|
||||||
// https://github.com/ckeditor/ckeditor5-upload/issues/92
|
|
||||||
if (eventInfo.stop.called) {
|
|
||||||
data.stopPropagation();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onDomEvent(domEvent) {
|
|
||||||
const nativeDataTransfer = 'clipboardData' in domEvent ? domEvent.clipboardData : domEvent.dataTransfer;
|
|
||||||
const cacheFiles = domEvent.type == 'drop' || domEvent.type == 'paste';
|
|
||||||
const evtData = {
|
|
||||||
dataTransfer: new DataTransfer(nativeDataTransfer, { cacheFiles })
|
|
||||||
};
|
|
||||||
if (domEvent.type == 'drop' || domEvent.type == 'dragover') {
|
|
||||||
evtData.dropRange = getDropViewRange(this.view, domEvent);
|
|
||||||
}
|
|
||||||
this.fire(domEvent.type, domEvent, evtData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function getDropViewRange(view, domEvent) {
|
|
||||||
const domDoc = domEvent.target.ownerDocument;
|
|
||||||
const x = domEvent.clientX;
|
|
||||||
const y = domEvent.clientY;
|
|
||||||
let domRange;
|
|
||||||
// Webkit & Blink.
|
|
||||||
if (domDoc.caretRangeFromPoint && domDoc.caretRangeFromPoint(x, y)) {
|
|
||||||
domRange = domDoc.caretRangeFromPoint(x, y);
|
|
||||||
}
|
|
||||||
// FF.
|
|
||||||
else if (domEvent.rangeParent) {
|
|
||||||
domRange = domDoc.createRange();
|
|
||||||
domRange.setStart(domEvent.rangeParent, domEvent.rangeOffset);
|
|
||||||
domRange.collapse(true);
|
|
||||||
}
|
|
||||||
if (domRange) {
|
|
||||||
return view.domConverter.domRangeToView(domRange);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
@ -1,265 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/clipboardpipeline
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import type { DataTransfer, DocumentFragment, Range, ViewDocumentFragment, ViewRange, Selection, DocumentSelection } from '@ckeditor/ckeditor5-engine';
|
|
||||||
import ClipboardMarkersUtils from './clipboardmarkersutils.js';
|
|
||||||
/**
|
|
||||||
* The clipboard pipeline feature. It is responsible for intercepting the `paste` and `drop` events and
|
|
||||||
* passing the pasted content through a series of events in order to insert it into the editor's content.
|
|
||||||
* It also handles the `cut` and `copy` events to fill the native clipboard with the serialized editor's data.
|
|
||||||
*
|
|
||||||
* # Input pipeline
|
|
||||||
*
|
|
||||||
* The behavior of the default handlers (all at a `low` priority):
|
|
||||||
*
|
|
||||||
* ## Event: `paste` or `drop`
|
|
||||||
*
|
|
||||||
* 1. Translates the event data.
|
|
||||||
* 2. Fires the {@link module:engine/view/document~Document#event:clipboardInput `view.Document#clipboardInput`} event.
|
|
||||||
*
|
|
||||||
* ## Event: `view.Document#clipboardInput`
|
|
||||||
*
|
|
||||||
* 1. If the `data.content` event field is already set (by some listener on a higher priority), it takes this content and fires the event
|
|
||||||
* from the last point.
|
|
||||||
* 2. Otherwise, it retrieves `text/html` or `text/plain` from `data.dataTransfer`.
|
|
||||||
* 3. Normalizes the raw data by applying simple filters on string data.
|
|
||||||
* 4. Processes the raw data to {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`} with the
|
|
||||||
* {@link module:engine/controller/datacontroller~DataController#htmlProcessor `DataController#htmlProcessor`}.
|
|
||||||
* 5. Fires the {@link module:clipboard/clipboardpipeline~ClipboardPipeline#event:inputTransformation
|
|
||||||
* `ClipboardPipeline#inputTransformation`} event with the view document fragment in the `data.content` event field.
|
|
||||||
*
|
|
||||||
* ## Event: `ClipboardPipeline#inputTransformation`
|
|
||||||
*
|
|
||||||
* 1. Converts {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`} from the `data.content` field to
|
|
||||||
* {@link module:engine/model/documentfragment~DocumentFragment `model.DocumentFragment`}.
|
|
||||||
* 2. Fires the {@link module:clipboard/clipboardpipeline~ClipboardPipeline#event:contentInsertion `ClipboardPipeline#contentInsertion`}
|
|
||||||
* event with the model document fragment in the `data.content` event field.
|
|
||||||
* **Note**: The `ClipboardPipeline#contentInsertion` event is fired within a model change block to allow other handlers
|
|
||||||
* to run in the same block without post-fixers called in between (i.e., the selection post-fixer).
|
|
||||||
*
|
|
||||||
* ## Event: `ClipboardPipeline#contentInsertion`
|
|
||||||
*
|
|
||||||
* 1. Calls {@link module:engine/model/model~Model#insertContent `model.insertContent()`} to insert `data.content`
|
|
||||||
* at the current selection position.
|
|
||||||
*
|
|
||||||
* # Output pipeline
|
|
||||||
*
|
|
||||||
* The behavior of the default handlers (all at a `low` priority):
|
|
||||||
*
|
|
||||||
* ## Event: `copy`, `cut` or `dragstart`
|
|
||||||
*
|
|
||||||
* 1. Retrieves the selected {@link module:engine/model/documentfragment~DocumentFragment `model.DocumentFragment`} by calling
|
|
||||||
* {@link module:engine/model/model~Model#getSelectedContent `model#getSelectedContent()`}.
|
|
||||||
* 2. Converts the model document fragment to {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`}.
|
|
||||||
* 3. Fires the {@link module:engine/view/document~Document#event:clipboardOutput `view.Document#clipboardOutput`} event
|
|
||||||
* with the view document fragment in the `data.content` event field.
|
|
||||||
*
|
|
||||||
* ## Event: `view.Document#clipboardOutput`
|
|
||||||
*
|
|
||||||
* 1. Processes `data.content` to HTML and plain text with the
|
|
||||||
* {@link module:engine/controller/datacontroller~DataController#htmlProcessor `DataController#htmlProcessor`}.
|
|
||||||
* 2. Updates the `data.dataTransfer` data for `text/html` and `text/plain` with the processed data.
|
|
||||||
* 3. For the `cut` method, calls {@link module:engine/model/model~Model#deleteContent `model.deleteContent()`}
|
|
||||||
* on the current selection.
|
|
||||||
*
|
|
||||||
* Read more about the clipboard integration in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.
|
|
||||||
*/
|
|
||||||
export default class ClipboardPipeline extends Plugin {
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName(): "ClipboardPipeline";
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get requires(): readonly [typeof ClipboardMarkersUtils];
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init(): void;
|
|
||||||
/**
|
|
||||||
* Fires Clipboard `'outputTransformation'` event for given parameters.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_fireOutputTransformationEvent(dataTransfer: DataTransfer, selection: Selection | DocumentSelection, method: 'copy' | 'cut' | 'dragstart'): void;
|
|
||||||
/**
|
|
||||||
* The clipboard paste pipeline.
|
|
||||||
*/
|
|
||||||
private _setupPasteDrop;
|
|
||||||
/**
|
|
||||||
* The clipboard copy/cut pipeline.
|
|
||||||
*/
|
|
||||||
private _setupCopyCut;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Fired with the `content`, `dataTransfer`, `method`, and `targetRanges` properties:
|
|
||||||
*
|
|
||||||
* * The `content` which comes from the clipboard (it was pasted or dropped) should be processed in order to be inserted into the editor.
|
|
||||||
* * The `dataTransfer` object is available in case the transformation functions need access to the raw clipboard data.
|
|
||||||
* * The `method` indicates the original DOM event (for example `'drop'` or `'paste'`).
|
|
||||||
* * The `targetRanges` property is an array of view ranges (it is available only for `'drop'`).
|
|
||||||
*
|
|
||||||
* It is a part of the {@glink framework/deep-dive/clipboard#input-pipeline clipboard input pipeline}.
|
|
||||||
*
|
|
||||||
* **Note**: You should not stop this event if you want to change the input data. You should modify the `content` property instead.
|
|
||||||
*
|
|
||||||
* @see module:clipboard/clipboardobserver~ClipboardObserver
|
|
||||||
* @see module:clipboard/clipboardpipeline~ClipboardPipeline
|
|
||||||
*
|
|
||||||
* @eventName ~ClipboardPipeline#inputTransformation
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ClipboardInputTransformationEvent = {
|
|
||||||
name: 'inputTransformation';
|
|
||||||
args: [data: ClipboardInputTransformationData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* The data of 'inputTransformation' event.
|
|
||||||
*/
|
|
||||||
export interface ClipboardInputTransformationData {
|
|
||||||
/**
|
|
||||||
* The event data.
|
|
||||||
* The content to be inserted into the editor. It can be modified by event listeners. Read more about the clipboard pipelines in
|
|
||||||
* the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.
|
|
||||||
*/
|
|
||||||
content: ViewDocumentFragment;
|
|
||||||
/**
|
|
||||||
* The data transfer instance.
|
|
||||||
*/
|
|
||||||
dataTransfer: DataTransfer;
|
|
||||||
/**
|
|
||||||
* The target drop ranges.
|
|
||||||
*/
|
|
||||||
targetRanges: Array<ViewRange> | null;
|
|
||||||
/**
|
|
||||||
* Whether the event was triggered by a paste or a drop operation.
|
|
||||||
*/
|
|
||||||
method: 'paste' | 'drop';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Fired with the `content`, `dataTransfer`, `method`, and `targetRanges` properties:
|
|
||||||
*
|
|
||||||
* * The `content` which comes from the clipboard (was pasted or dropped) should be processed in order to be inserted into the editor.
|
|
||||||
* * The `dataTransfer` object is available in case the transformation functions need access to the raw clipboard data.
|
|
||||||
* * The `method` indicates the original DOM event (for example `'drop'` or `'paste'`).
|
|
||||||
* * The `targetRanges` property is an array of view ranges (it is available only for `'drop'`).
|
|
||||||
*
|
|
||||||
* Event handlers can modify the content according to the final insertion position.
|
|
||||||
*
|
|
||||||
* It is a part of the {@glink framework/deep-dive/clipboard#input-pipeline clipboard input pipeline}.
|
|
||||||
*
|
|
||||||
* **Note**: You should not stop this event if you want to change the input data. You should modify the `content` property instead.
|
|
||||||
*
|
|
||||||
* @see module:clipboard/clipboardobserver~ClipboardObserver
|
|
||||||
* @see module:clipboard/clipboardpipeline~ClipboardPipeline
|
|
||||||
* @see module:clipboard/clipboardpipeline~ClipboardPipeline#event:inputTransformation
|
|
||||||
*
|
|
||||||
* @eventName ~ClipboardPipeline#contentInsertion
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ClipboardContentInsertionEvent = {
|
|
||||||
name: 'contentInsertion';
|
|
||||||
args: [data: ClipboardContentInsertionData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* The data of 'contentInsertion' event.
|
|
||||||
*/
|
|
||||||
export interface ClipboardContentInsertionData {
|
|
||||||
/**
|
|
||||||
* The content to be inserted into the editor.
|
|
||||||
* Read more about the clipboard pipelines in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.
|
|
||||||
*/
|
|
||||||
content: DocumentFragment;
|
|
||||||
/**
|
|
||||||
* Whether the event was triggered by a paste or a drop operation.
|
|
||||||
*/
|
|
||||||
method: 'paste' | 'drop';
|
|
||||||
/**
|
|
||||||
* The data transfer instance.
|
|
||||||
*/
|
|
||||||
dataTransfer: DataTransfer;
|
|
||||||
/**
|
|
||||||
* The target drop ranges.
|
|
||||||
*/
|
|
||||||
targetRanges: Array<ViewRange> | null;
|
|
||||||
/**
|
|
||||||
* The result of the `model.insertContent()` call
|
|
||||||
* (inserted by the event handler at a low priority).
|
|
||||||
*/
|
|
||||||
resultRange?: Range;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Fired on {@link module:engine/view/document~Document#event:copy} and {@link module:engine/view/document~Document#event:cut}
|
|
||||||
* with a copy of the selected content. The content can be processed before it ends up in the clipboard.
|
|
||||||
*
|
|
||||||
* It is a part of the {@glink framework/deep-dive/clipboard#output-pipeline clipboard output pipeline}.
|
|
||||||
*
|
|
||||||
* @see module:clipboard/clipboardobserver~ClipboardObserver
|
|
||||||
* @see module:clipboard/clipboardpipeline~ClipboardPipeline
|
|
||||||
*
|
|
||||||
* @eventName module:engine/view/document~Document#clipboardOutput
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ViewDocumentClipboardOutputEvent = {
|
|
||||||
name: 'clipboardOutput';
|
|
||||||
args: [data: ViewDocumentClipboardOutputEventData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* The value of the 'clipboardOutput' event.
|
|
||||||
*/
|
|
||||||
export interface ViewDocumentClipboardOutputEventData {
|
|
||||||
/**
|
|
||||||
* The data transfer instance.
|
|
||||||
*
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
dataTransfer: DataTransfer;
|
|
||||||
/**
|
|
||||||
* Content to be put into the clipboard. It can be modified by the event listeners.
|
|
||||||
* Read more about the clipboard pipelines in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.
|
|
||||||
*/
|
|
||||||
content: ViewDocumentFragment;
|
|
||||||
/**
|
|
||||||
* Whether the event was triggered by a copy or cut operation.
|
|
||||||
*/
|
|
||||||
method: 'copy' | 'cut' | 'dragstart';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Fired on {@link module:engine/view/document~Document#event:copy}, {@link module:engine/view/document~Document#event:cut}
|
|
||||||
* and {@link module:engine/view/document~Document#event:dragstart}. The content can be processed before it ends up in the clipboard.
|
|
||||||
*
|
|
||||||
* It is a part of the {@glink framework/deep-dive/clipboard#output-pipeline clipboard output pipeline}.
|
|
||||||
*
|
|
||||||
* @eventName ~ClipboardPipeline#outputTransformation
|
|
||||||
* @param data The event data.
|
|
||||||
*/
|
|
||||||
export type ClipboardOutputTransformationEvent = {
|
|
||||||
name: 'outputTransformation';
|
|
||||||
args: [data: ClipboardOutputTransformationData];
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* The value of the 'outputTransformation' event.
|
|
||||||
*/
|
|
||||||
export interface ClipboardOutputTransformationData {
|
|
||||||
/**
|
|
||||||
* The data transfer instance.
|
|
||||||
*
|
|
||||||
* @readonly
|
|
||||||
*/
|
|
||||||
dataTransfer: DataTransfer;
|
|
||||||
/**
|
|
||||||
* Content to be put into the clipboard. It can be modified by the event listeners.
|
|
||||||
* Read more about the clipboard pipelines in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.
|
|
||||||
*/
|
|
||||||
content: DocumentFragment;
|
|
||||||
/**
|
|
||||||
* Whether the event was triggered by a copy or cut operation.
|
|
||||||
*/
|
|
||||||
method: 'copy' | 'cut' | 'dragstart';
|
|
||||||
}
|
|
||||||
|
|
@ -1,277 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/clipboardpipeline
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import { EventInfo } from '@ckeditor/ckeditor5-utils';
|
|
||||||
import ClipboardObserver from './clipboardobserver.js';
|
|
||||||
import plainTextToHtml from './utils/plaintexttohtml.js';
|
|
||||||
import normalizeClipboardHtml from './utils/normalizeclipboarddata.js';
|
|
||||||
import viewToPlainText from './utils/viewtoplaintext.js';
|
|
||||||
import ClipboardMarkersUtils from './clipboardmarkersutils.js';
|
|
||||||
// Input pipeline events overview:
|
|
||||||
//
|
|
||||||
// ┌──────────────────────┐ ┌──────────────────────┐
|
|
||||||
// │ view.Document │ │ view.Document │
|
|
||||||
// │ paste │ │ drop │
|
|
||||||
// └───────────┬──────────┘ └───────────┬──────────┘
|
|
||||||
// │ │
|
|
||||||
// └────────────────┌────────────────┘
|
|
||||||
// │
|
|
||||||
// ┌─────────V────────┐
|
|
||||||
// │ view.Document │ Retrieves text/html or text/plain from data.dataTransfer
|
|
||||||
// │ clipboardInput │ and processes it to view.DocumentFragment.
|
|
||||||
// └─────────┬────────┘
|
|
||||||
// │
|
|
||||||
// ┌───────────V───────────┐
|
|
||||||
// │ ClipboardPipeline │ Converts view.DocumentFragment to model.DocumentFragment.
|
|
||||||
// │ inputTransformation │
|
|
||||||
// └───────────┬───────────┘
|
|
||||||
// │
|
|
||||||
// ┌──────────V──────────┐
|
|
||||||
// │ ClipboardPipeline │ Calls model.insertContent().
|
|
||||||
// │ contentInsertion │
|
|
||||||
// └─────────────────────┘
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Output pipeline events overview:
|
|
||||||
//
|
|
||||||
// ┌──────────────────────┐ ┌──────────────────────┐
|
|
||||||
// │ view.Document │ │ view.Document │ Retrieves the selected model.DocumentFragment
|
|
||||||
// │ copy │ │ cut │ and fires the `outputTransformation` event.
|
|
||||||
// └───────────┬──────────┘ └───────────┬──────────┘
|
|
||||||
// │ │
|
|
||||||
// └────────────────┌────────────────┘
|
|
||||||
// │
|
|
||||||
// ┌───────────V───────────┐
|
|
||||||
// │ ClipboardPipeline │ Processes model.DocumentFragment and converts it to
|
|
||||||
// │ outputTransformation │ view.DocumentFragment.
|
|
||||||
// └───────────┬───────────┘
|
|
||||||
// │
|
|
||||||
// ┌─────────V────────┐
|
|
||||||
// │ view.Document │ Processes view.DocumentFragment to text/html and text/plain
|
|
||||||
// │ clipboardOutput │ and stores the results in data.dataTransfer.
|
|
||||||
// └──────────────────┘
|
|
||||||
//
|
|
||||||
/**
|
|
||||||
* The clipboard pipeline feature. It is responsible for intercepting the `paste` and `drop` events and
|
|
||||||
* passing the pasted content through a series of events in order to insert it into the editor's content.
|
|
||||||
* It also handles the `cut` and `copy` events to fill the native clipboard with the serialized editor's data.
|
|
||||||
*
|
|
||||||
* # Input pipeline
|
|
||||||
*
|
|
||||||
* The behavior of the default handlers (all at a `low` priority):
|
|
||||||
*
|
|
||||||
* ## Event: `paste` or `drop`
|
|
||||||
*
|
|
||||||
* 1. Translates the event data.
|
|
||||||
* 2. Fires the {@link module:engine/view/document~Document#event:clipboardInput `view.Document#clipboardInput`} event.
|
|
||||||
*
|
|
||||||
* ## Event: `view.Document#clipboardInput`
|
|
||||||
*
|
|
||||||
* 1. If the `data.content` event field is already set (by some listener on a higher priority), it takes this content and fires the event
|
|
||||||
* from the last point.
|
|
||||||
* 2. Otherwise, it retrieves `text/html` or `text/plain` from `data.dataTransfer`.
|
|
||||||
* 3. Normalizes the raw data by applying simple filters on string data.
|
|
||||||
* 4. Processes the raw data to {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`} with the
|
|
||||||
* {@link module:engine/controller/datacontroller~DataController#htmlProcessor `DataController#htmlProcessor`}.
|
|
||||||
* 5. Fires the {@link module:clipboard/clipboardpipeline~ClipboardPipeline#event:inputTransformation
|
|
||||||
* `ClipboardPipeline#inputTransformation`} event with the view document fragment in the `data.content` event field.
|
|
||||||
*
|
|
||||||
* ## Event: `ClipboardPipeline#inputTransformation`
|
|
||||||
*
|
|
||||||
* 1. Converts {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`} from the `data.content` field to
|
|
||||||
* {@link module:engine/model/documentfragment~DocumentFragment `model.DocumentFragment`}.
|
|
||||||
* 2. Fires the {@link module:clipboard/clipboardpipeline~ClipboardPipeline#event:contentInsertion `ClipboardPipeline#contentInsertion`}
|
|
||||||
* event with the model document fragment in the `data.content` event field.
|
|
||||||
* **Note**: The `ClipboardPipeline#contentInsertion` event is fired within a model change block to allow other handlers
|
|
||||||
* to run in the same block without post-fixers called in between (i.e., the selection post-fixer).
|
|
||||||
*
|
|
||||||
* ## Event: `ClipboardPipeline#contentInsertion`
|
|
||||||
*
|
|
||||||
* 1. Calls {@link module:engine/model/model~Model#insertContent `model.insertContent()`} to insert `data.content`
|
|
||||||
* at the current selection position.
|
|
||||||
*
|
|
||||||
* # Output pipeline
|
|
||||||
*
|
|
||||||
* The behavior of the default handlers (all at a `low` priority):
|
|
||||||
*
|
|
||||||
* ## Event: `copy`, `cut` or `dragstart`
|
|
||||||
*
|
|
||||||
* 1. Retrieves the selected {@link module:engine/model/documentfragment~DocumentFragment `model.DocumentFragment`} by calling
|
|
||||||
* {@link module:engine/model/model~Model#getSelectedContent `model#getSelectedContent()`}.
|
|
||||||
* 2. Converts the model document fragment to {@link module:engine/view/documentfragment~DocumentFragment `view.DocumentFragment`}.
|
|
||||||
* 3. Fires the {@link module:engine/view/document~Document#event:clipboardOutput `view.Document#clipboardOutput`} event
|
|
||||||
* with the view document fragment in the `data.content` event field.
|
|
||||||
*
|
|
||||||
* ## Event: `view.Document#clipboardOutput`
|
|
||||||
*
|
|
||||||
* 1. Processes `data.content` to HTML and plain text with the
|
|
||||||
* {@link module:engine/controller/datacontroller~DataController#htmlProcessor `DataController#htmlProcessor`}.
|
|
||||||
* 2. Updates the `data.dataTransfer` data for `text/html` and `text/plain` with the processed data.
|
|
||||||
* 3. For the `cut` method, calls {@link module:engine/model/model~Model#deleteContent `model.deleteContent()`}
|
|
||||||
* on the current selection.
|
|
||||||
*
|
|
||||||
* Read more about the clipboard integration in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.
|
|
||||||
*/
|
|
||||||
export default class ClipboardPipeline extends Plugin {
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName() {
|
|
||||||
return 'ClipboardPipeline';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get requires() {
|
|
||||||
return [ClipboardMarkersUtils];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
const editor = this.editor;
|
|
||||||
const view = editor.editing.view;
|
|
||||||
view.addObserver(ClipboardObserver);
|
|
||||||
this._setupPasteDrop();
|
|
||||||
this._setupCopyCut();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Fires Clipboard `'outputTransformation'` event for given parameters.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_fireOutputTransformationEvent(dataTransfer, selection, method) {
|
|
||||||
const clipboardMarkersUtils = this.editor.plugins.get('ClipboardMarkersUtils');
|
|
||||||
this.editor.model.enqueueChange({ isUndoable: method === 'cut' }, () => {
|
|
||||||
const documentFragment = clipboardMarkersUtils._copySelectedFragmentWithMarkers(method, selection);
|
|
||||||
this.fire('outputTransformation', {
|
|
||||||
dataTransfer,
|
|
||||||
content: documentFragment,
|
|
||||||
method
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The clipboard paste pipeline.
|
|
||||||
*/
|
|
||||||
_setupPasteDrop() {
|
|
||||||
const editor = this.editor;
|
|
||||||
const model = editor.model;
|
|
||||||
const view = editor.editing.view;
|
|
||||||
const viewDocument = view.document;
|
|
||||||
const clipboardMarkersUtils = this.editor.plugins.get('ClipboardMarkersUtils');
|
|
||||||
// Pasting is disabled when selection is in non-editable place.
|
|
||||||
// Dropping is disabled in drag and drop handler.
|
|
||||||
this.listenTo(viewDocument, 'clipboardInput', (evt, data) => {
|
|
||||||
if (data.method == 'paste' && !editor.model.canEditAt(editor.model.document.selection)) {
|
|
||||||
evt.stop();
|
|
||||||
}
|
|
||||||
}, { priority: 'highest' });
|
|
||||||
this.listenTo(viewDocument, 'clipboardInput', (evt, data) => {
|
|
||||||
const dataTransfer = data.dataTransfer;
|
|
||||||
let content;
|
|
||||||
// Some feature could already inject content in the higher priority event handler (i.e., codeBlock).
|
|
||||||
if (data.content) {
|
|
||||||
content = data.content;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
let contentData = '';
|
|
||||||
if (dataTransfer.getData('text/html')) {
|
|
||||||
contentData = normalizeClipboardHtml(dataTransfer.getData('text/html'));
|
|
||||||
}
|
|
||||||
else if (dataTransfer.getData('text/plain')) {
|
|
||||||
contentData = plainTextToHtml(dataTransfer.getData('text/plain'));
|
|
||||||
}
|
|
||||||
content = this.editor.data.htmlProcessor.toView(contentData);
|
|
||||||
}
|
|
||||||
const eventInfo = new EventInfo(this, 'inputTransformation');
|
|
||||||
this.fire(eventInfo, {
|
|
||||||
content,
|
|
||||||
dataTransfer,
|
|
||||||
targetRanges: data.targetRanges,
|
|
||||||
method: data.method
|
|
||||||
});
|
|
||||||
// If CKEditor handled the input, do not bubble the original event any further.
|
|
||||||
// This helps external integrations recognize this fact and act accordingly.
|
|
||||||
// https://github.com/ckeditor/ckeditor5-upload/issues/92
|
|
||||||
if (eventInfo.stop.called) {
|
|
||||||
evt.stop();
|
|
||||||
}
|
|
||||||
view.scrollToTheSelection();
|
|
||||||
}, { priority: 'low' });
|
|
||||||
this.listenTo(this, 'inputTransformation', (evt, data) => {
|
|
||||||
if (data.content.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const dataController = this.editor.data;
|
|
||||||
// Convert the pasted content into a model document fragment.
|
|
||||||
// The conversion is contextual, but in this case an "all allowed" context is needed
|
|
||||||
// and for that we use the $clipboardHolder item.
|
|
||||||
const modelFragment = dataController.toModel(data.content, '$clipboardHolder');
|
|
||||||
if (modelFragment.childCount == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
evt.stop();
|
|
||||||
// Fire content insertion event in a single change block to allow other handlers to run in the same block
|
|
||||||
// without post-fixers called in between (i.e., the selection post-fixer).
|
|
||||||
model.change(() => {
|
|
||||||
this.fire('contentInsertion', {
|
|
||||||
content: modelFragment,
|
|
||||||
method: data.method,
|
|
||||||
dataTransfer: data.dataTransfer,
|
|
||||||
targetRanges: data.targetRanges
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, { priority: 'low' });
|
|
||||||
this.listenTo(this, 'contentInsertion', (evt, data) => {
|
|
||||||
data.resultRange = clipboardMarkersUtils._pasteFragmentWithMarkers(data.content);
|
|
||||||
}, { priority: 'low' });
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The clipboard copy/cut pipeline.
|
|
||||||
*/
|
|
||||||
_setupCopyCut() {
|
|
||||||
const editor = this.editor;
|
|
||||||
const modelDocument = editor.model.document;
|
|
||||||
const view = editor.editing.view;
|
|
||||||
const viewDocument = view.document;
|
|
||||||
const onCopyCut = (evt, data) => {
|
|
||||||
const dataTransfer = data.dataTransfer;
|
|
||||||
data.preventDefault();
|
|
||||||
this._fireOutputTransformationEvent(dataTransfer, modelDocument.selection, evt.name);
|
|
||||||
};
|
|
||||||
this.listenTo(viewDocument, 'copy', onCopyCut, { priority: 'low' });
|
|
||||||
this.listenTo(viewDocument, 'cut', (evt, data) => {
|
|
||||||
// Cutting is disabled when selection is in non-editable place.
|
|
||||||
// See: https://github.com/ckeditor/ckeditor5-clipboard/issues/26.
|
|
||||||
if (!editor.model.canEditAt(editor.model.document.selection)) {
|
|
||||||
data.preventDefault();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
onCopyCut(evt, data);
|
|
||||||
}
|
|
||||||
}, { priority: 'low' });
|
|
||||||
this.listenTo(this, 'outputTransformation', (evt, data) => {
|
|
||||||
const content = editor.data.toView(data.content);
|
|
||||||
viewDocument.fire('clipboardOutput', {
|
|
||||||
dataTransfer: data.dataTransfer,
|
|
||||||
content,
|
|
||||||
method: data.method
|
|
||||||
});
|
|
||||||
}, { priority: 'low' });
|
|
||||||
this.listenTo(viewDocument, 'clipboardOutput', (evt, data) => {
|
|
||||||
if (!data.content.isEmpty) {
|
|
||||||
data.dataTransfer.setData('text/html', this.editor.data.htmlProcessor.toData(data.content));
|
|
||||||
data.dataTransfer.setData('text/plain', viewToPlainText(data.content));
|
|
||||||
}
|
|
||||||
if (data.method == 'cut') {
|
|
||||||
editor.model.deleteContent(modelDocument.selection);
|
|
||||||
}
|
|
||||||
}, { priority: 'low' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/dragdrop
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import { Widget } from '@ckeditor/ckeditor5-widget';
|
|
||||||
import ClipboardPipeline from './clipboardpipeline.js';
|
|
||||||
import DragDropTarget from './dragdroptarget.js';
|
|
||||||
import DragDropBlockToolbar from './dragdropblocktoolbar.js';
|
|
||||||
import '../theme/clipboard.css';
|
|
||||||
/**
|
|
||||||
* The drag and drop feature. It works on top of the {@link module:clipboard/clipboardpipeline~ClipboardPipeline}.
|
|
||||||
*
|
|
||||||
* Read more about the clipboard integration in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export default class DragDrop extends Plugin {
|
|
||||||
/**
|
|
||||||
* The live range over the original content that is being dragged.
|
|
||||||
*/
|
|
||||||
private _draggedRange;
|
|
||||||
/**
|
|
||||||
* The UID of current dragging that is used to verify if the drop started in the same editor as the drag start.
|
|
||||||
*
|
|
||||||
* **Note**: This is a workaround for broken 'dragend' events (they are not fired if the source text node got removed).
|
|
||||||
*/
|
|
||||||
private _draggingUid;
|
|
||||||
/**
|
|
||||||
* The reference to the model element that currently has a `draggable` attribute set (it is set while dragging).
|
|
||||||
*/
|
|
||||||
private _draggableElement;
|
|
||||||
/**
|
|
||||||
* A delayed callback removing draggable attributes.
|
|
||||||
*/
|
|
||||||
private _clearDraggableAttributesDelayed;
|
|
||||||
/**
|
|
||||||
* Whether the dragged content can be dropped only in block context.
|
|
||||||
*/
|
|
||||||
private _blockMode;
|
|
||||||
/**
|
|
||||||
* DOM Emitter.
|
|
||||||
*/
|
|
||||||
private _domEmitter;
|
|
||||||
/**
|
|
||||||
* The DOM element used to generate dragged preview image.
|
|
||||||
*/
|
|
||||||
private _previewContainer?;
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName(): "DragDrop";
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get requires(): readonly [typeof ClipboardPipeline, typeof Widget, typeof DragDropTarget, typeof DragDropBlockToolbar];
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init(): void;
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
destroy(): void;
|
|
||||||
/**
|
|
||||||
* Drag and drop events handling.
|
|
||||||
*/
|
|
||||||
private _setupDragging;
|
|
||||||
/**
|
|
||||||
* Integration with the `clipboardInput` event.
|
|
||||||
*/
|
|
||||||
private _setupClipboardInputIntegration;
|
|
||||||
/**
|
|
||||||
* Integration with the `contentInsertion` event of the clipboard pipeline.
|
|
||||||
*/
|
|
||||||
private _setupContentInsertionIntegration;
|
|
||||||
/**
|
|
||||||
* Adds listeners that add the `draggable` attribute to the elements while the mouse button is down so the dragging could start.
|
|
||||||
*/
|
|
||||||
private _setupDraggableAttributeHandling;
|
|
||||||
/**
|
|
||||||
* Removes the `draggable` attribute from the element that was used for dragging.
|
|
||||||
*/
|
|
||||||
private _clearDraggableAttributes;
|
|
||||||
/**
|
|
||||||
* Deletes the dragged content from its original range and clears the dragging state.
|
|
||||||
*
|
|
||||||
* @param moved Whether the move succeeded.
|
|
||||||
*/
|
|
||||||
private _finalizeDragging;
|
|
||||||
/**
|
|
||||||
* Sets the dragged source range based on event target and document selection.
|
|
||||||
*/
|
|
||||||
private _prepareDraggedRange;
|
|
||||||
/**
|
|
||||||
* Updates the dragged preview image.
|
|
||||||
*/
|
|
||||||
private _updatePreview;
|
|
||||||
}
|
|
||||||
|
|
@ -1,577 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/dragdrop
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import { LiveRange, MouseObserver } from '@ckeditor/ckeditor5-engine';
|
|
||||||
import { Widget, isWidget } from '@ckeditor/ckeditor5-widget';
|
|
||||||
import { env, uid, global, createElement, DomEmitterMixin, delay, Rect } from '@ckeditor/ckeditor5-utils';
|
|
||||||
import ClipboardPipeline from './clipboardpipeline.js';
|
|
||||||
import ClipboardObserver from './clipboardobserver.js';
|
|
||||||
import DragDropTarget from './dragdroptarget.js';
|
|
||||||
import DragDropBlockToolbar from './dragdropblocktoolbar.js';
|
|
||||||
import '../theme/clipboard.css';
|
|
||||||
// Drag and drop events overview:
|
|
||||||
//
|
|
||||||
// ┌──────────────────┐
|
|
||||||
// │ mousedown │ Sets the draggable attribute.
|
|
||||||
// └─────────┬────────┘
|
|
||||||
// │
|
|
||||||
// └─────────────────────┐
|
|
||||||
// │ │
|
|
||||||
// │ ┌─────────V────────┐
|
|
||||||
// │ │ mouseup │ Dragging did not start, removes the draggable attribute.
|
|
||||||
// │ └──────────────────┘
|
|
||||||
// │
|
|
||||||
// ┌─────────V────────┐ Retrieves the selected model.DocumentFragment
|
|
||||||
// │ dragstart │ and converts it to view.DocumentFragment.
|
|
||||||
// └─────────┬────────┘
|
|
||||||
// │
|
|
||||||
// ┌─────────V────────┐ Processes view.DocumentFragment to text/html and text/plain
|
|
||||||
// │ clipboardOutput │ and stores the results in data.dataTransfer.
|
|
||||||
// └─────────┬────────┘
|
|
||||||
// │
|
|
||||||
// │ DOM dragover
|
|
||||||
// ┌────────────┐
|
|
||||||
// │ │
|
|
||||||
// ┌─────────V────────┐ │
|
|
||||||
// │ dragging │ │ Updates the drop target marker.
|
|
||||||
// └─────────┬────────┘ │
|
|
||||||
// │ │
|
|
||||||
// ┌─────────────└────────────┘
|
|
||||||
// │ │ │
|
|
||||||
// │ ┌─────────V────────┐ │
|
|
||||||
// │ │ dragleave │ │ Removes the drop target marker.
|
|
||||||
// │ └─────────┬────────┘ │
|
|
||||||
// │ │ │
|
|
||||||
// ┌───│─────────────┘ │
|
|
||||||
// │ │ │ │
|
|
||||||
// │ │ ┌─────────V────────┐ │
|
|
||||||
// │ │ │ dragenter │ │ Focuses the editor view.
|
|
||||||
// │ │ └─────────┬────────┘ │
|
|
||||||
// │ │ │ │
|
|
||||||
// │ │ └────────────┘
|
|
||||||
// │ │
|
|
||||||
// │ └─────────────┐
|
|
||||||
// │ │ │
|
|
||||||
// │ │ ┌─────────V────────┐
|
|
||||||
// └───┐ │ drop │ (The default handler of the clipboard pipeline).
|
|
||||||
// │ └─────────┬────────┘
|
|
||||||
// │ │
|
|
||||||
// │ ┌─────────V────────┐ Resolves the final data.targetRanges.
|
|
||||||
// │ │ clipboardInput │ Aborts if dropping on dragged content.
|
|
||||||
// │ └─────────┬────────┘
|
|
||||||
// │ │
|
|
||||||
// │ ┌─────────V────────┐
|
|
||||||
// │ │ clipboardInput │ (The default handler of the clipboard pipeline).
|
|
||||||
// │ └─────────┬────────┘
|
|
||||||
// │ │
|
|
||||||
// │ ┌───────────V───────────┐
|
|
||||||
// │ │ inputTransformation │ (The default handler of the clipboard pipeline).
|
|
||||||
// │ └───────────┬───────────┘
|
|
||||||
// │ │
|
|
||||||
// │ ┌──────────V──────────┐
|
|
||||||
// │ │ contentInsertion │ Updates the document selection to drop range.
|
|
||||||
// │ └──────────┬──────────┘
|
|
||||||
// │ │
|
|
||||||
// │ ┌──────────V──────────┐
|
|
||||||
// │ │ contentInsertion │ (The default handler of the clipboard pipeline).
|
|
||||||
// │ └──────────┬──────────┘
|
|
||||||
// │ │
|
|
||||||
// │ ┌──────────V──────────┐
|
|
||||||
// │ │ contentInsertion │ Removes the content from the original range if the insertion was successful.
|
|
||||||
// │ └──────────┬──────────┘
|
|
||||||
// │ │
|
|
||||||
// └─────────────┐
|
|
||||||
// │
|
|
||||||
// ┌─────────V────────┐
|
|
||||||
// │ dragend │ Removes the drop marker and cleans the state.
|
|
||||||
// └──────────────────┘
|
|
||||||
//
|
|
||||||
/**
|
|
||||||
* The drag and drop feature. It works on top of the {@link module:clipboard/clipboardpipeline~ClipboardPipeline}.
|
|
||||||
*
|
|
||||||
* Read more about the clipboard integration in the {@glink framework/deep-dive/clipboard clipboard deep-dive} guide.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export default class DragDrop extends Plugin {
|
|
||||||
constructor() {
|
|
||||||
super(...arguments);
|
|
||||||
/**
|
|
||||||
* A delayed callback removing draggable attributes.
|
|
||||||
*/
|
|
||||||
this._clearDraggableAttributesDelayed = delay(() => this._clearDraggableAttributes(), 40);
|
|
||||||
/**
|
|
||||||
* Whether the dragged content can be dropped only in block context.
|
|
||||||
*/
|
|
||||||
// TODO handle drag from other editor instance
|
|
||||||
// TODO configure to use block, inline or both
|
|
||||||
this._blockMode = false;
|
|
||||||
/**
|
|
||||||
* DOM Emitter.
|
|
||||||
*/
|
|
||||||
this._domEmitter = new (DomEmitterMixin())();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName() {
|
|
||||||
return 'DragDrop';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get requires() {
|
|
||||||
return [ClipboardPipeline, Widget, DragDropTarget, DragDropBlockToolbar];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
const editor = this.editor;
|
|
||||||
const view = editor.editing.view;
|
|
||||||
this._draggedRange = null;
|
|
||||||
this._draggingUid = '';
|
|
||||||
this._draggableElement = null;
|
|
||||||
view.addObserver(ClipboardObserver);
|
|
||||||
view.addObserver(MouseObserver);
|
|
||||||
this._setupDragging();
|
|
||||||
this._setupContentInsertionIntegration();
|
|
||||||
this._setupClipboardInputIntegration();
|
|
||||||
this._setupDraggableAttributeHandling();
|
|
||||||
this.listenTo(editor, 'change:isReadOnly', (evt, name, isReadOnly) => {
|
|
||||||
if (isReadOnly) {
|
|
||||||
this.forceDisabled('readOnlyMode');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.clearForceDisabled('readOnlyMode');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.on('change:isEnabled', (evt, name, isEnabled) => {
|
|
||||||
if (!isEnabled) {
|
|
||||||
this._finalizeDragging(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (env.isAndroid) {
|
|
||||||
this.forceDisabled('noAndroidSupport');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
if (this._draggedRange) {
|
|
||||||
this._draggedRange.detach();
|
|
||||||
this._draggedRange = null;
|
|
||||||
}
|
|
||||||
if (this._previewContainer) {
|
|
||||||
this._previewContainer.remove();
|
|
||||||
}
|
|
||||||
this._domEmitter.stopListening();
|
|
||||||
this._clearDraggableAttributesDelayed.cancel();
|
|
||||||
return super.destroy();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Drag and drop events handling.
|
|
||||||
*/
|
|
||||||
_setupDragging() {
|
|
||||||
const editor = this.editor;
|
|
||||||
const model = editor.model;
|
|
||||||
const view = editor.editing.view;
|
|
||||||
const viewDocument = view.document;
|
|
||||||
const dragDropTarget = editor.plugins.get(DragDropTarget);
|
|
||||||
// The handler for the drag start; it is responsible for setting data transfer object.
|
|
||||||
this.listenTo(viewDocument, 'dragstart', (evt, data) => {
|
|
||||||
// Don't drag the editable element itself.
|
|
||||||
if (data.target && data.target.is('editableElement')) {
|
|
||||||
data.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._prepareDraggedRange(data.target);
|
|
||||||
if (!this._draggedRange) {
|
|
||||||
data.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._draggingUid = uid();
|
|
||||||
data.dataTransfer.effectAllowed = this.isEnabled ? 'copyMove' : 'copy';
|
|
||||||
data.dataTransfer.setData('application/ckeditor5-dragging-uid', this._draggingUid);
|
|
||||||
const draggedSelection = model.createSelection(this._draggedRange.toRange());
|
|
||||||
const clipboardPipeline = this.editor.plugins.get('ClipboardPipeline');
|
|
||||||
clipboardPipeline._fireOutputTransformationEvent(data.dataTransfer, draggedSelection, 'dragstart');
|
|
||||||
const { dataTransfer, domTarget, domEvent } = data;
|
|
||||||
const { clientX } = domEvent;
|
|
||||||
this._updatePreview({ dataTransfer, domTarget, clientX });
|
|
||||||
data.stopPropagation();
|
|
||||||
if (!this.isEnabled) {
|
|
||||||
this._draggedRange.detach();
|
|
||||||
this._draggedRange = null;
|
|
||||||
this._draggingUid = '';
|
|
||||||
}
|
|
||||||
}, { priority: 'low' });
|
|
||||||
// The handler for finalizing drag and drop. It should always be triggered after dragging completes
|
|
||||||
// even if it was completed in a different application.
|
|
||||||
// Note: This is not fired if source text node got removed while downcasting a marker.
|
|
||||||
this.listenTo(viewDocument, 'dragend', (evt, data) => {
|
|
||||||
this._finalizeDragging(!data.dataTransfer.isCanceled && data.dataTransfer.dropEffect == 'move');
|
|
||||||
}, { priority: 'low' });
|
|
||||||
// Reset block dragging mode even if dropped outside the editable.
|
|
||||||
this._domEmitter.listenTo(global.document, 'dragend', () => {
|
|
||||||
this._blockMode = false;
|
|
||||||
}, { useCapture: true });
|
|
||||||
// Dragging over the editable.
|
|
||||||
this.listenTo(viewDocument, 'dragenter', () => {
|
|
||||||
if (!this.isEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
view.focus();
|
|
||||||
});
|
|
||||||
// Dragging out of the editable.
|
|
||||||
this.listenTo(viewDocument, 'dragleave', () => {
|
|
||||||
// We do not know if the mouse left the editor or just some element in it, so let us wait a few milliseconds
|
|
||||||
// to check if 'dragover' is not fired.
|
|
||||||
dragDropTarget.removeDropMarkerDelayed();
|
|
||||||
});
|
|
||||||
// Handler for moving dragged content over the target area.
|
|
||||||
this.listenTo(viewDocument, 'dragging', (evt, data) => {
|
|
||||||
if (!this.isEnabled) {
|
|
||||||
data.dataTransfer.dropEffect = 'none';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { clientX, clientY } = data.domEvent;
|
|
||||||
dragDropTarget.updateDropMarker(data.target, data.targetRanges, clientX, clientY, this._blockMode, this._draggedRange);
|
|
||||||
// If this is content being dragged from another editor, moving out of current editor instance
|
|
||||||
// is not possible until 'dragend' event case will be fixed.
|
|
||||||
if (!this._draggedRange) {
|
|
||||||
data.dataTransfer.dropEffect = 'copy';
|
|
||||||
}
|
|
||||||
// In Firefox it is already set and effect allowed remains the same as originally set.
|
|
||||||
if (!env.isGecko) {
|
|
||||||
if (data.dataTransfer.effectAllowed == 'copy') {
|
|
||||||
data.dataTransfer.dropEffect = 'copy';
|
|
||||||
}
|
|
||||||
else if (['all', 'copyMove'].includes(data.dataTransfer.effectAllowed)) {
|
|
||||||
data.dataTransfer.dropEffect = 'move';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
evt.stop();
|
|
||||||
}, { priority: 'low' });
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Integration with the `clipboardInput` event.
|
|
||||||
*/
|
|
||||||
_setupClipboardInputIntegration() {
|
|
||||||
const editor = this.editor;
|
|
||||||
const view = editor.editing.view;
|
|
||||||
const viewDocument = view.document;
|
|
||||||
const dragDropTarget = editor.plugins.get(DragDropTarget);
|
|
||||||
// Update the event target ranges and abort dropping if dropping over itself.
|
|
||||||
this.listenTo(viewDocument, 'clipboardInput', (evt, data) => {
|
|
||||||
if (data.method != 'drop') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { clientX, clientY } = data.domEvent;
|
|
||||||
const targetRange = dragDropTarget.getFinalDropRange(data.target, data.targetRanges, clientX, clientY, this._blockMode, this._draggedRange);
|
|
||||||
if (!targetRange) {
|
|
||||||
this._finalizeDragging(false);
|
|
||||||
evt.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Since we cannot rely on the drag end event, we must check if the local drag range is from the current drag and drop
|
|
||||||
// or it is from some previous not cleared one.
|
|
||||||
if (this._draggedRange && this._draggingUid != data.dataTransfer.getData('application/ckeditor5-dragging-uid')) {
|
|
||||||
this._draggedRange.detach();
|
|
||||||
this._draggedRange = null;
|
|
||||||
this._draggingUid = '';
|
|
||||||
}
|
|
||||||
// Do not do anything if some content was dragged within the same document to the same position.
|
|
||||||
const isMove = getFinalDropEffect(data.dataTransfer) == 'move';
|
|
||||||
if (isMove && this._draggedRange && this._draggedRange.containsRange(targetRange, true)) {
|
|
||||||
this._finalizeDragging(false);
|
|
||||||
evt.stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Override the target ranges with the one adjusted to the best one for a drop.
|
|
||||||
data.targetRanges = [editor.editing.mapper.toViewRange(targetRange)];
|
|
||||||
}, { priority: 'high' });
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Integration with the `contentInsertion` event of the clipboard pipeline.
|
|
||||||
*/
|
|
||||||
_setupContentInsertionIntegration() {
|
|
||||||
const clipboardPipeline = this.editor.plugins.get(ClipboardPipeline);
|
|
||||||
clipboardPipeline.on('contentInsertion', (evt, data) => {
|
|
||||||
if (!this.isEnabled || data.method !== 'drop') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Update the selection to the target range in the same change block to avoid selection post-fixing
|
|
||||||
// and to be able to clone text attributes for plain text dropping.
|
|
||||||
const ranges = data.targetRanges.map(viewRange => this.editor.editing.mapper.toModelRange(viewRange));
|
|
||||||
this.editor.model.change(writer => writer.setSelection(ranges));
|
|
||||||
}, { priority: 'high' });
|
|
||||||
clipboardPipeline.on('contentInsertion', (evt, data) => {
|
|
||||||
if (!this.isEnabled || data.method !== 'drop') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Remove dragged range content, remove markers, clean after dragging.
|
|
||||||
const isMove = getFinalDropEffect(data.dataTransfer) == 'move';
|
|
||||||
// Whether any content was inserted (insertion might fail if the schema is disallowing some elements
|
|
||||||
// (for example an image caption allows only the content of a block but not blocks themselves.
|
|
||||||
// Some integrations might not return valid range (i.e., table pasting).
|
|
||||||
const isSuccess = !data.resultRange || !data.resultRange.isCollapsed;
|
|
||||||
this._finalizeDragging(isSuccess && isMove);
|
|
||||||
}, { priority: 'lowest' });
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Adds listeners that add the `draggable` attribute to the elements while the mouse button is down so the dragging could start.
|
|
||||||
*/
|
|
||||||
_setupDraggableAttributeHandling() {
|
|
||||||
const editor = this.editor;
|
|
||||||
const view = editor.editing.view;
|
|
||||||
const viewDocument = view.document;
|
|
||||||
// Add the 'draggable' attribute to the widget while pressing the selection handle.
|
|
||||||
// This is required for widgets to be draggable. In Chrome it will enable dragging text nodes.
|
|
||||||
this.listenTo(viewDocument, 'mousedown', (evt, data) => {
|
|
||||||
// The lack of data can be caused by editor tests firing fake mouse events. This should not occur
|
|
||||||
// in real-life scenarios but this greatly simplifies editor tests that would otherwise fail a lot.
|
|
||||||
if (env.isAndroid || !data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._clearDraggableAttributesDelayed.cancel();
|
|
||||||
// Check if this is a mousedown over the widget (but not a nested editable).
|
|
||||||
let draggableElement = findDraggableWidget(data.target);
|
|
||||||
// Note: There is a limitation that if more than a widget is selected (a widget and some text)
|
|
||||||
// and dragging starts on the widget, then only the widget is dragged.
|
|
||||||
// If this was not a widget then we should check if we need to drag some text content.
|
|
||||||
// In Chrome set a 'draggable' attribute on closest editable to allow immediate dragging of the selected text range.
|
|
||||||
// In Firefox this is not needed. In Safari it makes the whole editable draggable (not just textual content).
|
|
||||||
// Disabled in read-only mode because draggable="true" + contenteditable="false" results
|
|
||||||
// in not firing selectionchange event ever, which makes the selection stuck in read-only mode.
|
|
||||||
if (env.isBlink && !editor.isReadOnly && !draggableElement && !viewDocument.selection.isCollapsed) {
|
|
||||||
const selectedElement = viewDocument.selection.getSelectedElement();
|
|
||||||
if (!selectedElement || !isWidget(selectedElement)) {
|
|
||||||
draggableElement = viewDocument.selection.editableElement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (draggableElement) {
|
|
||||||
view.change(writer => {
|
|
||||||
writer.setAttribute('draggable', 'true', draggableElement);
|
|
||||||
});
|
|
||||||
// Keep the reference to the model element in case the view element gets removed while dragging.
|
|
||||||
this._draggableElement = editor.editing.mapper.toModelElement(draggableElement);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Remove the draggable attribute in case no dragging started (only mousedown + mouseup).
|
|
||||||
this.listenTo(viewDocument, 'mouseup', () => {
|
|
||||||
if (!env.isAndroid) {
|
|
||||||
this._clearDraggableAttributesDelayed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Removes the `draggable` attribute from the element that was used for dragging.
|
|
||||||
*/
|
|
||||||
_clearDraggableAttributes() {
|
|
||||||
const editing = this.editor.editing;
|
|
||||||
editing.view.change(writer => {
|
|
||||||
// Remove 'draggable' attribute.
|
|
||||||
if (this._draggableElement && this._draggableElement.root.rootName != '$graveyard') {
|
|
||||||
writer.removeAttribute('draggable', editing.mapper.toViewElement(this._draggableElement));
|
|
||||||
}
|
|
||||||
this._draggableElement = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Deletes the dragged content from its original range and clears the dragging state.
|
|
||||||
*
|
|
||||||
* @param moved Whether the move succeeded.
|
|
||||||
*/
|
|
||||||
_finalizeDragging(moved) {
|
|
||||||
const editor = this.editor;
|
|
||||||
const model = editor.model;
|
|
||||||
const dragDropTarget = editor.plugins.get(DragDropTarget);
|
|
||||||
dragDropTarget.removeDropMarker();
|
|
||||||
this._clearDraggableAttributes();
|
|
||||||
if (editor.plugins.has('WidgetToolbarRepository')) {
|
|
||||||
const widgetToolbarRepository = editor.plugins.get('WidgetToolbarRepository');
|
|
||||||
widgetToolbarRepository.clearForceDisabled('dragDrop');
|
|
||||||
}
|
|
||||||
this._draggingUid = '';
|
|
||||||
if (this._previewContainer) {
|
|
||||||
this._previewContainer.remove();
|
|
||||||
this._previewContainer = undefined;
|
|
||||||
}
|
|
||||||
if (!this._draggedRange) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Delete moved content.
|
|
||||||
if (moved && this.isEnabled) {
|
|
||||||
model.change(writer => {
|
|
||||||
const selection = model.createSelection(this._draggedRange);
|
|
||||||
model.deleteContent(selection, { doNotAutoparagraph: true });
|
|
||||||
// Check result selection if it does not require auto-paragraphing of empty container.
|
|
||||||
const selectionParent = selection.getFirstPosition().parent;
|
|
||||||
if (selectionParent.isEmpty &&
|
|
||||||
!model.schema.checkChild(selectionParent, '$text') &&
|
|
||||||
model.schema.checkChild(selectionParent, 'paragraph')) {
|
|
||||||
writer.insertElement('paragraph', selectionParent, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this._draggedRange.detach();
|
|
||||||
this._draggedRange = null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Sets the dragged source range based on event target and document selection.
|
|
||||||
*/
|
|
||||||
_prepareDraggedRange(target) {
|
|
||||||
const editor = this.editor;
|
|
||||||
const model = editor.model;
|
|
||||||
const selection = model.document.selection;
|
|
||||||
// Check if this is dragstart over the widget (but not a nested editable).
|
|
||||||
const draggableWidget = target ? findDraggableWidget(target) : null;
|
|
||||||
if (draggableWidget) {
|
|
||||||
const modelElement = editor.editing.mapper.toModelElement(draggableWidget);
|
|
||||||
this._draggedRange = LiveRange.fromRange(model.createRangeOn(modelElement));
|
|
||||||
this._blockMode = model.schema.isBlock(modelElement);
|
|
||||||
// Disable toolbars so they won't obscure the drop area.
|
|
||||||
if (editor.plugins.has('WidgetToolbarRepository')) {
|
|
||||||
const widgetToolbarRepository = editor.plugins.get('WidgetToolbarRepository');
|
|
||||||
widgetToolbarRepository.forceDisabled('dragDrop');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If this was not a widget we should check if we need to drag some text content.
|
|
||||||
if (selection.isCollapsed && !selection.getFirstPosition().parent.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const blocks = Array.from(selection.getSelectedBlocks());
|
|
||||||
const draggedRange = selection.getFirstRange();
|
|
||||||
if (blocks.length == 0) {
|
|
||||||
this._draggedRange = LiveRange.fromRange(draggedRange);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const blockRange = getRangeIncludingFullySelectedParents(model, blocks);
|
|
||||||
if (blocks.length > 1) {
|
|
||||||
this._draggedRange = LiveRange.fromRange(blockRange);
|
|
||||||
this._blockMode = true;
|
|
||||||
// TODO block mode for dragging from outside editor? or inline? or both?
|
|
||||||
}
|
|
||||||
else if (blocks.length == 1) {
|
|
||||||
const touchesBlockEdges = draggedRange.start.isTouching(blockRange.start) &&
|
|
||||||
draggedRange.end.isTouching(blockRange.end);
|
|
||||||
this._draggedRange = LiveRange.fromRange(touchesBlockEdges ? blockRange : draggedRange);
|
|
||||||
this._blockMode = touchesBlockEdges;
|
|
||||||
}
|
|
||||||
model.change(writer => writer.setSelection(this._draggedRange.toRange()));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Updates the dragged preview image.
|
|
||||||
*/
|
|
||||||
_updatePreview({ dataTransfer, domTarget, clientX }) {
|
|
||||||
const view = this.editor.editing.view;
|
|
||||||
const editable = view.document.selection.editableElement;
|
|
||||||
const domEditable = view.domConverter.mapViewToDom(editable);
|
|
||||||
const computedStyle = global.window.getComputedStyle(domEditable);
|
|
||||||
if (!this._previewContainer) {
|
|
||||||
this._previewContainer = createElement(global.document, 'div', {
|
|
||||||
style: 'position: fixed; left: -999999px;'
|
|
||||||
});
|
|
||||||
global.document.body.appendChild(this._previewContainer);
|
|
||||||
}
|
|
||||||
else if (this._previewContainer.firstElementChild) {
|
|
||||||
this._previewContainer.removeChild(this._previewContainer.firstElementChild);
|
|
||||||
}
|
|
||||||
const domRect = new Rect(domEditable);
|
|
||||||
// If domTarget is inside the editable root, browsers will display the preview correctly by themselves.
|
|
||||||
if (domEditable.contains(domTarget)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const domEditablePaddingLeft = parseFloat(computedStyle.paddingLeft);
|
|
||||||
const preview = createElement(global.document, 'div');
|
|
||||||
preview.className = 'ck ck-content';
|
|
||||||
preview.style.width = computedStyle.width;
|
|
||||||
preview.style.paddingLeft = `${domRect.left - clientX + domEditablePaddingLeft}px`;
|
|
||||||
/**
|
|
||||||
* Set white background in drag and drop preview if iOS.
|
|
||||||
* Check: https://github.com/ckeditor/ckeditor5/issues/15085
|
|
||||||
*/
|
|
||||||
if (env.isiOS) {
|
|
||||||
preview.style.backgroundColor = 'white';
|
|
||||||
}
|
|
||||||
preview.innerHTML = dataTransfer.getData('text/html');
|
|
||||||
dataTransfer.setDragImage(preview, 0, 0);
|
|
||||||
this._previewContainer.appendChild(preview);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns the drop effect that should be a result of dragging the content.
|
|
||||||
* This function is handling a quirk when checking the effect in the 'drop' DOM event.
|
|
||||||
*/
|
|
||||||
function getFinalDropEffect(dataTransfer) {
|
|
||||||
if (env.isGecko) {
|
|
||||||
return dataTransfer.dropEffect;
|
|
||||||
}
|
|
||||||
return ['all', 'copyMove'].includes(dataTransfer.effectAllowed) ? 'move' : 'copy';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns a widget element that should be dragged.
|
|
||||||
*/
|
|
||||||
function findDraggableWidget(target) {
|
|
||||||
// This is directly an editable so not a widget for sure.
|
|
||||||
if (target.is('editableElement')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// TODO: Let's have a isWidgetSelectionHandleDomElement() helper in ckeditor5-widget utils.
|
|
||||||
if (target.hasClass('ck-widget__selection-handle')) {
|
|
||||||
return target.findAncestor(isWidget);
|
|
||||||
}
|
|
||||||
// Direct hit on a widget.
|
|
||||||
if (isWidget(target)) {
|
|
||||||
return target;
|
|
||||||
}
|
|
||||||
// Find closest ancestor that is either a widget or an editable element...
|
|
||||||
const ancestor = target.findAncestor(node => isWidget(node) || node.is('editableElement'));
|
|
||||||
// ...and if closer was the widget then enable dragging it.
|
|
||||||
if (isWidget(ancestor)) {
|
|
||||||
return ancestor;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Recursively checks if common parent of provided elements doesn't have any other children. If that's the case,
|
|
||||||
* it returns range including this parent. Otherwise, it returns only the range from first to last element.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
*
|
|
||||||
* <blockQuote>
|
|
||||||
* <paragraph>[Test 1</paragraph>
|
|
||||||
* <paragraph>Test 2</paragraph>
|
|
||||||
* <paragraph>Test 3]</paragraph>
|
|
||||||
* <blockQuote>
|
|
||||||
*
|
|
||||||
* Because all elements inside the `blockQuote` are selected, the range is extended to include the `blockQuote` too.
|
|
||||||
* If only first and second paragraphs would be selected, the range would not include it.
|
|
||||||
*/
|
|
||||||
function getRangeIncludingFullySelectedParents(model, elements) {
|
|
||||||
const firstElement = elements[0];
|
|
||||||
const lastElement = elements[elements.length - 1];
|
|
||||||
const parent = firstElement.getCommonAncestor(lastElement);
|
|
||||||
const startPosition = model.createPositionBefore(firstElement);
|
|
||||||
const endPosition = model.createPositionAfter(lastElement);
|
|
||||||
if (parent &&
|
|
||||||
parent.is('element') &&
|
|
||||||
!model.schema.isLimit(parent)) {
|
|
||||||
const parentRange = model.createRangeOn(parent);
|
|
||||||
const touchesStart = startPosition.isTouching(parentRange.start);
|
|
||||||
const touchesEnd = endPosition.isTouching(parentRange.end);
|
|
||||||
if (touchesStart && touchesEnd) {
|
|
||||||
// Selection includes all elements in the parent.
|
|
||||||
return getRangeIncludingFullySelectedParents(model, [parent]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return model.createRange(startPosition, endPosition);
|
|
||||||
}
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/dragdropblocktoolbar
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
/**
|
|
||||||
* Integration of a block Drag and Drop support with the block toolbar.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export default class DragDropBlockToolbar extends Plugin {
|
|
||||||
/**
|
|
||||||
* Whether current dragging is started by block toolbar button dragging.
|
|
||||||
*/
|
|
||||||
private _isBlockDragging;
|
|
||||||
/**
|
|
||||||
* DOM Emitter.
|
|
||||||
*/
|
|
||||||
private _domEmitter;
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName(): "DragDropBlockToolbar";
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init(): void;
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
destroy(): void;
|
|
||||||
/**
|
|
||||||
* The `dragstart` event handler.
|
|
||||||
*/
|
|
||||||
private _handleBlockDragStart;
|
|
||||||
/**
|
|
||||||
* The `dragover` and `drop` event handler.
|
|
||||||
*/
|
|
||||||
private _handleBlockDragging;
|
|
||||||
/**
|
|
||||||
* The `dragend` event handler.
|
|
||||||
*/
|
|
||||||
private _handleBlockDragEnd;
|
|
||||||
}
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/dragdropblocktoolbar
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import { env, global, DomEmitterMixin } from '@ckeditor/ckeditor5-utils';
|
|
||||||
import ClipboardObserver from './clipboardobserver.js';
|
|
||||||
/**
|
|
||||||
* Integration of a block Drag and Drop support with the block toolbar.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export default class DragDropBlockToolbar extends Plugin {
|
|
||||||
constructor() {
|
|
||||||
super(...arguments);
|
|
||||||
/**
|
|
||||||
* Whether current dragging is started by block toolbar button dragging.
|
|
||||||
*/
|
|
||||||
this._isBlockDragging = false;
|
|
||||||
/**
|
|
||||||
* DOM Emitter.
|
|
||||||
*/
|
|
||||||
this._domEmitter = new (DomEmitterMixin())();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName() {
|
|
||||||
return 'DragDropBlockToolbar';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
const editor = this.editor;
|
|
||||||
this.listenTo(editor, 'change:isReadOnly', (evt, name, isReadOnly) => {
|
|
||||||
if (isReadOnly) {
|
|
||||||
this.forceDisabled('readOnlyMode');
|
|
||||||
this._isBlockDragging = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.clearForceDisabled('readOnlyMode');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (env.isAndroid) {
|
|
||||||
this.forceDisabled('noAndroidSupport');
|
|
||||||
}
|
|
||||||
if (editor.plugins.has('BlockToolbar')) {
|
|
||||||
const blockToolbar = editor.plugins.get('BlockToolbar');
|
|
||||||
const element = blockToolbar.buttonView.element;
|
|
||||||
this._domEmitter.listenTo(element, 'dragstart', (evt, data) => this._handleBlockDragStart(data));
|
|
||||||
this._domEmitter.listenTo(global.document, 'dragover', (evt, data) => this._handleBlockDragging(data));
|
|
||||||
this._domEmitter.listenTo(global.document, 'drop', (evt, data) => this._handleBlockDragging(data));
|
|
||||||
this._domEmitter.listenTo(global.document, 'dragend', () => this._handleBlockDragEnd(), { useCapture: true });
|
|
||||||
if (this.isEnabled) {
|
|
||||||
element.setAttribute('draggable', 'true');
|
|
||||||
}
|
|
||||||
this.on('change:isEnabled', (evt, name, isEnabled) => {
|
|
||||||
element.setAttribute('draggable', isEnabled ? 'true' : 'false');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
this._domEmitter.stopListening();
|
|
||||||
return super.destroy();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The `dragstart` event handler.
|
|
||||||
*/
|
|
||||||
_handleBlockDragStart(domEvent) {
|
|
||||||
if (!this.isEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const model = this.editor.model;
|
|
||||||
const selection = model.document.selection;
|
|
||||||
const view = this.editor.editing.view;
|
|
||||||
const blocks = Array.from(selection.getSelectedBlocks());
|
|
||||||
const draggedRange = model.createRange(model.createPositionBefore(blocks[0]), model.createPositionAfter(blocks[blocks.length - 1]));
|
|
||||||
model.change(writer => writer.setSelection(draggedRange));
|
|
||||||
this._isBlockDragging = true;
|
|
||||||
view.focus();
|
|
||||||
view.getObserver(ClipboardObserver).onDomEvent(domEvent);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The `dragover` and `drop` event handler.
|
|
||||||
*/
|
|
||||||
_handleBlockDragging(domEvent) {
|
|
||||||
if (!this.isEnabled || !this._isBlockDragging) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const clientX = domEvent.clientX + (this.editor.locale.contentLanguageDirection == 'ltr' ? 100 : -100);
|
|
||||||
const clientY = domEvent.clientY;
|
|
||||||
const target = document.elementFromPoint(clientX, clientY);
|
|
||||||
const view = this.editor.editing.view;
|
|
||||||
if (!target || !target.closest('.ck-editor__editable')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
view.getObserver(ClipboardObserver).onDomEvent({
|
|
||||||
...domEvent,
|
|
||||||
type: domEvent.type,
|
|
||||||
dataTransfer: domEvent.dataTransfer,
|
|
||||||
target,
|
|
||||||
clientX,
|
|
||||||
clientY,
|
|
||||||
preventDefault: () => domEvent.preventDefault(),
|
|
||||||
stopPropagation: () => domEvent.stopPropagation()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The `dragend` event handler.
|
|
||||||
*/
|
|
||||||
_handleBlockDragEnd() {
|
|
||||||
this._isBlockDragging = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/dragdroptarget
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import { type Range, type LiveRange, type ViewElement, type ViewRange } from '@ckeditor/ckeditor5-engine';
|
|
||||||
/**
|
|
||||||
* Part of the Drag and Drop handling. Responsible for finding and displaying the drop target.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export default class DragDropTarget extends Plugin {
|
|
||||||
/**
|
|
||||||
* A delayed callback removing the drop marker.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
readonly removeDropMarkerDelayed: import("@ckeditor/ckeditor5-utils").DelayedFunc<() => void>;
|
|
||||||
/**
|
|
||||||
* A throttled callback updating the drop marker.
|
|
||||||
*/
|
|
||||||
private readonly _updateDropMarkerThrottled;
|
|
||||||
/**
|
|
||||||
* A throttled callback reconverting the drop parker.
|
|
||||||
*/
|
|
||||||
private readonly _reconvertMarkerThrottled;
|
|
||||||
/**
|
|
||||||
* The horizontal drop target line view.
|
|
||||||
*/
|
|
||||||
private _dropTargetLineView;
|
|
||||||
/**
|
|
||||||
* DOM Emitter.
|
|
||||||
*/
|
|
||||||
private _domEmitter;
|
|
||||||
/**
|
|
||||||
* Map of document scrollable elements.
|
|
||||||
*/
|
|
||||||
private _scrollables;
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName(): "DragDropTarget";
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init(): void;
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
destroy(): void;
|
|
||||||
/**
|
|
||||||
* Finds the drop target range and updates the drop marker.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
updateDropMarker(targetViewElement: ViewElement, targetViewRanges: Array<ViewRange> | null, clientX: number, clientY: number, blockMode: boolean, draggedRange: LiveRange | null): void;
|
|
||||||
/**
|
|
||||||
* Finds the final drop target range.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
getFinalDropRange(targetViewElement: ViewElement, targetViewRanges: Array<ViewRange> | null, clientX: number, clientY: number, blockMode: boolean, draggedRange: LiveRange | null): Range | null;
|
|
||||||
/**
|
|
||||||
* Removes the drop target marker.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
removeDropMarker(): void;
|
|
||||||
/**
|
|
||||||
* Creates downcast conversion for the drop target marker.
|
|
||||||
*/
|
|
||||||
private _setupDropMarker;
|
|
||||||
/**
|
|
||||||
* Updates the drop target marker to the provided range.
|
|
||||||
*
|
|
||||||
* @param targetRange The range to set the marker to.
|
|
||||||
*/
|
|
||||||
private _updateDropMarker;
|
|
||||||
/**
|
|
||||||
* Creates the UI element for vertical (in-line) drop target.
|
|
||||||
*/
|
|
||||||
private _createDropTargetPosition;
|
|
||||||
/**
|
|
||||||
* Updates the horizontal drop target line.
|
|
||||||
*/
|
|
||||||
private _updateDropTargetLine;
|
|
||||||
/**
|
|
||||||
* Finds the closest scrollable element rect for the given view element.
|
|
||||||
*/
|
|
||||||
private _getScrollableRect;
|
|
||||||
}
|
|
||||||
|
|
@ -1,379 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/dragdroptarget
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import { global, Rect, DomEmitterMixin, delay, ResizeObserver } from '@ckeditor/ckeditor5-utils';
|
|
||||||
import LineView from './lineview.js';
|
|
||||||
import { throttle } from 'lodash-es';
|
|
||||||
/**
|
|
||||||
* Part of the Drag and Drop handling. Responsible for finding and displaying the drop target.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
export default class DragDropTarget extends Plugin {
|
|
||||||
constructor() {
|
|
||||||
super(...arguments);
|
|
||||||
/**
|
|
||||||
* A delayed callback removing the drop marker.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
this.removeDropMarkerDelayed = delay(() => this.removeDropMarker(), 40);
|
|
||||||
/**
|
|
||||||
* A throttled callback updating the drop marker.
|
|
||||||
*/
|
|
||||||
this._updateDropMarkerThrottled = throttle(targetRange => this._updateDropMarker(targetRange), 40);
|
|
||||||
/**
|
|
||||||
* A throttled callback reconverting the drop parker.
|
|
||||||
*/
|
|
||||||
this._reconvertMarkerThrottled = throttle(() => {
|
|
||||||
if (this.editor.model.markers.has('drop-target')) {
|
|
||||||
this.editor.editing.reconvertMarker('drop-target');
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
/**
|
|
||||||
* The horizontal drop target line view.
|
|
||||||
*/
|
|
||||||
this._dropTargetLineView = new LineView();
|
|
||||||
/**
|
|
||||||
* DOM Emitter.
|
|
||||||
*/
|
|
||||||
this._domEmitter = new (DomEmitterMixin())();
|
|
||||||
/**
|
|
||||||
* Map of document scrollable elements.
|
|
||||||
*/
|
|
||||||
this._scrollables = new Map();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName() {
|
|
||||||
return 'DragDropTarget';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
this._setupDropMarker();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
destroy() {
|
|
||||||
this._domEmitter.stopListening();
|
|
||||||
for (const { resizeObserver } of this._scrollables.values()) {
|
|
||||||
resizeObserver.destroy();
|
|
||||||
}
|
|
||||||
this._updateDropMarkerThrottled.cancel();
|
|
||||||
this.removeDropMarkerDelayed.cancel();
|
|
||||||
this._reconvertMarkerThrottled.cancel();
|
|
||||||
return super.destroy();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Finds the drop target range and updates the drop marker.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
updateDropMarker(targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange) {
|
|
||||||
this.removeDropMarkerDelayed.cancel();
|
|
||||||
const targetRange = findDropTargetRange(this.editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange);
|
|
||||||
/* istanbul ignore next -- @preserve */
|
|
||||||
if (!targetRange) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (draggedRange && draggedRange.containsRange(targetRange)) {
|
|
||||||
// Target range is inside the dragged range.
|
|
||||||
return this.removeDropMarker();
|
|
||||||
}
|
|
||||||
this._updateDropMarkerThrottled(targetRange);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Finds the final drop target range.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
getFinalDropRange(targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange) {
|
|
||||||
const targetRange = findDropTargetRange(this.editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange);
|
|
||||||
// The dragging markers must be removed after searching for the target range because sometimes
|
|
||||||
// the target lands on the marker itself.
|
|
||||||
this.removeDropMarker();
|
|
||||||
return targetRange;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Removes the drop target marker.
|
|
||||||
*
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
removeDropMarker() {
|
|
||||||
const model = this.editor.model;
|
|
||||||
this.removeDropMarkerDelayed.cancel();
|
|
||||||
this._updateDropMarkerThrottled.cancel();
|
|
||||||
this._dropTargetLineView.isVisible = false;
|
|
||||||
if (model.markers.has('drop-target')) {
|
|
||||||
model.change(writer => {
|
|
||||||
writer.removeMarker('drop-target');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Creates downcast conversion for the drop target marker.
|
|
||||||
*/
|
|
||||||
_setupDropMarker() {
|
|
||||||
const editor = this.editor;
|
|
||||||
editor.ui.view.body.add(this._dropTargetLineView);
|
|
||||||
// Drop marker conversion for hovering over widgets.
|
|
||||||
editor.conversion.for('editingDowncast').markerToHighlight({
|
|
||||||
model: 'drop-target',
|
|
||||||
view: {
|
|
||||||
classes: ['ck-clipboard-drop-target-range']
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Drop marker conversion for in text and block drop target.
|
|
||||||
editor.conversion.for('editingDowncast').markerToElement({
|
|
||||||
model: 'drop-target',
|
|
||||||
view: (data, { writer }) => {
|
|
||||||
// Inline drop.
|
|
||||||
if (editor.model.schema.checkChild(data.markerRange.start, '$text')) {
|
|
||||||
this._dropTargetLineView.isVisible = false;
|
|
||||||
return this._createDropTargetPosition(writer);
|
|
||||||
}
|
|
||||||
// Block drop.
|
|
||||||
else {
|
|
||||||
if (data.markerRange.isCollapsed) {
|
|
||||||
this._updateDropTargetLine(data.markerRange);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._dropTargetLineView.isVisible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Updates the drop target marker to the provided range.
|
|
||||||
*
|
|
||||||
* @param targetRange The range to set the marker to.
|
|
||||||
*/
|
|
||||||
_updateDropMarker(targetRange) {
|
|
||||||
const editor = this.editor;
|
|
||||||
const markers = editor.model.markers;
|
|
||||||
editor.model.change(writer => {
|
|
||||||
if (markers.has('drop-target')) {
|
|
||||||
if (!markers.get('drop-target').getRange().isEqual(targetRange)) {
|
|
||||||
writer.updateMarker('drop-target', { range: targetRange });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
writer.addMarker('drop-target', {
|
|
||||||
range: targetRange,
|
|
||||||
usingOperation: false,
|
|
||||||
affectsData: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Creates the UI element for vertical (in-line) drop target.
|
|
||||||
*/
|
|
||||||
_createDropTargetPosition(writer) {
|
|
||||||
return writer.createUIElement('span', { class: 'ck ck-clipboard-drop-target-position' }, function (domDocument) {
|
|
||||||
const domElement = this.toDomElement(domDocument);
|
|
||||||
// Using word joiner to make this marker as high as text and also making text not break on marker.
|
|
||||||
domElement.append('\u2060', domDocument.createElement('span'), '\u2060');
|
|
||||||
return domElement;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Updates the horizontal drop target line.
|
|
||||||
*/
|
|
||||||
_updateDropTargetLine(range) {
|
|
||||||
const editing = this.editor.editing;
|
|
||||||
const nodeBefore = range.start.nodeBefore;
|
|
||||||
const nodeAfter = range.start.nodeAfter;
|
|
||||||
const nodeParent = range.start.parent;
|
|
||||||
const viewElementBefore = nodeBefore ? editing.mapper.toViewElement(nodeBefore) : null;
|
|
||||||
const domElementBefore = viewElementBefore ? editing.view.domConverter.mapViewToDom(viewElementBefore) : null;
|
|
||||||
const viewElementAfter = nodeAfter ? editing.mapper.toViewElement(nodeAfter) : null;
|
|
||||||
const domElementAfter = viewElementAfter ? editing.view.domConverter.mapViewToDom(viewElementAfter) : null;
|
|
||||||
const viewElementParent = editing.mapper.toViewElement(nodeParent);
|
|
||||||
if (!viewElementParent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const domElementParent = editing.view.domConverter.mapViewToDom(viewElementParent);
|
|
||||||
const domScrollableRect = this._getScrollableRect(viewElementParent);
|
|
||||||
const { scrollX, scrollY } = global.window;
|
|
||||||
const rectBefore = domElementBefore ? new Rect(domElementBefore) : null;
|
|
||||||
const rectAfter = domElementAfter ? new Rect(domElementAfter) : null;
|
|
||||||
const rectParent = new Rect(domElementParent).excludeScrollbarsAndBorders();
|
|
||||||
const above = rectBefore ? rectBefore.bottom : rectParent.top;
|
|
||||||
const below = rectAfter ? rectAfter.top : rectParent.bottom;
|
|
||||||
const parentStyle = global.window.getComputedStyle(domElementParent);
|
|
||||||
const top = (above <= below ? (above + below) / 2 : below);
|
|
||||||
if (domScrollableRect.top < top && top < domScrollableRect.bottom) {
|
|
||||||
const left = rectParent.left + parseFloat(parentStyle.paddingLeft);
|
|
||||||
const right = rectParent.right - parseFloat(parentStyle.paddingRight);
|
|
||||||
const leftClamped = Math.max(left + scrollX, domScrollableRect.left);
|
|
||||||
const rightClamped = Math.min(right + scrollX, domScrollableRect.right);
|
|
||||||
this._dropTargetLineView.set({
|
|
||||||
isVisible: true,
|
|
||||||
left: leftClamped,
|
|
||||||
top: top + scrollY,
|
|
||||||
width: rightClamped - leftClamped
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._dropTargetLineView.isVisible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Finds the closest scrollable element rect for the given view element.
|
|
||||||
*/
|
|
||||||
_getScrollableRect(viewElement) {
|
|
||||||
const rootName = viewElement.root.rootName;
|
|
||||||
let domScrollable;
|
|
||||||
if (this._scrollables.has(rootName)) {
|
|
||||||
domScrollable = this._scrollables.get(rootName).domElement;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const domElement = this.editor.editing.view.domConverter.mapViewToDom(viewElement);
|
|
||||||
domScrollable = findScrollableElement(domElement);
|
|
||||||
this._domEmitter.listenTo(domScrollable, 'scroll', this._reconvertMarkerThrottled, { usePassive: true });
|
|
||||||
const resizeObserver = new ResizeObserver(domScrollable, this._reconvertMarkerThrottled);
|
|
||||||
this._scrollables.set(rootName, {
|
|
||||||
domElement: domScrollable,
|
|
||||||
resizeObserver
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return new Rect(domScrollable).excludeScrollbarsAndBorders();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns fixed selection range for given position and target element.
|
|
||||||
*/
|
|
||||||
function findDropTargetRange(editor, targetViewElement, targetViewRanges, clientX, clientY, blockMode, draggedRange) {
|
|
||||||
const model = editor.model;
|
|
||||||
const mapper = editor.editing.mapper;
|
|
||||||
const targetModelElement = getClosestMappedModelElement(editor, targetViewElement);
|
|
||||||
let modelElement = targetModelElement;
|
|
||||||
while (modelElement) {
|
|
||||||
if (!blockMode) {
|
|
||||||
if (model.schema.checkChild(modelElement, '$text')) {
|
|
||||||
if (targetViewRanges) {
|
|
||||||
const targetViewPosition = targetViewRanges[0].start;
|
|
||||||
const targetModelPosition = mapper.toModelPosition(targetViewPosition);
|
|
||||||
const canDropOnPosition = !draggedRange || Array
|
|
||||||
.from(draggedRange.getItems())
|
|
||||||
.every(item => model.schema.checkChild(targetModelPosition, item));
|
|
||||||
if (canDropOnPosition) {
|
|
||||||
if (model.schema.checkChild(targetModelPosition, '$text')) {
|
|
||||||
return model.createRange(targetModelPosition);
|
|
||||||
}
|
|
||||||
else if (targetViewPosition) {
|
|
||||||
// This is the case of dropping inside a span wrapper of an inline image.
|
|
||||||
return findDropTargetRangeForElement(editor, getClosestMappedModelElement(editor, targetViewPosition.parent), clientX, clientY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (model.schema.isInline(modelElement)) {
|
|
||||||
return findDropTargetRangeForElement(editor, modelElement, clientX, clientY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (model.schema.isBlock(modelElement)) {
|
|
||||||
return findDropTargetRangeForElement(editor, modelElement, clientX, clientY);
|
|
||||||
}
|
|
||||||
else if (model.schema.checkChild(modelElement, '$block')) {
|
|
||||||
const childNodes = Array.from(modelElement.getChildren())
|
|
||||||
.filter((node) => node.is('element') && !shouldIgnoreElement(editor, node));
|
|
||||||
let startIndex = 0;
|
|
||||||
let endIndex = childNodes.length;
|
|
||||||
if (endIndex == 0) {
|
|
||||||
return model.createRange(model.createPositionAt(modelElement, 'end'));
|
|
||||||
}
|
|
||||||
while (startIndex < endIndex - 1) {
|
|
||||||
const middleIndex = Math.floor((startIndex + endIndex) / 2);
|
|
||||||
const side = findElementSide(editor, childNodes[middleIndex], clientX, clientY);
|
|
||||||
if (side == 'before') {
|
|
||||||
endIndex = middleIndex;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
startIndex = middleIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return findDropTargetRangeForElement(editor, childNodes[startIndex], clientX, clientY);
|
|
||||||
}
|
|
||||||
modelElement = modelElement.parent;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns true for elements which should be ignored.
|
|
||||||
*/
|
|
||||||
function shouldIgnoreElement(editor, modelElement) {
|
|
||||||
const mapper = editor.editing.mapper;
|
|
||||||
const domConverter = editor.editing.view.domConverter;
|
|
||||||
const viewElement = mapper.toViewElement(modelElement);
|
|
||||||
if (!viewElement) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const domElement = domConverter.mapViewToDom(viewElement);
|
|
||||||
return global.window.getComputedStyle(domElement).float != 'none';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns target range relative to the given element.
|
|
||||||
*/
|
|
||||||
function findDropTargetRangeForElement(editor, modelElement, clientX, clientY) {
|
|
||||||
const model = editor.model;
|
|
||||||
return model.createRange(model.createPositionAt(modelElement, findElementSide(editor, modelElement, clientX, clientY)));
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Resolves whether drop marker should be before or after the given element.
|
|
||||||
*/
|
|
||||||
function findElementSide(editor, modelElement, clientX, clientY) {
|
|
||||||
const mapper = editor.editing.mapper;
|
|
||||||
const domConverter = editor.editing.view.domConverter;
|
|
||||||
const viewElement = mapper.toViewElement(modelElement);
|
|
||||||
const domElement = domConverter.mapViewToDom(viewElement);
|
|
||||||
const rect = new Rect(domElement);
|
|
||||||
if (editor.model.schema.isInline(modelElement)) {
|
|
||||||
return clientX < (rect.left + rect.right) / 2 ? 'before' : 'after';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return clientY < (rect.top + rect.bottom) / 2 ? 'before' : 'after';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns the closest model element for the specified view element.
|
|
||||||
*/
|
|
||||||
function getClosestMappedModelElement(editor, element) {
|
|
||||||
const mapper = editor.editing.mapper;
|
|
||||||
const view = editor.editing.view;
|
|
||||||
const targetModelElement = mapper.toModelElement(element);
|
|
||||||
if (targetModelElement) {
|
|
||||||
return targetModelElement;
|
|
||||||
}
|
|
||||||
// Find mapped ancestor if the target is inside not mapped element (for example inline code element).
|
|
||||||
const viewPosition = view.createPositionBefore(element);
|
|
||||||
const viewElement = mapper.findMappedViewAncestor(viewPosition);
|
|
||||||
return mapper.toModelElement(viewElement);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns the closest scrollable ancestor DOM element.
|
|
||||||
*
|
|
||||||
* It is assumed that `domNode` is attached to the document.
|
|
||||||
*/
|
|
||||||
function findScrollableElement(domNode) {
|
|
||||||
let domElement = domNode;
|
|
||||||
do {
|
|
||||||
domElement = domElement.parentElement;
|
|
||||||
const overflow = global.window.getComputedStyle(domElement).overflowY;
|
|
||||||
if (overflow == 'auto' || overflow == 'scroll') {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (domElement.tagName != 'BODY');
|
|
||||||
return domElement;
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard
|
|
||||||
*/
|
|
||||||
export { default as Clipboard } from './clipboard.js';
|
|
||||||
export { default as ClipboardPipeline, type ClipboardContentInsertionEvent, type ClipboardContentInsertionData, type ClipboardInputTransformationEvent, type ClipboardInputTransformationData, type ClipboardOutputTransformationEvent, type ClipboardOutputTransformationData, type ViewDocumentClipboardOutputEvent } from './clipboardpipeline.js';
|
|
||||||
export { default as ClipboardMarkersUtils, type ClipboardMarkerRestrictedAction, type ClipboardMarkerConfiguration } from './clipboardmarkersutils.js';
|
|
||||||
export type { ClipboardEventData } from './clipboardobserver.js';
|
|
||||||
export { default as DragDrop } from './dragdrop.js';
|
|
||||||
export { default as PastePlainText } from './pasteplaintext.js';
|
|
||||||
export { default as DragDropTarget } from './dragdroptarget.js';
|
|
||||||
export { default as DragDropBlockToolbar } from './dragdropblocktoolbar.js';
|
|
||||||
export type { ViewDocumentClipboardInputEvent, ViewDocumentCopyEvent, ViewDocumentCutEvent } from './clipboardobserver.js';
|
|
||||||
import './augmentation.js';
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard
|
|
||||||
*/
|
|
||||||
export { default as Clipboard } from './clipboard.js';
|
|
||||||
export { default as ClipboardPipeline } from './clipboardpipeline.js';
|
|
||||||
export { default as ClipboardMarkersUtils } from './clipboardmarkersutils.js';
|
|
||||||
export { default as DragDrop } from './dragdrop.js';
|
|
||||||
export { default as PastePlainText } from './pasteplaintext.js';
|
|
||||||
export { default as DragDropTarget } from './dragdroptarget.js';
|
|
||||||
export { default as DragDropBlockToolbar } from './dragdropblocktoolbar.js';
|
|
||||||
import './augmentation.js';
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/lineview
|
|
||||||
*/
|
|
||||||
import { View } from '@ckeditor/ckeditor5-ui';
|
|
||||||
/**
|
|
||||||
* The horizontal drop target line view.
|
|
||||||
*/
|
|
||||||
export default class LineView extends View {
|
|
||||||
/**
|
|
||||||
* Controls whether the line is visible.
|
|
||||||
*
|
|
||||||
* @observable
|
|
||||||
* @default false
|
|
||||||
*/
|
|
||||||
isVisible: boolean;
|
|
||||||
/**
|
|
||||||
* Controls the line position x coordinate.
|
|
||||||
*
|
|
||||||
* @observable
|
|
||||||
* @default null
|
|
||||||
*/
|
|
||||||
left: number | null;
|
|
||||||
/**
|
|
||||||
* Controls the line width.
|
|
||||||
*
|
|
||||||
* @observable
|
|
||||||
* @default null
|
|
||||||
*/
|
|
||||||
width: number | null;
|
|
||||||
/**
|
|
||||||
* Controls the line position y coordinate.
|
|
||||||
*
|
|
||||||
* @observable
|
|
||||||
* @default null
|
|
||||||
*/
|
|
||||||
top: number | null;
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
constructor();
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/lineview
|
|
||||||
*/
|
|
||||||
/* istanbul ignore file -- @preserve */
|
|
||||||
import { View } from '@ckeditor/ckeditor5-ui';
|
|
||||||
import { toUnit } from '@ckeditor/ckeditor5-utils';
|
|
||||||
const toPx = toUnit('px');
|
|
||||||
/**
|
|
||||||
* The horizontal drop target line view.
|
|
||||||
*/
|
|
||||||
export default class LineView extends View {
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
const bind = this.bindTemplate;
|
|
||||||
this.set({
|
|
||||||
isVisible: false,
|
|
||||||
left: null,
|
|
||||||
top: null,
|
|
||||||
width: null
|
|
||||||
});
|
|
||||||
this.setTemplate({
|
|
||||||
tag: 'div',
|
|
||||||
attributes: {
|
|
||||||
class: [
|
|
||||||
'ck',
|
|
||||||
'ck-clipboard-drop-target-line',
|
|
||||||
bind.if('isVisible', 'ck-hidden', value => !value)
|
|
||||||
],
|
|
||||||
style: {
|
|
||||||
left: bind.to('left', left => toPx(left)),
|
|
||||||
top: bind.to('top', top => toPx(top)),
|
|
||||||
width: bind.to('width', width => toPx(width))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/pasteplaintext
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import ClipboardPipeline from './clipboardpipeline.js';
|
|
||||||
/**
|
|
||||||
* The plugin detects the user's intention to paste plain text.
|
|
||||||
*
|
|
||||||
* For example, it detects the <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd> keystroke.
|
|
||||||
*/
|
|
||||||
export default class PastePlainText extends Plugin {
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName(): "PastePlainText";
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get requires(): readonly [typeof ClipboardPipeline];
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init(): void;
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/pasteplaintext
|
|
||||||
*/
|
|
||||||
import { Plugin } from '@ckeditor/ckeditor5-core';
|
|
||||||
import ClipboardObserver from './clipboardobserver.js';
|
|
||||||
import ClipboardPipeline from './clipboardpipeline.js';
|
|
||||||
/**
|
|
||||||
* The plugin detects the user's intention to paste plain text.
|
|
||||||
*
|
|
||||||
* For example, it detects the <kbd>Ctrl/Cmd</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd> keystroke.
|
|
||||||
*/
|
|
||||||
export default class PastePlainText extends Plugin {
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get pluginName() {
|
|
||||||
return 'PastePlainText';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
static get requires() {
|
|
||||||
return [ClipboardPipeline];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @inheritDoc
|
|
||||||
*/
|
|
||||||
init() {
|
|
||||||
const editor = this.editor;
|
|
||||||
const model = editor.model;
|
|
||||||
const view = editor.editing.view;
|
|
||||||
const viewDocument = view.document;
|
|
||||||
const selection = model.document.selection;
|
|
||||||
let shiftPressed = false;
|
|
||||||
view.addObserver(ClipboardObserver);
|
|
||||||
this.listenTo(viewDocument, 'keydown', (evt, data) => {
|
|
||||||
shiftPressed = data.shiftKey;
|
|
||||||
});
|
|
||||||
editor.plugins.get(ClipboardPipeline).on('contentInsertion', (evt, data) => {
|
|
||||||
// Plain text can be determined based on the event flag (#7799) or auto-detection (#1006). If detected,
|
|
||||||
// preserve selection attributes on pasted items.
|
|
||||||
if (!shiftPressed && !isPlainTextFragment(data.content, model.schema)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
model.change(writer => {
|
|
||||||
// Formatting attributes should be preserved.
|
|
||||||
const textAttributes = Array.from(selection.getAttributes())
|
|
||||||
.filter(([key]) => model.schema.getAttributeProperties(key).isFormatting);
|
|
||||||
if (!selection.isCollapsed) {
|
|
||||||
model.deleteContent(selection, { doNotAutoparagraph: true });
|
|
||||||
}
|
|
||||||
// Also preserve other attributes if they survived the content deletion (because they were not fully selected).
|
|
||||||
// For example linkHref is not a formatting attribute but it should be preserved if pasted text was in the middle
|
|
||||||
// of a link.
|
|
||||||
textAttributes.push(...selection.getAttributes());
|
|
||||||
const range = writer.createRangeIn(data.content);
|
|
||||||
for (const item of range.getItems()) {
|
|
||||||
if (item.is('$textProxy')) {
|
|
||||||
writer.setAttributes(textAttributes, item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns true if specified `documentFragment` represents a plain text.
|
|
||||||
*/
|
|
||||||
function isPlainTextFragment(documentFragment, schema) {
|
|
||||||
if (documentFragment.childCount > 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const child = documentFragment.getChild(0);
|
|
||||||
if (schema.isObject(child)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return Array.from(child.getAttributeKeys()).length == 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/utils/normalizeclipboarddata
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Removes some popular browser quirks out of the clipboard data (HTML).
|
|
||||||
* Removes all HTML comments. These are considered an internal thing and it makes little sense if they leak into the editor data.
|
|
||||||
*
|
|
||||||
* @param data The HTML data to normalize.
|
|
||||||
* @returns Normalized HTML.
|
|
||||||
*/
|
|
||||||
export default function normalizeClipboardData(data: string): string;
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/utils/normalizeclipboarddata
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Removes some popular browser quirks out of the clipboard data (HTML).
|
|
||||||
* Removes all HTML comments. These are considered an internal thing and it makes little sense if they leak into the editor data.
|
|
||||||
*
|
|
||||||
* @param data The HTML data to normalize.
|
|
||||||
* @returns Normalized HTML.
|
|
||||||
*/
|
|
||||||
export default function normalizeClipboardData(data) {
|
|
||||||
return data
|
|
||||||
.replace(/<span(?: class="Apple-converted-space"|)>(\s+)<\/span>/g, (fullMatch, spaces) => {
|
|
||||||
// Handle the most popular and problematic case when even a single space becomes an nbsp;.
|
|
||||||
// Decode those to normal spaces. Read more in https://github.com/ckeditor/ckeditor5-clipboard/issues/2.
|
|
||||||
if (spaces.length == 1) {
|
|
||||||
return ' ';
|
|
||||||
}
|
|
||||||
return spaces;
|
|
||||||
})
|
|
||||||
// Remove all HTML comments.
|
|
||||||
.replace(/<!--[\s\S]*?-->/g, '');
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/utils/plaintexttohtml
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Converts plain text to its HTML-ized version.
|
|
||||||
*
|
|
||||||
* @param text The plain text to convert.
|
|
||||||
* @returns HTML generated from the plain text.
|
|
||||||
*/
|
|
||||||
export default function plainTextToHtml(text: string): string;
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/utils/plaintexttohtml
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Converts plain text to its HTML-ized version.
|
|
||||||
*
|
|
||||||
* @param text The plain text to convert.
|
|
||||||
* @returns HTML generated from the plain text.
|
|
||||||
*/
|
|
||||||
export default function plainTextToHtml(text) {
|
|
||||||
text = text
|
|
||||||
// Encode &.
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
// Encode <>.
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
// Creates a paragraph for each double line break.
|
|
||||||
.replace(/\r?\n\r?\n/g, '</p><p>')
|
|
||||||
// Creates a line break for each single line break.
|
|
||||||
.replace(/\r?\n/g, '<br>')
|
|
||||||
// Replace tabs with four spaces.
|
|
||||||
.replace(/\t/g, ' ')
|
|
||||||
// Preserve trailing spaces (only the first and last one – the rest is handled below).
|
|
||||||
.replace(/^\s/, ' ')
|
|
||||||
.replace(/\s$/, ' ')
|
|
||||||
// Preserve other subsequent spaces now.
|
|
||||||
.replace(/\s\s/g, ' ');
|
|
||||||
if (text.includes('</p><p>') || text.includes('<br>')) {
|
|
||||||
// If we created paragraphs above, add the trailing ones.
|
|
||||||
text = `<p>${text}</p>`;
|
|
||||||
}
|
|
||||||
// TODO:
|
|
||||||
// * What about '\nfoo' vs ' foo'?
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* @module clipboard/utils/viewtoplaintext
|
|
||||||
*/
|
|
||||||
import type { ViewDocumentFragment, ViewItem } from '@ckeditor/ckeditor5-engine';
|
|
||||||
/**
|
|
||||||
* Converts {@link module:engine/view/item~Item view item} and all of its children to plain text.
|
|
||||||
*
|
|
||||||
* @param viewItem View item to convert.
|
|
||||||
* @returns Plain text representation of `viewItem`.
|
|
||||||
*/
|
|
||||||
export default function viewToPlainText(viewItem: ViewItem | ViewDocumentFragment): string;
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
/**
|
|
||||||
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
// Elements which should not have empty-line padding.
|
|
||||||
// Most `view.ContainerElement` want to be separate by new-line, but some are creating one structure
|
|
||||||
// together (like `<li>`) so it is better to separate them by only one "\n".
|
|
||||||
const smallPaddingElements = ['figcaption', 'li'];
|
|
||||||
const listElements = ['ol', 'ul'];
|
|
||||||
/**
|
|
||||||
* Converts {@link module:engine/view/item~Item view item} and all of its children to plain text.
|
|
||||||
*
|
|
||||||
* @param viewItem View item to convert.
|
|
||||||
* @returns Plain text representation of `viewItem`.
|
|
||||||
*/
|
|
||||||
export default function viewToPlainText(viewItem) {
|
|
||||||
if (viewItem.is('$text') || viewItem.is('$textProxy')) {
|
|
||||||
return viewItem.data;
|
|
||||||
}
|
|
||||||
if (viewItem.is('element', 'img') && viewItem.hasAttribute('alt')) {
|
|
||||||
return viewItem.getAttribute('alt');
|
|
||||||
}
|
|
||||||
if (viewItem.is('element', 'br')) {
|
|
||||||
return '\n'; // Convert soft breaks to single line break (#8045).
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Item is a document fragment, attribute element or container element. It doesn't
|
|
||||||
* have it's own text value, so we need to convert its children elements.
|
|
||||||
*/
|
|
||||||
let text = '';
|
|
||||||
let prev = null;
|
|
||||||
for (const child of viewItem.getChildren()) {
|
|
||||||
text += newLinePadding(child, prev) + viewToPlainText(child);
|
|
||||||
prev = child;
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Returns new line padding to prefix the given elements with.
|
|
||||||
*/
|
|
||||||
function newLinePadding(element, previous) {
|
|
||||||
if (!previous) {
|
|
||||||
// Don't add padding to first elements in a level.
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (element.is('element', 'li') && !element.isEmpty && element.getChild(0).is('containerElement')) {
|
|
||||||
// Separate document list items with empty lines.
|
|
||||||
return '\n\n';
|
|
||||||
}
|
|
||||||
if (listElements.includes(element.name) && listElements.includes(previous.name)) {
|
|
||||||
/**
|
|
||||||
* Because `<ul>` and `<ol>` are AttributeElements, two consecutive lists will not have any padding between
|
|
||||||
* them (see the `if` statement below). To fix this, we need to make an exception for this case.
|
|
||||||
*/
|
|
||||||
return '\n\n';
|
|
||||||
}
|
|
||||||
if (!element.is('containerElement') && !previous.is('containerElement')) {
|
|
||||||
// Don't add padding between non-container elements.
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (smallPaddingElements.includes(element.name) || smallPaddingElements.includes(previous.name)) {
|
|
||||||
// Add small padding between selected container elements.
|
|
||||||
return '\n';
|
|
||||||
}
|
|
||||||
// Add empty lines between container elements.
|
|
||||||
return '\n\n';
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
|
|
||||||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
|
||||||
*/
|
|
||||||
|
|
||||||
.ck.ck-editor__editable {
|
|
||||||
/*
|
|
||||||
* Vertical drop target (in text).
|
|
||||||
*/
|
|
||||||
& .ck.ck-clipboard-drop-target-position {
|
|
||||||
display: inline;
|
|
||||||
position: relative;
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
& span {
|
|
||||||
position: absolute;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Styles of the widget being dragged (its preview).
|
|
||||||
*/
|
|
||||||
& .ck-widget:-webkit-drag {
|
|
||||||
& > .ck-widget__selection-handle {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > .ck-widget__type-around {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ck.ck-clipboard-drop-target-line {
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
@ -1,311 +0,0 @@
|
||||||
Changelog
|
|
||||||
=========
|
|
||||||
|
|
||||||
All changes in the package are documented in the main repository. See: https://github.com/ckeditor/ckeditor5/blob/master/CHANGELOG.md.
|
|
||||||
|
|
||||||
Changes for the past releases are available below.
|
|
||||||
|
|
||||||
## [19.0.0](https://github.com/ckeditor/ckeditor5-core/compare/v18.0.0...v19.0.0) (April 29, 2020)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [18.0.0](https://github.com/ckeditor/ckeditor5-core/compare/v17.0.0...v18.0.0) (March 19, 2020)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Updated translations. ([c898ffc](https://github.com/ckeditor/ckeditor5-core/commit/c898ffc))
|
|
||||||
|
|
||||||
|
|
||||||
## [17.0.0](https://github.com/ckeditor/ckeditor5-core/compare/v16.0.0...v17.0.0) (February 19, 2020)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Introduced the concept of editor contexts and context plugins. Contexts provide a common, higher-level environment for solutions which use multiple editors and/or plugins that work outside an editor. Closes [ckeditor/ckeditor5#5891](https://github.com/ckeditor/ckeditor5/issues/5891). ([672e55e](https://github.com/ckeditor/ckeditor5-core/commit/672e55e))
|
|
||||||
* Added vertical alignment icons. Moved horizontal alignment icons form `@ckeditor/ckeditor5-alignment` (see [ckeditor/ckeditor5-table#227](https://github.com/ckeditor/ckeditor5-table/issues/227)). ([ada4a79](https://github.com/ckeditor/ckeditor5-core/commit/ada4a79))
|
|
||||||
* Introduced `Plugin#isEnabled`, `Plugin#forceDisabled()` and `Plugin#clearForceDisabled()`. ([7449450](https://github.com/ckeditor/ckeditor5-core/commit/7449450))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Updated translations. ([6a2b584](https://github.com/ckeditor/ckeditor5-core/commit/6a2b584))
|
|
||||||
|
|
||||||
|
|
||||||
## [16.0.0](https://github.com/ckeditor/ckeditor5-core/compare/v15.0.0...v16.0.0) (December 4, 2019)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Updated translations. ([6d8950e](https://github.com/ckeditor/ckeditor5-core/commit/6d8950e))
|
|
||||||
|
|
||||||
|
|
||||||
## [15.0.0](https://github.com/ckeditor/ckeditor5-core/compare/v12.3.0...v15.0.0) (October 23, 2019)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Added custom error handling to the `editor.execute()` method. Part of [ckeditor/ckeditor5#1304](https://github.com/ckeditor/ckeditor5/issues/1304). ([c1babca](https://github.com/ckeditor/ckeditor5-core/commit/c1babca))
|
|
||||||
* Updated translations. ([a7d36a8](https://github.com/ckeditor/ckeditor5-core/commit/a7d36a8))
|
|
||||||
|
|
||||||
|
|
||||||
## [12.3.0](https://github.com/ckeditor/ckeditor5-core/compare/v12.2.1...v12.3.0) (August 26, 2019)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Allowed configuration of the editor content language via `conifg.language`. See [ckeditor/ckeditor5#1151](https://github.com/ckeditor/ckeditor5/issues/1151). ([22079dd](https://github.com/ckeditor/ckeditor5-core/commit/22079dd))
|
|
||||||
* Introduced a `secureSourceElement()` utility that prevents from initialising more than one editor on the same DOM element. See [ckeditor/ckeditor5#746](https://github.com/ckeditor/ckeditor5/issues/746). ([6a59058](https://github.com/ckeditor/ckeditor5-core/commit/6a59058))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* The issue tracker for this package was moved to https://github.com/ckeditor/ckeditor5/issues. See [ckeditor/ckeditor5#1988](https://github.com/ckeditor/ckeditor5/issues/1988). ([6d72325](https://github.com/ckeditor/ckeditor5-core/commit/6d72325))
|
|
||||||
* Updated translations. ([e29d56e](https://github.com/ckeditor/ckeditor5-core/commit/e29d56e))
|
|
||||||
|
|
||||||
|
|
||||||
## [12.2.1](https://github.com/ckeditor/ckeditor5-core/compare/v12.2.0...v12.2.1) (July 10, 2019)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [12.2.0](https://github.com/ckeditor/ckeditor5-core/compare/v12.1.1...v12.2.0) (July 4, 2019)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Added an editor instance reference to the native editable DOM element under the `ckeditorInstance` property. Closes [ckeditor/ckeditor5#1838](https://github.com/ckeditor/ckeditor5/issues/1838). ([fa94600](https://github.com/ckeditor/ckeditor5-core/commit/fa94600))
|
|
||||||
|
|
||||||
Implemented the `EditorUI#setEditableElement()` method.
|
|
||||||
Deprecated the `EditorUI#_editableElements` property.
|
|
||||||
* Introduced `MultiCommand` which acts as a composite command – it can group many commands under the hood. ([ebcbd01](https://github.com/ckeditor/ckeditor5-core/commit/ebcbd01))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Introduce the `editor-wrong-element` error thrown when the editor is created over a wrong element. Closes [ckeditor/ckeditor5#1591](https://github.com/ckeditor/ckeditor5/issues/1591). ([9945fc6](https://github.com/ckeditor/ckeditor5-core/commit/9945fc6))
|
|
||||||
* Updated translations. ([43f5b6e](https://github.com/ckeditor/ckeditor5-core/commit/43f5b6e))
|
|
||||||
|
|
||||||
|
|
||||||
## [12.1.1](https://github.com/ckeditor/ckeditor5-core/compare/v12.1.0...v12.1.1) (June 6, 2019)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Updated translations. ([ed6bd3c](https://github.com/ckeditor/ckeditor5-core/commit/ed6bd3c))
|
|
||||||
|
|
||||||
|
|
||||||
## [12.1.0](https://github.com/ckeditor/ckeditor5-core/compare/v12.0.0...v12.1.0) (April 4, 2019)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Introduced `Command#disable()` and `Command#enable()`. Closes [#165](https://github.com/ckeditor/ckeditor5-core/issues/165). ([030ca2b](https://github.com/ckeditor/ckeditor5-core/commit/030ca2b))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Optimized icons. ([a5f8d34](https://github.com/ckeditor/ckeditor5-core/commit/a5f8d34))
|
|
||||||
* Updated translations. ([2dedc43](https://github.com/ckeditor/ckeditor5-core/commit/2dedc43))
|
|
||||||
|
|
||||||
|
|
||||||
## [12.0.0](https://github.com/ckeditor/ckeditor5-core/compare/v11.1.0...v12.0.0) (February 28, 2019)
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
* Fixed memory leaks during editor initialization and destruction. Created helpers for testing memory usage. Closes [ckeditor/ckeditor5#1341](https://github.com/ckeditor/ckeditor5/issues/1341). ([11ca135](https://github.com/ckeditor/ckeditor5-core/commit/11ca135))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Editor UI classes API refactoring. See breaking changes. Closes [ckeditor/ckeditor5#1449](https://github.com/ckeditor/ckeditor5/issues/1449). ([aca1ff1](https://github.com/ckeditor/ckeditor5-core/commit/aca1ff1))
|
|
||||||
* Remove `editor#pluginsReady` event. Closes [ckeditor/ckeditor5#1477](https://github.com/ckeditor/ckeditor5/issues/1477). ([6d63538](https://github.com/ckeditor/ckeditor5-core/commit/6d63538))
|
|
||||||
* The `Editor#getData()` method now accepts `options.trim` parameter. By default it will now return an empty string when the editor is empty (instead of returning `'<p> </p>'` as before). ([4f8abd1](https://github.com/ckeditor/ckeditor5-core/commit/4f8abd1))
|
|
||||||
* Throw an error from `editor.plugins.get()` if the plugin is not loaded. Closes [#148](https://github.com/ckeditor/ckeditor5-core/issues/148). ([a56b47a](https://github.com/ckeditor/ckeditor5-core/commit/a56b47a))
|
|
||||||
* Updated translations. ([4cf6f4f](https://github.com/ckeditor/ckeditor5-core/commit/4cf6f4f)) ([a8367a5](https://github.com/ckeditor/ckeditor5-core/commit/a8367a5)) ([0e09317](https://github.com/ckeditor/ckeditor5-core/commit/0e09317))
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* Upgraded minimal versions of Node to `8.0.0` and npm to `5.7.1`. See: [ckeditor/ckeditor5#1507](https://github.com/ckeditor/ckeditor5/issues/1507). ([612ea3c](https://github.com/ckeditor/ckeditor5-cloud-services/commit/612ea3c))
|
|
||||||
* The `Editor#getData()` method now returns an empty string by default when editor content is empty (instead of returning `'<p> </p>'` as before).
|
|
||||||
* The `editor#pluginsReady` event was removed. Use plugin `afterInit()` method instead.
|
|
||||||
* Removed `EditorWithUI#element` property. The `EditorUI#element` property should be used instead.
|
|
||||||
* Removed `EditorWithUI#uiReady` event. The `EditorUI#ready` event should be used instead.
|
|
||||||
* Removed `view` parameter in `EditorUI` constructor. Only subclasses should use it without passing it further to `EditorUI`.
|
|
||||||
* Removed `EditorUI#view` property. The `view` property from subclasses (like `ClassicEditorUI#view`) should be used directly instead.
|
|
||||||
* The `editor.plugins.get()` will now throw an error if the plugin is not loaded. Use `editor.plugins.has()` to check if plugin is available.
|
|
||||||
|
|
||||||
|
|
||||||
## [11.1.0](https://github.com/ckeditor/ckeditor5-core/compare/v11.0.1...v11.1.0) (December 5, 2018)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Implemented the `config.extraPlugins` option. Closes [#146](https://github.com/ckeditor/ckeditor5-core/issues/146). ([4b5c3d4](https://github.com/ckeditor/ckeditor5-core/commit/4b5c3d4))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Improved SVG icons size. See [ckeditor/ckeditor5-theme-lark#206](https://github.com/ckeditor/ckeditor5-theme-lark/issues/206). ([c4795fb](https://github.com/ckeditor/ckeditor5-core/commit/c4795fb))
|
|
||||||
|
|
||||||
|
|
||||||
## [11.0.1](https://github.com/ckeditor/ckeditor5-core/compare/v11.0.0...v11.0.1) (October 8, 2018)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Updated translations. ([873d193](https://github.com/ckeditor/ckeditor5-core/commit/873d193))
|
|
||||||
|
|
||||||
|
|
||||||
## [11.0.0](https://github.com/ckeditor/ckeditor5-core/compare/v10.1.0...v11.0.0) (July 18, 2018)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Added the observable `Editor#state` property. Closes [#124](https://github.com/ckeditor/ckeditor5-core/issues/124). ([ec89d8d](https://github.com/ckeditor/ckeditor5-core/commit/ec89d8d))
|
|
||||||
* Imported the module providing the `CKEDITOR_VERSION` global constant in the `Editor` class (see [ckeditor/ckeditor5#1005](https://github.com/ckeditor/ckeditor5/issues/1005)). ([a1a9144](https://github.com/ckeditor/ckeditor5-core/commit/a1a9144))
|
|
||||||
* Introduced the `#element` property to the `EditorWithUI` interface. The `#element` property from the `ElementApi` interface has been renamed to `#sourceElement`. Most editors implement both interfaces, which means that the old `editor.element` property is now called `editor.sourceElement` and there is a new `editor.element` property with a new meaning. Closes [#64](https://github.com/ckeditor/ckeditor5-core/issues/64). ([eb43b63](https://github.com/ckeditor/ckeditor5-core/commit/eb43b63))
|
|
||||||
* Introduced the `EditorUI#update` event. Closes [#130](https://github.com/ckeditor/ckeditor5-core/issues/130). ([734166a](https://github.com/ckeditor/ckeditor5-core/commit/734166a))
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
* Editor#destroy waits for the initialization. Closes [#134](https://github.com/ckeditor/ckeditor5-core/issues/134). ([ad1da26](https://github.com/ckeditor/ckeditor5-core/commit/ad1da26))
|
|
||||||
* The `ClassicTestEditor` should not render its UI in the `constructor()`. Closes [#137](https://github.com/ckeditor/ckeditor5-core/issues/137). ([46fdc36](https://github.com/ckeditor/ckeditor5-core/commit/46fdc36))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Refreshed the pilcrow icon (see [ckeditor/ckeditor5-ui#394](https://github.com/ckeditor/ckeditor5-ui/issues/394)). ([ce33acb](https://github.com/ckeditor/ckeditor5-core/commit/ce33acb))
|
|
||||||
* Split `Editor.build` into `Editor.builtinPlugins` and `Editor.defaultConfig`. Closes [#140](https://github.com/ckeditor/ckeditor5-core/issues/140). ([c13ec79](https://github.com/ckeditor/ckeditor5-core/commit/c13ec79))
|
|
||||||
* Updated translations. ([ba21a12](https://github.com/ckeditor/ckeditor5-core/commit/ba21a12))
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* `Editor.build` was split to `Editor.builtinPlugins` and `Editor.defaultConfig`.
|
|
||||||
* The `editor.element` property was renamed to `editor.sourceElement`.
|
|
||||||
* The `editor.updateElement()` method was renamed to `editor.updateSourceElement()`.
|
|
||||||
* The `EditorUI` is now a class (no longer an interface).
|
|
||||||
|
|
||||||
|
|
||||||
## [10.1.0](https://github.com/ckeditor/ckeditor5-core/compare/v10.0.0...v10.1.0) (June 21, 2018)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Introduced `PendingActions` plugin. Closes [#126](https://github.com/ckeditor/ckeditor5-core/issues/126). ([e1af648](https://github.com/ckeditor/ckeditor5-core/commit/e1af648))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Updated translations.
|
|
||||||
|
|
||||||
|
|
||||||
## [10.0.0](https://github.com/ckeditor/ckeditor5-core/compare/v1.0.0-beta.4...v10.0.0) (April 25, 2018)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Changed the license to GPL2+ only. See [ckeditor/ckeditor5#991](https://github.com/ckeditor/ckeditor5/issues/991). ([0ccf614](https://github.com/ckeditor/ckeditor5-core/commit/0ccf614))
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* The license under which CKEditor 5 is released was changed from a triple GPL, LGPL, and MPL license to a GPL2+. See [ckeditor/ckeditor5#991](https://github.com/ckeditor/ckeditor5/issues/991) for more information.
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-beta.4](https://github.com/ckeditor/ckeditor5-core/compare/v1.0.0-beta.2...v1.0.0-beta.4) (April 19, 2018)
|
|
||||||
|
|
||||||
Internal changes only (updated dependencies, documentation, etc.).
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-beta.2](https://github.com/ckeditor/ckeditor5-core/compare/v1.0.0-beta.1...v1.0.0-beta.2) (April 10, 2018)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Made the check and cancel icons thicker and fill-friendly (see [ckeditor/ckeditor5#810](https://github.com/ckeditor/ckeditor5/issues/810)). ([6584541](https://github.com/ckeditor/ckeditor5-core/commit/6584541))
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-beta.1](https://github.com/ckeditor/ckeditor5-core/compare/v1.0.0-alpha.2...v1.0.0-beta.1) (March 15, 2018)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Moved `EditingController`, `DataController` and `EditingKeystrokeHandler` from `StandardEditor` to the `Editor` class. Closes [#110](https://github.com/ckeditor/ckeditor5-core/issues/110). ([5a2031e](https://github.com/ckeditor/ckeditor5-core/commit/5a2031e))
|
|
||||||
* Removed the `StandardEditor` class in favor of `DataInterface` and `ElementInterface` mixins. Added `EditorWithUI` interface. Closes [#115](https://github.com/ckeditor/ckeditor5-core/issues/115). Closes [#113](https://github.com/ckeditor/ckeditor5-core/issues/113). Closes https://github.com/ckeditor/ckeditor5/issues/303. ([fe81992](https://github.com/ckeditor/ckeditor5-core/commit/fe81992))
|
|
||||||
* `Command` should listen to `model.Document#event:change`. ([912570d](https://github.com/ckeditor/ckeditor5-core/commit/912570d))
|
|
||||||
* Changed `config.lang` to `config.language` to align to the naming convention. ([8720973](https://github.com/ckeditor/ckeditor5-core/commit/8720973))
|
|
||||||
* Removed `loadDataFromElement()` method from `ElementApiMixin`. Closes [#120](https://github.com/ckeditor/ckeditor5-core/issues/120). ([4976e10](https://github.com/ckeditor/ckeditor5-core/commit/4976e10))
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* The `StandardEditor` class was removed. Use `Editor` class with `DataInterface` and `ElementInterface` mixins.
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-alpha.2](https://github.com/ckeditor/ckeditor5-core/compare/v1.0.0-alpha.1...v1.0.0-alpha.2) (November 14, 2017)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Updated translations. ([1003fa4](https://github.com/ckeditor/ckeditor5-core/commit/1003fa4))
|
|
||||||
|
|
||||||
|
|
||||||
## [1.0.0-alpha.1](https://github.com/ckeditor/ckeditor5-core/compare/v0.9.0...v1.0.0-alpha.1) (October 3, 2017)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* The `StandardEditor` should automatically update the contents of its source textarea upon submission of the form. Closes https://github.com/ckeditor/ckeditor5/issues/544. ([ce46fde](https://github.com/ckeditor/ckeditor5-core/commit/ce46fde))
|
|
||||||
|
|
||||||
|
|
||||||
## [0.9.0](https://github.com/ckeditor/ckeditor5-core/compare/v0.8.1...v0.9.0) (September 3, 2017)
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
* `EditingKeystrokeHandler` should prevent default action only for commands. Closes [#90](https://github.com/ckeditor/ckeditor5-core/issues/90). ([82ff39a](https://github.com/ckeditor/ckeditor5-core/commit/82ff39a))
|
|
||||||
* `ToggleAttributeCommand` should listen to reliable events to determine its state. Closes [#50](https://github.com/ckeditor/ckeditor5-core/issues/50). ([6816505](https://github.com/ckeditor/ckeditor5-core/commit/6816505))
|
|
||||||
* SVG icons should not define own fill if controlled by the styles. Closes [#79](https://github.com/ckeditor/ckeditor5-core/issues/79). ([fadf5ec](https://github.com/ckeditor/ckeditor5-core/commit/fadf5ec))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* `EditingKeystrokeHandler` should support priorities and proper cancelling. Closes [#101](https://github.com/ckeditor/ckeditor5-core/issues/101). ([c74b9a3](https://github.com/ckeditor/ckeditor5-core/commit/c74b9a3))
|
|
||||||
* `Editor#destroy()` will destroy all loaded plugins. Closes [#86](https://github.com/ckeditor/ckeditor5-core/issues/86). ([77e5217](https://github.com/ckeditor/ckeditor5-core/commit/77e5217))
|
|
||||||
|
|
||||||
Added default implementation for `Plugin#destroy()`. Introduced `PluginCollection#destroy()` method which calls `Plugin#destroy()` for every loaded plugin.
|
|
||||||
* `PluginCollection` will warn if the user wants to load two or more plugins with the same name. Closes [#85](https://github.com/ckeditor/ckeditor5-core/issues/85). ([e00a282](https://github.com/ckeditor/ckeditor5-core/commit/e00a282))
|
|
||||||
* Introduced `Editor#isReadOnly` property which disables all commands and prevents from modifying the document. Closes [#96](https://github.com/ckeditor/ckeditor5-core/issues/96). Closes https://github.com/ckeditor/ckeditor5/issues/492. ([1ca5608](https://github.com/ckeditor/ckeditor5-core/commit/1ca5608))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Bound `EditingController#isReadOnly` to the editor. Closes [#98](https://github.com/ckeditor/ckeditor5-core/issues/98). ([ec02906](https://github.com/ckeditor/ckeditor5-core/commit/ec02906))
|
|
||||||
* Cleaned up SVG icons. ([ffac7e7](https://github.com/ckeditor/ckeditor5-core/commit/ffac7e7))
|
|
||||||
* Introduced `PluginInterface`. A plugin does not need to inherit directly from the `Plugin` class, as long as it implements some minimal interface. See [#78](https://github.com/ckeditor/ckeditor5-core/issues/78). ([f476f34](https://github.com/ckeditor/ckeditor5-core/commit/f476f34))
|
|
||||||
* Removed `ToggleAttributeCommand` class as well as other helpers from the `core/command` namespace. Closes [#14](https://github.com/ckeditor/ckeditor5-core/issues/14). ([7c68581](https://github.com/ckeditor/ckeditor5-core/commit/7c68581))
|
|
||||||
* The command API has been redesigned. The `Command` methods are now public and consistent. Commands can be used in a standalone mode (without the editor). The `CommandCollection` was introduced and replaced the `Map` of commands used in `editor.commands`. Closes [#88](https://github.com/ckeditor/ckeditor5-core/issues/88). ([b76983b](https://github.com/ckeditor/ckeditor5-core/commit/b76983b))
|
|
||||||
|
|
||||||
Besides changes mentioned in this point and in the "Breaking changes" section, other minor changes were done:
|
|
||||||
|
|
||||||
* `Editor#execute()` now accepts multiple command arguments.
|
|
||||||
* `Command#value` property was standardized.
|
|
||||||
|
|
||||||
### BREAKING CHANGES
|
|
||||||
|
|
||||||
* The `ToggleAttributeCommand` was moved to the `ckeditor5-basic-styles` package as `AttributeCommand` and the other command helpers to `ckeditor5-engine` as `Schema` methods.
|
|
||||||
* The `Command`'s protected `_doExecute()` and `_checkEnabled()` methods have been replaced by public `execute()` and `refresh()` methods.
|
|
||||||
* The `Command`'s `refreshState` event was removed and one should use `change:isEnabled` to control and override its state.
|
|
||||||
* The `core/command/command` module has been moved to the root directory (so the `Command` class is `core/command~Command` now).
|
|
||||||
* The `Command#refresh()` method is now automatically called on `editor.document#changesDone`.
|
|
||||||
* The `editor.commands` map was replaced by a `CommandCollection` instance so `editor.commands.set()` calls need to be replaced with `editor.commands.add()`.
|
|
||||||
|
|
||||||
### Note
|
|
||||||
|
|
||||||
* Plugin naming convention has changed.
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.1](https://github.com/ckeditor/ckeditor5-core/compare/v0.8.0...v0.8.1) (May 7, 2017)
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Updated translations. ([993596a](https://github.com/ckeditor/ckeditor5-core/commit/993596a))
|
|
||||||
|
|
||||||
|
|
||||||
## [0.8.0](https://github.com/ckeditor/ckeditor5-core/compare/v0.7.0...v0.8.0) (April 5, 2017)
|
|
||||||
|
|
||||||
### Bug fixes
|
|
||||||
|
|
||||||
* This time, we introduced support for `config.removePlugins` for real (we said that we did this in the previous release, but we did not). Closes [#49](https://github.com/ckeditor/ckeditor5-core/issues/49). ([5834fed](https://github.com/ckeditor/ckeditor5-core/commit/5834fed))
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Added support for building plugins and default configurations into `Editor` classes. Closes [#67](https://github.com/ckeditor/ckeditor5-core/issues/67). ([a1fa64f](https://github.com/ckeditor/ckeditor5-core/commit/a1fa64f))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Updated translations. ([1296b03](https://github.com/ckeditor/ckeditor5-core/commit/1296b03))
|
|
||||||
|
|
||||||
|
|
||||||
## [0.7.0](https://github.com/ckeditor/ckeditor5-core/compare/v0.6.0...v0.7.0) (March 6, 2017)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* Added support for loading plugins by name and the `config.removePlugins` option. Closes [#49](https://github.com/ckeditor/ckeditor5/issues/49). ([dfee52e](https://github.com/ckeditor/ckeditor5-core/commit/dfee52e))
|
|
||||||
* Added the "low-vision" icon. Closes [#68](https://github.com/ckeditor/ckeditor5/issues/68). ([4c3d306](https://github.com/ckeditor/ckeditor5-core/commit/4c3d306))
|
|
||||||
|
|
||||||
### Other changes
|
|
||||||
|
|
||||||
* Uploaded translations. ([a39e84b](https://github.com/ckeditor/ckeditor5-core/commit/a39e84b))
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
Software License Agreement
|
|
||||||
==========================
|
|
||||||
|
|
||||||
**CKEditor 5 core editor architecture** – https://github.com/ckeditor/ckeditor5-core <br>
|
|
||||||
Copyright (c) 2003–2024, [CKSource Holding sp. z o.o.](https://cksource.com) All rights reserved.
|
|
||||||
|
|
||||||
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
|
|
||||||
|
|
||||||
Sources of Intellectual Property Included in CKEditor
|
|
||||||
-----------------------------------------------------
|
|
||||||
|
|
||||||
Where not otherwise indicated, all CKEditor content is authored by CKSource engineers and consists of CKSource-owned intellectual property. In some specific instances, CKEditor will incorporate work done by developers outside of CKSource with their express permission.
|
|
||||||
|
|
||||||
The following libraries are included in CKEditor under the [MIT license](https://opensource.org/licenses/MIT):
|
|
||||||
|
|
||||||
* Lodash - Copyright (c) JS Foundation and other contributors https://js.foundation/. Based on Underscore.js, copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors http://underscorejs.org/.
|
|
||||||
|
|
||||||
Trademarks
|
|
||||||
----------
|
|
||||||
|
|
||||||
**CKEditor** is a trademark of [CKSource Holding sp. z o.o.](https://cksource.com) All other brand and product names are trademarks, registered trademarks, or service marks of their respective holders.
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
CKEditor 5 core editor architecture
|
|
||||||
========================================
|
|
||||||
|
|
||||||
[](https://www.npmjs.com/package/@ckeditor/ckeditor5-core)
|
|
||||||
[](https://coveralls.io/github/ckeditor/ckeditor5?branch=master)
|
|
||||||
[](https://app.travis-ci.com/github/ckeditor/ckeditor5)
|
|
||||||
|
|
||||||
This package implements CKEditor 5's core editor architecture – a set of classes and interfaces which glue everything together.
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
For general introduction see the [Overview of CKEditor 5 framework](https://ckeditor.com/docs/ckeditor5/latest/framework/index.html) guide and then the [core editor architecture guide](https://ckeditor.com/docs/ckeditor5/latest/framework/architecture/core-editor-architecture.html).
|
|
||||||
|
|
||||||
Additionally, see the [`@ckeditor/ckeditor5-core` package](https://ckeditor.com/docs/ckeditor5/latest/api/core.html) page in [CKEditor 5 documentation](https://ckeditor.com/docs/ckeditor5/latest/) for even more information.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the `LICENSE.md` file or [https://ckeditor.com/legal/ckeditor-oss-license](https://ckeditor.com/legal/ckeditor-oss-license).
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"Cancel": "Label for the Cancel button.",
|
|
||||||
"Clear": "Label for the Clear button.",
|
|
||||||
"Remove color": "The label used by a button next to the color palette in the color picker that removes the color (resets it to an empty value, example usages in font color or table properties).",
|
|
||||||
"Restore default": "The label used by a button next to the color palette in the color picker that restores the default value if the default table properties are specified.",
|
|
||||||
"Save": "Label for the Save button.",
|
|
||||||
"Show more items": "Label of a toolbar button which reveals more toolbar items.",
|
|
||||||
"%0 of %1": "Label for an ‘X of Y’ status of a typical next/previous navigation. For instance, ‘Page 5 of 20’ or 'Search result 5 of 20'.",
|
|
||||||
"Cannot upload file:": "A generic error message displayed on upload failure. The file name is concatenated to this text.",
|
|
||||||
"Rich Text Editor. Editing area: %0": "Accessible label of the specific editing area of the editor acting as a root of the entire application.",
|
|
||||||
"Insert with file manager": "The label for the insert image with the file manager toolbar button with visible label in insert image dropdown.",
|
|
||||||
"Replace with file manager": "The label for the replace image with the file manager toolbar button with visible label in insert image dropdown.",
|
|
||||||
"Insert image with file manager": "The label for the insert image with the file manager toolbar button.",
|
|
||||||
"Replace image with file manager": "The label for the replace image with the file manager toolbar button.",
|
|
||||||
"Toggle caption off": "The button label for the object (e.g. image, table) toolbar for hiding the attached caption.",
|
|
||||||
"Toggle caption on": "The button label for the object (e.g. image, table) toolbar for showing the attached caption.",
|
|
||||||
"Content editing keystrokes": "Accessibility help dialog category header text for keystrokes related to content creation.",
|
|
||||||
"These keyboard shortcuts allow for quick access to content editing features.": "Accessibility help dialog text further explaining the purpose of the \"Content editing keystrokes\" category.",
|
|
||||||
"User interface and content navigation keystrokes": "Accessibility help dialog category header text for keystrokes related to navigation in the user interface.",
|
|
||||||
"Use the following keystrokes for more efficient navigation in the CKEditor 5 user interface.": "Accessibility help dialog text further explaining the purpose of the \"User interface and content navigation keystrokes\" category.",
|
|
||||||
"Close contextual balloons, dropdowns, and dialogs": "Keystroke description for assistive technologies: keystroke for closing contextual balloons, dropdowns, and dialogs.",
|
|
||||||
"Open the accessibility help dialog": "Keystroke description for assistive technologies: keystroke for opening the accessibility help dialog.",
|
|
||||||
"Move focus between form fields (inputs, buttons, etc.)": "Keystroke description for assistive technologies: keystroke for moving between fields.",
|
|
||||||
"Move focus to the menu bar, navigate between menu bars": "Keystroke description for assistive technologies: keystroke for moving focus to the menu bar.",
|
|
||||||
"Move focus to the toolbar, navigate between toolbars": "Keystroke description for assistive technologies: keystroke for moving focus to the toolbar.",
|
|
||||||
"Navigate through the toolbar or menu bar": "Keystroke description for assistive technologies: keystroke for navigating through the toolbar.",
|
|
||||||
"Execute the currently focused button. Executing buttons that interact with the editor content moves the focus back to the content.": "Keystroke description for assistive technologies: keystroke for executing currently focused button.",
|
|
||||||
"Accept": "Label of the button confirming the changes done in the current interface."
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue