From 34bb8e0774e6f99e661d314a96a99df07a33acd0 Mon Sep 17 00:00:00 2001 From: Antoine ORY-LAMBALLE Date: Sun, 25 Jun 2023 16:01:28 +0200 Subject: [PATCH 01/13] feat: add auto-preview on paste --- README.md | 4 ++ src/index.js | 133 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 132 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 35e25e4..2c1effb 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ const editor = EditorJS({ class: LinkTool, config: { endpoint: 'http://localhost:8008/fetchUrl', // Your backend endpoint for url data fetching, + createOnPaste: true, // true to catch non-image pasted links and create a preview automatically + key: 'linkTool', // Required if createOnPaste is true - must match the tool key } } }, @@ -71,6 +73,8 @@ Link Tool supports these configuration parameters: | ---------|-------------|------------------------------------------------| | endpoint | `string` | **Required:** the endpoint for link data fetching. | | headers | `object` | **Optional:** the headers used in the GET request. | +| createOnPaste | `boolean` | **Optional:** true to catch non-image pasted links and create a preview automatically. | +| key | `string` | **Optional:** Required if createOnPaste is true - must match the tool key. | ## Output data diff --git a/src/index.js b/src/index.js index ad92ca2..c7db898 100644 --- a/src/index.js +++ b/src/index.js @@ -17,6 +17,8 @@ * @typedef {object} LinkToolConfig * @property {string} endpoint - the endpoint for link data fetching * @property {object} headers - the headers used in the GET request + * @property {boolean} createOnPaste - true to catch non-image pasted links and create a preview automatically + * @property {string} key - Required if createOnPaste is true - must match the tool key, i.e. the key used in the editorjs config */ import './index.css'; @@ -24,6 +26,8 @@ import 'url-polyfill'; import ajax from '@codexteam/ajax'; import { IconLink } from '@codexteam/icons'; +const noPreviewIcon = ''; + /** * @typedef {object} UploadResponseFormat * @description This format expected from backend on link data fetching @@ -67,11 +71,26 @@ export default class LinkTool { return true; } + /** + * Available image tools + * + * @returns {Array} + */ + static get tunes() { + return [ + { + name: 'urlOnly', + icon: noPreviewIcon, + title: 'Remove preview', + }, + ]; + } + /** * @param {object} options - Tool constructor options fot from Editor.js * @param {LinkToolData} options.data - previously saved data * @param {LinkToolConfig} options.config - user config for Tool - * @param {object} options.api - Editor.js API + * @param {import('@editorjs/editorjs').API} options.api - Editor.js API * @param {boolean} options.readOnly - read-only mode flag */ constructor({ data, config, api, readOnly }) { @@ -82,6 +101,7 @@ export default class LinkTool { * Tool's initial config */ this.config = { + ...config, endpoint: config.endpoint || '', headers: config.headers || {}, }; @@ -105,6 +125,18 @@ export default class LinkTool { }; this.data = data; + + if (config.createOnPaste) { + this.api.listeners.on(this.api.ui.nodes.wrapper, 'paste', this.handlePaste.bind(this)); + + // If a block was programmatically created with a link, start fetching + setTimeout(() => { + if (data.link) { + this.nodes.input.textContent = data.link; + this.startFetching({}, true); + } + }); + } } /** @@ -258,7 +290,7 @@ export default class LinkTool { * * @param {PasteEvent|KeyboardEvent} event - fetching could be fired by a pase or keydown events */ - startFetching(event) { + startFetching(event, fallbackToText) { let url = this.nodes.input.textContent; if (event.type === 'paste') { @@ -266,7 +298,7 @@ export default class LinkTool { } this.removeErrorStyle(); - this.fetchLinkData(url); + this.fetchLinkData(url, fallbackToText); } /** @@ -386,7 +418,7 @@ export default class LinkTool { * * @param {string} url - link source url */ - async fetchLinkData(url) { + async fetchLinkData(url, fallbackToText) { this.showProgress(); this.data = { link: url }; @@ -401,9 +433,21 @@ export default class LinkTool { this.onFetch(body); } catch (error) { - this.fetchingFailed(this.api.i18n.t('Couldn\'t fetch the link data')); + if (fallbackToText) { + this.replaceBlockWithParagraph(); + } else { + this.fetchingFailed(this.api.i18n.t('Couldn\'t fetch the link data')); + } } } + + /** + * Replace this link block with a standard paragraph (text) block. Example: as a fallback for pasted URLs which fetch failed. + */ + replaceBlockWithParagraph() { + const newBlock = this.api.blocks.insert('paragraph', {text: this.nodes.input.textContent}, undefined, this.api.blocks.getCurrentBlockIndex(), true, true); + this.api.caret.setToBlock(newBlock.id); + } /** * Link data fetching callback @@ -477,4 +521,83 @@ export default class LinkTool { return el; } + + /** + * Custom paste handler, so that we can choose more accurately whether it should catch the paste or not. + * + * @param {PasteEvent | KeyboardEvent} event + */ + handlePaste(event) { + const pasteConfig = { + patterns: { + embed: /^(?!.*\.(?:gif|jpe?g|tiff|png)(?:\?|$))https?:\/\/\S+$/i, + }, + }; + const patterns = pasteConfig.patterns; + const url = (event.clipboardData || window.clipboardData)?.getData('text'); + if (!url) return; + + const currentBlock = this.api.blocks.getBlockByIndex(this.api.blocks.getCurrentBlockIndex()); + const isParagraph = currentBlock.name === 'paragraph'; + const isCurrentBlockEmpty = currentBlock.isEmpty; + + if (patterns.embed.test(url) && isParagraph && isCurrentBlockEmpty) { + event.preventDefault(); // Prevent the default paste behavior + this.insertPastedBlock(url); + } + } + + /** + * Insert a link block + * @param {string} content + */ + insertPastedBlock(content) { + const pluginName = this.getPluginName(); + const newBlock = this.api.blocks.insert(pluginName, {link: content}, undefined, this.api.blocks.getCurrentBlockIndex(), true, true); + } + + /** + * Get the name of the plugin dynamically from the Editor.js configuration. + * Ideally, we should get it dynamically without requiring the user to provide a config, but I haven't found how to do that. + * @returns {string} The name of the plugin + */ + getPluginName() { + if (!this.config.key) { + throw new Error(`You need to provide the tool key in the plugin config, e.g. { linkTool: { class: LinkTool, config: { key: 'linkTool' } } }`); + } + return this.config.key; + } + + /** + * Returns configuration for block tunes: remove the preview and keep the URL only + * + * @public + * + * @returns {Array} + */ + renderSettings() { + // Merge default tunes with the ones that might be added by user + // @see https://github.com/editor-js/image/pull/49 + const tunes = this.config.actions && this.config.createOnPaste ? LinkTool.tunes.concat(this.config.actions) : LinkTool.tunes; + + return tunes.map(tune => ({ + icon: tune.icon, + label: this.api.i18n.t(tune.title), + name: tune.name, + toggle: tune.toggle, + isActive: this.data[tune.name], + onActivate: () => { + /* If it'a user defined tune, execute it's callback stored in action property */ + if (typeof tune.action === 'function') { + tune.action(tune.name); + + return; + } + + if (tune.name === 'urlOnly') { + this.replaceBlockWithParagraph(); + } + }, + })); + } } From 6c5a9872ffcaecdacf5c450e7269c2ed02a7c4e2 Mon Sep 17 00:00:00 2001 From: Antoine ORY-LAMBALLE Date: Sun, 25 Jun 2023 16:05:26 +0200 Subject: [PATCH 02/13] fix: linting --- src/index.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/index.js b/src/index.js index c7db898..c64ad59 100644 --- a/src/index.js +++ b/src/index.js @@ -289,6 +289,7 @@ export default class LinkTool { * Activates link data fetching by url * * @param {PasteEvent|KeyboardEvent} event - fetching could be fired by a pase or keydown events + * @param {boolean} fallbackToText if true and the fetch fails, falls back to rendering a paragraph block with the raw link instead of a link block with a failure. */ startFetching(event, fallbackToText) { let url = this.nodes.input.textContent; @@ -417,6 +418,7 @@ export default class LinkTool { * Sends to backend pasted url and receives link data * * @param {string} url - link source url + * @param {boolean} fallbackToText if true and the fetch fails, falls back to rendering a paragraph block with the raw link instead of a link block with a failure. */ async fetchLinkData(url, fallbackToText) { this.showProgress(); @@ -440,12 +442,13 @@ export default class LinkTool { } } } - + /** * Replace this link block with a standard paragraph (text) block. Example: as a fallback for pasted URLs which fetch failed. */ replaceBlockWithParagraph() { - const newBlock = this.api.blocks.insert('paragraph', {text: this.nodes.input.textContent}, undefined, this.api.blocks.getCurrentBlockIndex(), true, true); + const newBlock = this.api.blocks.insert('paragraph', { text: this.nodes.input.textContent }, undefined, this.api.blocks.getCurrentBlockIndex(), true, true); + this.api.caret.setToBlock(newBlock.id); } @@ -525,7 +528,7 @@ export default class LinkTool { /** * Custom paste handler, so that we can choose more accurately whether it should catch the paste or not. * - * @param {PasteEvent | KeyboardEvent} event + * @param {PasteEvent | KeyboardEvent} event the paste event */ handlePaste(event) { const pasteConfig = { @@ -534,13 +537,16 @@ export default class LinkTool { }, }; const patterns = pasteConfig.patterns; - const url = (event.clipboardData || window.clipboardData)?.getData('text'); - if (!url) return; + const url = (event.clipboardData || window.clipboardData).getData('text'); + + if (!url) { + return; + } const currentBlock = this.api.blocks.getBlockByIndex(this.api.blocks.getCurrentBlockIndex()); const isParagraph = currentBlock.name === 'paragraph'; const isCurrentBlockEmpty = currentBlock.isEmpty; - + if (patterns.embed.test(url) && isParagraph && isCurrentBlockEmpty) { event.preventDefault(); // Prevent the default paste behavior this.insertPastedBlock(url); @@ -549,22 +555,26 @@ export default class LinkTool { /** * Insert a link block - * @param {string} content + * + * @param {string} link the URL to include in the created link block */ - insertPastedBlock(content) { + insertPastedBlock(link) { const pluginName = this.getPluginName(); - const newBlock = this.api.blocks.insert(pluginName, {link: content}, undefined, this.api.blocks.getCurrentBlockIndex(), true, true); + + this.api.blocks.insert(pluginName, { link }, undefined, this.api.blocks.getCurrentBlockIndex(), true, true); } /** * Get the name of the plugin dynamically from the Editor.js configuration. * Ideally, we should get it dynamically without requiring the user to provide a config, but I haven't found how to do that. + * * @returns {string} The name of the plugin */ getPluginName() { if (!this.config.key) { throw new Error(`You need to provide the tool key in the plugin config, e.g. { linkTool: { class: LinkTool, config: { key: 'linkTool' } } }`); } + return this.config.key; } From 2f296ac9a3798aff1ccfdbd5ca8be76f498269ff Mon Sep 17 00:00:00 2001 From: Antoine ORY-LAMBALLE Date: Sun, 25 Jun 2023 16:39:46 +0200 Subject: [PATCH 03/13] feat: auto-open the block settings after auto-link When a URL is pasted and a link block created, it automatically opens the block settings, to show the user they can remove the preview. --- src/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.js b/src/index.js index c64ad59..a3122c9 100644 --- a/src/index.js +++ b/src/index.js @@ -562,6 +562,9 @@ export default class LinkTool { const pluginName = this.getPluginName(); this.api.blocks.insert(pluginName, { link }, undefined, this.api.blocks.getCurrentBlockIndex(), true, true); + setTimeout(() => { + this.api.toolbar.toggleBlockSettings(true); + }); } /** From 406e7ed255a3189e82556536571d0ab0e2b3f1e6 Mon Sep 17 00:00:00 2001 From: Antoine ORY-LAMBALLE Date: Sun, 25 Jun 2023 17:05:53 +0200 Subject: [PATCH 04/13] fix: close settings when removing the preview --- src/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.js b/src/index.js index a3122c9..afdb0c2 100644 --- a/src/index.js +++ b/src/index.js @@ -609,6 +609,7 @@ export default class LinkTool { if (tune.name === 'urlOnly') { this.replaceBlockWithParagraph(); + this.api.toolbar.toggleBlockSettings(false); } }, })); From ec2da3bb3b384def0531e8e9812a0795b18200eb Mon Sep 17 00:00:00 2001 From: Antoine ORY-LAMBALLE Date: Sun, 25 Jun 2023 22:15:56 +0200 Subject: [PATCH 05/13] fix: also close settings when falling back to text --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index afdb0c2..4c59e85 100644 --- a/src/index.js +++ b/src/index.js @@ -450,6 +450,7 @@ export default class LinkTool { const newBlock = this.api.blocks.insert('paragraph', { text: this.nodes.input.textContent }, undefined, this.api.blocks.getCurrentBlockIndex(), true, true); this.api.caret.setToBlock(newBlock.id); + this.api.toolbar.toggleBlockSettings(false); } /** @@ -609,7 +610,6 @@ export default class LinkTool { if (tune.name === 'urlOnly') { this.replaceBlockWithParagraph(); - this.api.toolbar.toggleBlockSettings(false); } }, })); From cf65ed09502bddb01c2632764a314b8858d52074 Mon Sep 17 00:00:00 2001 From: Dindonneau Date: Mon, 11 Dec 2023 18:52:37 +0100 Subject: [PATCH 06/13] feat(videoLinks): prevent embed link on videos --- src/index.js | 74 +++++++++++++++++++++++++++++++++++------------- src/videoLink.js | 27 ++++++++++++++++++ 2 files changed, 82 insertions(+), 19 deletions(-) create mode 100644 src/videoLink.js diff --git a/src/index.js b/src/index.js index 4c59e85..63013ea 100644 --- a/src/index.js +++ b/src/index.js @@ -25,8 +25,10 @@ import './index.css'; import 'url-polyfill'; import ajax from '@codexteam/ajax'; import { IconLink } from '@codexteam/icons'; +import { isVideoLink } from './videoLink'; -const noPreviewIcon = ''; +const noPreviewIcon = + ''; /** * @typedef {object} UploadResponseFormat @@ -127,7 +129,11 @@ export default class LinkTool { this.data = data; if (config.createOnPaste) { - this.api.listeners.on(this.api.ui.nodes.wrapper, 'paste', this.handlePaste.bind(this)); + this.api.listeners.on( + this.api.ui.nodes.wrapper, + 'paste', + this.handlePaste.bind(this) + ); // If a block was programmatically created with a link, start fetching setTimeout(() => { @@ -197,10 +203,13 @@ export default class LinkTool { * @param {LinkToolData} data - data to store */ set data(data) { - this._data = Object.assign({}, { - link: data.link || this._data.link, - meta: data.meta || this._data.meta, - }); + this._data = Object.assign( + {}, + { + link: data.link || this._data.link, + meta: data.meta || this._data.meta, + } + ); } /** @@ -379,7 +388,7 @@ export default class LinkTool { this.nodes.linkContent.appendChild(this.nodes.linkText); try { - this.nodes.linkText.textContent = (new URL(this.data.link)).hostname; + this.nodes.linkText.textContent = new URL(this.data.link).hostname; } catch (e) { this.nodes.linkText.textContent = this.data.link; } @@ -425,20 +434,20 @@ export default class LinkTool { this.data = { link: url }; try { - const { body } = await (ajax.get({ + const { body } = await ajax.get({ url: this.config.endpoint, headers: this.config.headers, data: { url, }, - })); + }); this.onFetch(body); } catch (error) { if (fallbackToText) { this.replaceBlockWithParagraph(); } else { - this.fetchingFailed(this.api.i18n.t('Couldn\'t fetch the link data')); + this.fetchingFailed(this.api.i18n.t("Couldn't fetch the link data")); } } } @@ -447,7 +456,14 @@ export default class LinkTool { * Replace this link block with a standard paragraph (text) block. Example: as a fallback for pasted URLs which fetch failed. */ replaceBlockWithParagraph() { - const newBlock = this.api.blocks.insert('paragraph', { text: this.nodes.input.textContent }, undefined, this.api.blocks.getCurrentBlockIndex(), true, true); + const newBlock = this.api.blocks.insert( + 'paragraph', + { text: this.nodes.input.textContent }, + undefined, + this.api.blocks.getCurrentBlockIndex(), + true, + true + ); this.api.caret.setToBlock(newBlock.id); this.api.toolbar.toggleBlockSettings(false); @@ -460,7 +476,9 @@ export default class LinkTool { */ onFetch(response) { if (!response || !response.success) { - this.fetchingFailed(this.api.i18n.t('Couldn\'t get this link data, try the other one')); + this.fetchingFailed( + this.api.i18n.t("Couldn't get this link data, try the other one") + ); return; } @@ -475,7 +493,9 @@ export default class LinkTool { }; if (!metaData) { - this.fetchingFailed(this.api.i18n.t('Wrong response format from the server')); + this.fetchingFailed( + this.api.i18n.t('Wrong response format from the server') + ); return; } @@ -544,11 +564,15 @@ export default class LinkTool { return; } - const currentBlock = this.api.blocks.getBlockByIndex(this.api.blocks.getCurrentBlockIndex()); + const currentBlock = this.api.blocks.getBlockByIndex( + this.api.blocks.getCurrentBlockIndex() + ); const isParagraph = currentBlock.name === 'paragraph'; const isCurrentBlockEmpty = currentBlock.isEmpty; + const shouldDisplayEmbedLink = + patterns.embed.test(url) && !isVideoLink(url); - if (patterns.embed.test(url) && isParagraph && isCurrentBlockEmpty) { + if (shouldDisplayEmbedLink && isParagraph && isCurrentBlockEmpty) { event.preventDefault(); // Prevent the default paste behavior this.insertPastedBlock(url); } @@ -562,7 +586,14 @@ export default class LinkTool { insertPastedBlock(link) { const pluginName = this.getPluginName(); - this.api.blocks.insert(pluginName, { link }, undefined, this.api.blocks.getCurrentBlockIndex(), true, true); + this.api.blocks.insert( + pluginName, + { link }, + undefined, + this.api.blocks.getCurrentBlockIndex(), + true, + true + ); setTimeout(() => { this.api.toolbar.toggleBlockSettings(true); }); @@ -576,7 +607,9 @@ export default class LinkTool { */ getPluginName() { if (!this.config.key) { - throw new Error(`You need to provide the tool key in the plugin config, e.g. { linkTool: { class: LinkTool, config: { key: 'linkTool' } } }`); + throw new Error( + `You need to provide the tool key in the plugin config, e.g. { linkTool: { class: LinkTool, config: { key: 'linkTool' } } }` + ); } return this.config.key; @@ -592,9 +625,12 @@ export default class LinkTool { renderSettings() { // Merge default tunes with the ones that might be added by user // @see https://github.com/editor-js/image/pull/49 - const tunes = this.config.actions && this.config.createOnPaste ? LinkTool.tunes.concat(this.config.actions) : LinkTool.tunes; + const tunes = + this.config.actions && this.config.createOnPaste + ? LinkTool.tunes.concat(this.config.actions) + : LinkTool.tunes; - return tunes.map(tune => ({ + return tunes.map((tune) => ({ icon: tune.icon, label: this.api.i18n.t(tune.title), name: tune.name, diff --git a/src/videoLink.js b/src/videoLink.js new file mode 100644 index 0000000..8a0c9b9 --- /dev/null +++ b/src/videoLink.js @@ -0,0 +1,27 @@ +/* eslint-disable */ + +const videoLinkRegex = { + vimeo: + /(?:http[s]?:\/\/)?(?:www.)?(?:player.)?vimeo\.co(?:.+\/([^\/]\d+)(?:#t=[\d]+)?s?$)/, + youtube: /(?:https?:\/\/)?(?:www\.)?(?:(?:youtu\.be\/)|(?:youtube\.com)\/(?:v\/|u\/\w\/|embed\/|watch))(?:(?:\?v=)?([^#&?=]*))?((?:[?&]\w*=\w*)*)/, + coub: /https?:\/\/coub\.com\/view\/([^\/\?\&]+)/, + vine: /https?:\/\/vine\.co\/v\/([^\/\?\&]+)/, + imgur: /https?:\/\/(?:i\.)?imgur\.com.*\/([a-zA-Z0-9]+)(?:\.gifv)?/, + gfycat: /https?:\/\/gfycat\.com(?:\/detail)?\/([a-zA-Z]+)/, + 'twitch-channel': /https?:\/\/www\.twitch\.tv\/([^\/\?\&]*)\/?$/, + 'twitch-video': /https?:\/\/www\.twitch\.tv\/(?:[^\/\?\&]*\/v|videos)\/([0-9]*)/, + 'yandex-music-album': /https?:\/\/music\.yandex\.ru\/album\/([0-9]*)\/?$/, + 'yandex-music-track': /https?:\/\/music\.yandex\.ru\/album\/([0-9]*)\/track\/([0-9]*)/, + 'yandex-music-playlist': /https?:\/\/music\.yandex\.ru\/users\/([^\/\?\&]*)\/playlists\/([0-9]*)/, + codepen: /https?:\/\/codepen\.io\/([^\/\?\&]*)\/pen\/([^\/\?\&]*)/, + instagram: /https?:\/\/www\.instagram\.com\/p\/([^\/\?\&]+)\/?.*/, + twitter: /^https?:\/\/twitter\.com\/(?:#!\/)?(\w+)\/status(?:es)?\/(\d+?.*)?$/, + pinterest: /https?:\/\/([^\/\?\&]*).pinterest.com\/pin\/([^\/\?\&]*)\/?$/, + facebook: /https?:\/\/www.facebook.com\/([^\/\?\&]*)\/(.*)/, + aparat: /(?:http[s]?:\/\/)?(?:www.)?aparat\.com\/v\/([^\/\?\&]+)\/?/, + miro: /https:\/\/miro.com\/\S+(\S{12})\/(\S+)?/, + github: /https?:\/\/gist.github.com\/([^\/\?\&]*)\/([^\/\?\&]*)/, +}; + +export const isVideoLink = (string) => + Object.values(videoLinkRegex).some((videoRegex) => videoRegex.test(string)); From 3621d8a548880e427c850a6f04f29fd084a780b4 Mon Sep 17 00:00:00 2001 From: Dindonneau Date: Mon, 18 Dec 2023 08:56:11 +0100 Subject: [PATCH 07/13] feat(videoLinkRegex): handle dailymotion regex --- src/videoLink.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/videoLink.js b/src/videoLink.js index 8a0c9b9..590a827 100644 --- a/src/videoLink.js +++ b/src/videoLink.js @@ -4,6 +4,7 @@ const videoLinkRegex = { vimeo: /(?:http[s]?:\/\/)?(?:www.)?(?:player.)?vimeo\.co(?:.+\/([^\/]\d+)(?:#t=[\d]+)?s?$)/, youtube: /(?:https?:\/\/)?(?:www\.)?(?:(?:youtu\.be\/)|(?:youtube\.com)\/(?:v\/|u\/\w\/|embed\/|watch))(?:(?:\?v=)?([^#&?=]*))?((?:[?&]\w*=\w*)*)/, + dailymotion: /(?:https?:\/\/)?(?:www\.)?dailymotion\.com\/video\/([^_]+)/, coub: /https?:\/\/coub\.com\/view\/([^\/\?\&]+)/, vine: /https?:\/\/vine\.co\/v\/([^\/\?\&]+)/, imgur: /https?:\/\/(?:i\.)?imgur\.com.*\/([a-zA-Z0-9]+)(?:\.gifv)?/, From 71758af336e50d3fc6b1fddc9a728456ca9c497e Mon Sep 17 00:00:00 2001 From: Dindonneau Date: Thu, 21 Dec 2023 10:27:47 +0100 Subject: [PATCH 08/13] chore(embedLink): new Layout --- src/index.css | 65 ++++++++++++++++++++++++++++++++++++++++++--------- src/index.js | 41 ++++++++++++++++++++++++++++++-- 2 files changed, 93 insertions(+), 13 deletions(-) diff --git a/src/index.css b/src/index.css index 01a0b57..da3b92b 100644 --- a/src/index.css +++ b/src/index.css @@ -63,7 +63,14 @@ } &__content { - display: block; + display: flex; + column-gap: 10px; + padding: 10px; + border-radius: 5px; + color: initial !important; + text-decoration: none !important; + + /* display: block; padding: 25px; border-radius: 2px; box-shadow: 0 0 0 2px #fff; @@ -74,15 +81,21 @@ content: ""; clear: both; display: table; - } + } */ &--rendered { background: #fff; + border: 1px solid #E9E9E9; + border-radius: 5px; + will-change: filter; + animation: link-in 450ms 1 cubic-bezier(0.215, 0.61, 0.355, 1); + + /* background: #fff; border: 1px solid rgba(201, 201, 204, 0.48); box-shadow: 0 1px 3px rgba(0,0,0, .1); border-radius: 6px; will-change: filter; - animation: link-in 450ms 1 cubic-bezier(0.215, 0.61, 0.355, 1); + animation: link-in 450ms 1 cubic-bezier(0.215, 0.61, 0.355, 1); */ &:hover { box-shadow: 0 0 3px rgba(0,0,0, .16); @@ -94,41 +107,71 @@ background-position: center center; background-repeat: no-repeat; background-size: cover; - margin: 0 0 0 30px; + min-width: 80px; + height: 80px; + aspect-ratio: 1/1; + border-radius: 5px; + + /* margin: 0 0 0 30px; width: 65px; height: 65px; border-radius: 3px; - float: right; + float: right; */ + } + + &__infos { + overflow: hidden; + flex: 1; } &__title { - font-size: 17px; + font-size: 15px; + font-weight: 600; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + /* font-size: 17px; font-weight: 600; line-height: 1.5em; margin: 0 0 10px 0; + ^&__anchor { margin-top: 25px; - } + } */ } &__description { - margin: 0 0 20px 0; + font-size: 13px; + line-height: 1.25; + height:2rem; + overflow: hidden; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + + /* margin: 0 0 20px 0; font-size: 15px; line-height: 1.55em; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; - overflow: hidden; + overflow: hidden; */ } &__anchor { - display: block; + font-size: 13px; + color: #F44545; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + /* display: block; font-size: 15px; line-height: 1em; color: #888 !important; border: 0 !important; - padding: 0 !important; + padding: 0 !important; */ } } diff --git a/src/index.js b/src/index.js index 63013ea..a597a27 100644 --- a/src/index.js +++ b/src/index.js @@ -116,6 +116,7 @@ export default class LinkTool { inputHolder: null, linkContent: null, linkImage: null, + linkInfos: null, linkTitle: null, linkDescription: null, linkText: null, @@ -155,6 +156,7 @@ export default class LinkTool { render() { this.nodes.wrapper = this.make('div', this.CSS.baseClass); this.nodes.container = this.make('div', this.CSS.container); + this.nodes.container = this.make('div', [this.CSS.container, 'not-prose']); this.nodes.inputHolder = this.makeInputHolder(); this.nodes.linkContent = this.prepareLinkPreview(); @@ -164,7 +166,8 @@ export default class LinkTool { */ if (Object.keys(this.data.meta).length) { this.nodes.container.appendChild(this.nodes.linkContent); - this.showLinkPreview(this.data.meta); + this.showLinkPreviewOverridden(this.data.meta); + // this.showLinkPreview(this.data.meta); } else { this.nodes.container.appendChild(this.nodes.inputHolder); } @@ -239,6 +242,7 @@ export default class LinkTool { linkContent: 'link-tool__content', linkContentRendered: 'link-tool__content--rendered', linkImage: 'link-tool__image', + linkInfos: 'link-tool__infos', linkTitle: 'link-tool__title', linkDescription: 'link-tool__description', linkText: 'link-tool__anchor', @@ -353,6 +357,7 @@ export default class LinkTool { }); this.nodes.linkImage = this.make('div', this.CSS.linkImage); + this.nodes.linkInfos = this.make('div', this.CSS.linkInfos); this.nodes.linkTitle = this.make('div', this.CSS.linkTitle); this.nodes.linkDescription = this.make('p', this.CSS.linkDescription); this.nodes.linkText = this.make('span', this.CSS.linkText); @@ -394,6 +399,37 @@ export default class LinkTool { } } + /** + * Compose link preview from fetched data + * + * @param {metaData} meta - link meta data + */ + showLinkPreviewOverridden({ image, title, description }) { + this.nodes.container.appendChild(this.nodes.linkContent); + + if (image && image.url) { + this.nodes.linkImage.style.backgroundImage = 'url(' + image.url + ')'; + this.nodes.linkContent.appendChild(this.nodes.linkImage); + } + + if (title) { + this.nodes.linkTitle.textContent = title; + this.nodes.linkInfos.appendChild(this.nodes.linkTitle); + } + + if (description) { + this.nodes.linkDescription.textContent = description; + this.nodes.linkInfos.appendChild(this.nodes.linkDescription); + } + + this.nodes.linkText.textContent = this.data.link; + this.nodes.linkInfos.appendChild(this.nodes.linkText); + + this.nodes.linkContent.classList.add(this.CSS.linkContentRendered); + this.nodes.linkContent.setAttribute('href', this.data.link); + this.nodes.linkContent.appendChild(this.nodes.linkInfos); + } + /** * Show loading progress bar */ @@ -502,7 +538,8 @@ export default class LinkTool { this.hideProgress().then(() => { this.nodes.inputHolder.remove(); - this.showLinkPreview(metaData); + this.showLinkPreviewOverridden(metaData); + // this.showLinkPreview(metaData); }); } From 5756d89c79080fff3f0ede8ccdf04d98f22e61a7 Mon Sep 17 00:00:00 2001 From: Dindonneau Date: Thu, 21 Dec 2023 17:22:19 +0100 Subject: [PATCH 09/13] chore(description): grey color fro description --- src/index.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.css b/src/index.css index da3b92b..9f4f49f 100644 --- a/src/index.css +++ b/src/index.css @@ -143,6 +143,7 @@ &__description { font-size: 13px; + color: #595959; line-height: 1.25; height:2rem; overflow: hidden; From 28cbda1032e39c6d54d0bcc1808d4ed3b1a0a4ee Mon Sep 17 00:00:00 2001 From: Dindonneau Date: Thu, 21 Dec 2023 18:15:03 +0100 Subject: [PATCH 10/13] chore(comments): remove commented code --- src/index.css | 50 -------------------------------------------------- src/index.js | 40 ++-------------------------------------- 2 files changed, 2 insertions(+), 88 deletions(-) diff --git a/src/index.css b/src/index.css index 9f4f49f..bdbbd9c 100644 --- a/src/index.css +++ b/src/index.css @@ -70,19 +70,6 @@ color: initial !important; text-decoration: none !important; - /* display: block; - padding: 25px; - border-radius: 2px; - box-shadow: 0 0 0 2px #fff; - color: initial !important; - text-decoration: none !important; - - &::after { - content: ""; - clear: both; - display: table; - } */ - &--rendered { background: #fff; border: 1px solid #E9E9E9; @@ -90,13 +77,6 @@ will-change: filter; animation: link-in 450ms 1 cubic-bezier(0.215, 0.61, 0.355, 1); - /* background: #fff; - border: 1px solid rgba(201, 201, 204, 0.48); - box-shadow: 0 1px 3px rgba(0,0,0, .1); - border-radius: 6px; - will-change: filter; - animation: link-in 450ms 1 cubic-bezier(0.215, 0.61, 0.355, 1); */ - &:hover { box-shadow: 0 0 3px rgba(0,0,0, .16); } @@ -111,12 +91,6 @@ height: 80px; aspect-ratio: 1/1; border-radius: 5px; - - /* margin: 0 0 0 30px; - width: 65px; - height: 65px; - border-radius: 3px; - float: right; */ } &__infos { @@ -130,15 +104,6 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - - /* font-size: 17px; - font-weight: 600; - line-height: 1.5em; - margin: 0 0 10px 0; - - + ^&__anchor { - margin-top: 25px; - } */ } &__description { @@ -150,14 +115,6 @@ display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; - - /* margin: 0 0 20px 0; - font-size: 15px; - line-height: 1.55em; - display: -webkit-box; - -webkit-line-clamp: 3; - -webkit-box-orient: vertical; - overflow: hidden; */ } &__anchor { @@ -166,13 +123,6 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - - /* display: block; - font-size: 15px; - line-height: 1em; - color: #888 !important; - border: 0 !important; - padding: 0 !important; */ } } diff --git a/src/index.js b/src/index.js index a597a27..4a516dd 100644 --- a/src/index.js +++ b/src/index.js @@ -166,8 +166,7 @@ export default class LinkTool { */ if (Object.keys(this.data.meta).length) { this.nodes.container.appendChild(this.nodes.linkContent); - this.showLinkPreviewOverridden(this.data.meta); - // this.showLinkPreview(this.data.meta); + this.showLinkPreview(this.data.meta); } else { this.nodes.container.appendChild(this.nodes.inputHolder); } @@ -378,40 +377,6 @@ export default class LinkTool { this.nodes.linkContent.appendChild(this.nodes.linkImage); } - if (title) { - this.nodes.linkTitle.textContent = title; - this.nodes.linkContent.appendChild(this.nodes.linkTitle); - } - - if (description) { - this.nodes.linkDescription.textContent = description; - this.nodes.linkContent.appendChild(this.nodes.linkDescription); - } - - this.nodes.linkContent.classList.add(this.CSS.linkContentRendered); - this.nodes.linkContent.setAttribute('href', this.data.link); - this.nodes.linkContent.appendChild(this.nodes.linkText); - - try { - this.nodes.linkText.textContent = new URL(this.data.link).hostname; - } catch (e) { - this.nodes.linkText.textContent = this.data.link; - } - } - - /** - * Compose link preview from fetched data - * - * @param {metaData} meta - link meta data - */ - showLinkPreviewOverridden({ image, title, description }) { - this.nodes.container.appendChild(this.nodes.linkContent); - - if (image && image.url) { - this.nodes.linkImage.style.backgroundImage = 'url(' + image.url + ')'; - this.nodes.linkContent.appendChild(this.nodes.linkImage); - } - if (title) { this.nodes.linkTitle.textContent = title; this.nodes.linkInfos.appendChild(this.nodes.linkTitle); @@ -538,8 +503,7 @@ export default class LinkTool { this.hideProgress().then(() => { this.nodes.inputHolder.remove(); - this.showLinkPreviewOverridden(metaData); - // this.showLinkPreview(metaData); + this.showLinkPreview(metaData); }); } From d0df27e59711abef65ae180a113142c8bf426622 Mon Sep 17 00:00:00 2001 From: Dindonneau Date: Mon, 1 Jul 2024 12:21:58 +0200 Subject: [PATCH 11/13] feat(index.css): highlightedEmbed --- src/index.css | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/index.css b/src/index.css index bdbbd9c..4a1e0ab 100644 --- a/src/index.css +++ b/src/index.css @@ -19,12 +19,12 @@ background-color: #fff3f6; border-color: #f3e0e0; color: #a95a5a; - box-shadow: inset 0 1px 3px 0 rgba(146, 62, 62, .05); + box-shadow: inset 0 1px 3px 0 rgba(146, 62, 62, 0.05); } } } - &[contentEditable=true][data-placeholder]::before{ + &[contentEditable="true"][data-placeholder]::before { position: absolute; content: attr(data-placeholder); color: #707684; @@ -32,15 +32,14 @@ opacity: 0; } - &[contentEditable=true][data-placeholder]:empty { - + &[contentEditable="true"][data-placeholder]:empty { &::before { opacity: 1; } &:focus::before { - opacity: 0; - } + opacity: 0; + } } } @@ -72,13 +71,13 @@ &--rendered { background: #fff; - border: 1px solid #E9E9E9; + border: 1px solid #e9e9e9; border-radius: 5px; will-change: filter; animation: link-in 450ms 1 cubic-bezier(0.215, 0.61, 0.355, 1); &:hover { - box-shadow: 0 0 3px rgba(0,0,0, .16); + box-shadow: 0 0 3px rgba(0, 0, 0, 0.16); } } } @@ -110,7 +109,7 @@ font-size: 13px; color: #595959; line-height: 1.25; - height:2rem; + height: 2rem; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; @@ -119,11 +118,20 @@ &__anchor { font-size: 13px; - color: #F44545; + color: #f44545; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + + &.highlight { + .link-tool__image { + flex-direction: column; + width: 100%; + height: auto; + aspect-ratio: 4/3; + } + } } @keyframes link-in { From 689734e2d0524ee7f0ea0f6e383c4ef7ff6a60af Mon Sep 17 00:00:00 2001 From: Dindonneau Date: Mon, 1 Jul 2024 12:33:26 +0200 Subject: [PATCH 12/13] hotfix(index.css): fix flex direction when highlighted --- src/index.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/index.css b/src/index.css index 4a1e0ab..38bb90d 100644 --- a/src/index.css +++ b/src/index.css @@ -125,7 +125,12 @@ } &.highlight { - .link-tool__image { + .link-tool { + &&__content { + flex-direction: column; + } + + &&__image { flex-direction: column; width: 100%; height: auto; From 6166955ac8f079152943e02c437daeca14df3e3d Mon Sep 17 00:00:00 2001 From: Dindonneau Date: Mon, 1 Jul 2024 12:42:04 +0200 Subject: [PATCH 13/13] hotfix(index.css): fix missing closing block --- src/index.css | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/index.css b/src/index.css index 38bb90d..a1ee230 100644 --- a/src/index.css +++ b/src/index.css @@ -126,15 +126,16 @@ &.highlight { .link-tool { - &&__content { + &__content { flex-direction: column; } - &&__image { - flex-direction: column; - width: 100%; - height: auto; - aspect-ratio: 4/3; + &__image { + flex-direction: column; + width: 100%; + height: auto; + aspect-ratio: 4/3; + } } } }