1
0

EditJavascriptCode.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. <template>
  2. <div class="mb-2 mt-4">
  3. <ui-textarea
  4. :model-value="data.description"
  5. autoresize
  6. :placeholder="t('common.description')"
  7. class="w-full mb-1"
  8. @change="updateData({ description: $event })"
  9. />
  10. <ui-input
  11. v-if="!data.everyNewTab"
  12. :model-value="data.timeout"
  13. :label="t('workflow.blocks.javascript-code.timeout.placeholder')"
  14. :title="t('workflow.blocks.javascript-code.timeout.title')"
  15. type="number"
  16. class="mb-2 w-full"
  17. @change="updateData({ timeout: +$event })"
  18. />
  19. <p class="text-sm ml-1 text-gray-600 dark:text-gray-200">
  20. {{ t('workflow.blocks.javascript-code.name') }}
  21. </p>
  22. <pre
  23. v-if="!state.showCodeModal"
  24. class="rounded-lg overflow-auto text-gray-200 p-4 max-h-80 bg-gray-900"
  25. @click="state.showCodeModal = true"
  26. v-text="data.code"
  27. />
  28. <ui-checkbox
  29. :model-value="data.everyNewTab"
  30. class="mt-2"
  31. @change="updateData({ everyNewTab: $event })"
  32. >
  33. {{ t('workflow.blocks.javascript-code.everyNewTab') }}
  34. </ui-checkbox>
  35. <ui-modal v-model="state.showCodeModal" content-class="max-w-3xl">
  36. <template #header>
  37. <ui-tabs v-model="state.activeTab" class="border-none">
  38. <ui-tab value="code">
  39. {{ t('workflow.blocks.javascript-code.modal.tabs.code') }}
  40. </ui-tab>
  41. <ui-tab value="preloadScript">
  42. {{ t('workflow.blocks.javascript-code.modal.tabs.preloadScript') }}
  43. </ui-tab>
  44. </ui-tabs>
  45. </template>
  46. <ui-tab-panels
  47. v-model="state.activeTab"
  48. class="overflow-auto"
  49. style="height: calc(100vh - 9rem)"
  50. >
  51. <ui-tab-panel value="code" class="h-full">
  52. <shared-codemirror
  53. v-model="state.code"
  54. :extensions="codemirrorExts"
  55. :style="{ height: data.everyNewTab ? '100%' : '87%' }"
  56. class="overflow-auto"
  57. />
  58. <template v-if="!data.everyNewTab">
  59. <p class="mt-1 text-sm">
  60. {{ t('workflow.blocks.javascript-code.availabeFuncs') }}
  61. </p>
  62. <p
  63. class="space-x-1 whitespace-nowrap overflow-x-auto overflow-y-hidden pb-1 scroll"
  64. >
  65. <a
  66. v-for="func in availableFuncs"
  67. :key="func.id"
  68. :href="`https://docs.automa.site/blocks/javascript-code.html#${func.id}`"
  69. target="_blank"
  70. rel="noopener"
  71. class="inline-block"
  72. >
  73. <code>
  74. {{ func.name }}
  75. </code>
  76. </a>
  77. </p>
  78. </template>
  79. </ui-tab-panel>
  80. <ui-tab-panel value="preloadScript">
  81. <div
  82. v-for="(script, index) in state.preloadScripts"
  83. :key="index"
  84. class="flex items-center mt-4"
  85. >
  86. <v-remixicon
  87. name="riDeleteBin7Line"
  88. class="mr-2 cursor-pointer"
  89. @click="state.preloadScripts.splice(index, 1)"
  90. />
  91. <ui-input
  92. v-model="state.preloadScripts[index].src"
  93. placeholder="http://example.com/script.js"
  94. class="flex-1 mr-4"
  95. />
  96. <ui-checkbox
  97. v-if="!data.everyNewTab"
  98. v-model="state.preloadScripts[index].removeAfterExec"
  99. >
  100. {{ t('workflow.blocks.javascript-code.removeAfterExec') }}
  101. </ui-checkbox>
  102. </div>
  103. <ui-button variant="accent" class="w-20 mt-4" @click="addScript">
  104. {{ t('common.add') }}
  105. </ui-button>
  106. </ui-tab-panel>
  107. </ui-tab-panels>
  108. </ui-modal>
  109. </div>
  110. </template>
  111. <script setup>
  112. import { watch, reactive, defineAsyncComponent } from 'vue';
  113. import { useI18n } from 'vue-i18n';
  114. import { syntaxTree } from '@codemirror/language';
  115. import { autocompletion, snippet } from '@codemirror/autocomplete';
  116. const SharedCodemirror = defineAsyncComponent(() =>
  117. import('@/components/newtab/shared/SharedCodemirror.vue')
  118. );
  119. const props = defineProps({
  120. data: {
  121. type: Object,
  122. default: () => ({}),
  123. },
  124. });
  125. const emit = defineEmits(['update:data']);
  126. const { t } = useI18n();
  127. const availableFuncs = [
  128. { name: 'automaNextBlock(data, insert?)', id: 'automanextblock-data' },
  129. { name: 'automaRefData(keyword, path?)', id: 'automarefdata-keyword-path' },
  130. {
  131. name: 'automaSetVariable(name, value)',
  132. id: 'automasetvariable-name-value',
  133. },
  134. { name: 'automaResetTimeout()', id: 'automaresettimeout' },
  135. ];
  136. const state = reactive({
  137. activeTab: 'code',
  138. code: `${props.data.code}`,
  139. preloadScripts: [...Object.values(props.data.preloadScripts || [])],
  140. showCodeModal: false,
  141. });
  142. function updateData(value) {
  143. emit('update:data', { ...props.data, ...value });
  144. }
  145. function addScript() {
  146. state.preloadScripts.push({ src: '', removeAfterExec: true });
  147. }
  148. const dontCompleteIn = [
  149. 'String',
  150. 'TemplateString',
  151. 'LineComment',
  152. 'BlockComment',
  153. 'VariableDefinition',
  154. 'PropertyDefinition',
  155. ];
  156. /* eslint-disable no-template-curly-in-string */
  157. function automaFuncsCompletion(context) {
  158. const word = context.matchBefore(/\w*/);
  159. const nodeBefore = syntaxTree(context.state).resolveInner(context.pos, -1);
  160. if (
  161. (word.from === word.to && !context.explicit) ||
  162. dontCompleteIn.includes(nodeBefore.name)
  163. )
  164. return null;
  165. return {
  166. from: word.from,
  167. options: [
  168. {
  169. label: 'automaNextBlock',
  170. type: 'function',
  171. apply: snippet('automaNextBlock(${data})'),
  172. info: () => {
  173. const container = document.createElement('div');
  174. container.innerHTML = `
  175. <code>automaNextBlock(<i>data</i>, <i>insert?</i>)</code>
  176. <p class="mt-2">
  177. Execute the next block
  178. <a href="https://docs.automa.site/blocks/javascript-code.html#automanextblock-data" target="_blank" class="underline">
  179. Read more
  180. </a>
  181. </p>
  182. `;
  183. return container;
  184. },
  185. },
  186. {
  187. label: 'automaSetVariable',
  188. type: 'function',
  189. apply: snippet("automaSetVariable('${name}', ${value})"),
  190. info: () => {
  191. const container = document.createElement('div');
  192. container.innerHTML = `
  193. <code>automaRefData(<i>name</i>, <i>value</i>)</code>
  194. <p class="mt-2">
  195. Set the value of a variable
  196. </p>
  197. `;
  198. return container;
  199. },
  200. },
  201. {
  202. label: 'automaRefData',
  203. type: 'function',
  204. apply: snippet("automaRefData('${keyword}', '${path}')"),
  205. info: () => {
  206. const container = document.createElement('div');
  207. container.innerHTML = `
  208. <code>automaRefData(<i>keyword</i>, <i>path</i>)</code>
  209. <p class="mt-2">
  210. Use this function to
  211. <a href="https://docs.automa.site/api-reference/reference-data.html" target="_blank" class="underline">
  212. reference data
  213. </a>
  214. </p>
  215. `;
  216. return container;
  217. },
  218. },
  219. {
  220. label: 'automaResetTimeout',
  221. type: 'function',
  222. info: 'Reset javascript execution timeout',
  223. apply: 'automaResetTimeout()',
  224. },
  225. ],
  226. };
  227. }
  228. const codemirrorExts = [autocompletion({ override: [automaFuncsCompletion] })];
  229. watch(
  230. () => state.code,
  231. (value) => {
  232. updateData({ code: value });
  233. }
  234. );
  235. watch(
  236. () => state.preloadScripts,
  237. (value) => {
  238. updateData({ preloadScripts: value });
  239. },
  240. { deep: true }
  241. );
  242. </script>
  243. <style scoped>
  244. code {
  245. @apply bg-gray-900 text-sm text-white p-1 rounded-md;
  246. }
  247. </style>