UNPKG

@etsoo/editor

Version:

ETSOO Free WYSIWYG HTML Editor

280 lines (276 loc) 9.85 kB
'use strict'; var shared = require('@etsoo/shared'); var EOPopup = require('./EOPopup.js'); /** * Favorite colors key */ const FavoriteColorsKey = "EOEditor-Favorite-Colors"; // Add to colors function addToColors(colors, colorText) { const color = shared.EColor.parse(colorText)?.toRGBColor(); if (color) colors.push(color); } // Get colors from stylesheet declarations function getColors(doc) { const colors = []; const sheets = doc.styleSheets; for (let c = 0; c < sheets.length; c++) { const sheet = sheets.item(c); if (sheet == null) continue; // CORS security rules are applicable for style-sheets try { for (let r = 0; r < sheet.cssRules.length; r++) { const rule = sheet.cssRules[r]; if ("style" in rule) { addToColors(colors, rule.style.color); addToColors(colors, rule.style.backgroundColor); addToColors(colors, rule.style.borderColor); } } } catch { } } return colors; } function getPointColor(event) { const ctx = event.target.getContext("2d"); const data = ctx.getImageData(event.offsetX, event.offsetY, 1, 1).data; return `rgb(${data.slice(0, 3).join(", ")})`; } function createColorStrip(canvas) { const ctx = canvas.getContext("2d"); const { width, height } = canvas; ctx.rect(0, 0, width, height); var grd1 = ctx.createLinearGradient(0, 0, 0, height); grd1.addColorStop(0, "rgb(255, 0, 0)"); grd1.addColorStop(0.17, "rgb(255, 255, 0)"); grd1.addColorStop(0.34, "rgb(0, 255, 0)"); grd1.addColorStop(0.51, "rgb(0, 255, 255)"); grd1.addColorStop(0.68, "rgb(0, 0, 255)"); grd1.addColorStop(0.85, "rgb(255, 0, 255)"); grd1.addColorStop(1, "rgb(255, 0, 0)"); ctx.fillStyle = grd1; ctx.fill(); } /** * EOEditor Palette */ class EOPalette extends EOPopup.EOPopup { /** * Style reference document */ refDocument; // Refered colors referedColors; /** * Apply label */ get applyLabel() { return this.getAttribute("applyLabel"); } set applyLabel(value) { if (value) this.setAttribute("applyLabel", value); else this.removeAttribute("applyLabel"); } constructor() { super(); } connectedCallback() { if (this.refDocument) { this.referedColors = getColors(this.refDocument); } } disconnectedCallback() { this.hide(); } /** * Popup palette * @param color Default color * @param callback Callback * @param rect Display location * @param hexColor Return Hex color */ popup(color, callback, rect, hexColor) { // Favorite colors const textFavoriteColors = window.localStorage.getItem(FavoriteColorsKey); const favoriteColors = textFavoriteColors ? JSON.parse(textFavoriteColors) : []; // CSS defined colors const colors = [ ...new Set([ ...favoriteColors, ...(this.referedColors ?? []), "transparent", new shared.EColor(0, 0, 0).toRGBColor(), new shared.EColor(255, 255, 255).toRGBColor(), new shared.EColor(255, 0, 0).toRGBColor(), new shared.EColor(0, 255, 0).toRGBColor(), new shared.EColor(0, 0, 255).toRGBColor(), new shared.EColor(0, 255, 255).toRGBColor(), new shared.EColor(255, 255, 0).toRGBColor(), new shared.EColor(255, 0, 255).toRGBColor() ]) ]; const html = `<style> .content1 { font-size: 12px; padding: 1em; } .preview { display: flex; gap: 6px; } .box { flex-grow: 2; border: 1px solid #ccc; background-color: ${color}; } .color-more { margin-top: 6px; display: flex; width: 290px; flex-wrap: wrap; gap: 4px; } .color-more .color-circle { width: 24px; height: 24px; border: 1px solid #ccc; border-radius: 50%; cursor: pointer; text-align: center; line-height: 22px; vertical-align: middle; } .color-container { display: flex; gap: 5px; margin-top: 6px; width: 290px; height: 200px; } </style> <div class="content1"> <div class="preview"> <div class="box"></div> <input size="12" value="${color}" /> <button style="min-width: 60px;">${this.applyLabel ?? "Apply"}</button> </div> <div class="color-more">${colors .map((c) => `<div class="color-circle" style="background-color: ${c}">${c === "transparent" ? "T" : ""}</div>`) .join("")}</div> <div class="color-container"> <div id="color-preview" style="height: 200; width: 40px;"></div> <canvas id="color-block" height="200" width="200"></canvas> <canvas id="color-strip" height="200" width="40"></canvas> </div> </div>`; this.show(html, rect, () => { }); const previewDiv = this.querySelector("div.preview"); const div = previewDiv.querySelector("div.box"); const input = previewDiv.querySelector("input"); input.addEventListener("change", () => { div.style.backgroundColor = input.value; }); const button = previewDiv.querySelector("button"); button.addEventListener("click", () => { const colorText = input.value.trim(); if (!colorText) { input.focus(); return; } const color = shared.EColor.format(colorText, hexColor); if (color == null) { input.focus(); return; } returnColor(color); }); const returnColor = (color) => { // Add to favorite colors const index = favoriteColors.indexOf(color); if (index !== 0) { if (index !== -1) { favoriteColors.splice(index, 1); } favoriteColors.unshift(color); if (favoriteColors.length > 16) favoriteColors.pop(); window.localStorage.setItem(FavoriteColorsKey, JSON.stringify(favoriteColors)); } callback(color); this.hide(); }; const colorPreview = this.querySelector("#color-preview"); const colorBlock = this.querySelector("canvas#color-block"); const ctx = colorBlock.getContext("2d"); const previewColor = (event) => { const color = getPointColor(event); colorPreview.style.backgroundColor = color; }; const setColor = (event) => { const color = getPointColor(event); div.style.backgroundColor = color; input.value = color; return color; }; colorBlock.addEventListener("mousemove", previewColor); colorBlock.addEventListener("click", setColor); this.querySelectorAll("div.color-circle").forEach((div) => { div.addEventListener("click", () => { const color = shared.EColor.format(div.style.backgroundColor, hexColor); if (color) returnColor(color); }); }); const colorStrip = this.querySelector("canvas#color-strip"); colorStrip.addEventListener("click", (event) => { const color = setColor(event); fillGradient(color); }); colorStrip.addEventListener("mousemove", previewColor); createColorStrip(colorStrip); const fillGradient = (color) => { colorPreview.style.backgroundColor = color; const { width, height } = colorBlock; ctx.fillStyle = color; ctx.fillRect(0, 0, width, height); var grdWhite = ctx.createLinearGradient(0, 0, width, 0); grdWhite.addColorStop(0, "rgba(255,255,255,1)"); grdWhite.addColorStop(1, "rgba(255,255,255,0)"); ctx.fillStyle = grdWhite; ctx.fillRect(0, 0, width, height); var grdBlack = ctx.createLinearGradient(0, 0, 0, height); grdBlack.addColorStop(0, "rgba(0,0,0,0)"); grdBlack.addColorStop(1, "rgba(0,0,0,1)"); ctx.fillStyle = grdBlack; ctx.fillRect(0, 0, width, height); }; fillGradient("rgba(255,0,0,1)"); } /** * Setup input choose and preview * @param input Input * @param hexColor Return Hex color */ setupInput(input, hexColor) { input.addEventListener("click", () => { this.popup(input.value, (color) => { input.value = color; input.style.borderColor = color; input.dispatchEvent(new Event("change")); }, undefined, hexColor); this.style.zIndex = "992"; }); input.addEventListener("change", () => { input.style.borderColor = input.value; }); input.dispatchEvent(new Event("change")); } } exports.EOPalette = EOPalette;