vue-libre-editor
Version:
A free WYSIWYG editor for Vue.js
1,254 lines (1,119 loc) • 152 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue')) :
typeof define === 'function' && define.amd ? define(['exports', 'vue'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VueLibreEditor = {}, global.Vue));
})(this, (function (exports, vue) { 'use strict';
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
const toCamelCase = (string) => string.replace(
/^([A-Z])|[\s-_]+(\w)/g,
(match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase()
);
const toPascalCase = (string) => {
const camelCase = toCamelCase(string);
return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
};
const mergeClasses = (...classes) => classes.filter((className, index, array) => {
return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
}).join(" ").trim();
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
var defaultAttributes = {
xmlns: "http://www.w3.org/2000/svg",
width: 24,
height: 24,
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": 2,
"stroke-linecap": "round",
"stroke-linejoin": "round"
};
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Icon = ({ size, strokeWidth = 2, absoluteStrokeWidth, color, iconNode, name, class: classes, ...props }, { slots }) => {
return vue.h(
"svg",
{
...defaultAttributes,
width: size || defaultAttributes.width,
height: size || defaultAttributes.height,
stroke: color || defaultAttributes.stroke,
"stroke-width": absoluteStrokeWidth ? Number(strokeWidth) * 24 / Number(size) : strokeWidth,
class: mergeClasses(
"lucide",
...name ? [`lucide-${toKebabCase(toPascalCase(name))}-icon`, `lucide-${toKebabCase(name)}`] : ["lucide-icon"]
),
...props
},
[...iconNode.map((child) => vue.h(...child)), ...slots.default ? [slots.default()] : []]
);
};
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const createLucideIcon = (iconName, iconNode) => (props, { slots }) => vue.h(
Icon,
{
...props,
iconNode,
name: iconName
},
slots
);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const AlignCenter = createLucideIcon("align-center", [
["path", { d: "M17 12H7", key: "16if0g" }],
["path", { d: "M19 18H5", key: "18s9l3" }],
["path", { d: "M21 6H3", key: "1jwq7v" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const AlignJustify = createLucideIcon("align-justify", [
["path", { d: "M3 12h18", key: "1i2n21" }],
["path", { d: "M3 18h18", key: "1h113x" }],
["path", { d: "M3 6h18", key: "d0wm0j" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const AlignLeft = createLucideIcon("align-left", [
["path", { d: "M15 12H3", key: "6jk70r" }],
["path", { d: "M17 18H3", key: "1amg6g" }],
["path", { d: "M21 6H3", key: "1jwq7v" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const AlignRight = createLucideIcon("align-right", [
["path", { d: "M21 12H9", key: "dn1m92" }],
["path", { d: "M21 18H7", key: "1ygte8" }],
["path", { d: "M21 6H3", key: "1jwq7v" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Bold = createLucideIcon("bold", [
[
"path",
{ d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8", key: "mg9rjx" }
]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Combine = createLucideIcon("combine", [
["path", { d: "M10 18H5a3 3 0 0 1-3-3v-1", key: "ru65g8" }],
["path", { d: "M14 2a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2", key: "e30een" }],
["path", { d: "M20 2a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2", key: "2ahx8o" }],
["path", { d: "m7 21 3-3-3-3", key: "127cv2" }],
["rect", { x: "14", y: "14", width: "8", height: "8", rx: "2", key: "1b0bso" }],
["rect", { x: "2", y: "2", width: "8", height: "8", rx: "2", key: "1x09vl" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Image$1 = createLucideIcon("image", [
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
["path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21", key: "1xmnt7" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const IndentDecrease = createLucideIcon("indent-decrease", [
["path", { d: "M21 12H11", key: "wd7e0v" }],
["path", { d: "M21 18H11", key: "4wu86t" }],
["path", { d: "M21 6H11", key: "6dy1d6" }],
["path", { d: "m7 8-4 4 4 4", key: "o5hrat" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const IndentIncrease = createLucideIcon("indent-increase", [
["path", { d: "M21 12H11", key: "wd7e0v" }],
["path", { d: "M21 18H11", key: "4wu86t" }],
["path", { d: "M21 6H11", key: "6dy1d6" }],
["path", { d: "m3 8 4 4-4 4", key: "1a3j6y" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Italic = createLucideIcon("italic", [
["line", { x1: "19", x2: "10", y1: "4", y2: "4", key: "15jd3p" }],
["line", { x1: "14", x2: "5", y1: "20", y2: "20", key: "bu0au3" }],
["line", { x1: "15", x2: "9", y1: "4", y2: "20", key: "uljnxc" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Link = createLucideIcon("link", [
["path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71", key: "1cjeqo" }],
["path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71", key: "19qd67" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Paintbrush = createLucideIcon("paintbrush", [
["path", { d: "m14.622 17.897-10.68-2.913", key: "vj2p1u" }],
[
"path",
{
d: "M18.376 2.622a1 1 0 1 1 3.002 3.002L17.36 9.643a.5.5 0 0 0 0 .707l.944.944a2.41 2.41 0 0 1 0 3.408l-.944.944a.5.5 0 0 1-.707 0L8.354 7.348a.5.5 0 0 1 0-.707l.944-.944a2.41 2.41 0 0 1 3.408 0l.944.944a.5.5 0 0 0 .707 0z",
key: "18tc5c"
}
],
[
"path",
{
d: "M9 8c-1.804 2.71-3.97 3.46-6.583 3.948a.507.507 0 0 0-.302.819l7.32 8.883a1 1 0 0 0 1.185.204C12.735 20.405 16 16.792 16 15",
key: "ytzfxy"
}
]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Redo = createLucideIcon("redo", [
["path", { d: "M21 7v6h-6", key: "3ptur4" }],
["path", { d: "M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3l3 2.7", key: "1kgawr" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Settings = createLucideIcon("settings", [
[
"path",
{
d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",
key: "1qme2f"
}
],
["circle", { cx: "12", cy: "12", r: "3", key: "1v7zrd" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const SquareSplitVertical = createLucideIcon("square-split-vertical", [
["path", { d: "M5 8V5c0-1 1-2 2-2h10c1 0 2 1 2 2v3", key: "1pi83i" }],
["path", { d: "M19 16v3c0 1-1 2-2 2H7c-1 0-2-1-2-2v-3", key: "ido5k7" }],
["line", { x1: "4", x2: "20", y1: "12", y2: "12", key: "1e0a9i" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Strikethrough = createLucideIcon("strikethrough", [
["path", { d: "M16 4H9a3 3 0 0 0-2.83 4", key: "43sutm" }],
["path", { d: "M14 12a4 4 0 0 1 0 8H6", key: "nlfj13" }],
["line", { x1: "4", x2: "20", y1: "12", y2: "12", key: "1e0a9i" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Table = createLucideIcon("table", [
["path", { d: "M12 3v18", key: "108xh3" }],
["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }],
["path", { d: "M3 9h18", key: "1pudct" }],
["path", { d: "M3 15h18", key: "5xshup" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Underline = createLucideIcon("underline", [
["path", { d: "M6 4v6a6 6 0 0 0 12 0V4", key: "9kb039" }],
["line", { x1: "4", x2: "20", y1: "20", y2: "20", key: "nun2al" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const Undo = createLucideIcon("undo", [
["path", { d: "M3 7v6h6", key: "1v2h90" }],
["path", { d: "M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13", key: "1r6uu6" }]
]);
/**
* @license lucide-vue-next v0.511.0 - ISC
*
* This source code is licensed under the ISC license.
* See the LICENSE file in the root directory of this source tree.
*/
const X = createLucideIcon("x", [
["path", { d: "M18 6 6 18", key: "1bl5f8" }],
["path", { d: "m6 6 12 12", key: "d8bk6v" }]
]);
const _export_sfc = (sfc, props) => {
const target = sfc.__vccOpts || sfc;
for (const [key, val] of props) {
target[key] = val;
}
return target;
};
const _sfc_main$6 = {
components: {
Bold, Italic, Underline, Strikethrough,
AlignLeft, AlignCenter, AlignRight, AlignJustify,
Outdent: IndentDecrease, Indent: IndentIncrease,
ImageIcon: Image$1, Link, Undo, Redo, X,
Table, Settings, SplitSquareVertical: SquareSplitVertical, Combine, Paintbrush
},
props: {
editor: {
type: Object,
default: null
},
theme: {
type: Object,
default: () => ({
headerBgColor: '#f3f4f6',
headerFgColor: '#111827',
contentBgColor: '#ffffff',
contentFgColor: '#111827',
edgeColor: '#d1d5db',
activeButtonBg: '#e5e7eb',
activeButtonFg: '#2563eb'
})
},
selectedElement: {
type: Object,
default: null
},
selectedCell: {
type: Object,
default: null
}
},
emits: ['execute-command', 'insert-image', 'insert-table', 'insert-link', 'edit-table', 'split-cell', 'merge-cells', 'cell-color'],
setup(props, { emit }) {
const showColorPicker = vue.ref(false);
const showBgColorPicker = vue.ref(false);
const textColor = vue.ref('#000000');
const bgColor = vue.ref('#ffffff');
const textColorPickerRef = vue.ref(null);
const bgColorPickerRef = vue.ref(null);
// Track command states in a reactive object
const commandStates = vue.reactive({
bold: false,
italic: false,
underline: false,
strikeThrough: false,
justifyLeft: false,
justifyCenter: false,
justifyRight: false,
justifyFull: false
});
// Update command states
const updateCommandStates = () => {
try {
commandStates.bold = document.queryCommandState('bold');
commandStates.italic = document.queryCommandState('italic');
commandStates.underline = document.queryCommandState('underline');
commandStates.strikeThrough = document.queryCommandState('strikeThrough');
commandStates.justifyLeft = document.queryCommandState('justifyLeft');
commandStates.justifyCenter = document.queryCommandState('justifyCenter');
commandStates.justifyRight = document.queryCommandState('justifyRight');
commandStates.justifyFull = document.queryCommandState('justifyFull');
} catch (e) {
console.warn('Error updating command states:', e);
}
};
// Set up event listeners for selection changes
vue.onMounted(() => {
document.addEventListener('selectionchange', updateCommandStates);
document.addEventListener('click', handleClickOutside);
// Initial update
updateCommandStates();
});
vue.onUnmounted(() => {
document.removeEventListener('selectionchange', updateCommandStates);
document.removeEventListener('click', handleClickOutside);
});
// Watch for theme changes and update command states
vue.watch(() => props.theme, () => {
// Force a re-render of active buttons when theme changes
updateCommandStates();
}, { deep: true });
const colors = [
'#000000', '#434343', '#666666', '#999999', '#b7b7b7',
'#d9d9d9', '#efefef', '#f3f3f3', '#ffffff', '#980000',
'#ff0000', '#ff9900', '#ffff00', '#00ff00', '#00ffff',
'#4a86e8', '#0000ff', '#9900ff', '#ff00ff', '#e6b8af',
'#f4cccc', '#fce5cd', '#fff2cc', '#d9ead3', '#d0e0e3',
'#c9daf8', '#cfe2f3', '#d9d2e9', '#ead1dc'
];
// Determine active button background color based on theme
const activeButtonBg = vue.computed(() => {
// If theme explicitly defines activeButtonBg, use it
if (props.theme.activeButtonBg) {
return props.theme.activeButtonBg;
}
// Otherwise, calculate based on the header background color
const isDark = isColorDark(props.theme.headerBgColor);
if (isDark) {
// For dark themes, use a lighter shade for better visibility
return lightenColor(props.theme.headerBgColor, 20);
} else {
// For light themes, use a slightly darker shade
return darkenColor(props.theme.headerBgColor, 10);
}
});
// Determine active icon color based on theme
const activeIconColor = vue.computed(() => {
// If theme explicitly defines activeButtonFg, use it
if (props.theme.activeButtonFg) {
return props.theme.activeButtonFg;
}
// Otherwise, use a blue accent color for active icons
return '#2563eb'; // Default blue accent
});
// Computed styles based on theme
const toolbarStyle = vue.computed(() => ({
backgroundColor: props.theme.headerBgColor,
color: props.theme.headerFgColor,
borderBottom: `1px solid ${props.theme.edgeColor}`
}));
const buttonStyle = vue.computed(() => ({
backgroundColor: 'transparent',
borderColor: props.theme.edgeColor,
color: props.theme.headerFgColor,
transition: 'all 0.2s ease'
}));
const activeButtonStyle = vue.computed(() => {
// Ensure we're returning a new object each time to force reactivity
return {
backgroundColor: activeButtonBg.value,
borderColor: props.theme.edgeColor,
color: props.theme.headerFgColor,
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.1)'
};
});
const selectStyle = vue.computed(() => ({
backgroundColor: props.theme.contentBgColor,
color: props.theme.contentFgColor,
borderColor: props.theme.edgeColor
}));
const dropdownStyle = vue.computed(() => ({
backgroundColor: props.theme.contentBgColor,
color: props.theme.contentFgColor,
borderColor: props.theme.edgeColor
}));
const inputStyle = vue.computed(() => ({
backgroundColor: props.theme.contentBgColor,
color: props.theme.contentFgColor,
borderColor: props.theme.edgeColor
}));
const applyButtonStyle = vue.computed(() => ({
backgroundColor: '#2563eb',
color: '#ffffff'
}));
// Function to check if a color is dark
const isColorDark = (hexColor) => {
// Convert hex to RGB
const r = parseInt(hexColor.substring(1, 3), 16);
const g = parseInt(hexColor.substring(3, 5), 16);
const b = parseInt(hexColor.substring(5, 7), 16);
// Calculate brightness (YIQ formula)
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
// Return true if the color is dark (brightness < 128)
return brightness < 128;
};
// Function to lighten a color
const lightenColor = (hexColor, amount) => {
// Convert hex to RGB
let r = parseInt(hexColor.substring(1, 3), 16);
let g = parseInt(hexColor.substring(3, 5), 16);
let b = parseInt(hexColor.substring(5, 7), 16);
// Lighten the color
r = Math.min(255, r + amount);
g = Math.min(255, g + amount);
b = Math.min(255, b + amount);
// Convert back to hex
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
};
// Function to darken a color
const darkenColor = (hexColor, amount) => {
// Convert hex to RGB
let r = parseInt(hexColor.substring(1, 3), 16);
let g = parseInt(hexColor.substring(3, 5), 16);
let b = parseInt(hexColor.substring(5, 7), 16);
// Darken the color
r = Math.max(0, r - amount);
g = Math.max(0, g - amount);
b = Math.max(0, b - amount);
// Convert back to hex
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
};
// Handle clicks outside of color pickers
const handleClickOutside = (event) => {
// Check if text color picker is open and click is outside
if (showColorPicker.value &&
textColorPickerRef.value &&
!textColorPickerRef.value.contains(event.target)) {
showColorPicker.value = false;
}
// Check if background color picker is open and click is outside
if (showBgColorPicker.value &&
bgColorPickerRef.value &&
!bgColorPickerRef.value.contains(event.target)) {
showBgColorPicker.value = false;
}
};
const toggleTextColorPicker = (event) => {
// Prevent the click event from propagating to document
event.stopPropagation();
// Toggle the color picker
showColorPicker.value = !showColorPicker.value;
// Close the other color picker if it's open
if (showColorPicker.value) {
showBgColorPicker.value = false;
}
};
const toggleBgColorPicker = (event) => {
// Prevent the click event from propagating to document
event.stopPropagation();
// Toggle the color picker
showBgColorPicker.value = !showBgColorPicker.value;
// Close the other color picker if it's open
if (showBgColorPicker.value) {
showColorPicker.value = false;
}
};
const executeCommand = (command, value = null) => {
emit('execute-command', command, value);
// Update command states immediately after executing a command
setTimeout(updateCommandStates, 10);
};
const changeTextColor = (color) => {
textColor.value = color;
executeCommand('foreColor', color);
showColorPicker.value = false;
};
const applyTextColor = () => {
executeCommand('foreColor', textColor.value);
showColorPicker.value = false;
};
const changeBackgroundColor = (color) => {
bgColor.value = color;
executeCommand('hiliteColor', color);
showBgColorPicker.value = false;
};
const applyBgColor = () => {
executeCommand('hiliteColor', bgColor.value);
showBgColorPicker.value = false;
};
const changeFontSize = (size) => {
// Get current selection
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
// If there's a selection, wrap it in a span with the font size
if (!selection.isCollapsed) {
const content = range.extractContents();
const span = document.createElement('span');
span.style.fontSize = `${size}px`;
span.appendChild(content);
range.insertNode(span);
// Update the selection to include the new span
selection.removeAllRanges();
selection.addRange(range);
// Emit the command to update the content
emit('execute-command', 'insertHTML', span.outerHTML);
} else {
// If no selection, insert a span at cursor position
emit('execute-command', 'insertHTML', `<span style="font-size: ${size}px"> </span>`);
}
// Update command states
updateCommandStates();
}
};
return {
commandStates,
showColorPicker,
showBgColorPicker,
textColor,
bgColor,
colors,
changeTextColor,
applyTextColor,
changeBackgroundColor,
applyBgColor,
changeFontSize,
textColorPickerRef,
bgColorPickerRef,
toggleTextColorPicker,
toggleBgColorPicker,
toolbarStyle,
buttonStyle,
selectStyle,
dropdownStyle,
inputStyle,
applyButtonStyle,
activeButtonStyle,
activeButtonBg,
activeIconColor,
executeCommand
};
}
};
const _hoisted_1$6 = {
class: "relative",
ref: "textColorPickerRef"
};
const _hoisted_2$6 = { class: "mb-2" };
const _hoisted_3$6 = { class: "grid grid-cols-5 gap-1" };
const _hoisted_4$5 = ["onClick"];
const _hoisted_5$5 = { class: "mt-2 flex justify-end" };
const _hoisted_6$3 = {
class: "relative",
ref: "bgColorPickerRef"
};
const _hoisted_7$2 = { class: "mb-2" };
const _hoisted_8$2 = { class: "grid grid-cols-5 gap-1" };
const _hoisted_9$2 = ["onClick"];
const _hoisted_10$1 = { class: "mt-2 flex justify-end" };
const _hoisted_11$1 = {
key: 0,
class: "flex items-center"
};
const _hoisted_12$1 = {
key: 1,
class: "flex items-center"
};
function _sfc_render$6(_ctx, _cache, $props, $setup, $data, $options) {
const _component_Bold = vue.resolveComponent("Bold");
const _component_Italic = vue.resolveComponent("Italic");
const _component_Underline = vue.resolveComponent("Underline");
const _component_Strikethrough = vue.resolveComponent("Strikethrough");
const _component_AlignLeft = vue.resolveComponent("AlignLeft");
const _component_AlignCenter = vue.resolveComponent("AlignCenter");
const _component_AlignRight = vue.resolveComponent("AlignRight");
const _component_AlignJustify = vue.resolveComponent("AlignJustify");
const _component_Outdent = vue.resolveComponent("Outdent");
const _component_Indent = vue.resolveComponent("Indent");
const _component_ImageIcon = vue.resolveComponent("ImageIcon");
const _component_Table = vue.resolveComponent("Table");
const _component_Settings = vue.resolveComponent("Settings");
const _component_Paintbrush = vue.resolveComponent("Paintbrush");
const _component_Link = vue.resolveComponent("Link");
const _component_Undo = vue.resolveComponent("Undo");
const _component_Redo = vue.resolveComponent("Redo");
const _component_X = vue.resolveComponent("X");
return (vue.openBlock(), vue.createElementBlock("div", {
class: "editor-toolbar flex flex-wrap gap-1 p-2",
style: vue.normalizeStyle($setup.toolbarStyle)
}, [
vue.createElementVNode("select", {
class: "h-8 px-2 border rounded text-sm",
style: vue.normalizeStyle($setup.selectStyle),
onChange: _cache[0] || (_cache[0] = $event => (_ctx.$emit('execute-command', 'fontName', $event.target.value)))
}, _cache[28] || (_cache[28] = [
vue.createStaticVNode("<option value=\"\" disabled selected data-v-2f778b9b>Font</option><option value=\"Arial\" data-v-2f778b9b>Arial</option><option value=\"Helvetica\" data-v-2f778b9b>Helvetica</option><option value=\"Times New Roman\" data-v-2f778b9b>Times New Roman</option><option value=\"Courier New\" data-v-2f778b9b>Courier New</option><option value=\"Georgia\" data-v-2f778b9b>Georgia</option><option value=\"Verdana\" data-v-2f778b9b>Verdana</option><option value=\"Impact\" data-v-2f778b9b>Impact</option>", 8)
]), 36),
vue.createElementVNode("select", {
class: "h-8 px-2 border rounded text-sm",
style: vue.normalizeStyle($setup.selectStyle),
onChange: _cache[1] || (_cache[1] = $event => ($setup.changeFontSize($event.target.value)))
}, _cache[29] || (_cache[29] = [
vue.createStaticVNode("<option value=\"\" disabled selected data-v-2f778b9b>Size</option><option value=\"8\" data-v-2f778b9b>8px</option><option value=\"10\" data-v-2f778b9b>10px</option><option value=\"12\" data-v-2f778b9b>12px</option><option value=\"14\" data-v-2f778b9b>14px</option><option value=\"16\" data-v-2f778b9b>16px</option><option value=\"18\" data-v-2f778b9b>18px</option><option value=\"20\" data-v-2f778b9b>20px</option><option value=\"24\" data-v-2f778b9b>24px</option><option value=\"28\" data-v-2f778b9b>28px</option><option value=\"32\" data-v-2f778b9b>32px</option><option value=\"36\" data-v-2f778b9b>36px</option><option value=\"48\" data-v-2f778b9b>48px</option><option value=\"72\" data-v-2f778b9b>72px</option>", 14)
]), 36),
vue.createElementVNode("div", _hoisted_1$6, [
vue.createElementVNode("button", {
class: "p-1 rounded flex items-center",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[2] || (_cache[2] = (...args) => ($setup.toggleTextColorPicker && $setup.toggleTextColorPicker(...args))),
title: "Text Color"
}, [
vue.createElementVNode("span", {
class: "w-4 h-4 border rounded-sm",
style: vue.normalizeStyle({ backgroundColor: $setup.textColor, borderColor: $props.theme.edgeColor })
}, null, 4)
], 4),
($setup.showColorPicker)
? (vue.openBlock(), vue.createElementBlock("div", {
key: 0,
class: "absolute top-full left-0 mt-1 p-2 border rounded shadow-lg z-10 w-64",
style: vue.normalizeStyle($setup.dropdownStyle)
}, [
vue.createElementVNode("div", _hoisted_2$6, [
vue.withDirectives(vue.createElementVNode("input", {
type: "text",
"onUpdate:modelValue": _cache[3] || (_cache[3] = $event => (($setup.textColor) = $event)),
class: "w-full px-2 py-1 border rounded text-sm",
style: vue.normalizeStyle($setup.inputStyle),
placeholder: "#000000",
onKeydown: _cache[4] || (_cache[4] = vue.withKeys((...args) => ($setup.applyTextColor && $setup.applyTextColor(...args)), ["enter"]))
}, null, 36), [
[vue.vModelText, $setup.textColor]
])
]),
vue.createElementVNode("div", _hoisted_3$6, [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList($setup.colors, (color) => {
return (vue.openBlock(), vue.createElementBlock("button", {
key: color,
class: "w-8 h-8 border rounded-sm hover:opacity-80 active:opacity-70",
style: vue.normalizeStyle({ backgroundColor: color, borderColor: $props.theme.edgeColor }),
onClick: $event => ($setup.changeTextColor(color))
}, null, 12, _hoisted_4$5))
}), 128))
]),
vue.createElementVNode("div", _hoisted_5$5, [
vue.createElementVNode("button", {
class: "px-2 py-1 rounded text-sm hover:opacity-90 active:opacity-80",
style: vue.normalizeStyle($setup.applyButtonStyle),
onClick: _cache[5] || (_cache[5] = (...args) => ($setup.applyTextColor && $setup.applyTextColor(...args)))
}, " Apply ", 4)
])
], 4))
: vue.createCommentVNode("", true)
], 512),
vue.createElementVNode("div", _hoisted_6$3, [
vue.createElementVNode("button", {
class: "p-1 rounded flex items-center",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[6] || (_cache[6] = (...args) => ($setup.toggleBgColorPicker && $setup.toggleBgColorPicker(...args))),
title: "Background Color"
}, [
vue.createElementVNode("span", {
class: "w-4 h-4 border rounded-sm",
style: vue.normalizeStyle({ backgroundColor: $setup.bgColor, borderColor: $props.theme.edgeColor })
}, null, 4)
], 4),
($setup.showBgColorPicker)
? (vue.openBlock(), vue.createElementBlock("div", {
key: 0,
class: "absolute top-full left-0 mt-1 p-2 border rounded shadow-lg z-10 w-64",
style: vue.normalizeStyle($setup.dropdownStyle)
}, [
vue.createElementVNode("div", _hoisted_7$2, [
vue.withDirectives(vue.createElementVNode("input", {
type: "text",
"onUpdate:modelValue": _cache[7] || (_cache[7] = $event => (($setup.bgColor) = $event)),
class: "w-full px-2 py-1 border rounded text-sm",
style: vue.normalizeStyle($setup.inputStyle),
placeholder: "#ffffff",
onKeydown: _cache[8] || (_cache[8] = vue.withKeys((...args) => ($setup.applyBgColor && $setup.applyBgColor(...args)), ["enter"]))
}, null, 36), [
[vue.vModelText, $setup.bgColor]
])
]),
vue.createElementVNode("div", _hoisted_8$2, [
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList($setup.colors, (color) => {
return (vue.openBlock(), vue.createElementBlock("button", {
key: color,
class: "w-8 h-8 border rounded-sm hover:opacity-80 active:opacity-70",
style: vue.normalizeStyle({ backgroundColor: color, borderColor: $props.theme.edgeColor }),
onClick: $event => ($setup.changeBackgroundColor(color))
}, null, 12, _hoisted_9$2))
}), 128))
]),
vue.createElementVNode("div", _hoisted_10$1, [
vue.createElementVNode("button", {
class: "px-2 py-1 rounded text-sm hover:opacity-90 active:opacity-80",
style: vue.normalizeStyle($setup.applyButtonStyle),
onClick: _cache[9] || (_cache[9] = (...args) => ($setup.applyBgColor && $setup.applyBgColor(...args)))
}, " Apply ", 4)
])
], 4))
: vue.createCommentVNode("", true)
], 512),
vue.createElementVNode("div", {
class: "h-8 w-px mx-1",
style: vue.normalizeStyle({ backgroundColor: $props.theme.edgeColor })
}, null, 4),
vue.createElementVNode("button", {
class: vue.normalizeClass(["p-1 rounded", { 'editor-button-active': $setup.commandStates.bold }]),
style: vue.normalizeStyle($setup.commandStates.bold ? $setup.activeButtonStyle : $setup.buttonStyle),
onClick: _cache[10] || (_cache[10] = $event => ($setup.executeCommand('bold'))),
title: "Bold"
}, [
vue.createVNode(_component_Bold, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $setup.commandStates.bold ? $setup.activeIconColor : $props.theme.headerFgColor })
}, null, 8, ["style"])
], 6),
vue.createElementVNode("button", {
class: vue.normalizeClass(["p-1 rounded", { 'editor-button-active': $setup.commandStates.italic }]),
style: vue.normalizeStyle($setup.commandStates.italic ? $setup.activeButtonStyle : $setup.buttonStyle),
onClick: _cache[11] || (_cache[11] = $event => ($setup.executeCommand('italic'))),
title: "Italic"
}, [
vue.createVNode(_component_Italic, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $setup.commandStates.italic ? $setup.activeIconColor : $props.theme.headerFgColor })
}, null, 8, ["style"])
], 6),
vue.createElementVNode("button", {
class: vue.normalizeClass(["p-1 rounded", { 'editor-button-active': $setup.commandStates.underline }]),
style: vue.normalizeStyle($setup.commandStates.underline ? $setup.activeButtonStyle : $setup.buttonStyle),
onClick: _cache[12] || (_cache[12] = $event => ($setup.executeCommand('underline'))),
title: "Underline"
}, [
vue.createVNode(_component_Underline, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $setup.commandStates.underline ? $setup.activeIconColor : $props.theme.headerFgColor })
}, null, 8, ["style"])
], 6),
vue.createElementVNode("button", {
class: vue.normalizeClass(["p-1 rounded", { 'editor-button-active': $setup.commandStates.strikeThrough }]),
style: vue.normalizeStyle($setup.commandStates.strikeThrough ? $setup.activeButtonStyle : $setup.buttonStyle),
onClick: _cache[13] || (_cache[13] = $event => ($setup.executeCommand('strikeThrough'))),
title: "Strike Through"
}, [
vue.createVNode(_component_Strikethrough, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $setup.commandStates.strikeThrough ? $setup.activeIconColor : $props.theme.headerFgColor })
}, null, 8, ["style"])
], 6),
vue.createElementVNode("div", {
class: "h-8 w-px mx-1",
style: vue.normalizeStyle({ backgroundColor: $props.theme.edgeColor })
}, null, 4),
vue.createElementVNode("button", {
class: vue.normalizeClass(["p-1 rounded", { 'editor-button-active': $setup.commandStates.justifyLeft }]),
style: vue.normalizeStyle($setup.commandStates.justifyLeft ? $setup.activeButtonStyle : $setup.buttonStyle),
onClick: _cache[14] || (_cache[14] = $event => ($setup.executeCommand('justifyLeft'))),
title: "Align Left"
}, [
vue.createVNode(_component_AlignLeft, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $setup.commandStates.justifyLeft ? $setup.activeIconColor : $props.theme.headerFgColor })
}, null, 8, ["style"])
], 6),
vue.createElementVNode("button", {
class: vue.normalizeClass(["p-1 rounded", { 'editor-button-active': $setup.commandStates.justifyCenter }]),
style: vue.normalizeStyle($setup.commandStates.justifyCenter ? $setup.activeButtonStyle : $setup.buttonStyle),
onClick: _cache[15] || (_cache[15] = $event => ($setup.executeCommand('justifyCenter'))),
title: "Align Center"
}, [
vue.createVNode(_component_AlignCenter, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $setup.commandStates.justifyCenter ? $setup.activeIconColor : $props.theme.headerFgColor })
}, null, 8, ["style"])
], 6),
vue.createElementVNode("button", {
class: vue.normalizeClass(["p-1 rounded", { 'editor-button-active': $setup.commandStates.justifyRight }]),
style: vue.normalizeStyle($setup.commandStates.justifyRight ? $setup.activeButtonStyle : $setup.buttonStyle),
onClick: _cache[16] || (_cache[16] = $event => ($setup.executeCommand('justifyRight'))),
title: "Align Right"
}, [
vue.createVNode(_component_AlignRight, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $setup.commandStates.justifyRight ? $setup.activeIconColor : $props.theme.headerFgColor })
}, null, 8, ["style"])
], 6),
vue.createElementVNode("button", {
class: vue.normalizeClass(["p-1 rounded", { 'editor-button-active': $setup.commandStates.justifyFull }]),
style: vue.normalizeStyle($setup.commandStates.justifyFull ? $setup.activeButtonStyle : $setup.buttonStyle),
onClick: _cache[17] || (_cache[17] = $event => ($setup.executeCommand('justifyFull'))),
title: "Justify"
}, [
vue.createVNode(_component_AlignJustify, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $setup.commandStates.justifyFull ? $setup.activeIconColor : $props.theme.headerFgColor })
}, null, 8, ["style"])
], 6),
vue.createElementVNode("div", {
class: "h-8 w-px mx-1",
style: vue.normalizeStyle({ backgroundColor: $props.theme.edgeColor })
}, null, 4),
vue.createElementVNode("button", {
class: "p-1 rounded",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[18] || (_cache[18] = $event => ($setup.executeCommand('outdent'))),
title: "Decrease Indent"
}, [
vue.createVNode(_component_Outdent, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $props.theme.headerFgColor })
}, null, 8, ["style"])
], 4),
vue.createElementVNode("button", {
class: "p-1 rounded",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[19] || (_cache[19] = $event => ($setup.executeCommand('indent'))),
title: "Increase Indent"
}, [
vue.createVNode(_component_Indent, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $props.theme.headerFgColor })
}, null, 8, ["style"])
], 4),
vue.createElementVNode("div", {
class: "h-8 w-px mx-1",
style: vue.normalizeStyle({ backgroundColor: $props.theme.edgeColor })
}, null, 4),
vue.createElementVNode("button", {
class: "p-1 rounded",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[20] || (_cache[20] = $event => (_ctx.$emit('insert-image'))),
title: "Insert Image"
}, [
vue.createVNode(_component_ImageIcon, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $props.theme.headerFgColor })
}, null, 8, ["style"])
], 4),
vue.createElementVNode("button", {
class: "p-1 rounded",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[21] || (_cache[21] = $event => (_ctx.$emit('insert-table'))),
title: "Insert Table"
}, [
vue.createVNode(_component_Table, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $props.theme.headerFgColor })
}, null, 8, ["style"])
], 4),
($props.selectedElement && $props.selectedElement.tagName === 'TABLE')
? (vue.openBlock(), vue.createElementBlock("div", _hoisted_11$1, [
vue.createElementVNode("button", {
class: "p-1 rounded",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[22] || (_cache[22] = $event => (_ctx.$emit('edit-table'))),
title: "Edit Table"
}, [
vue.createVNode(_component_Settings, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $props.theme.headerFgColor })
}, null, 8, ["style"])
], 4)
]))
: vue.createCommentVNode("", true),
($props.selectedCell && ($props.selectedCell.tagName === 'TD' || $props.selectedCell.tagName === 'TH'))
? (vue.openBlock(), vue.createElementBlock("div", _hoisted_12$1, [
vue.createElementVNode("button", {
class: "p-1 rounded",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[23] || (_cache[23] = $event => (_ctx.$emit('cell-color'))),
title: "Cell Background Color"
}, [
vue.createVNode(_component_Paintbrush, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $props.theme.headerFgColor })
}, null, 8, ["style"])
], 4)
]))
: vue.createCommentVNode("", true),
vue.createElementVNode("button", {
class: "p-1 rounded",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[24] || (_cache[24] = $event => (_ctx.$emit('insert-link'))),
title: "Insert Link"
}, [
vue.createVNode(_component_Link, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $props.theme.headerFgColor })
}, null, 8, ["style"])
], 4),
vue.createElementVNode("div", {
class: "h-8 w-px mx-1",
style: vue.normalizeStyle({ backgroundColor: $props.theme.edgeColor })
}, null, 4),
vue.createElementVNode("button", {
class: "p-1 rounded",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[25] || (_cache[25] = $event => ($setup.executeCommand('undo'))),
title: "Undo"
}, [
vue.createVNode(_component_Undo, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $props.theme.headerFgColor })
}, null, 8, ["style"])
], 4),
vue.createElementVNode("button", {
class: "p-1 rounded",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[26] || (_cache[26] = $event => ($setup.executeCommand('redo'))),
title: "Redo"
}, [
vue.createVNode(_component_Redo, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $props.theme.headerFgColor })
}, null, 8, ["style"])
], 4),
vue.createElementVNode("div", {
class: "h-8 w-px mx-1",
style: vue.normalizeStyle({ backgroundColor: $props.theme.edgeColor })
}, null, 4),
vue.createElementVNode("button", {
class: "p-1 rounded",
style: vue.normalizeStyle($setup.buttonStyle),
onClick: _cache[27] || (_cache[27] = $event => ($setup.executeCommand('removeFormat'))),
title: "Clear Formatting"
}, [
vue.createVNode(_component_X, {
class: "h-4 w-4",
style: vue.normalizeStyle({ color: $props.theme.headerFgColor })
}, null, 8, ["style"])
], 4)
], 4))
}
const EditorToolbar = /*#__PURE__*/_export_sfc(_sfc_main$6, [['render',_sfc_render$6],['__scopeId',"data-v-2f778b9b"]]);
const _sfc_main$5 = {
props: {
theme: {
type: Object,
default: () => ({
headerBgColor: '#f3f4f6',
headerFgColor: '#111827',
contentBgColor: '#ffffff',
contentFgColor: '#111827',
edgeColor: '#d1d5db'
})
}
},
emits: ['close', 'insert'],
setup(props, { emit }) {
const imageUrl = vue.ref('');
// Computed styles based on theme
const dialogStyle = vue.computed(() => ({
backgroundColor: props.theme.contentBgColor,
color: props.theme.contentFgColor,
border: `1px solid ${props.theme.edgeColor}`,
fontFamily: 'inherit'
}));
const headerStyle = vue.computed(() => ({
backgroundColor: props.theme.headerBgColor,
color: props.theme.headerFgColor,
padding: '8px',
margin: '-24px -24px 16px -24px',
borderTopLeftRadius: '0.5rem',
borderTopRightRadius: '0.5rem',
borderBottom: `1px solid ${props.theme.edgeColor}`
}));
const inputStyle = vue.computed(() => ({
backgroundColor: props.theme.contentBgColor,
color: props.theme.contentFgColor,
borderColor: props.theme.edgeColor
}));
const cancelButtonStyle = vue.computed(() => ({
backgroundColor: '#e5e7eb',
color: '#111827'
}));
const confirmButtonStyle = vue.computed(() => ({
backgroundColor: '#2563eb',
color: '#ffffff'
}));
const insertImage = () => {
if (imageUrl.value) {
emit('insert', imageUrl.value);
}
};
return {
imageUrl,
insertImage,
dialogStyle,
headerStyle,
inputStyle,
cancelButtonStyle,
confirmButtonStyle
};
}
};
const _hoisted_1$5 = { class: "mb-4" };
const _hoisted_2$5 = { class: "flex justify-end space-x-2" };
const _hoisted_3$5 = ["disabled"];
function _sfc_render$5(_ctx, _cache, $props, $setup, $data, $options) {
return (vue.openBlock(), vue.createElementBlock("div", {
class: "rounded-lg p-6 w-full max-w-md shadow-lg",
style: vue.normalizeStyle($setup.dialogStyle)
}, [
vue.createElementVNode("h3", {
class: "text-lg font-medium mb-4",
style: vue.normalizeStyle($setup.headerStyle)
}, "Insert Image", 4),
vue.createElementVNode("div", _hoisted_1$5, [
vue.createElementVNode("label", {
class: "block text-sm font-medium mb-1",
style: vue.normalizeStyle({ color: $props.theme.contentFgColor })
}, "Image URL:", 4),
vue.withDirectives(vue.createElementVNode("input", {
type: "text",
"onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (($setup.imageUrl) = $event)),
class: "w-full px-3 py-2 border rounded-md",
style: vue.normalizeStyle($setup.inputStyle),
placeholder: "https://example.com/image.jpg",
onKeydown: _cache[1] || (_cache[1] = vue.withKeys((...args) => ($setup.insertImage && $setup.insertImage(...args)), ["enter"]))
}, null, 36), [
[vue.vModelText, $setup.imageUrl]
])
]),
vue.createElementVNode("div", _hoisted_2$5, [
vue.createElementVNode("button", {
class: "px-4 py-2 rounded hover:opacity-90 active:opacity-80",
style: vue.normalizeStyle($setup.cancelButtonStyle),
onClick: _cache[2] || (_cache[2] = $event => (_ctx.$emit('close')))