diff --git a/apps/scully-docs/src/assets/clipboard.min.js b/apps/scully-docs/src/assets/clipboard.min.js new file mode 100644 index 000000000..91395d65f --- /dev/null +++ b/apps/scully-docs/src/assets/clipboard.min.js @@ -0,0 +1,515 @@ +/*! + * clipboard.js v2.0.6 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */ +!(function (t, e) { + 'object' == typeof exports && 'object' == typeof module + ? (module.exports = e()) + : 'function' == typeof define && define.amd + ? define([], e) + : 'object' == typeof exports + ? (exports.ClipboardJS = e()) + : (t.ClipboardJS = e()); +})(this, function () { + return ( + (o = {}), + (r.m = n = [ + function (t, e) { + t.exports = function (t) { + var e; + if ('SELECT' === t.nodeName) t.focus(), (e = t.value); + else if ('INPUT' === t.nodeName || 'TEXTAREA' === t.nodeName) { + var n = t.hasAttribute('readonly'); + n || t.setAttribute('readonly', ''), + t.select(), + t.setSelectionRange(0, t.value.length), + n || t.removeAttribute('readonly'), + (e = t.value); + } else { + t.hasAttribute('contenteditable') && t.focus(); + var o = window.getSelection(), + r = document.createRange(); + r.selectNodeContents(t), o.removeAllRanges(), o.addRange(r), (e = o.toString()); + } + return e; + }; + }, + function (t, e) { + function n() {} + (n.prototype = { + on: function (t, e, n) { + var o = this.e || (this.e = {}); + return (o[t] || (o[t] = [])).push({ fn: e, ctx: n }), this; + }, + once: function (t, e, n) { + var o = this; + function r() { + o.off(t, r), e.apply(n, arguments); + } + return (r._ = e), this.on(t, r, n); + }, + emit: function (t) { + for ( + var e = [].slice.call(arguments, 1), n = ((this.e || (this.e = {}))[t] || []).slice(), o = 0, r = n.length; + o < r; + o++ + ) + n[o].fn.apply(n[o].ctx, e); + return this; + }, + off: function (t, e) { + var n = this.e || (this.e = {}), + o = n[t], + r = []; + if (o && e) for (var i = 0, a = o.length; i < a; i++) o[i].fn !== e && o[i].fn._ !== e && r.push(o[i]); + return r.length ? (n[t] = r) : delete n[t], this; + }, + }), + (t.exports = n), + (t.exports.TinyEmitter = n); + }, + function (t, e, n) { + var d = n(3), + h = n(4); + t.exports = function (t, e, n) { + if (!t && !e && !n) throw new Error('Missing required arguments'); + if (!d.string(e)) throw new TypeError('Second argument must be a String'); + if (!d.fn(n)) throw new TypeError('Third argument must be a Function'); + if (d.node(t)) + return ( + (s = e), + (f = n), + (u = t).addEventListener(s, f), + { + destroy: function () { + u.removeEventListener(s, f); + }, + } + ); + if (d.nodeList(t)) + return ( + (a = t), + (c = e), + (l = n), + Array.prototype.forEach.call(a, function (t) { + t.addEventListener(c, l); + }), + { + destroy: function () { + Array.prototype.forEach.call(a, function (t) { + t.removeEventListener(c, l); + }); + }, + } + ); + if (d.string(t)) return (o = t), (r = e), (i = n), h(document.body, o, r, i); + throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList'); + var o, r, i, a, c, l, u, s, f; + }; + }, + function (t, n) { + (n.node = function (t) { + return void 0 !== t && t instanceof HTMLElement && 1 === t.nodeType; + }), + (n.nodeList = function (t) { + var e = Object.prototype.toString.call(t); + return ( + void 0 !== t && + ('[object NodeList]' === e || '[object HTMLCollection]' === e) && + 'length' in t && + (0 === t.length || n.node(t[0])) + ); + }), + (n.string = function (t) { + return 'string' == typeof t || t instanceof String; + }), + (n.fn = function (t) { + return '[object Function]' === Object.prototype.toString.call(t); + }); + }, + function (t, e, n) { + var a = n(5); + function i(t, e, n, o, r) { + var i = function (e, n, t, o) { + return function (t) { + (t.delegateTarget = a(t.target, n)), t.delegateTarget && o.call(e, t); + }; + }.apply(this, arguments); + return ( + t.addEventListener(n, i, r), + { + destroy: function () { + t.removeEventListener(n, i, r); + }, + } + ); + } + t.exports = function (t, e, n, o, r) { + return 'function' == typeof t.addEventListener + ? i.apply(null, arguments) + : 'function' == typeof n + ? i.bind(null, document).apply(null, arguments) + : ('string' == typeof t && (t = document.querySelectorAll(t)), + Array.prototype.map.call(t, function (t) { + return i(t, e, n, o, r); + })); + }; + }, + function (t, e) { + if ('undefined' != typeof Element && !Element.prototype.matches) { + var n = Element.prototype; + n.matches = + n.matchesSelector || n.mozMatchesSelector || n.msMatchesSelector || n.oMatchesSelector || n.webkitMatchesSelector; + } + t.exports = function (t, e) { + for (; t && 9 !== t.nodeType; ) { + if ('function' == typeof t.matches && t.matches(e)) return t; + t = t.parentNode; + } + }; + }, + function (t, e, n) { + 'use strict'; + n.r(e); + var o = n(0), + r = n.n(o), + i = + 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator + ? function (t) { + return typeof t; + } + : function (t) { + return t && 'function' == typeof Symbol && t.constructor === Symbol && t !== Symbol.prototype + ? 'symbol' + : typeof t; + }; + function a(t, e) { + for (var n = 0; n < e.length; n++) { + var o = e[n]; + (o.enumerable = o.enumerable || !1), + (o.configurable = !0), + 'value' in o && (o.writable = !0), + Object.defineProperty(t, o.key, o); + } + } + function c(t) { + !(function (t, e) { + if (!(t instanceof e)) throw new TypeError('Cannot call a class as a function'); + })(this, c), + this.resolveOptions(t), + this.initSelection(); + } + var l = + ((function (t, e, n) { + return e && a(t.prototype, e), n && a(t, n), t; + })(c, [ + { + key: 'resolveOptions', + value: function (t) { + var e = 0 < arguments.length && void 0 !== t ? t : {}; + (this.action = e.action), + (this.container = e.container), + (this.emitter = e.emitter), + (this.target = e.target), + (this.text = e.text), + (this.trigger = e.trigger), + (this.selectedText = ''); + }, + }, + { + key: 'initSelection', + value: function () { + this.text ? this.selectFake() : this.target && this.selectTarget(); + }, + }, + { + key: 'selectFake', + value: function () { + var t = this, + e = 'rtl' == document.documentElement.getAttribute('dir'); + this.removeFake(), + (this.fakeHandlerCallback = function () { + return t.removeFake(); + }), + (this.fakeHandler = this.container.addEventListener('click', this.fakeHandlerCallback) || !0), + (this.fakeElem = document.createElement('textarea')), + (this.fakeElem.style.fontSize = '12pt'), + (this.fakeElem.style.border = '0'), + (this.fakeElem.style.padding = '0'), + (this.fakeElem.style.margin = '0'), + (this.fakeElem.style.position = 'absolute'), + (this.fakeElem.style[e ? 'right' : 'left'] = '-9999px'); + var n = window.pageYOffset || document.documentElement.scrollTop; + (this.fakeElem.style.top = n + 'px'), + this.fakeElem.setAttribute('readonly', ''), + (this.fakeElem.value = this.text), + this.container.appendChild(this.fakeElem), + (this.selectedText = r()(this.fakeElem)), + this.copyText(); + }, + }, + { + key: 'removeFake', + value: function () { + this.fakeHandler && + (this.container.removeEventListener('click', this.fakeHandlerCallback), + (this.fakeHandler = null), + (this.fakeHandlerCallback = null)), + this.fakeElem && (this.container.removeChild(this.fakeElem), (this.fakeElem = null)); + }, + }, + { + key: 'selectTarget', + value: function () { + (this.selectedText = r()(this.target)), this.copyText(); + }, + }, + { + key: 'copyText', + value: function () { + var e = void 0; + try { + e = document.execCommand(this.action); + } catch (t) { + e = !1; + } + this.handleResult(e); + }, + }, + { + key: 'handleResult', + value: function (t) { + this.emitter.emit(t ? 'success' : 'error', { + action: this.action, + text: this.selectedText, + trigger: this.trigger, + clearSelection: this.clearSelection.bind(this), + }); + }, + }, + { + key: 'clearSelection', + value: function () { + this.trigger && this.trigger.focus(), document.activeElement.blur(), window.getSelection().removeAllRanges(); + }, + }, + { + key: 'destroy', + value: function () { + this.removeFake(); + }, + }, + { + key: 'action', + set: function (t) { + var e = 0 < arguments.length && void 0 !== t ? t : 'copy'; + if (((this._action = e), 'copy' !== this._action && 'cut' !== this._action)) + throw new Error('Invalid "action" value, use either "copy" or "cut"'); + }, + get: function () { + return this._action; + }, + }, + { + key: 'target', + set: function (t) { + if (void 0 !== t) { + if (!t || 'object' !== (void 0 === t ? 'undefined' : i(t)) || 1 !== t.nodeType) + throw new Error('Invalid "target" value, use a valid Element'); + if ('copy' === this.action && t.hasAttribute('disabled')) + throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute'); + if ('cut' === this.action && (t.hasAttribute('readonly') || t.hasAttribute('disabled'))) + throw new Error( + 'Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes' + ); + this._target = t; + } + }, + get: function () { + return this._target; + }, + }, + ]), + c), + u = n(1), + s = n.n(u), + f = n(2), + d = n.n(f), + h = + 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator + ? function (t) { + return typeof t; + } + : function (t) { + return t && 'function' == typeof Symbol && t.constructor === Symbol && t !== Symbol.prototype + ? 'symbol' + : typeof t; + }, + p = function (t, e, n) { + return e && y(t.prototype, e), n && y(t, n), t; + }; + function y(t, e) { + for (var n = 0; n < e.length; n++) { + var o = e[n]; + (o.enumerable = o.enumerable || !1), + (o.configurable = !0), + 'value' in o && (o.writable = !0), + Object.defineProperty(t, o.key, o); + } + } + var m = + ((function (t, e) { + if ('function' != typeof e && null !== e) + throw new TypeError('Super expression must either be null or a function, not ' + typeof e); + (t.prototype = Object.create(e && e.prototype, { + constructor: { value: t, enumerable: !1, writable: !0, configurable: !0 }, + })), + e && (Object.setPrototypeOf ? Object.setPrototypeOf(t, e) : (t.__proto__ = e)); + })(v, s.a), + p( + v, + [ + { + key: 'resolveOptions', + value: function (t) { + var e = 0 < arguments.length && void 0 !== t ? t : {}; + (this.action = 'function' == typeof e.action ? e.action : this.defaultAction), + (this.target = 'function' == typeof e.target ? e.target : this.defaultTarget), + (this.text = 'function' == typeof e.text ? e.text : this.defaultText), + (this.container = 'object' === h(e.container) ? e.container : document.body); + }, + }, + { + key: 'listenClick', + value: function (t) { + var e = this; + this.listener = d()(t, 'click', function (t) { + return e.onClick(t); + }); + }, + }, + { + key: 'onClick', + value: function (t) { + var e = t.delegateTarget || t.currentTarget; + this.clipboardAction && (this.clipboardAction = null), + (this.clipboardAction = new l({ + action: this.action(e), + target: this.target(e), + text: this.text(e), + container: this.container, + trigger: e, + emitter: this, + })); + }, + }, + { + key: 'defaultAction', + value: function (t) { + return b('action', t); + }, + }, + { + key: 'defaultTarget', + value: function (t) { + var e = b('target', t); + if (e) return document.querySelector(e); + }, + }, + { + key: 'defaultText', + value: function (t) { + return b('text', t); + }, + }, + { + key: 'destroy', + value: function () { + this.listener.destroy(), this.clipboardAction && (this.clipboardAction.destroy(), (this.clipboardAction = null)); + }, + }, + ], + [ + { + key: 'isSupported', + value: function (t) { + var e = 0 < arguments.length && void 0 !== t ? t : ['copy', 'cut'], + n = 'string' == typeof e ? [e] : e, + o = !!document.queryCommandSupported; + return ( + n.forEach(function (t) { + o = o && !!document.queryCommandSupported(t); + }), + o + ); + }, + }, + ] + ), + v); + function v(t, e) { + !(function (t, e) { + if (!(t instanceof e)) throw new TypeError('Cannot call a class as a function'); + })(this, v); + var n = (function (t, e) { + if (!t) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + return !e || ('object' != typeof e && 'function' != typeof e) ? t : e; + })(this, (v.__proto__ || Object.getPrototypeOf(v)).call(this)); + return n.resolveOptions(e), n.listenClick(t), n; + } + function b(t, e) { + var n = 'data-clipboard-' + t; + if (e.hasAttribute(n)) return e.getAttribute(n); + } + e.default = m; + }, + ]), + (r.c = o), + (r.d = function (t, e, n) { + r.o(t, e) || Object.defineProperty(t, e, { enumerable: !0, get: n }); + }), + (r.r = function (t) { + 'undefined' != typeof Symbol && Symbol.toStringTag && Object.defineProperty(t, Symbol.toStringTag, { value: 'Module' }), + Object.defineProperty(t, '__esModule', { value: !0 }); + }), + (r.t = function (e, t) { + if ((1 & t && (e = r(e)), 8 & t)) return e; + if (4 & t && 'object' == typeof e && e && e.__esModule) return e; + var n = Object.create(null); + if ((r.r(n), Object.defineProperty(n, 'default', { enumerable: !0, value: e }), 2 & t && 'string' != typeof e)) + for (var o in e) + r.d( + n, + o, + function (t) { + return e[t]; + }.bind(null, o) + ); + return n; + }), + (r.n = function (t) { + var e = + t && t.__esModule + ? function () { + return t.default; + } + : function () { + return t; + }; + return r.d(e, 'a', e), e; + }), + (r.o = function (t, e) { + return Object.prototype.hasOwnProperty.call(t, e); + }), + (r.p = ''), + r((r.s = 6)).default + ); + function r(t) { + if (o[t]) return o[t].exports; + var e = (o[t] = { i: t, l: !1, exports: {} }); + return n[t].call(e.exports, e, e.exports, r), (e.l = !0), e.exports; + } + var n, o; +}); diff --git a/apps/scully-docs/src/styles/code.css b/apps/scully-docs/src/styles/code.css index 11e2e4520..563c953f4 100644 --- a/apps/scully-docs/src/styles/code.css +++ b/apps/scully-docs/src/styles/code.css @@ -6,16 +6,15 @@ font-size: 13px; font-weight: 500; border-radius: 4px; - overflow-x: auto; } .docs-page-content pre { - position: relative; align-self: stretch; margin-bottom: 24px; padding: 16px 22px; padding-right: 42px; background: var(--scully-night); + overflow-x: auto; } .docs-page-content pre:not([class]) { @@ -38,11 +37,12 @@ letter-spacing: 0.5px; background: none; box-shadow: none; - overflow-x: auto; + display: block; + padding-top: 20px; } -.docs-page-content pre code::before { +.docs-page-content pre::before { content: attr(class); - position: relative; + position: absolute; display: block; margin-bottom: 6px; color: rgba(255, 255, 255, 0.5); diff --git a/libs/plugins/scully-plugin-copy-to-clipboard/.eslintrc b/libs/plugins/scully-plugin-copy-to-clipboard/.eslintrc new file mode 100644 index 000000000..22293ae82 --- /dev/null +++ b/libs/plugins/scully-plugin-copy-to-clipboard/.eslintrc @@ -0,0 +1 @@ +{"extends":"../../../.eslintrc","rules":{},"ignorePatterns":["!**/*"]} \ No newline at end of file diff --git a/libs/plugins/scully-plugin-copy-to-clipboard/README.md b/libs/plugins/scully-plugin-copy-to-clipboard/README.md new file mode 100644 index 000000000..61f830919 --- /dev/null +++ b/libs/plugins/scully-plugin-copy-to-clipboard/README.md @@ -0,0 +1,54 @@ +# Scully Copy To Clipboard Plugin + +Copy to Clipboard Plugin add `copy` button in code snippets generated from markdown file with scully. + +## Plugin Type + +Render Plugin + +## Usage + +1. This plugin requires the `clipboard.min.js`. Download it from [here](https://clipboardjs.com/) and add it in `assets` folder of your application. + +2. In the application's `scully..config.ts`, Import `CopyToClipboard` plugin and add it in `defaultPostHandlers`. + +```typescript +import { CopyToClipboard } from '@scullyio/scully-plugin-copy-to-clipboard'; + +const defaultPostRenderers = [CopyToClipboard]; + +export const config: ScullyConfig = { + defaultPostRenderers, + routes: { + '/route/:slug': { + type: 'contentFolder', + postRenderers: [...defaultPostRenderers], + slug: { + folder: './folder', + }, + }, + }, + // Other Configuration... +}; +``` + +## Copy To Clipboard Plugin Configuration + +Provide custom plugin configuration in application's `scully..config.ts`. + +```typescript +setPluginConfig(CopyToClipboard, { + copyBtnInitialText: '📄', + copyBtnOnClickText: '✅', + customBtnClass: 'customClass', + clipboardJSPath: + 'https://cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js', +}); +``` + +| Property | Default | Description | +| ------------------ | -------------------------- | ------------------------------------------------------------- | +| customBtnClass | `''` | Add custom css class for `copy` button to apply styles | +| clipboardJSPath | `/assets/clipboard.min.js` | Specify clipboard js path, you can also specify CDN link here | +| copyBtnInitialText | `Copy` | `copy` button initial text | +| copyBtnOnClickText | `Copied!` | `copy` button text once code snippet is copied in clipboard | diff --git a/libs/plugins/scully-plugin-copy-to-clipboard/jest.config.js b/libs/plugins/scully-plugin-copy-to-clipboard/jest.config.js new file mode 100644 index 000000000..2edcd9a4a --- /dev/null +++ b/libs/plugins/scully-plugin-copy-to-clipboard/jest.config.js @@ -0,0 +1,14 @@ +module.exports = { + name: 'plugins-scully-plugin-copy-to-clipboard', + preset: '../../../jest.config.js', + globals: { + 'ts-jest': { + tsConfig: '/tsconfig.spec.json', + }, + }, + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], + coverageDirectory: '../../../coverage/libs/plugins/scully-plugin-copy-to-clipboard', +}; diff --git a/libs/plugins/scully-plugin-copy-to-clipboard/package.json b/libs/plugins/scully-plugin-copy-to-clipboard/package.json new file mode 100644 index 000000000..8a4ca5a5d --- /dev/null +++ b/libs/plugins/scully-plugin-copy-to-clipboard/package.json @@ -0,0 +1,24 @@ +{ + "name": "@scullyio/scully-plugin-copy-to-clipboard", + "version": "0.0.1", + "author": "Ankit Prajapati", + "license": "MIT", + "repository": { + "type": "GIT", + "url": "https://github.com/scullyio/scully/tree/main/libs/plugins/scully-plugin-copy-to-clipboard" + }, + "peerDependencies": { + "@scullyio/scully": "*" + }, + "dependencies": { + "jsdom": "^16.2.2" + }, + "keywords": [ + "angular", + "scully", + "prismjs", + "copy-to-clipboard", + "scully-plugin", + "plugin" + ] +} diff --git a/libs/plugins/scully-plugin-copy-to-clipboard/src/index.ts b/libs/plugins/scully-plugin-copy-to-clipboard/src/index.ts new file mode 100644 index 000000000..61cac87d4 --- /dev/null +++ b/libs/plugins/scully-plugin-copy-to-clipboard/src/index.ts @@ -0,0 +1 @@ +export * from './lib/plugins-scully-plugin-copy-to-clipboard'; diff --git a/libs/plugins/scully-plugin-copy-to-clipboard/src/lib/plugins-scully-plugin-copy-to-clipboard.spec.ts b/libs/plugins/scully-plugin-copy-to-clipboard/src/lib/plugins-scully-plugin-copy-to-clipboard.spec.ts new file mode 100644 index 000000000..7a38158d7 --- /dev/null +++ b/libs/plugins/scully-plugin-copy-to-clipboard/src/lib/plugins-scully-plugin-copy-to-clipboard.spec.ts @@ -0,0 +1,7 @@ +import { CopyToClipboard } from './plugins-scully-plugin-copy-to-clipboard'; + +describe('ScullyPluginCopyToClipboard', () => { + it('should work', () => { + expect(CopyToClipboard).toEqual('CopyToClipboard'); + }); +}); diff --git a/libs/plugins/scully-plugin-copy-to-clipboard/src/lib/plugins-scully-plugin-copy-to-clipboard.ts b/libs/plugins/scully-plugin-copy-to-clipboard/src/lib/plugins-scully-plugin-copy-to-clipboard.ts new file mode 100644 index 000000000..fd8319d16 --- /dev/null +++ b/libs/plugins/scully-plugin-copy-to-clipboard/src/lib/plugins-scully-plugin-copy-to-clipboard.ts @@ -0,0 +1,121 @@ +import { registerPlugin, getPluginConfig, logWarn, yellow, HandledRoute } from '@scullyio/scully'; +import { JSDOM } from 'jsdom'; + +export const CopyToClipboard = 'CopyToClipboard'; + +export interface CopyToClipboardPluginConfig { + /** add custom css class for copy button to apply styles */ + customBtnClass?: string; + /** specify clipboard js path, default `/assets/clipboard.min.js` */ + clipboardJSPath?: string; + /** copy button initial text, default `Copy` */ + copyBtnInitialText?: string; + /** copy button text once code snippet is copied in clipboard , default `Copied!` */ + copyBtnOnClickText?: string; + /** Selector that finds the code snippets, defaults to 'pre>code' */ + selector?: string; +} + +const defaultConfig: CopyToClipboardPluginConfig = { + customBtnClass: '', + clipboardJSPath: '/assets/clipboard.min.js', + copyBtnInitialText: 'Copy', + copyBtnOnClickText: 'Copied!', + selector: 'pre>code', +}; + +const copyToClipboardPlugin = async (html: string, options: HandledRoute): Promise => { + const pluginConfig = { ...defaultConfig, ...getPluginConfig(CopyToClipboard) }; + + try { + const dom = new JSDOM(html); + const { window } = dom; + const { document } = window; + + const scullyCodeSnippets = document.querySelectorAll(pluginConfig.selector); + + /** Return unchanged HTML if document doesn't contain any code snippet */ + if (!scullyCodeSnippets.length) { + return html; + } + + /** Prepend copy to clipboard button on each code snippet pre */ + scullyCodeSnippets.forEach((snippet) => { + /** get the parent as I now have the 'code' thing */ + snippet = snippet.parentElement; + // return `
${formattedCode}
`; + + const wrapper = document.createElement('div'); + wrapper.style.position = 'relative'; + /** Copy to Clipboard Button Element */ + const copyBtnEl = document.createElement('button'); + copyBtnEl.className = 'copyToClipboard ' + pluginConfig.customBtnClass; + copyBtnEl.textContent = pluginConfig.copyBtnInitialText; + + snippet.prepend(copyBtnEl); + snippet.parentNode.insertBefore(wrapper, snippet); + wrapper.appendChild(snippet); + }); + + /** append clipboard script to the body */ + const clipboardScriptEl = document.createElement('script'); + clipboardScriptEl.defer = true; + clipboardScriptEl.async = true; + clipboardScriptEl.setAttribute('sk', ''); + clipboardScriptEl.innerHTML = ` + const s = document.createElement('script'); + s.src = '${pluginConfig.clipboardJSPath}'; + s.addEventListener('load', () => registerCopyToClipboard()); + s.addEventListener('error', () => console.warn('could not load "${pluginConfig.clipboardJSPath}", make sure you have it copied into your assets folder' )); + document.body.appendChild(s) + + function registerCopyToClipboard() { + const clip = new ClipboardJS('pre .copyToClipboard', { + target: function (trigger) { + return trigger.nextElementSibling; + }, + }); + + clip.on('success', function (event) { + event.trigger.textContent = '${pluginConfig.copyBtnOnClickText}'; + event.clearSelection(); + setTimeout(function () { + event.trigger.textContent = '${pluginConfig.copyBtnInitialText}'; + }, 2000); + }); + } + `; + /** append copy to clipboard button styles */ + const styleEl = document.createElement('style'); + styleEl.innerHTML = ` + .copyToClipboard { + position: absolute; + right: 0; + top: 0; + padding: 5px; + color: rgb(27, 172, 78); + border-radius: 4px; + margin: 5px; + border: 1px solid rgb(27, 172, 78); + } + + .copyToClipboard:hover { + background-color: rgb(27, 172, 78); + color: white; + border: 1px solid rgb(27, 172, 78); + } + `; + + document.body.appendChild(clipboardScriptEl); + document.body.appendChild(styleEl); + return dom.serialize(); + } catch (e) { + logWarn(`error in ${CopyToClipboard} Plugin, didn't parse for route "${yellow(options.route)}"`, e); + } + + return html; +}; + +const validator = async () => []; + +registerPlugin('render', CopyToClipboard, copyToClipboardPlugin, validator); diff --git a/libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.json b/libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.json new file mode 100644 index 000000000..78e44dc67 --- /dev/null +++ b/libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": [], + "files": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.lib.json b/libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.lib.json new file mode 100644 index 000000000..9c463b51e --- /dev/null +++ b/libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.lib.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../../dist/out-tsc", + "declaration": true, + "rootDir": "./src", + "types": ["node"] + }, + "exclude": ["**/*.spec.ts"], + "include": ["**/*.ts"] +} diff --git a/libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.spec.json b/libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.spec.json new file mode 100644 index 000000000..65aff5094 --- /dev/null +++ b/libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/*.d.ts"] +} diff --git a/nx.json b/nx.json index 15b6d367f..bc28e9185 100644 --- a/nx.json +++ b/nx.json @@ -66,6 +66,9 @@ }, "plugins-scully-plugin-critical-css": { "tags": [] + }, + "plugins-scully-plugin-copy-to-clipboard": { + "tags": [] } }, "affected": { diff --git a/scully.scully-docs.config.ts b/scully.scully-docs.config.ts index 4d1138e27..76334b0ba 100644 --- a/scully.scully-docs.config.ts +++ b/scully.scully-docs.config.ts @@ -3,6 +3,7 @@ import { docLink } from '@scullyio/scully-plugin-docs-link-update'; import { GoogleAnalytics } from '@scullyio/scully-plugin-google-analytics'; import { LogRocket } from '@scullyio/scully-plugin-logrocket'; import { Sentry } from '@scullyio/scully-plugin-sentry'; +import { CopyToClipboard } from '@scullyio/scully-plugin-copy-to-clipboard'; import { removeScripts, RemoveScriptsConfig } from '@scullyio/scully-plugin-remove-scripts'; const marked = require('marked'); import { readFileSync } from 'fs-extra'; @@ -16,7 +17,7 @@ const { document } = window; setPluginConfig('md', { enableSyntaxHighlighting: true }); -const defaultPostRenderers = [LogRocket, GoogleAnalytics, removeScripts, 'seoHrefOptimise', criticalCSS]; +const defaultPostRenderers = [LogRocket, GoogleAnalytics, removeScripts, 'seoHrefOptimise', criticalCSS, CopyToClipboard]; if (prod) { /* diff --git a/tsconfig.base.json b/tsconfig.base.json index d760f0d3c..3c566af9e 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -27,7 +27,8 @@ "@scullyio/scully-plugin-google-analytics": ["libs/plugins/google-analytics/src/index.ts"], "@scullyio/scully-plugin-logrocket": ["libs/plugins/logrocket/src/index.ts"], "@scullyio/scully-plugin-remove-scripts": ["libs/plugins/scully-plugin-remove-scripts/src/index.ts"], - "@scullyio/scully-plugin-sentry": ["libs/plugins/sentry/src/index.ts"] + "@scullyio/scully-plugin-sentry": ["libs/plugins/sentry/src/index.ts"], + "@scullyio/scully-plugin-copy-to-clipboard": ["libs/plugins/scully-plugin-copy-to-clipboard/src/index.ts"] } }, "angularCompilerOptions": { diff --git a/tsconfig.json b/tsconfig.json index f642a1e1a..cda6b8875 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,8 @@ "@scullyio/scully-plugin-logrocket": ["libs/plugins/logrocket/src/index.ts"], "@scullyio/scully-plugin-sentry": ["libs/plugins/sentry/src/index.ts"], "@scullyio/scully-plugin-critical-css": ["libs/plugins/scully-plugin-critical-css/src/index.ts"], - "@scullyio/scully-plugin-remove-scripts": ["libs/plugins/scully-plugin-remove-scripts/src/index.ts"] + "@scullyio/scully-plugin-remove-scripts": ["libs/plugins/scully-plugin-remove-scripts/src/index.ts"], + "@scullyio/scully-plugin-copy-to-clipboard": ["libs/plugins/scully-plugin-copy-to-clipboard/src/index.ts"] } }, "angularCompilerOptions": { diff --git a/workspace.json b/workspace.json index a3f289f93..0695ec819 100644 --- a/workspace.json +++ b/workspace.json @@ -655,6 +655,43 @@ } } } + }, + "plugins-scully-plugin-copy-to-clipboard": { + "root": "libs/plugins/scully-plugin-copy-to-clipboard", + "sourceRoot": "libs/plugins/scully-plugin-copy-to-clipboard/src", + "projectType": "library", + "schematics": {}, + "architect": { + "lint": { + "builder": "@nrwl/linter:lint", + "options": { + "linter": "eslint", + "tsConfig": [ + "libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.lib.json", + "libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.spec.json" + ], + "exclude": ["**/node_modules/**", "!libs/plugins/scully-plugin-copy-to-clipboard/**/*"] + } + }, + "test": { + "builder": "@nrwl/jest:jest", + "options": { + "jestConfig": "libs/plugins/scully-plugin-copy-to-clipboard/jest.config.js", + "tsConfig": "libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.spec.json", + "passWithNoTests": true + } + }, + "build": { + "builder": "@nrwl/node:package", + "options": { + "outputPath": "dist/libs/scully-plugin-copy-to-clipboard", + "tsConfig": "libs/plugins/scully-plugin-copy-to-clipboard/tsconfig.lib.json", + "packageJson": "libs/plugins/scully-plugin-copy-to-clipboard/package.json", + "main": "libs/plugins/scully-plugin-copy-to-clipboard/src/index.ts", + "assets": ["libs/plugins/scully-plugin-copy-to-clipboard/*.md"] + } + } + } } }, "cli": {