feat: update major UI

This commit is contained in:
hanif salafi 2025-09-23 22:37:43 +07:00
parent ca11d06341
commit a6e5e7e319
6284 changed files with 1050 additions and 733017 deletions

View File

@ -38,11 +38,12 @@ export const metadata: Metadata = {
export default async function RootLayout({
children,
params: { locale },
params,
}: Readonly<{
children: React.ReactNode;
params: { locale: string };
params: Promise<{ locale: string }>;
}>) {
const { locale } = await params;
const messages = await getMessages();
const direction = getLangDir(locale);
return (

30
app/layout.tsx Normal file
View File

@ -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>
);
}

5
app/page.tsx Normal file
View File

@ -0,0 +1,5 @@
import AutoRedirect from '@/components/auto-redirect';
export default function RootPage() {
return <AutoRedirect />;
}

View File

@ -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;
}

View File

@ -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>
);
}

101
components/editor/index.tsx Normal file
View File

@ -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}
/>
);
}

View File

@ -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>
);
}

View File

@ -85,7 +85,7 @@ export default function LocalSwitcher() {
<SelectItem value="in" className="border-none">
<div className="flex items-center gap-1">
<Image
src="/images/all-img/flag-3.png"
src="/flag-3.png"
alt="flag"
width={24}
height={24}
@ -99,7 +99,7 @@ export default function LocalSwitcher() {
<SelectItem value="en" className="border-none">
<div className="flex items-center gap-1">
<Image
src="/images/all-img/flag-1.png"
src="/flag-1.png"
alt="flag"
width={24}
height={24}

View File

@ -73,7 +73,7 @@ const ProfileInfo = () => {
{detail !== undefined ? (
<div className="flex items-center gap-3 text-default-800">
<Image
src={"/assets/avatar-profile.png"}
src={"/avatar-profile.png"}
alt={"Image"}
width={36}
height={36}

View File

@ -37,6 +37,7 @@ const MenuItem = ({ href, label, icon, active, id, collapsed }: MenuItemProps) =
const { hovered } = hoverConfig;
const isDesktop = useMediaQuery("(min-width: 1280px)");
const [mobileMenuConfig, setMobileMenuConfig] = useMobileMenuConfig();
const { transform, transition, setNodeRef, isDragging, attributes, listeners } = useSortable({
id: id,
@ -54,12 +55,13 @@ const MenuItem = ({ href, label, icon, active, id, collapsed }: MenuItemProps) =
<Button
ref={setNodeRef}
style={style}
variant={active ? "default" : "ghost"}
color={active ? "default" : "secondary"}
variant="ghost"
color="secondary"
fullWidth
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,
'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
size={collapsed ? "icon" : "default"}
@ -99,13 +101,13 @@ const MenuItem = ({ href, label, icon, active, id, collapsed }: MenuItemProps) =
if (config.sidebar === 'compact' && isDesktop) {
return (
<Button
variant={active ? "default" : "ghost"}
variant="ghost"
fullWidth
color={active ? "default" : "secondary"}
className="flex-col h-auto py-1.5 px-3.5 capitalize font-semibold"
color="secondary"
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
>
<Link href={href}>
<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 (
<Button
onClick={() => setMobileMenuConfig({ ...mobileMenuConfig, isOpen: false })}
variant={active ? "default" : "ghost"}
variant="ghost"
fullWidth
color={active ? "default" : "secondary"}
className={cn('', {
'justify-start text-sm font-medium capitalize h-auto py-2.5 md:px-3 px-3': !collapsed || hovered,
'hover:ring-transparent hover:ring-offset-0': !active
})}
color="secondary"
className={cn('', {
'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,
'bg-secondary text-white hover:text-secondary-foreground': active
})}
asChild
size={(collapsed && !hovered) ? "icon" : "default"}
size={collapsed ? "icon" : "default"}
>
<Link href={href}>
<Icon icon={icon} className={cn('h-5 w-5 ', {
'me-4': !collapsed || hovered
'me-4': !collapsed
})} />
{(!collapsed || hovered) && (
{!collapsed && (
<p
className={cn(
"max-w-[200px] truncate",

View File

@ -31,8 +31,8 @@ const SidebarContent = ({ children }: { children: React.ReactNode }) => {
return (
<aside
onMouseEnter={() => config.sidebar === 'draggable' && setHoverConfig({ hovered: true })}
onMouseLeave={() => config.sidebar === 'draggable' && setHoverConfig({ hovered: false })}
onMouseEnter={() => setHoverConfig({ hovered: true })}
onMouseLeave={() => setHoverConfig({ hovered: false })}
className={cn('fixed z-50 w-[248px] bg-sidebar shadow-base block', sidebarTheme, {
'w-[72px]': config.collapsed && config.sidebar !== 'compact',

View File

@ -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

View File

@ -307,7 +307,11 @@ export const useOTPVerification = () => {
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) {
throw new Error(response.message || "OTP verification failed");

View File

@ -3,6 +3,7 @@
import { useState, useCallback, useEffect } from "react";
import { useRouter } from "@/components/navigation";
import { toast } from "sonner";
import Cookies from "js-cookie";
import {
LoginFormData,
ProfileData,
@ -83,8 +84,6 @@ export const useAuth = (): AuthContextType => {
// Attempt login
const response = await doLogin({
...credentials,
grantType: AUTH_CONSTANTS.GRANT_TYPE,
clientId: AUTH_CONSTANTS.CLIENT_ID,
});
if (response?.error) {
@ -92,7 +91,7 @@ export const useAuth = (): AuthContextType => {
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) {
throw new Error("Invalid response from server");
@ -103,45 +102,78 @@ export const useAuth = (): AuthContextType => {
// Get user profile
const profileResponse = await getProfile(access_token);
const profile: ProfileData = profileResponse?.data?.data;
const profile = profileResponse?.data?.data;
if (!profile) {
throw new Error("Failed to fetch user profile");
}
// Validate user eligibility
// if (!isUserEligible(profile)) {
// clearAllCookies();
// warning(
// "Akun Anda tidak dapat digunakan untuk masuk ke MediaHub Polri",
// "/auth"
// );
// return;
// }
const dateTime: any = new Date();
// Set profile cookies
setProfileCookies(profile);
const newTime: any = dateTime.getTime() + 10 * 60 * 1000;
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
loginRateLimiter.resetAttempts(credentials.username);
// Navigate based on user role
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;
router.push("/admin/dashboard");
} catch (error: any) {
const errorMessage = error?.message || "Login failed";
setState((prev) => ({
@ -203,7 +235,7 @@ export const useEmailValidation = () => {
throw new Error(response?.message || "Email validation failed");
}
const message = response?.data?.message;
const message = response?.data?.messages[0];
switch (message) {
case "Continue to setup email":
@ -306,8 +338,11 @@ export const useOTPVerification = () => {
if (otp.length !== 6) {
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) {
throw new Error(response.message || "OTP verification failed");

View File

@ -2,30 +2,47 @@ import createMiddleware from "next-intl/middleware";
import { NextRequest, NextResponse } from "next/server";
import { locales } from "@/config";
// export default async function middleware(request: NextRequest) {
// // Step 1: Use the incoming request (example)
// const defaultLocale = "in";
// // const defaultLocale = request.headers.get("dashcode-locale") || "in";
export default async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const defaultLocale = "in";
// // Step 2: Create and call the next-intl middleware (example)
// const handleI18nRouting = createMiddleware({
// locales: ["in", "en"],
// defaultLocale: "in",
// });
// const response = handleI18nRouting(request);
// Check if the pathname already has a locale
const pathnameHasLocale = locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);
// // Step 3: Alter the response (example)
// response.headers.set("dashcode-locale", defaultLocale);
// If pathname doesn't have a locale, redirect to default locale
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({
locales: locales,
defaultLocale: "in",
});
// Use next-intl middleware for internationalized routes
const handleI18nRouting = createMiddleware({
locales: locales,
defaultLocale: defaultLocale,
});
return handleI18nRouting(request);
}
export const config = {
// Match only internationalized pathnames
matcher: ["/", "/(in|en|ar)/:path*"],
// Match all pathnames except for
// - API routes
// - _next (Next.js internals)
// - _static (inside /public)
// - all root files inside /public (e.g. favicon.ico)
matcher: ['/((?!api|_next|_static|.*\\..*).*)']
};

1866
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,8 @@
"lint": "next lint"
},
"dependencies": {
"ckeditor5": "^41.0.0",
"ckeditor5": "^42.0.0",
"@ckeditor/ckeditor5-react": "^10.0.0",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",

BIN
public/avatar-profile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
public/flag-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 B

BIN
public/flag-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/flag-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -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&nbsp;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.).

View File

@ -1,21 +0,0 @@
Software License Agreement
==========================
**CKEditor&nbsp;5 clipboard feature** https://github.com/ckeditor/ckeditor5-clipboard <br>
Copyright (c) 20032024, [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.

View File

@ -1,16 +0,0 @@
CKEditor&nbsp;5 clipboard feature
========================================
[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-clipboard.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-clipboard)
[![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5?branch=master)
[![Build Status](https://travis-ci.com/ckeditor/ckeditor5.svg?branch=master)](https://app.travis-ci.com/github/ckeditor/ckeditor5)
This package implements the clipboard (copy, cut, paste) support for CKEditor&nbsp;5.
## Documentation
See the [`@ckeditor/ckeditor5-clipboard` package](https://ckeditor.com/docs/ckeditor5/latest/api/clipboard.html) page in [CKEditor&nbsp;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).

View File

@ -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."
}

View File

@ -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 "الصقْ المحتوى كنص عادي"

View File

@ -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 "Поставяне на съдържанието като обикновен текст"

View File

@ -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 "প্লেইন টেক্সট হিসেবে কন্টেন্ট পেস্ট করুন"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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 "Επικόλληση περιεχομένου ως απλό κείμενο"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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ä"

View File

@ -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"

View File

@ -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 "הדבקת תוכן כטקסט רגיל"

View File

@ -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 "कॉन्टेंट को प्लेन टेक्स्ट के रूप में पेस्ट करें"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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 "コンテンツをプレーンテキストとして貼り付ける"

View File

@ -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 "콘텐츠를 일반 텍스트로 붙여넣기"

View File

@ -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ą"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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 "Вставить содержанрие в виде обычного текста"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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 "วางเนื้อหาเป็นข้อความธรรมดา"

View File

@ -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"

View File

@ -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 "Вставити вміст як простий текст"

View File

@ -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"

View File

@ -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 "将内容粘贴为纯文本"

View File

@ -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 "將內容貼上為純文字"

View File

@ -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"
}

View File

@ -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;
}
}

View File

@ -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 {};

View File

@ -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;
}

View File

@ -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'
}
]
});
}
}

View File

@ -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;
};

View File

@ -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}`;
}
}

View File

@ -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];
};

View File

@ -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;
}

View File

@ -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';
}

View File

@ -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' });
}
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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';

View File

@ -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';

View File

@ -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();
}

View File

@ -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))
}
}
});
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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, '');
}

View File

@ -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;

View File

@ -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, '&amp;')
// Encode <>.
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
// 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, '&nbsp;&nbsp;&nbsp;&nbsp;')
// Preserve trailing spaces (only the first and last one the rest is handled below).
.replace(/^\s/, '&nbsp;')
.replace(/\s$/, '&nbsp;')
// Preserve other subsequent spaces now.
.replace(/\s\s/g, ' &nbsp;');
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;
}

View File

@ -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;

View File

@ -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';
}

View File

@ -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;
}

View File

@ -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>&nbsp;</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>&nbsp;</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&nbsp;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))

View File

@ -1,21 +0,0 @@
Software License Agreement
==========================
**CKEditor&nbsp;5 core editor architecture** https://github.com/ckeditor/ckeditor5-core <br>
Copyright (c) 20032024, [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.

View File

@ -1,18 +0,0 @@
CKEditor&nbsp;5 core editor architecture
========================================
[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-core.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-core)
[![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5?branch=master)
[![Build Status](https://travis-ci.com/ckeditor/ckeditor5.svg?branch=master)](https://app.travis-ci.com/github/ckeditor/ckeditor5)
This package implements CKEditor&nbsp;5's core editor architecture &ndash; a set of classes and interfaces which glue everything together.
## Documentation
For general introduction see the [Overview of CKEditor&nbsp;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&nbsp;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).

View File

@ -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