From e097e4c2b20be03f7bfb65d1271bc94ed77a9256 Mon Sep 17 00:00:00 2001 From: Mats Mikkel Rummelhoff Date: Mon, 7 Aug 2023 14:29:40 +0200 Subject: [PATCH] Moves JS/CSS to Vite buildchain --- .env-example | 2 + build/.gitignore | 24 + build/.nvmrc | 1 + build/package.json | 25 + build/postcss.config.cjs | 6 + build/src/aimate.js | 165 ++++ build/src/aimate.scss | 38 + build/src/components/CustomPromptModal.js | 85 ++ build/tailwind.config.cjs | 497 ++++++++++++ build/vite.config.js | 35 + build/yarn.lock | 903 ++++++++++++++++++++++ src/AIMate.php | 44 +- src/_aimate.php | 5 - src/config.php | 7 +- src/controllers/DefaultController.php | 28 +- src/helpers/OpenAiHelper.php | 35 + src/models/Prompt.php | 99 +-- src/models/PromptConfig.php | 11 +- src/models/Settings.php | 3 + src/services/OpenAI.php | 50 -- src/templates/button.twig | 239 ++---- src/web/assets/AiMateAsset.php | 195 +++++ src/web/assets/dist/aimate.css | 1 + src/web/assets/dist/aimate.js | 15 + src/web/assets/dist/aimate.js.map | 1 + src/web/assets/dist/manifest.json | 14 + 26 files changed, 2199 insertions(+), 329 deletions(-) create mode 100644 .env-example create mode 100644 build/.gitignore create mode 100644 build/.nvmrc create mode 100644 build/package.json create mode 100644 build/postcss.config.cjs create mode 100644 build/src/aimate.js create mode 100644 build/src/aimate.scss create mode 100644 build/src/components/CustomPromptModal.js create mode 100644 build/tailwind.config.cjs create mode 100644 build/vite.config.js create mode 100644 build/yarn.lock delete mode 100644 src/_aimate.php create mode 100644 src/helpers/OpenAiHelper.php delete mode 100644 src/services/OpenAI.php create mode 100644 src/web/assets/AiMateAsset.php create mode 100644 src/web/assets/dist/aimate.css create mode 100644 src/web/assets/dist/aimate.js create mode 100644 src/web/assets/dist/aimate.js.map create mode 100644 src/web/assets/dist/manifest.json diff --git a/.env-example b/.env-example new file mode 100644 index 0000000..c7cdfc9 --- /dev/null +++ b/.env-example @@ -0,0 +1,2 @@ +VITE_DEVSERVER_HOST=0.0.0.0 +VITE_DEVSERVER_PORT=5173 diff --git a/build/.gitignore b/build/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/build/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/build/.nvmrc b/build/.nvmrc new file mode 100644 index 0000000..7fd0237 --- /dev/null +++ b/build/.nvmrc @@ -0,0 +1 @@ +v16.15.0 diff --git a/build/package.json b/build/package.json new file mode 100644 index 0000000..f2901e8 --- /dev/null +++ b/build/package.json @@ -0,0 +1,25 @@ +{ + "name": "RedirectMate", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "start": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^0.27.2", + "craftcms-sass": "^3.5.7", + "isbot": "^3.5.0", + "qs": "^6.11.0", + "ua-parser-js": "^1.0.2" + }, + "devDependencies": { + "autoprefixer": "^10.4.7", + "postcss": "^8.4.14", + "sass": "^1.54.0", + "tailwindcss": "^3.1.6", + "vite": "^3.0.0" + } +} diff --git a/build/postcss.config.cjs b/build/postcss.config.cjs new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/build/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/build/src/aimate.js b/build/src/aimate.js new file mode 100644 index 0000000..470aa52 --- /dev/null +++ b/build/src/aimate.js @@ -0,0 +1,165 @@ +import CustomPromptModal from "./components/CustomPromptModal.js"; + +import './aimate.scss'; + +$(() => { + + let customPromptModal; + + const onPromptClick = async e => { + e.preventDefault(); + const {currentTarget: promptLink} = e; + const menu = promptLink.closest('[data-aimate-menu]'); + const menuButton = document.querySelector(`button[aria-controls="${menu.id}"]`); + if (menuButton.classList.contains('loading')) { + return; + } + $(menuButton).trigger('click'); + const {field: fieldId, element: elementId, site: siteId} = menuButton.dataset; + const field = menuButton.closest('.field'); + if (!field) { + Craft.cp.displayError('Field not found'); + return; + } + const {type: fieldType} = field.dataset; + let input; + if (fieldType === 'craft\\fields\\Table') { + input = menuButton.closest('td.textual').querySelector('input,textarea'); + } else { + input = menuButton.closest('.input').querySelector('input,textarea'); + } + if (!input) { + Craft.cp.displayError('Input not found'); + return; + } + let params = {}; + if (promptLink.hasAttribute('data-custom')) { + if (!customPromptModal) { + customPromptModal = new CustomPromptModal(); + } + customPromptModal.show(); + const customPrompt = await customPromptModal.getText(); + if (!customPrompt) { + return; + } + params.custom = customPrompt; + } else { + const {prompt} = promptLink.dataset; + if (!prompt) { + Craft.cp.displayError('No prompt'); + return; + } + params.prompt = prompt; + } + const elementEditor = $(input.closest('[data-element-editor]')).data('elementEditor'); + params = { + ...params, + elementId, + siteId + }; + if (elementEditor) { + params = { + ...params, + draftId: elementEditor.settings.draftId, + isProvisionalDraft: elementEditor.settings.isProvisionalDraft + }; + } + menuButton.classList.add('loading'); + Craft.sendActionRequest( + 'POST', + '_aimate/default/do-prompt', + { + data: { + text: input.value, + ...params + } + } + ) + .then(res => { + const {data} = res; + if (!data.text) { + return; + } + const field = input.closest('.field'); + const {type: fieldType} = field.dataset; + if (fieldType === 'craft\\ckeditor\\Field') { + const editable = field.querySelector('.ck-editor__editable'); + const ckEditorInstance = editable ? editable.ckeditorInstance : null; + if (!ckEditorInstance) { + throw new Error('Unable to find CKEditor instance in DOM'); + } + ckEditorInstance.focus(); + ckEditorInstance.execute('selectAll'); + const viewFragment = ckEditorInstance.data.processor.toView(data.text); + const modelFragment = ckEditorInstance.data.toModel(viewFragment); + ckEditorInstance.model.insertContent(modelFragment); + } else if (fieldType === 'craft\\redactor\\Field') { + const redactorInstance = $R(`#${input.id}`); + redactorInstance.editor.focus(); + redactorInstance.insertion.set(data.text); + redactorInstance.source.getElement().val(data.text); + } else { + input.focus(); + input.select(); + if (!document.execCommand('insertText', false, data.text)) { + input.setRangeText(data.text); + } + } + if (elementEditor) { + elementEditor.checkForm(); + } + }) + .catch(({response}) => { + Craft.cp.displayError(response.message || response.data.message); + }) + .catch(error => { + console.error(error); + }) + .then(() => { + menuButton.classList.remove('loading'); + input.focus(); + }); + }; + + const initTableRow = $tr => { + const field = $tr.get(0).closest('.field'); + if (!field) { + return; + } + const aimateButton = field.querySelector('button[data-aimate]'); + if (!aimateButton) { + return; + } + const tds = $tr.children().get().filter(td => td.classList.contains('singleline-cell', 'multiline-cell')); + const trId = $tr.index(); + tds.forEach(td => { + const $aimateButton = $(aimateButton).clone(false, true); + const menuId = $aimateButton.attr('aria-controls'); + const $menu = $($(`#${menuId}`)).clone(false, true); + const textarea = td.querySelector('textarea'); + const tdId = `${trId}-${$(td).index()}`; + $menu.attr('id', `${$menu.attr('id')}-${trId}-${tdId}`); + $('body').append($menu); + $aimateButton.attr('aria-controls', $menu.attr('id')); + $(textarea.parentNode).prepend($aimateButton.disclosureMenu()); + }); + }; + + $('.field table.editable tbody tr').each(function () { + initTableRow($(this)); + }); + + const rowInitFn = Craft.EditableTable.Row.prototype.init; + Craft.EditableTable.Row.prototype.init = function () { + rowInitFn.apply(this, arguments); + initTableRow(this.$tr); + }; + + $('body').on('click', '[data-aimate-menu] a', onPromptClick); + +}); + +// Accept HMR as per: https://vitejs.dev/guide/api-hmr.html +if (import.meta.hot) { + import.meta.hot.accept(); +} diff --git a/build/src/aimate.scss b/build/src/aimate.scss new file mode 100644 index 0000000..2687d02 --- /dev/null +++ b/build/src/aimate.scss @@ -0,0 +1,38 @@ +@import "../node_modules/craftcms-sass/mixins"; + +button[data-aimate] { + display: inline-block !important; + position: absolute; + right: 0; + top: 0; + z-index: 1; +} + +button[data-aimate] + input.nicetext, +button[data-aimate] + textarea.nicetext, +td.textual button[data-aimate] + textarea { + padding-right: 60px; +} + +.field[data-type="craft\\\\fields\\\\Table"] .input > button[data-aimate] { + display: none !important; +} + +.field[data-type="craft\\\\fields\\\\Table"] tbody td.textual button[data-aimate] { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +.field[data-type="craft\\\\fields\\\\Table"] tbody td.textual { + position: relative; + z-index: 1; +} + +.field[data-type="craft\\\\fields\\\\Table"] tbody td.textual.focus { + z-index: 2; +} + +//.aimate-customprompt .ProseMirror { +// height: 100%; +// padding: 10px; +//} diff --git a/build/src/components/CustomPromptModal.js b/build/src/components/CustomPromptModal.js new file mode 100644 index 0000000..6dd4d4f --- /dev/null +++ b/build/src/components/CustomPromptModal.js @@ -0,0 +1,85 @@ +const CustomPromptModal = Garnish.Modal.extend({ + init: function () { + + this.base(); + this.setSettings({ + resizable: true, + }); + + const $modalContainerDiv = $( + '' + ) + .addClass() + .appendTo(Garnish.$bod); + + const $body = $(` +
+
+

Custom prompt

+

+ + Add a <text> token to the prompt to include the current value in the field. +

+
+
+ +
+
+ `).appendTo( + $modalContainerDiv.empty() + ); + + const $footer = $('